summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPetr Mrázek <peterix@gmail.com>2014-02-04 00:53:05 +0100
committerPetr Mrázek <peterix@gmail.com>2014-02-04 00:53:05 +0100
commitb82eb5873e99dce6aec735b08929e3add52e53e7 (patch)
treef8b60861bc5006cc989e65b87b22498f2b1d4412
parenta17caba2c9c2aa5960581db01e4b66472a9c019c (diff)
parent946d49675cb4725c31ab49a51f3bcae302f89a19 (diff)
downloadMultiMC-b82eb5873e99dce6aec735b08929e3add52e53e7.tar
MultiMC-b82eb5873e99dce6aec735b08929e3add52e53e7.tar.gz
MultiMC-b82eb5873e99dce6aec735b08929e3add52e53e7.tar.lz
MultiMC-b82eb5873e99dce6aec735b08929e3add52e53e7.tar.xz
MultiMC-b82eb5873e99dce6aec735b08929e3add52e53e7.zip
Add 'depends/groupview/' from commit '946d49675cb4725c31ab49a51f3bcae302f89a19'
git-subtree-dir: depends/groupview git-subtree-mainline: a17caba2c9c2aa5960581db01e4b66472a9c019c git-subtree-split: 946d49675cb4725c31ab49a51f3bcae302f89a19
-rw-r--r--depends/groupview/.clang-format24
-rw-r--r--depends/groupview/.gitignore2
-rw-r--r--depends/groupview/CMakeLists.txt40
-rw-r--r--depends/groupview/Group.cpp169
-rw-r--r--depends/groupview/Group.h67
-rw-r--r--depends/groupview/GroupView.cpp908
-rw-r--r--depends/groupview/GroupView.h139
-rw-r--r--depends/groupview/GroupedProxyModel.cpp21
-rw-r--r--depends/groupview/GroupedProxyModel.h14
-rw-r--r--depends/groupview/InstanceDelegate.cpp281
-rw-r--r--depends/groupview/InstanceDelegate.h29
-rw-r--r--depends/groupview/main.cpp98
-rw-r--r--depends/groupview/main.h54
13 files changed, 1846 insertions, 0 deletions
diff --git a/depends/groupview/.clang-format b/depends/groupview/.clang-format
new file mode 100644
index 00000000..167a8fa7
--- /dev/null
+++ b/depends/groupview/.clang-format
@@ -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
diff --git a/depends/groupview/.gitignore b/depends/groupview/.gitignore
new file mode 100644
index 00000000..a5d18fa3
--- /dev/null
+++ b/depends/groupview/.gitignore
@@ -0,0 +1,2 @@
+build/
+*.user*
diff --git a/depends/groupview/CMakeLists.txt b/depends/groupview/CMakeLists.txt
new file mode 100644
index 00000000..e2a85950
--- /dev/null
+++ b/depends/groupview/CMakeLists.txt
@@ -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)
diff --git a/depends/groupview/Group.cpp b/depends/groupview/Group.cpp
new file mode 100644
index 00000000..f216cc6e
--- /dev/null
+++ b/depends/groupview/Group.cpp
@@ -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();
+}
diff --git a/depends/groupview/Group.h b/depends/groupview/Group.h
new file mode 100644
index 00000000..455ee1a8
--- /dev/null
+++ b/depends/groupview/Group.h
@@ -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)
diff --git a/depends/groupview/GroupView.cpp b/depends/groupview/GroupView.cpp
new file mode 100644
index 00000000..89e3e223
--- /dev/null
+++ b/depends/groupview/GroupView.cpp
@@ -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 &current = 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;
+}
diff --git a/depends/groupview/GroupView.h b/depends/groupview/GroupView.h
new file mode 100644
index 00000000..329a3503
--- /dev/null
+++ b/depends/groupview/GroupView.h
@@ -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;
+};
diff --git a/depends/groupview/GroupedProxyModel.cpp b/depends/groupview/GroupedProxyModel.cpp
new file mode 100644
index 00000000..57d7ff5c
--- /dev/null
+++ b/depends/groupview/GroupedProxyModel.cpp
@@ -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;
+ }
+}
diff --git a/depends/groupview/GroupedProxyModel.h b/depends/groupview/GroupedProxyModel.h
new file mode 100644
index 00000000..cae87ecd
--- /dev/null
+++ b/depends/groupview/GroupedProxyModel.h
@@ -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;
+};
diff --git a/depends/groupview/InstanceDelegate.cpp b/depends/groupview/InstanceDelegate.cpp
new file mode 100644
index 00000000..8527e2bc
--- /dev/null
+++ b/depends/groupview/InstanceDelegate.cpp
@@ -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;
+}
diff --git a/depends/groupview/InstanceDelegate.h b/depends/groupview/InstanceDelegate.h
new file mode 100644
index 00000000..de2f429b
--- /dev/null
+++ b/depends/groupview/InstanceDelegate.h
@@ -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;
+};
diff --git a/depends/groupview/main.cpp b/depends/groupview/main.cpp
new file mode 100644
index 00000000..df14e3bd
--- /dev/null
+++ b/depends/groupview/main.cpp
@@ -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();
+}
diff --git a/depends/groupview/main.h b/depends/groupview/main.h
new file mode 100644
index 00000000..2a358329
--- /dev/null
+++ b/depends/groupview/main.h
@@ -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;
+};