Add 'depends/groupview/' from commit '946d49675cb4725c31ab49a51f3bcae302f89a19'
git-subtree-dir: depends/groupview git-subtree-mainline:a17caba2c9git-subtree-split:946d49675c
This commit is contained in:
		
							
								
								
									
										24
									
								
								depends/groupview/.clang-format
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								depends/groupview/.clang-format
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| UseTab: true | ||||
| IndentWidth: 4 | ||||
| TabWidth:    4 | ||||
| ConstructorInitializerIndentWidth: 4 | ||||
| AccessModifierOffset: -4 | ||||
| IndentCaseLabels: false | ||||
| IndentFunctionDeclarationAfterType: false | ||||
| NamespaceIndentation: None | ||||
|  | ||||
| BreakBeforeBraces: Allman | ||||
| AllowShortIfStatementsOnASingleLine: false | ||||
| ColumnLimit: 96 | ||||
| MaxEmptyLinesToKeep: 1 | ||||
|  | ||||
| Standard: Cpp11 | ||||
| Cpp11BracedListStyle: true | ||||
|  | ||||
| SpacesInParentheses: false | ||||
| SpaceInEmptyParentheses: false | ||||
| SpacesInCStyleCastParentheses: false | ||||
| SpaceAfterControlStatementKeyword: true | ||||
|  | ||||
| AlignTrailingComments: true | ||||
| SpacesBeforeTrailingComments: 1 | ||||
							
								
								
									
										2
									
								
								depends/groupview/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								depends/groupview/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| build/ | ||||
| *.user* | ||||
							
								
								
									
										40
									
								
								depends/groupview/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								depends/groupview/CMakeLists.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| cmake_minimum_required(VERSION 2.8.9) | ||||
|  | ||||
| project(GroupView) | ||||
|  | ||||
| set(CMAKE_AUTOMOC ON) | ||||
|  | ||||
| IF(APPLE) | ||||
| 	message(STATUS "Using APPLE CMAKE_CXX_FLAGS") | ||||
| 	SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall") | ||||
| ELSEIF(UNIX) | ||||
| 	# assume GCC, add C++0x/C++11 stuff | ||||
| 	MESSAGE(STATUS "Using UNIX CMAKE_CXX_FLAGS") | ||||
| 	SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall") | ||||
| ELSEIF(MINGW) | ||||
| 	MESSAGE(STATUS "Using MINGW CMAKE_CXX_FLAGS") | ||||
| 	SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11 -Wall") | ||||
| ENDIF() | ||||
|  | ||||
| find_package(Qt5Core REQUIRED) | ||||
| find_package(Qt5Gui REQUIRED) | ||||
| find_package(Qt5Widgets REQUIRED) | ||||
|  | ||||
| include_directories(${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS}) | ||||
|  | ||||
| set(SOURCES | ||||
| 	main.cpp | ||||
| 	main.h | ||||
|  | ||||
| 	GroupView.h | ||||
| 	GroupView.cpp | ||||
| 	Group.h | ||||
| 	Group.cpp | ||||
| 	GroupedProxyModel.h | ||||
| 	GroupedProxyModel.cpp | ||||
| 	InstanceDelegate.h | ||||
| 	InstanceDelegate.cpp | ||||
| ) | ||||
|  | ||||
| add_executable(GroupView ${SOURCES}) | ||||
| qt5_use_modules(GroupView Core Gui Widgets) | ||||
							
								
								
									
										169
									
								
								depends/groupview/Group.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								depends/groupview/Group.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,169 @@ | ||||
| #include "Group.h" | ||||
|  | ||||
| #include <QModelIndex> | ||||
| #include <QPainter> | ||||
| #include <QtMath> | ||||
|  | ||||
| #include "GroupView.h" | ||||
|  | ||||
| Group::Group(const QString &text, GroupView *view) : view(view), text(text), collapsed(false) | ||||
| { | ||||
| } | ||||
|  | ||||
| Group::Group(const Group *other) | ||||
| 	: view(other->view), text(other->text), collapsed(other->collapsed) | ||||
| { | ||||
| } | ||||
|  | ||||
| void Group::update() | ||||
| { | ||||
| 	firstItemIndex = firstItem().row(); | ||||
|  | ||||
| 	rowHeights = QVector<int>(numRows()); | ||||
| 	for (int i = 0; i < numRows(); ++i) | ||||
| 	{ | ||||
| 		rowHeights[i] = view->categoryRowHeight( | ||||
| 			view->model()->index(i * view->itemsPerRow() + firstItemIndex, 0)); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| Group::HitResults Group::hitScan(const QPoint &pos) const | ||||
| { | ||||
| 	Group::HitResults results = Group::NoHit; | ||||
| 	int y_start = verticalPosition(); | ||||
| 	int body_start = y_start + headerHeight(); | ||||
| 	int body_end = body_start + contentHeight() + 5; // FIXME: wtf is this 5? | ||||
| 	int y = pos.y(); | ||||
| 	// int x = pos.x(); | ||||
| 	if(y < y_start) | ||||
| 	{ | ||||
| 		results = Group::NoHit; | ||||
| 	} | ||||
| 	else if(y < body_start) | ||||
| 	{ | ||||
| 		results = Group::HeaderHit; | ||||
| 		int collapseSize = headerHeight() - 4; | ||||
|  | ||||
| 		// the icon | ||||
| 		QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y_start, collapseSize, collapseSize); | ||||
| 		if(iconRect.contains(pos)) | ||||
| 		{ | ||||
| 			results |= Group::CheckboxHit; | ||||
| 		} | ||||
| 	} | ||||
| 	else if (y < body_end) | ||||
| 	{ | ||||
| 		results |= Group::BodyHit; | ||||
| 	} | ||||
| 	return results; | ||||
| } | ||||
|  | ||||
| void Group::drawHeader(QPainter *painter, const int y) | ||||
| { | ||||
| 	painter->save(); | ||||
|  | ||||
| 	int height = headerHeight() - 4; | ||||
| 	int collapseSize = height; | ||||
|  | ||||
| 	// the icon | ||||
| 	QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y, collapseSize, collapseSize); | ||||
| 	painter->setPen(QPen(Qt::black, 1)); | ||||
| 	painter->drawRect(iconRect); | ||||
| 	static const int margin = 2; | ||||
| 	QRect iconSubrect = iconRect.adjusted(margin, margin, -margin, -margin); | ||||
| 	int midX = iconSubrect.center().x(); | ||||
| 	int midY = iconSubrect.center().y(); | ||||
| 	if (collapsed) | ||||
| 	{ | ||||
| 		painter->drawLine(midX, iconSubrect.top(), midX, iconSubrect.bottom()); | ||||
| 	} | ||||
| 	painter->drawLine(iconSubrect.left(), midY, iconSubrect.right(), midY); | ||||
|  | ||||
| 	// the text | ||||
| 	int textWidth = painter->fontMetrics().width(text); | ||||
| 	QRect textRect = QRect(iconRect.right() + 4, y, textWidth, headerHeight()); | ||||
| 	painter->setBrush(view->viewOptions().palette.text()); | ||||
| 	view->style()->drawItemText(painter, textRect, Qt::AlignHCenter | Qt::AlignVCenter, | ||||
| 								view->viewport()->palette(), true, text); | ||||
|  | ||||
| 	// the line | ||||
| 	painter->drawLine(textRect.right() + 4, y + headerHeight() / 2, | ||||
| 					  view->contentWidth() - view->m_rightMargin, y + headerHeight() / 2); | ||||
|  | ||||
| 	painter->restore(); | ||||
| } | ||||
|  | ||||
| int Group::totalHeight() const | ||||
| { | ||||
| 	return headerHeight() + 5 + contentHeight(); // FIXME: wtf is that '5'? | ||||
| } | ||||
|  | ||||
| int Group::headerHeight() const | ||||
| { | ||||
| 	return view->viewport()->fontMetrics().height() + 4; | ||||
| } | ||||
|  | ||||
| int Group::contentHeight() const | ||||
| { | ||||
| 	if (collapsed) | ||||
| 	{ | ||||
| 		return 0; | ||||
| 	} | ||||
| 	int result = 0; | ||||
| 	for (int i = 0; i < rowHeights.size(); ++i) | ||||
| 	{ | ||||
| 		result += rowHeights[i]; | ||||
| 	} | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| int Group::numRows() const | ||||
| { | ||||
| 	return qMax(1, qCeil((qreal)numItems() / (qreal)view->itemsPerRow())); | ||||
| } | ||||
|  | ||||
| int Group::verticalPosition() const | ||||
| { | ||||
| 	int res = 0; | ||||
| 	const QList<Group *> cats = view->m_groups; | ||||
| 	for (int i = 0; i < cats.size(); ++i) | ||||
| 	{ | ||||
| 		if (cats.at(i) == this) | ||||
| 		{ | ||||
| 			break; | ||||
| 		} | ||||
| 		res += cats.at(i)->totalHeight() + view->m_categoryMargin; | ||||
| 	} | ||||
| 	return res; | ||||
| } | ||||
|  | ||||
| QList<QModelIndex> Group::items() const | ||||
| { | ||||
| 	QList<QModelIndex> indices; | ||||
| 	for (int i = 0; i < view->model()->rowCount(); ++i) | ||||
| 	{ | ||||
| 		const QModelIndex index = view->model()->index(i, 0); | ||||
| 		if (index.data(GroupViewRoles::GroupRole).toString() == text) | ||||
| 		{ | ||||
| 			indices.append(index); | ||||
| 		} | ||||
| 	} | ||||
| 	return indices; | ||||
| } | ||||
|  | ||||
| int Group::numItems() const | ||||
| { | ||||
| 	return items().size(); | ||||
| } | ||||
|  | ||||
| QModelIndex Group::firstItem() const | ||||
| { | ||||
| 	QList<QModelIndex> indices = items(); | ||||
| 	return indices.isEmpty() ? QModelIndex() : indices.first(); | ||||
| } | ||||
|  | ||||
| QModelIndex Group::lastItem() const | ||||
| { | ||||
| 	QList<QModelIndex> indices = items(); | ||||
| 	return indices.isEmpty() ? QModelIndex() : indices.last(); | ||||
| } | ||||
							
								
								
									
										67
									
								
								depends/groupview/Group.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								depends/groupview/Group.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <QString> | ||||
| #include <QRect> | ||||
| #include <QVector> | ||||
|  | ||||
| class GroupView; | ||||
| class QPainter; | ||||
| class QModelIndex; | ||||
|  | ||||
| struct Group | ||||
| { | ||||
| /* constructors */ | ||||
| 	Group(const QString &text, GroupView *view); | ||||
| 	Group(const Group *other); | ||||
|  | ||||
| /* data */ | ||||
| 	GroupView *view; | ||||
| 	QString text; | ||||
| 	bool collapsed; | ||||
| 	QVector<int> rowHeights; | ||||
| 	int firstItemIndex; | ||||
|  | ||||
| /* logic */ | ||||
| 	/// do stuff. and things. TODO: redo. | ||||
| 	void update(); | ||||
|  | ||||
| 	/// draw the header at y-position. | ||||
| 	void drawHeader(QPainter *painter, const int y); | ||||
|  | ||||
| 	/// height of the group, in total. includes a small bit of padding. | ||||
| 	int totalHeight() const; | ||||
|  | ||||
| 	/// height of the group header, in pixels | ||||
| 	int headerHeight() const; | ||||
|  | ||||
| 	/// height of the group content, in pixels | ||||
| 	int contentHeight() const; | ||||
|  | ||||
| 	/// the number of visual rows this group has | ||||
| 	int numRows() const; | ||||
|  | ||||
| 	/// the height at which this group starts, in pixels | ||||
| 	int verticalPosition() const; | ||||
|  | ||||
| 	enum HitResult | ||||
| 	{ | ||||
| 		NoHit = 0x0, | ||||
| 		TextHit = 0x1, | ||||
| 		CheckboxHit = 0x2, | ||||
| 		HeaderHit = 0x4, | ||||
| 		BodyHit = 0x8 | ||||
| 	}; | ||||
| 	Q_DECLARE_FLAGS(HitResults, HitResult) | ||||
|  | ||||
| 	/// shoot! BANG! what did we hit? | ||||
| 	HitResults hitScan (const QPoint &pos) const; | ||||
|  | ||||
| 	/// super derpy thing. | ||||
| 	QList<QModelIndex> items() const; | ||||
| 	/// I don't even | ||||
| 	int numItems() const; | ||||
| 	QModelIndex firstItem() const; | ||||
| 	QModelIndex lastItem() const; | ||||
| }; | ||||
|  | ||||
| Q_DECLARE_OPERATORS_FOR_FLAGS(Group::HitResults) | ||||
							
								
								
									
										908
									
								
								depends/groupview/GroupView.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										908
									
								
								depends/groupview/GroupView.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,908 @@ | ||||
| #include "GroupView.h" | ||||
|  | ||||
| #include <QPainter> | ||||
| #include <QApplication> | ||||
| #include <QtMath> | ||||
| #include <QDebug> | ||||
| #include <QMouseEvent> | ||||
| #include <QListView> | ||||
| #include <QPersistentModelIndex> | ||||
| #include <QDrag> | ||||
| #include <QMimeData> | ||||
| #include <QScrollBar> | ||||
|  | ||||
| #include "Group.h" | ||||
|  | ||||
| template <typename T> bool listsIntersect(const QList<T> &l1, const QList<T> t2) | ||||
| { | ||||
| 	for (auto &item : l1) | ||||
| 	{ | ||||
| 		if (t2.contains(item)) | ||||
| 		{ | ||||
| 			return true; | ||||
| 		} | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| GroupView::GroupView(QWidget *parent) | ||||
| 	: QAbstractItemView(parent), m_leftMargin(5), m_rightMargin(5), m_bottomMargin(5), | ||||
| 	  m_categoryMargin(5) //, m_updatesDisabled(false), m_categoryEditor(0), m_editedCategory(0) | ||||
| { | ||||
| 	// setViewMode(IconMode); | ||||
| 	// setMovement(Snap); | ||||
| 	setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); | ||||
| 	setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); | ||||
| 	// setWordWrap(true); | ||||
| 	// setDragDropMode(QListView::InternalMove); | ||||
| 	setAcceptDrops(true); | ||||
| 	m_spacing = 5; | ||||
| } | ||||
|  | ||||
| GroupView::~GroupView() | ||||
| { | ||||
| 	qDeleteAll(m_groups); | ||||
| 	m_groups.clear(); | ||||
| } | ||||
|  | ||||
| void GroupView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, | ||||
| 							const QVector<int> &roles) | ||||
| { | ||||
| 	if (roles.contains(GroupViewRoles::GroupRole) || roles.contains(Qt::DisplayRole)) | ||||
| 	{ | ||||
| 		updateGeometries(); | ||||
| 	} | ||||
| 	viewport()->update(); | ||||
| } | ||||
| void GroupView::rowsInserted(const QModelIndex &parent, int start, int end) | ||||
| { | ||||
| 	updateGeometries(); | ||||
| 	viewport()->update(); | ||||
| } | ||||
|  | ||||
| void GroupView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) | ||||
| { | ||||
| 	updateGeometries(); | ||||
| 	viewport()->update(); | ||||
| } | ||||
|  | ||||
| void GroupView::updateGeometries() | ||||
| { | ||||
| 	int previousScroll = verticalScrollBar()->value(); | ||||
|  | ||||
| 	QMap<QString, Group *> cats; | ||||
|  | ||||
| 	for (int i = 0; i < model()->rowCount(); ++i) | ||||
| 	{ | ||||
| 		const QString groupName = | ||||
| 			model()->index(i, 0).data(GroupViewRoles::GroupRole).toString(); | ||||
| 		if (!cats.contains(groupName)) | ||||
| 		{ | ||||
| 			Group *old = this->category(groupName); | ||||
| 			if (old) | ||||
| 			{ | ||||
| 				cats.insert(groupName, new Group(old)); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				cats.insert(groupName, new Group(groupName, this)); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/*if (m_editedCategory) | ||||
| 	{ | ||||
| 		m_editedCategory = cats[m_editedCategory->text]; | ||||
| 	}*/ | ||||
|  | ||||
| 	qDeleteAll(m_groups); | ||||
| 	m_groups = cats.values(); | ||||
|  | ||||
| 	for (auto cat : m_groups) | ||||
| 	{ | ||||
| 		cat->update(); | ||||
| 	} | ||||
|  | ||||
| 	if (m_groups.isEmpty()) | ||||
| 	{ | ||||
| 		verticalScrollBar()->setRange(0, 0); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		int totalHeight = 0; | ||||
| 		for (auto category : m_groups) | ||||
| 		{ | ||||
| 			totalHeight += category->totalHeight() + m_categoryMargin; | ||||
| 		} | ||||
| 		// remove the last margin (we don't want it) | ||||
| 		totalHeight -= m_categoryMargin; | ||||
| 		totalHeight += m_bottomMargin; | ||||
| 		verticalScrollBar()->setRange(0, totalHeight - height()); | ||||
| 	} | ||||
|  | ||||
| 	verticalScrollBar()->setValue(qMin(previousScroll, verticalScrollBar()->maximum())); | ||||
|  | ||||
| 	viewport()->update(); | ||||
| } | ||||
|  | ||||
| bool GroupView::isIndexHidden(const QModelIndex &index) const | ||||
| { | ||||
| 	Group *cat = category(index); | ||||
| 	if (cat) | ||||
| 	{ | ||||
| 		return cat->collapsed; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		return false; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| Group *GroupView::category(const QModelIndex &index) const | ||||
| { | ||||
| 	return category(index.data(GroupViewRoles::GroupRole).toString()); | ||||
| } | ||||
|  | ||||
| Group *GroupView::category(const QString &cat) const | ||||
| { | ||||
| 	for (auto group : m_groups) | ||||
| 	{ | ||||
| 		if (group->text == cat) | ||||
| 		{ | ||||
| 			return group; | ||||
| 		} | ||||
| 	} | ||||
| 	return nullptr; | ||||
| } | ||||
|  | ||||
| Group *GroupView::categoryAt(const QPoint &pos) const | ||||
| { | ||||
| 	for (auto group : m_groups) | ||||
| 	{ | ||||
| 		if(group->hitScan(pos) & Group::CheckboxHit) | ||||
| 		{ | ||||
| 			return group; | ||||
| 		} | ||||
| 	} | ||||
| 	return nullptr; | ||||
| } | ||||
|  | ||||
| int GroupView::itemsPerRow() const | ||||
| { | ||||
| 	return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + m_spacing)); | ||||
| } | ||||
|  | ||||
| int GroupView::contentWidth() const | ||||
| { | ||||
| 	return width() - m_leftMargin - m_rightMargin; | ||||
| } | ||||
|  | ||||
| int GroupView::itemWidth() const | ||||
| { | ||||
| 	return itemDelegate() | ||||
| 		->sizeHint(viewOptions(), model()->index(model()->rowCount() - 1, 0)) | ||||
| 		.width(); | ||||
| } | ||||
|  | ||||
| int GroupView::categoryRowHeight(const QModelIndex &index) const | ||||
| { | ||||
| 	QModelIndexList indices; | ||||
| 	int internalRow = categoryInternalPosition(index).second; | ||||
| 	for (auto &i : category(index)->items()) | ||||
| 	{ | ||||
| 		if (categoryInternalPosition(i).second == internalRow) | ||||
| 		{ | ||||
| 			indices.append(i); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	int largestHeight = 0; | ||||
| 	for (auto &i : indices) | ||||
| 	{ | ||||
| 		largestHeight = | ||||
| 			qMax(largestHeight, itemDelegate()->sizeHint(viewOptions(), i).height()); | ||||
| 	} | ||||
| 	return largestHeight; | ||||
| } | ||||
|  | ||||
| QPair<int, int> GroupView::categoryInternalPosition(const QModelIndex &index) const | ||||
| { | ||||
| 	QList<QModelIndex> indices = category(index)->items(); | ||||
| 	int x = 0; | ||||
| 	int y = 0; | ||||
| 	const int perRow = itemsPerRow(); | ||||
| 	for (int i = 0; i < indices.size(); ++i) | ||||
| 	{ | ||||
| 		if (indices.at(i) == index) | ||||
| 		{ | ||||
| 			break; | ||||
| 		} | ||||
| 		++x; | ||||
| 		if (x == perRow) | ||||
| 		{ | ||||
| 			x = 0; | ||||
| 			++y; | ||||
| 		} | ||||
| 	} | ||||
| 	return qMakePair(x, y); | ||||
| } | ||||
|  | ||||
| int GroupView::categoryInternalRowTop(const QModelIndex &index) const | ||||
| { | ||||
| 	Group *cat = category(index); | ||||
| 	int categoryInternalRow = categoryInternalPosition(index).second; | ||||
| 	int result = 0; | ||||
| 	for (int i = 0; i < categoryInternalRow; ++i) | ||||
| 	{ | ||||
| 		result += cat->rowHeights.at(i); | ||||
| 	} | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| int GroupView::itemHeightForCategoryRow(const Group *category, const int internalRow) const | ||||
| { | ||||
| 	for (auto &i : category->items()) | ||||
| 	{ | ||||
| 		QPair<int, int> pos = categoryInternalPosition(i); | ||||
| 		if (pos.second == internalRow) | ||||
| 		{ | ||||
| 			return categoryRowHeight(i); | ||||
| 		} | ||||
| 	} | ||||
| 	return -1; | ||||
| } | ||||
|  | ||||
| void GroupView::mousePressEvent(QMouseEvent *event) | ||||
| { | ||||
| 	// endCategoryEditor(); | ||||
|  | ||||
| 	QPoint pos = event->pos() + offset(); | ||||
| 	QPersistentModelIndex index = indexAt(pos); | ||||
|  | ||||
| 	m_pressedIndex = index; | ||||
| 	m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex); | ||||
| 	QItemSelectionModel::SelectionFlags selection_flags = selectionCommand(index, event); | ||||
| 	m_pressedPosition = pos; | ||||
|  | ||||
| 	m_pressedCategory = categoryAt(m_pressedPosition); | ||||
| 	if (m_pressedCategory) | ||||
| 	{ | ||||
| 		setState(m_pressedCategory->collapsed ? ExpandingState : CollapsingState); | ||||
| 		event->accept(); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	if (index.isValid() && (index.flags() & Qt::ItemIsEnabled)) | ||||
| 	{ | ||||
| 		// we disable scrollTo for mouse press so the item doesn't change position | ||||
| 		// when the user is interacting with it (ie. clicking on it) | ||||
| 		bool autoScroll = hasAutoScroll(); | ||||
| 		setAutoScroll(false); | ||||
| 		selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); | ||||
| 		setAutoScroll(autoScroll); | ||||
| 		QRect rect(m_pressedPosition, pos); | ||||
| 		setSelection(rect, QItemSelectionModel::ClearAndSelect); | ||||
|  | ||||
| 		// signal handlers may change the model | ||||
| 		emit pressed(index); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		// Forces a finalize() even if mouse is pressed, but not on a item | ||||
| 		selectionModel()->select(QModelIndex(), QItemSelectionModel::Select); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void GroupView::mouseMoveEvent(QMouseEvent *event) | ||||
| { | ||||
| 	QPoint topLeft; | ||||
| 	QPoint pos = event->pos() + offset(); | ||||
|  | ||||
| 	if (state() == ExpandingState || state() == CollapsingState) | ||||
| 	{ | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	if (state() == DraggingState) | ||||
| 	{ | ||||
| 		topLeft = m_pressedPosition - offset(); | ||||
| 		if ((topLeft - event->pos()).manhattanLength() > QApplication::startDragDistance()) | ||||
| 		{ | ||||
| 			m_pressedIndex = QModelIndex(); | ||||
| 			startDrag(model()->supportedDragActions()); | ||||
| 			setState(NoState); | ||||
| 			stopAutoScroll(); | ||||
| 		} | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	if (selectionMode() != SingleSelection) | ||||
| 	{ | ||||
| 		topLeft = m_pressedPosition - offset(); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		topLeft = pos; | ||||
| 	} | ||||
|  | ||||
| 	if (m_pressedIndex.isValid() && (state() != DragSelectingState) && | ||||
| 		(event->buttons() != Qt::NoButton) && !selectedIndexes().isEmpty()) | ||||
| 	{ | ||||
| 		setState(DraggingState); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	if ((event->buttons() & Qt::LeftButton) && selectionModel()) | ||||
| 	{ | ||||
| 		setState(DragSelectingState); | ||||
|  | ||||
| 		setSelection(QRect(pos, pos), QItemSelectionModel::ClearAndSelect); | ||||
| 		QModelIndex index = indexAt(pos); | ||||
|  | ||||
| 		// set at the end because it might scroll the view | ||||
| 		if (index.isValid() && (index != selectionModel()->currentIndex()) && | ||||
| 			(index.flags() & Qt::ItemIsEnabled)) | ||||
| 		{ | ||||
| 			selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void GroupView::mouseReleaseEvent(QMouseEvent *event) | ||||
| { | ||||
| 	QPoint pos = event->pos() + offset(); | ||||
| 	QPersistentModelIndex index = indexAt(pos); | ||||
|  | ||||
| 	bool click = (index == m_pressedIndex && index.isValid()) || | ||||
| 				 (m_pressedCategory && m_pressedCategory == categoryAt(pos)); | ||||
|  | ||||
| 	if (click && m_pressedCategory) | ||||
| 	{ | ||||
| 		if (state() == ExpandingState) | ||||
| 		{ | ||||
| 			m_pressedCategory->collapsed = false; | ||||
| 			updateGeometries(); | ||||
| 			viewport()->update(); | ||||
| 			event->accept(); | ||||
| 			return; | ||||
| 		} | ||||
| 		else if (state() == CollapsingState) | ||||
| 		{ | ||||
| 			m_pressedCategory->collapsed = true; | ||||
| 			updateGeometries(); | ||||
| 			viewport()->update(); | ||||
| 			event->accept(); | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	m_ctrlDragSelectionFlag = QItemSelectionModel::NoUpdate; | ||||
|  | ||||
| 	setState(NoState); | ||||
|  | ||||
| 	if (click) | ||||
| 	{ | ||||
| 		if (event->button() == Qt::LeftButton) | ||||
| 		{ | ||||
| 			emit clicked(index); | ||||
| 		} | ||||
| 		QStyleOptionViewItem option = viewOptions(); | ||||
| 		if (m_pressedAlreadySelected) | ||||
| 		{ | ||||
| 			option.state |= QStyle::State_Selected; | ||||
| 		} | ||||
| 		if ((model()->flags(index) & Qt::ItemIsEnabled) && | ||||
| 			style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this)) | ||||
| 		{ | ||||
| 			emit activated(index); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void GroupView::mouseDoubleClickEvent(QMouseEvent *event) | ||||
| { | ||||
| 	QModelIndex index = indexAt(event->pos()); | ||||
| 	if (!index.isValid() || !(index.flags() & Qt::ItemIsEnabled) || (m_pressedIndex != index)) | ||||
| 	{ | ||||
| 		QMouseEvent me(QEvent::MouseButtonPress, event->localPos(), event->windowPos(), | ||||
| 					   event->screenPos(), event->button(), event->buttons(), | ||||
| 					   event->modifiers()); | ||||
| 		mousePressEvent(&me); | ||||
| 		return; | ||||
| 	} | ||||
| 	// signal handlers may change the model | ||||
| 	QPersistentModelIndex persistent = index; | ||||
| 	emit doubleClicked(persistent); | ||||
| } | ||||
|  | ||||
| void GroupView::paintEvent(QPaintEvent *event) | ||||
| { | ||||
| 	QPainter painter(this->viewport()); | ||||
|  | ||||
| 	int y = -verticalOffset(); | ||||
| 	for (int i = 0; i < m_groups.size(); ++i) | ||||
| 	{ | ||||
| 		Group *category = m_groups.at(i); | ||||
| 		category->drawHeader(&painter, y); | ||||
| 		y += category->totalHeight() + m_categoryMargin; | ||||
| 	} | ||||
|  | ||||
| 	for (int i = 0; i < model()->rowCount(); ++i) | ||||
| 	{ | ||||
| 		const QModelIndex index = model()->index(i, 0); | ||||
| 		if (isIndexHidden(index)) | ||||
| 		{ | ||||
| 			continue; | ||||
| 		} | ||||
| 		Qt::ItemFlags flags = index.flags(); | ||||
| 		QStyleOptionViewItemV4 option(viewOptions()); | ||||
| 		option.rect = visualRect(index); | ||||
| 		option.widget = this; | ||||
| 		option.features |= | ||||
| 			QStyleOptionViewItemV2::WrapText; // FIXME: what is the meaning of this anyway? | ||||
| 		if (flags & Qt::ItemIsSelectable && selectionModel()->isSelected(index)) | ||||
| 		{ | ||||
| 			option.state |= selectionModel()->isSelected(index) ? QStyle::State_Selected | ||||
| 																: QStyle::State_None; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			option.state &= ~QStyle::State_Selected; | ||||
| 		} | ||||
| 		option.state |= (index == currentIndex()) ? QStyle::State_HasFocus : QStyle::State_None; | ||||
| 		if (!(flags & Qt::ItemIsEnabled)) | ||||
| 		{ | ||||
| 			option.state &= ~QStyle::State_Enabled; | ||||
| 		} | ||||
| 		itemDelegate()->paint(&painter, option, index); | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| 	 * Drop indicators for manual reordering... | ||||
| 	 */ | ||||
| #if 0 | ||||
| 	if (!m_lastDragPosition.isNull()) | ||||
| 	{ | ||||
| 		QPair<Group *, int> pair = rowDropPos(m_lastDragPosition); | ||||
| 		Group *category = pair.first; | ||||
| 		int row = pair.second; | ||||
| 		if (category) | ||||
| 		{ | ||||
| 			int internalRow = row - category->firstItemIndex; | ||||
| 			QLine line; | ||||
| 			if (internalRow >= category->numItems()) | ||||
| 			{ | ||||
| 				QRect toTheRightOfRect = visualRect(category->lastItem()); | ||||
| 				line = QLine(toTheRightOfRect.topRight(), toTheRightOfRect.bottomRight()); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				QRect toTheLeftOfRect = visualRect(model()->index(row, 0)); | ||||
| 				line = QLine(toTheLeftOfRect.topLeft(), toTheLeftOfRect.bottomLeft()); | ||||
| 			} | ||||
| 			painter.save(); | ||||
| 			painter.setPen(QPen(Qt::black, 3)); | ||||
| 			painter.drawLine(line); | ||||
| 			painter.restore(); | ||||
| 		} | ||||
| 	} | ||||
| #endif | ||||
| } | ||||
|  | ||||
| void GroupView::resizeEvent(QResizeEvent *event) | ||||
| { | ||||
| 	// QListView::resizeEvent(event); | ||||
|  | ||||
| 	//	if (m_categoryEditor) | ||||
| 	//	{ | ||||
| 	//		m_categoryEditor->resize(qMax(contentWidth() / 2, | ||||
| 	// m_editedCategory->textRect.width()), | ||||
| 	// m_categoryEditor->height()); | ||||
| 	//	} | ||||
|  | ||||
| 	updateGeometries(); | ||||
| } | ||||
|  | ||||
| void GroupView::dragEnterEvent(QDragEnterEvent *event) | ||||
| { | ||||
| 	if (!isDragEventAccepted(event)) | ||||
| 	{ | ||||
| 		return; | ||||
| 	} | ||||
| 	m_lastDragPosition = event->pos() + offset(); | ||||
| 	viewport()->update(); | ||||
| 	event->accept(); | ||||
| } | ||||
|  | ||||
| void GroupView::dragMoveEvent(QDragMoveEvent *event) | ||||
| { | ||||
| 	if (!isDragEventAccepted(event)) | ||||
| 	{ | ||||
| 		return; | ||||
| 	} | ||||
| 	m_lastDragPosition = event->pos() + offset(); | ||||
| 	viewport()->update(); | ||||
| 	event->accept(); | ||||
| } | ||||
|  | ||||
| void GroupView::dragLeaveEvent(QDragLeaveEvent *event) | ||||
| { | ||||
| 	m_lastDragPosition = QPoint(); | ||||
| 	viewport()->update(); | ||||
| } | ||||
|  | ||||
| void GroupView::dropEvent(QDropEvent *event) | ||||
| { | ||||
| 	m_lastDragPosition = QPoint(); | ||||
|  | ||||
| 	stopAutoScroll(); | ||||
| 	setState(NoState); | ||||
|  | ||||
| 	if (event->source() != this || !(event->possibleActions() & Qt::MoveAction)) | ||||
| 	{ | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	QPair<Group *, int> dropPos = rowDropPos(event->pos() + offset()); | ||||
| 	const Group *category = dropPos.first; | ||||
| 	const int row = dropPos.second; | ||||
|  | ||||
| 	if (row == -1) | ||||
| 	{ | ||||
| 		viewport()->update(); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	const QString categoryText = category->text; | ||||
| 	if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex())) | ||||
| 	{ | ||||
| 		model()->setData(model()->index(row, 0), categoryText, | ||||
| 						 GroupViewRoles::GroupRole); | ||||
| 		event->setDropAction(Qt::MoveAction); | ||||
| 		event->accept(); | ||||
| 	} | ||||
| 	updateGeometries(); | ||||
| 	viewport()->update(); | ||||
| } | ||||
|  | ||||
| void GroupView::startDrag(Qt::DropActions supportedActions) | ||||
| { | ||||
| 	QModelIndexList indexes = selectionModel()->selectedIndexes(); | ||||
| 	if (indexes.count() > 0) | ||||
| 	{ | ||||
| 		QMimeData *data = model()->mimeData(indexes); | ||||
| 		if (!data) | ||||
| 		{ | ||||
| 			return; | ||||
| 		} | ||||
| 		QRect rect; | ||||
| 		QPixmap pixmap = renderToPixmap(indexes, &rect); | ||||
| 		//rect.translate(offset()); | ||||
| 		// rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); | ||||
| 		QDrag *drag = new QDrag(this); | ||||
| 		drag->setPixmap(pixmap); | ||||
| 		drag->setMimeData(data); | ||||
| 		drag->setHotSpot(m_pressedPosition - rect.topLeft()); | ||||
| 		Qt::DropAction defaultDropAction = Qt::IgnoreAction; | ||||
| 		if (this->defaultDropAction() != Qt::IgnoreAction && | ||||
| 			(supportedActions & this->defaultDropAction())) | ||||
| 		{ | ||||
| 			defaultDropAction = this->defaultDropAction(); | ||||
| 		} | ||||
| 		if (drag->exec(supportedActions, defaultDropAction) == Qt::MoveAction) | ||||
| 		{ | ||||
| 			const QItemSelection selection = selectionModel()->selection(); | ||||
|  | ||||
| 			for (auto it = selection.constBegin(); it != selection.constEnd(); ++it) | ||||
| 			{ | ||||
| 				QModelIndex parent = (*it).parent(); | ||||
| 				if ((*it).left() != 0) | ||||
| 				{ | ||||
| 					continue; | ||||
| 				} | ||||
| 				if ((*it).right() != (model()->columnCount(parent) - 1)) | ||||
| 				{ | ||||
| 					continue; | ||||
| 				} | ||||
| 				int count = (*it).bottom() - (*it).top() + 1; | ||||
| 				model()->removeRows((*it).top(), count, parent); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| QRect GroupView::visualRect(const QModelIndex &index) const | ||||
| { | ||||
| 	return geometryRect(index).translated(-offset()); | ||||
| } | ||||
|  | ||||
| QRect GroupView::geometryRect(const QModelIndex &index) const | ||||
| { | ||||
| 	if (!index.isValid() || isIndexHidden(index) || index.column() > 0) | ||||
| 	{ | ||||
| 		return QRect(); | ||||
| 	} | ||||
|  | ||||
| 	const Group *cat = category(index); | ||||
| 	QPair<int, int> pos = categoryInternalPosition(index); | ||||
| 	int x = pos.first; | ||||
| 	// int y = pos.second; | ||||
|  | ||||
| 	QRect out; | ||||
| 	out.setTop(cat->verticalPosition() + cat->headerHeight() + 5 + categoryInternalRowTop(index)); | ||||
| 	out.setLeft(m_spacing + x * (itemWidth() + m_spacing)); | ||||
| 	out.setSize(itemDelegate()->sizeHint(viewOptions(), index)); | ||||
|  | ||||
| 	return out; | ||||
| } | ||||
|  | ||||
| /* | ||||
| void CategorizedView::startCategoryEditor(Category *category) | ||||
| { | ||||
| 	if (m_categoryEditor != 0) | ||||
| 	{ | ||||
| 		return; | ||||
| 	} | ||||
| 	m_editedCategory = category; | ||||
| 	m_categoryEditor = new QLineEdit(m_editedCategory->text, this); | ||||
| 	QRect rect = m_editedCategory->textRect; | ||||
| 	rect.setWidth(qMax(contentWidth() / 2, rect.width())); | ||||
| 	m_categoryEditor->setGeometry(rect); | ||||
| 	m_categoryEditor->show(); | ||||
| 	m_categoryEditor->setFocus(); | ||||
| 	connect(m_categoryEditor, &QLineEdit::returnPressed, this, | ||||
| &CategorizedView::endCategoryEditor); | ||||
| } | ||||
|  | ||||
| void CategorizedView::endCategoryEditor() | ||||
| { | ||||
| 	if (m_categoryEditor == 0) | ||||
| 	{ | ||||
| 		return; | ||||
| 	} | ||||
| 	m_editedCategory->text = m_categoryEditor->text(); | ||||
| 	m_updatesDisabled = true; | ||||
| 	foreach (const QModelIndex &index, itemsForCategory(m_editedCategory)) | ||||
| 	{ | ||||
| 		const_cast<QAbstractItemModel *>(index.model())->setData(index, | ||||
| m_categoryEditor->text(), CategoryRole); | ||||
| 	} | ||||
| 	m_updatesDisabled = false; | ||||
| 	delete m_categoryEditor; | ||||
| 	m_categoryEditor = 0; | ||||
| 	m_editedCategory = 0; | ||||
| 	updateGeometries(); | ||||
| } | ||||
| */ | ||||
|  | ||||
| QModelIndex GroupView::indexAt(const QPoint &point) const | ||||
| { | ||||
| 	for (int i = 0; i < model()->rowCount(); ++i) | ||||
| 	{ | ||||
| 		QModelIndex index = model()->index(i, 0); | ||||
| 		if (geometryRect(index).contains(point)) | ||||
| 		{ | ||||
| 			return index; | ||||
| 		} | ||||
| 	} | ||||
| 	return QModelIndex(); | ||||
| } | ||||
|  | ||||
| void GroupView::setSelection(const QRect &rect, | ||||
| 							 const QItemSelectionModel::SelectionFlags commands) | ||||
| { | ||||
| 	for (int i = 0; i < model()->rowCount(); ++i) | ||||
| 	{ | ||||
| 		QModelIndex index = model()->index(i, 0); | ||||
| 		QRect itemRect = geometryRect(index); | ||||
| 		if (itemRect.intersects(rect)) | ||||
| 		{ | ||||
| 			selectionModel()->select(index, commands); | ||||
| 			update(itemRect.translated(-offset())); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| } | ||||
|  | ||||
| QPixmap GroupView::renderToPixmap(const QModelIndexList &indices, QRect *r) const | ||||
| { | ||||
| 	Q_ASSERT(r); | ||||
| 	auto paintPairs = draggablePaintPairs(indices, r); | ||||
| 	if (paintPairs.isEmpty()) | ||||
| 	{ | ||||
| 		return QPixmap(); | ||||
| 	} | ||||
| 	QPixmap pixmap(r->size()); | ||||
| 	pixmap.fill(Qt::transparent); | ||||
| 	QPainter painter(&pixmap); | ||||
| 	QStyleOptionViewItem option = viewOptions(); | ||||
| 	option.state |= QStyle::State_Selected; | ||||
| 	for (int j = 0; j < paintPairs.count(); ++j) | ||||
| 	{ | ||||
| 		option.rect = paintPairs.at(j).first.translated(-r->topLeft()); | ||||
| 		const QModelIndex ¤t = paintPairs.at(j).second; | ||||
| 		itemDelegate()->paint(&painter, option, current); | ||||
| 	} | ||||
| 	return pixmap; | ||||
| } | ||||
|  | ||||
| QList<QPair<QRect, QModelIndex>> GroupView::draggablePaintPairs(const QModelIndexList &indices, | ||||
| 																QRect *r) const | ||||
| { | ||||
| 	Q_ASSERT(r); | ||||
| 	QRect &rect = *r; | ||||
| 	QList<QPair<QRect, QModelIndex>> ret; | ||||
| 	for (int i = 0; i < indices.count(); ++i) | ||||
| 	{ | ||||
| 		const QModelIndex &index = indices.at(i); | ||||
| 		const QRect current = geometryRect(index); | ||||
| 		ret += qMakePair(current, index); | ||||
| 		rect |= current; | ||||
| 	} | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| bool GroupView::isDragEventAccepted(QDropEvent *event) | ||||
| { | ||||
| 	if (event->source() != this) | ||||
| 	{ | ||||
| 		return false; | ||||
| 	} | ||||
| 	if (!listsIntersect(event->mimeData()->formats(), model()->mimeTypes())) | ||||
| 	{ | ||||
| 		return false; | ||||
| 	} | ||||
| 	if (!model()->canDropMimeData(event->mimeData(), event->dropAction(), | ||||
| 								  rowDropPos(event->pos()).second, 0, QModelIndex())) | ||||
| 	{ | ||||
| 		return false; | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| QPair<Group *, int> GroupView::rowDropPos(const QPoint &pos) | ||||
| { | ||||
| 	// check that we aren't on a category header and calculate which category we're in | ||||
| 	Group *category = 0; | ||||
| 	{ | ||||
| 		int y = 0; | ||||
| 		for (auto cat : m_groups) | ||||
| 		{ | ||||
| 			if (pos.y() > y && pos.y() < (y + cat->headerHeight())) | ||||
| 			{ | ||||
| 				return qMakePair(nullptr, -1); | ||||
| 			} | ||||
| 			y += cat->totalHeight() + m_categoryMargin; | ||||
| 			if (pos.y() < y) | ||||
| 			{ | ||||
| 				category = cat; | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 		if (category == 0) | ||||
| 		{ | ||||
| 			return qMakePair(nullptr, -1); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	QList<QModelIndex> indices = category->items(); | ||||
|  | ||||
| 	// calculate the internal column | ||||
| 	int internalColumn = -1; | ||||
| 	{ | ||||
| 		const int itemWidth = this->itemWidth(); | ||||
| 		if (pos.x() >= (itemWidth * itemsPerRow())) | ||||
| 		{ | ||||
| 			internalColumn = itemsPerRow(); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			for (int i = 0, c = 0; i < contentWidth(); i += itemWidth + 10 /*spacing()*/, ++c) | ||||
| 			{ | ||||
| 				if (pos.x() > (i - itemWidth / 2) && pos.x() <= (i + itemWidth / 2)) | ||||
| 				{ | ||||
| 					internalColumn = c; | ||||
| 					break; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		if (internalColumn == -1) | ||||
| 		{ | ||||
| 			return qMakePair(nullptr, -1); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// calculate the internal row | ||||
| 	int internalRow = -1; | ||||
| 	{ | ||||
| 		// FIXME rework the drag and drop code | ||||
| 		const int top = category->verticalPosition(); | ||||
| 		for (int r = 0, h = top; r < category->numRows(); | ||||
| 			 h += itemHeightForCategoryRow(category, r), ++r) | ||||
| 		{ | ||||
| 			if (pos.y() > h && pos.y() < (h + itemHeightForCategoryRow(category, r))) | ||||
| 			{ | ||||
| 				internalRow = r; | ||||
| 				break; | ||||
| 			} | ||||
| 		} | ||||
| 		if (internalRow == -1) | ||||
| 		{ | ||||
| 			return qMakePair(nullptr, -1); | ||||
| 		} | ||||
| 		// this happens if we're in the margin between a one category and another | ||||
| 		// categories header | ||||
| 		if (internalRow > (indices.size() / itemsPerRow())) | ||||
| 		{ | ||||
| 			return qMakePair(nullptr, -1); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// flaten the internalColumn/internalRow to one row | ||||
| 	int categoryRow = internalRow * itemsPerRow() + internalColumn; | ||||
|  | ||||
| 	// this is used if we're past the last item | ||||
| 	if (categoryRow >= indices.size()) | ||||
| 	{ | ||||
| 		return qMakePair(category, indices.last().row() + 1); | ||||
| 	} | ||||
|  | ||||
| 	return qMakePair(category, indices.at(categoryRow).row()); | ||||
| } | ||||
|  | ||||
| QPoint GroupView::offset() const | ||||
| { | ||||
| 	return QPoint(horizontalOffset(), verticalOffset()); | ||||
| } | ||||
|  | ||||
| QRegion GroupView::visualRegionForSelection(const QItemSelection &selection) const | ||||
| { | ||||
| 	QRegion region; | ||||
| 	for (auto &range : selection) | ||||
| 	{ | ||||
| 		int start_row = range.top(); | ||||
| 		int end_row = range.bottom(); | ||||
| 		for (int row = start_row; row <= end_row; ++row) | ||||
| 		{ | ||||
| 			int start_column = range.left(); | ||||
| 			int end_column = range.right(); | ||||
| 			for (int column = start_column; column <= end_column; ++column) | ||||
| 			{ | ||||
| 				QModelIndex index = model()->index(row, column, rootIndex()); | ||||
| 				region += visualRect(index); // OK | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return region; | ||||
| } | ||||
| QModelIndex GroupView::moveCursor(QAbstractItemView::CursorAction cursorAction, | ||||
| 								  Qt::KeyboardModifiers modifiers) | ||||
| { | ||||
| 	auto current = currentIndex(); | ||||
| 	if(!current.isValid()) | ||||
| 	{ | ||||
| 		qDebug() << "model row: invalid"; | ||||
| 		return current; | ||||
| 	} | ||||
| 	qDebug() << "model row: " << current.row(); | ||||
| 	auto cat = category(current); | ||||
| 	int i = m_groups.indexOf(cat); | ||||
| 	if(i >= 0) | ||||
| 	{ | ||||
| 		// this is a pile of something foul | ||||
| 		auto real_group = m_groups[i]; | ||||
| 		int beginning_row = 0; | ||||
| 		for(auto group: m_groups) | ||||
| 		{ | ||||
| 			if(group == real_group) | ||||
| 				break; | ||||
| 			beginning_row += group->numRows(); | ||||
| 		} | ||||
| 		qDebug() << "category: " << real_group->text; | ||||
| 		QPair<int, int> pos = categoryInternalPosition(current); | ||||
| 		int row = beginning_row + pos.second; | ||||
| 		qDebug() << "row: " << row; | ||||
| 		qDebug() << "column: " << pos.first; | ||||
| 	} | ||||
| 	return current; | ||||
| } | ||||
							
								
								
									
										139
									
								
								depends/groupview/GroupView.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								depends/groupview/GroupView.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,139 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <QListView> | ||||
| #include <QLineEdit> | ||||
| #include <QScrollBar> | ||||
|  | ||||
| struct GroupViewRoles | ||||
| { | ||||
| 	enum | ||||
| 	{ | ||||
| 		GroupRole = Qt::UserRole, | ||||
| 		ProgressValueRole, | ||||
| 		ProgressMaximumRole | ||||
| 	}; | ||||
| }; | ||||
|  | ||||
| struct Group; | ||||
|  | ||||
| class GroupView : public QAbstractItemView | ||||
| { | ||||
| 	Q_OBJECT | ||||
|  | ||||
| public: | ||||
| 	GroupView(QWidget *parent = 0); | ||||
| 	~GroupView(); | ||||
|  | ||||
| 	QRect geometryRect(const QModelIndex &index) const; | ||||
| 	virtual QRect visualRect(const QModelIndex &index) const override; | ||||
| 	QModelIndex indexAt(const QPoint &point) const; | ||||
| 	void setSelection(const QRect &rect, | ||||
| 					  const QItemSelectionModel::SelectionFlags commands) override; | ||||
|  | ||||
| 	virtual int horizontalOffset() const override | ||||
| 	{ | ||||
| 		return horizontalScrollBar()->value(); | ||||
| 	} | ||||
|  | ||||
| 	virtual int verticalOffset() const override | ||||
| 	{ | ||||
| 		return verticalScrollBar()->value(); | ||||
| 	} | ||||
|  | ||||
| 	virtual void scrollContentsBy(int dx, int dy) override | ||||
| 	{ | ||||
| 		scrollDirtyRegion(dx, dy); | ||||
| 		viewport()->scroll(dx, dy); | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| 	 * TODO! | ||||
| 	 */ | ||||
| 	virtual void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) override | ||||
| 	{ | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	virtual QModelIndex moveCursor(CursorAction cursorAction, | ||||
| 								   Qt::KeyboardModifiers modifiers) override; | ||||
|  | ||||
| 	virtual QRegion visualRegionForSelection(const QItemSelection &selection) const override; | ||||
|  | ||||
| protected | ||||
| slots: | ||||
| 	virtual void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, | ||||
| 							 const QVector<int> &roles) override; | ||||
| 	virtual void rowsInserted(const QModelIndex &parent, int start, int end) override; | ||||
| 	virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) override; | ||||
| 	virtual void updateGeometries() override; | ||||
|  | ||||
| protected: | ||||
| 	virtual bool isIndexHidden(const QModelIndex &index) const override; | ||||
| 	void mousePressEvent(QMouseEvent *event) override; | ||||
| 	void mouseMoveEvent(QMouseEvent *event) override; | ||||
| 	void mouseReleaseEvent(QMouseEvent *event) override; | ||||
| 	void mouseDoubleClickEvent(QMouseEvent *event) override; | ||||
| 	void paintEvent(QPaintEvent *event) override; | ||||
| 	void resizeEvent(QResizeEvent *event) override; | ||||
|  | ||||
| 	void dragEnterEvent(QDragEnterEvent *event) override; | ||||
| 	void dragMoveEvent(QDragMoveEvent *event) override; | ||||
| 	void dragLeaveEvent(QDragLeaveEvent *event) override; | ||||
| 	void dropEvent(QDropEvent *event) override; | ||||
|  | ||||
| 	void startDrag(Qt::DropActions supportedActions) override; | ||||
|  | ||||
| private: | ||||
| 	friend struct Group; | ||||
|  | ||||
| 	QList<Group *> m_groups; | ||||
|  | ||||
| 	int m_leftMargin; | ||||
| 	int m_rightMargin; | ||||
| 	int m_bottomMargin; | ||||
| 	int m_categoryMargin; | ||||
|  | ||||
| 	// bool m_updatesDisabled; | ||||
|  | ||||
| 	Group *category(const QModelIndex &index) const; | ||||
| 	Group *category(const QString &cat) const; | ||||
| 	Group *categoryAt(const QPoint &pos) const; | ||||
|  | ||||
| 	int itemsPerRow() const; | ||||
| 	int contentWidth() const; | ||||
|  | ||||
| private: | ||||
| 	int itemWidth() const; | ||||
| 	int categoryRowHeight(const QModelIndex &index) const; | ||||
|  | ||||
| 	/*QLineEdit *m_categoryEditor; | ||||
| 	Category *m_editedCategory; | ||||
| 	void startCategoryEditor(Category *category); | ||||
|  | ||||
| private slots: | ||||
| 	void endCategoryEditor();*/ | ||||
|  | ||||
| private: /* variables */ | ||||
| 	QPoint m_pressedPosition; | ||||
| 	QPersistentModelIndex m_pressedIndex; | ||||
| 	bool m_pressedAlreadySelected; | ||||
| 	Group *m_pressedCategory; | ||||
| 	QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag; | ||||
| 	QPoint m_lastDragPosition; | ||||
| 	int m_spacing = 5; | ||||
|  | ||||
| private: /* methods */ | ||||
| 	QPair<int, int> categoryInternalPosition(const QModelIndex &index) const; | ||||
| 	int categoryInternalRowTop(const QModelIndex &index) const; | ||||
| 	int itemHeightForCategoryRow(const Group *category, const int internalRow) const; | ||||
|  | ||||
| 	QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const; | ||||
| 	QList<QPair<QRect, QModelIndex>> draggablePaintPairs(const QModelIndexList &indices, | ||||
| 														 QRect *r) const; | ||||
|  | ||||
| 	bool isDragEventAccepted(QDropEvent *event); | ||||
|  | ||||
| 	QPair<Group *, int> rowDropPos(const QPoint &pos); | ||||
|  | ||||
| 	QPoint offset() const; | ||||
| }; | ||||
							
								
								
									
										21
									
								
								depends/groupview/GroupedProxyModel.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								depends/groupview/GroupedProxyModel.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| #include "GroupedProxyModel.h" | ||||
|  | ||||
| #include "GroupView.h" | ||||
|  | ||||
| GroupedProxyModel::GroupedProxyModel(QObject *parent) : QSortFilterProxyModel(parent) | ||||
| { | ||||
| } | ||||
|  | ||||
| bool GroupedProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const | ||||
| { | ||||
| 	const QString leftCategory = left.data(GroupViewRoles::GroupRole).toString(); | ||||
| 	const QString rightCategory = right.data(GroupViewRoles::GroupRole).toString(); | ||||
| 	if (leftCategory == rightCategory) | ||||
| 	{ | ||||
| 		return left.row() < right.row(); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		return leftCategory < rightCategory; | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										14
									
								
								depends/groupview/GroupedProxyModel.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								depends/groupview/GroupedProxyModel.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <QSortFilterProxyModel> | ||||
|  | ||||
| class GroupedProxyModel : public QSortFilterProxyModel | ||||
| { | ||||
| 	Q_OBJECT | ||||
|  | ||||
| public: | ||||
| 	GroupedProxyModel(QObject *parent = 0); | ||||
|  | ||||
| protected: | ||||
| 	bool lessThan(const QModelIndex &left, const QModelIndex &right) const; | ||||
| }; | ||||
							
								
								
									
										281
									
								
								depends/groupview/InstanceDelegate.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										281
									
								
								depends/groupview/InstanceDelegate.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,281 @@ | ||||
| /* Copyright 2013 MultiMC Contributors | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #include "InstanceDelegate.h" | ||||
| #include <QPainter> | ||||
| #include <QTextOption> | ||||
| #include <QTextLayout> | ||||
| #include <QApplication> | ||||
| #include <QtCore/qmath.h> | ||||
|  | ||||
| #include "GroupView.h" | ||||
|  | ||||
| // Origin: Qt | ||||
| static void viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height, | ||||
| 							   qreal &widthUsed) | ||||
| { | ||||
| 	height = 0; | ||||
| 	widthUsed = 0; | ||||
| 	textLayout.beginLayout(); | ||||
| 	QString str = textLayout.text(); | ||||
| 	while (true) | ||||
| 	{ | ||||
| 		QTextLine line = textLayout.createLine(); | ||||
| 		if (!line.isValid()) | ||||
| 			break; | ||||
| 		if (line.textLength() == 0) | ||||
| 			break; | ||||
| 		line.setLineWidth(lineWidth); | ||||
| 		line.setPosition(QPointF(0, height)); | ||||
| 		height += line.height(); | ||||
| 		widthUsed = qMax(widthUsed, line.naturalTextWidth()); | ||||
| 	} | ||||
| 	textLayout.endLayout(); | ||||
| } | ||||
|  | ||||
| #define QFIXED_MAX (INT_MAX / 256) | ||||
|  | ||||
| ListViewDelegate::ListViewDelegate(QObject *parent) : QStyledItemDelegate(parent) | ||||
| { | ||||
| } | ||||
|  | ||||
| void drawSelectionRect(QPainter *painter, const QStyleOptionViewItemV4 &option, | ||||
| 					   const QRect &rect) | ||||
| { | ||||
| 	if ((option.state & QStyle::State_Selected)) | ||||
| 		painter->fillRect(rect, option.palette.brush(QPalette::Highlight)); | ||||
| 	else | ||||
| 	{ | ||||
| 		QColor backgroundColor = option.palette.color(QPalette::Background); | ||||
| 		backgroundColor.setAlpha(160); | ||||
| 		painter->fillRect(rect, QBrush(backgroundColor)); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void drawFocusRect(QPainter *painter, const QStyleOptionViewItemV4 &option, const QRect &rect) | ||||
| { | ||||
| 	if (!(option.state & QStyle::State_HasFocus)) | ||||
| 		return; | ||||
| 	QStyleOptionFocusRect opt; | ||||
| 	opt.direction = option.direction; | ||||
| 	opt.fontMetrics = option.fontMetrics; | ||||
| 	opt.palette = option.palette; | ||||
| 	opt.rect = rect; | ||||
| 	// opt.state           = option.state | QStyle::State_KeyboardFocusChange | | ||||
| 	// QStyle::State_Item; | ||||
| 	auto col = option.state & QStyle::State_Selected ? QPalette::Highlight : QPalette::Base; | ||||
| 	opt.backgroundColor = option.palette.color(col); | ||||
| 	// Apparently some widget styles expect this hint to not be set | ||||
| 	painter->setRenderHint(QPainter::Antialiasing, false); | ||||
|  | ||||
| 	QStyle *style = option.widget ? option.widget->style() : QApplication::style(); | ||||
|  | ||||
| 	style->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, painter, option.widget); | ||||
|  | ||||
| 	painter->setRenderHint(QPainter::Antialiasing); | ||||
| } | ||||
|  | ||||
| // TODO this can be made a lot prettier | ||||
| void drawProgressOverlay(QPainter *painter, const QStyleOptionViewItemV4 &option, | ||||
| 						 const int value, const int maximum) | ||||
| { | ||||
| 	if (maximum == 0 || value == maximum) | ||||
| 	{ | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	painter->save(); | ||||
|  | ||||
| 	qreal percent = (qreal)value / (qreal)maximum; | ||||
| 	QColor color = option.palette.color(QPalette::Dark); | ||||
| 	color.setAlphaF(0.70f); | ||||
| 	painter->setBrush(color); | ||||
| 	painter->setPen(QPen(QBrush(), 0)); | ||||
| 	painter->drawPie(option.rect, 90 * 16, -percent * 360 * 16); | ||||
|  | ||||
| 	painter->restore(); | ||||
| } | ||||
|  | ||||
| static QSize viewItemTextSize(const QStyleOptionViewItemV4 *option) | ||||
| { | ||||
| 	QStyle *style = option->widget ? option->widget->style() : QApplication::style(); | ||||
| 	QTextOption textOption; | ||||
| 	textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); | ||||
| 	QTextLayout textLayout; | ||||
| 	textLayout.setTextOption(textOption); | ||||
| 	textLayout.setFont(option->font); | ||||
| 	textLayout.setText(option->text); | ||||
| 	const int textMargin = | ||||
| 		style->pixelMetric(QStyle::PM_FocusFrameHMargin, option, option->widget) + 1; | ||||
| 	QRect bounds(0, 0, 100 - 2 * textMargin, 600); | ||||
| 	qreal height = 0, widthUsed = 0; | ||||
| 	viewItemTextLayout(textLayout, bounds.width(), height, widthUsed); | ||||
| 	const QSize size(qCeil(widthUsed), qCeil(height)); | ||||
| 	return QSize(size.width() + 2 * textMargin, size.height()); | ||||
| } | ||||
|  | ||||
| void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, | ||||
| 							 const QModelIndex &index) const | ||||
| { | ||||
| 	QStyleOptionViewItemV4 opt = option; | ||||
| 	initStyleOption(&opt, index); | ||||
| 	painter->save(); | ||||
| 	painter->setClipRect(opt.rect); | ||||
|  | ||||
| 	opt.features |= QStyleOptionViewItem::WrapText; | ||||
| 	opt.text = index.data().toString(); | ||||
| 	opt.textElideMode = Qt::ElideRight; | ||||
| 	opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; | ||||
|  | ||||
| 	QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); | ||||
|  | ||||
| 	// const int iconSize =  style->pixelMetric(QStyle::PM_IconViewIconSize); | ||||
| 	const int iconSize = 48; | ||||
| 	QRect iconbox = opt.rect; | ||||
| 	const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, opt.widget) + 1; | ||||
| 	QRect textRect = opt.rect; | ||||
| 	QRect textHighlightRect = textRect; | ||||
| 	// clip the decoration on top, remove width padding | ||||
| 	textRect.adjust(textMargin, iconSize + textMargin + 5, -textMargin, 0); | ||||
|  | ||||
| 	textHighlightRect.adjust(0, iconSize + 5, 0, 0); | ||||
|  | ||||
| 	// draw background | ||||
| 	{ | ||||
| 		// FIXME: unused | ||||
| 		// QSize textSize = viewItemTextSize ( &opt ); | ||||
| 		QPalette::ColorGroup cg; | ||||
| 		QStyleOptionViewItemV4 opt2(opt); | ||||
|  | ||||
| 		if ((opt.widget && opt.widget->isEnabled()) || (opt.state & QStyle::State_Enabled)) | ||||
| 		{ | ||||
| 			if (!(opt.state & QStyle::State_Active)) | ||||
| 				cg = QPalette::Inactive; | ||||
| 			else | ||||
| 				cg = QPalette::Normal; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			cg = QPalette::Disabled; | ||||
| 		} | ||||
| 		opt2.palette.setCurrentColorGroup(cg); | ||||
|  | ||||
| 		// fill in background, if any | ||||
| 		if (opt.backgroundBrush.style() != Qt::NoBrush) | ||||
| 		{ | ||||
| 			QPointF oldBO = painter->brushOrigin(); | ||||
| 			painter->setBrushOrigin(opt.rect.topLeft()); | ||||
| 			painter->fillRect(opt.rect, opt.backgroundBrush); | ||||
| 			painter->setBrushOrigin(oldBO); | ||||
| 		} | ||||
|  | ||||
| 		if (opt.showDecorationSelected) | ||||
| 		{ | ||||
| 			drawSelectionRect(painter, opt2, opt.rect); | ||||
| 			drawFocusRect(painter, opt2, opt.rect); | ||||
| 			// painter->fillRect ( opt.rect, opt.palette.brush ( cg, QPalette::Highlight ) ); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
|  | ||||
| 			// if ( opt.state & QStyle::State_Selected ) | ||||
| 			{ | ||||
| 				// QRect textRect = subElementRect ( QStyle::SE_ItemViewItemText,  opt, | ||||
| 				// opt.widget ); | ||||
| 				// painter->fillRect ( textHighlightRect, opt.palette.brush ( cg, | ||||
| 				// QPalette::Highlight ) ); | ||||
| 				drawSelectionRect(painter, opt2, textHighlightRect); | ||||
| 				drawFocusRect(painter, opt2, textHighlightRect); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// draw the icon | ||||
| 	{ | ||||
| 		QIcon::Mode mode = QIcon::Normal; | ||||
| 		if (!(opt.state & QStyle::State_Enabled)) | ||||
| 			mode = QIcon::Disabled; | ||||
| 		else if (opt.state & QStyle::State_Selected) | ||||
| 			mode = QIcon::Selected; | ||||
| 		QIcon::State state = opt.state & QStyle::State_Open ? QIcon::On : QIcon::Off; | ||||
|  | ||||
| 		iconbox.setHeight(iconSize); | ||||
| 		opt.icon.paint(painter, iconbox, Qt::AlignCenter, mode, state); | ||||
| 	} | ||||
| 	// set the text colors | ||||
| 	QPalette::ColorGroup cg = | ||||
| 		opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; | ||||
| 	if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) | ||||
| 		cg = QPalette::Inactive; | ||||
| 	if (opt.state & QStyle::State_Selected) | ||||
| 	{ | ||||
| 		painter->setPen(opt.palette.color(cg, QPalette::HighlightedText)); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		painter->setPen(opt.palette.color(cg, QPalette::Text)); | ||||
| 	} | ||||
|  | ||||
| 	// draw the text | ||||
| 	QTextOption textOption; | ||||
| 	textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); | ||||
| 	textOption.setTextDirection(opt.direction); | ||||
| 	textOption.setAlignment(QStyle::visualAlignment(opt.direction, opt.displayAlignment)); | ||||
| 	QTextLayout textLayout; | ||||
| 	textLayout.setTextOption(textOption); | ||||
| 	textLayout.setFont(opt.font); | ||||
| 	textLayout.setText(opt.text); | ||||
|  | ||||
| 	qreal width, height; | ||||
| 	viewItemTextLayout(textLayout, textRect.width(), height, width); | ||||
|  | ||||
| 	const int lineCount = textLayout.lineCount(); | ||||
|  | ||||
| 	const QRect layoutRect = QStyle::alignedRect( | ||||
| 		opt.direction, opt.displayAlignment, QSize(textRect.width(), int(height)), textRect); | ||||
| 	const QPointF position = layoutRect.topLeft(); | ||||
| 	for (int i = 0; i < lineCount; ++i) | ||||
| 	{ | ||||
| 		const QTextLine line = textLayout.lineAt(i); | ||||
| 		line.draw(painter, position); | ||||
| 	} | ||||
|  | ||||
| 	drawProgressOverlay(painter, opt, | ||||
| 						index.data(GroupViewRoles::ProgressValueRole).toInt(), | ||||
| 						index.data(GroupViewRoles::ProgressMaximumRole).toInt()); | ||||
|  | ||||
| 	painter->restore(); | ||||
| } | ||||
|  | ||||
| QSize ListViewDelegate::sizeHint(const QStyleOptionViewItem &option, | ||||
| 								 const QModelIndex &index) const | ||||
| { | ||||
| 	QStyleOptionViewItemV4 opt = option; | ||||
| 	initStyleOption(&opt, index); | ||||
| 	opt.features |= QStyleOptionViewItem::WrapText; | ||||
| 	opt.text = index.data().toString(); | ||||
| 	opt.textElideMode = Qt::ElideRight; | ||||
| 	opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter; | ||||
|  | ||||
| 	QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); | ||||
| 	const int textMargin = | ||||
| 		style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, opt.widget) + 1; | ||||
| 	int height = 48 + textMargin * 2 + 5; // TODO: turn constants into variables | ||||
| 	QSize szz = viewItemTextSize(&opt); | ||||
| 	height += szz.height(); | ||||
| 	// FIXME: maybe the icon items could scale and keep proportions? | ||||
| 	QSize sz(100, height); | ||||
| 	return sz; | ||||
| } | ||||
							
								
								
									
										29
									
								
								depends/groupview/InstanceDelegate.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								depends/groupview/InstanceDelegate.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| /* Copyright 2013 MultiMC Contributors | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <QStyledItemDelegate> | ||||
|  | ||||
| class ListViewDelegate : public QStyledItemDelegate | ||||
| { | ||||
| public: | ||||
| 	explicit ListViewDelegate(QObject *parent = 0); | ||||
|  | ||||
| protected: | ||||
| 	void paint(QPainter *painter, const QStyleOptionViewItem &option, | ||||
| 			   const QModelIndex &index) const; | ||||
| 	QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; | ||||
| }; | ||||
							
								
								
									
										98
									
								
								depends/groupview/main.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								depends/groupview/main.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | ||||
| #include "main.h" | ||||
|  | ||||
| #include <QApplication> | ||||
| #include <QStandardItemModel> | ||||
| #include <QPainter> | ||||
| #include <QTime> | ||||
|  | ||||
| #include "GroupView.h" | ||||
| #include "GroupedProxyModel.h" | ||||
| #include "InstanceDelegate.h" | ||||
|  | ||||
| Progresser *progresser; | ||||
|  | ||||
| QPixmap icon(const Qt::GlobalColor color) | ||||
| { | ||||
| 	QPixmap p = QPixmap(32, 32); | ||||
| 	p.fill(QColor(color)); | ||||
| 	return p; | ||||
| } | ||||
| QPixmap icon(const int number) | ||||
| { | ||||
| 	QPixmap p = icon(Qt::white); | ||||
| 	QPainter painter(&p); | ||||
| 	QFont font = painter.font(); | ||||
| 	font.setBold(true); | ||||
| 	font.setPixelSize(28); | ||||
| 	painter.setFont(font); | ||||
| 	painter.drawText(QRect(QPoint(0, 0), p.size()), Qt::AlignVCenter | Qt::AlignHCenter, | ||||
| 					 QString::number(number)); | ||||
| 	painter.end(); | ||||
| 	return p; | ||||
| } | ||||
| QStandardItem *createItem(const Qt::GlobalColor color, const QString &text, | ||||
| 						  const QString &category) | ||||
| { | ||||
| 	QStandardItem *item = new QStandardItem; | ||||
| 	item->setText(text); | ||||
| 	item->setData(icon(color), Qt::DecorationRole); | ||||
| 	item->setData(category, GroupViewRoles::GroupRole); | ||||
| 	item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); | ||||
| 	// progresser->addTrackedIndex(item); | ||||
| 	return item; | ||||
| } | ||||
| QStandardItem *createItem(const int index, const QString &category) | ||||
| { | ||||
| 	QStandardItem *item = new QStandardItem; | ||||
| 	item->setText(QString("Item #%1").arg(index)); | ||||
| 	item->setData(icon(index), Qt::DecorationRole); | ||||
| 	item->setData(category, GroupViewRoles::GroupRole); | ||||
| 	item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); | ||||
| 	// progresser->addTrackedIndex(item); | ||||
| 	return item; | ||||
| } | ||||
|  | ||||
| int main(int argc, char *argv[]) | ||||
| { | ||||
| 	QApplication a(argc, argv); | ||||
|  | ||||
| 	qsrand(QTime::currentTime().msec()); | ||||
|  | ||||
| 	progresser = new Progresser(); | ||||
|  | ||||
| 	QStandardItemModel model; | ||||
| 	model.setRowCount(10); | ||||
| 	model.setColumnCount(1); | ||||
|  | ||||
| 	model.setItem( | ||||
| 		0, createItem(Qt::red, | ||||
| 					  "Red is a color. Some more text. I'm out of ideas. 42. What's your name?", | ||||
| 					  "Colorful")); | ||||
| 	model.setItem(1, createItem(Qt::blue, "Blue", "Colorful")); | ||||
| 	model.setItem(2, createItem(Qt::yellow, "Yellow", "Colorful")); | ||||
|  | ||||
| 	model.setItem(3, createItem(Qt::black, "Black", "Not Colorful")); | ||||
| 	model.setItem(4, createItem(Qt::darkGray, "Dark Gray", "Not Colorful")); | ||||
| 	model.setItem(5, createItem(Qt::gray, "Gray", "Not Colorful")); | ||||
| 	model.setItem(6, createItem(Qt::lightGray, "Light Gray", "Not Colorful")); | ||||
| 	model.setItem(7, createItem(Qt::white, "White", "Not Colorful")); | ||||
|  | ||||
| 	model.setItem(8, createItem(Qt::darkGreen, "Dark Green", "")); | ||||
| 	model.setItem(9, progresser->addTrackedIndex(createItem(Qt::green, "Green", ""))); | ||||
|  | ||||
| 	for (int i = 0; i < 20; ++i) | ||||
| 	{ | ||||
| 		model.setItem(i + 10, createItem(i + 1, "Items 1-20")); | ||||
| 	} | ||||
|  | ||||
| 	GroupedProxyModel pModel; | ||||
| 	pModel.setSourceModel(&model); | ||||
|  | ||||
| 	GroupView w; | ||||
| 	w.setItemDelegate(new ListViewDelegate); | ||||
| 	w.setModel(&pModel); | ||||
| 	w.resize(640, 480); | ||||
| 	w.show(); | ||||
|  | ||||
| 	return a.exec(); | ||||
| } | ||||
							
								
								
									
										54
									
								
								depends/groupview/main.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								depends/groupview/main.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <QObject> | ||||
| #include <QTimer> | ||||
| #include <QList> | ||||
| #include <QStandardItem> | ||||
| #include <QDebug> | ||||
|  | ||||
| #include "GroupView.h" | ||||
|  | ||||
| class Progresser : public QObject | ||||
| { | ||||
| 	Q_OBJECT | ||||
| public: | ||||
| 	explicit Progresser(QObject *parent = 0) : QObject(parent) | ||||
| 	{ | ||||
| 		QTimer *timer = new QTimer(this); | ||||
| 		connect(timer, SIGNAL(timeout()), this, SLOT(timeout())); | ||||
| 		timer->start(50); | ||||
| 	} | ||||
|  | ||||
| 	QStandardItem *addTrackedIndex(QStandardItem *item) | ||||
| 	{ | ||||
| 		item->setData(1000, GroupViewRoles::ProgressMaximumRole); | ||||
| 		m_items.append(item); | ||||
| 		return item; | ||||
| 	} | ||||
|  | ||||
| public | ||||
| slots: | ||||
| 	void timeout() | ||||
| 	{ | ||||
| 		QList<QStandardItem *> toRemove; | ||||
| 		for (auto item : m_items) | ||||
| 		{ | ||||
| 			int maximum = item->data(GroupViewRoles::ProgressMaximumRole).toInt(); | ||||
| 			int value = item->data(GroupViewRoles::ProgressValueRole).toInt(); | ||||
| 			int newvalue = std::min(value + 3, maximum); | ||||
| 			item->setData(newvalue, GroupViewRoles::ProgressValueRole); | ||||
|  | ||||
| 			if(newvalue >= maximum) | ||||
| 			{ | ||||
| 				toRemove.append(item); | ||||
| 			} | ||||
| 		} | ||||
| 		for(auto remove : toRemove) | ||||
| 		{ | ||||
| 			m_items.removeAll(remove); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| private: | ||||
| 	QList<QStandardItem *> m_items; | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user