From ccbf341dc8d8e515d9cf918bff7ff9435c477847 Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Tue, 24 Dec 2013 11:47:30 +0100 Subject: Initial commit. Basics work. Next: Drag and Drop --- .gitignore | 2 + CMakeLists.txt | 35 +++ CategorizedProxyModel.cpp | 12 + CategorizedProxyModel.h | 18 ++ CategorizedView.cpp | 587 ++++++++++++++++++++++++++++++++++++++++++++++ CategorizedView.h | 96 ++++++++ main.cpp | 53 +++++ 7 files changed, 803 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 CategorizedProxyModel.cpp create mode 100644 CategorizedProxyModel.h create mode 100644 CategorizedView.cpp create mode 100644 CategorizedView.h create mode 100644 main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a5d18fa3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +*.user* diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..8a246bcf --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,35 @@ +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 + + CategorizedView.h + CategorizedView.cpp + CategorizedProxyModel.h + CategorizedProxyModel.cpp +) + +add_executable(GroupView ${SOURCES}) +qt5_use_modules(GroupView Core Gui Widgets) diff --git a/CategorizedProxyModel.cpp b/CategorizedProxyModel.cpp new file mode 100644 index 00000000..2b54ce1b --- /dev/null +++ b/CategorizedProxyModel.cpp @@ -0,0 +1,12 @@ +#include "CategorizedProxyModel.h" + +#include "CategorizedView.h" + +CategorizedProxyModel::CategorizedProxyModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ +} +bool CategorizedProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + return left.data(CategorizedView::CategoryRole).toString() < right.data(CategorizedView::CategoryRole).toString(); +} diff --git a/CategorizedProxyModel.h b/CategorizedProxyModel.h new file mode 100644 index 00000000..6e4f3fdc --- /dev/null +++ b/CategorizedProxyModel.h @@ -0,0 +1,18 @@ +#ifndef CATEGORIZEDPROXYMODEL_H +#define CATEGORIZEDPROXYMODEL_H + +#include + +class CategorizedProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + CategorizedProxyModel(QObject *parent = 0); + +protected: + bool lessThan(const QModelIndex &left, const QModelIndex &right) const; +}; + + +#endif // CATEGORIZEDPROXYMODEL_H diff --git a/CategorizedView.cpp b/CategorizedView.cpp new file mode 100644 index 00000000..46b1e072 --- /dev/null +++ b/CategorizedView.cpp @@ -0,0 +1,587 @@ +#include "CategorizedView.h" + +#include +#include +#include +#include +#include + +CategorizedView::Category::Category(const QString &text, CategorizedView *view) + : view(view), text(text), collapsed(false) +{ +} +CategorizedView::Category::Category(const CategorizedView::Category *other) : + view(other->view), text(other->text), collapsed(other->collapsed), iconRect(other->iconRect), textRect(other->textRect) +{ +} + +void CategorizedView::Category::drawHeader(QPainter *painter, const int y) +{ + painter->save(); + + int height = headerHeight() - 4; + int collapseSize = height; + + // the icon + iconRect = QRect(view->m_rightMargin + 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); + textRect = QRect(iconRect.right() + 4, y, textWidth, headerHeight()); + painter->drawText(textRect, text, QTextOption(Qt::AlignHCenter | Qt::AlignVCenter)); + + // the line + painter->drawLine(textRect.right() + 4, y + headerHeight() / 2, view->contentWidth() - view->m_rightMargin, y + headerHeight() / 2); + + painter->restore(); +} + +int CategorizedView::Category::totalHeight() const +{ + return headerHeight() + 5 + contentHeight(); +} +int CategorizedView::Category::headerHeight() const +{ + return qApp->fontMetrics().height() + 4; +} +int CategorizedView::Category::contentHeight() const +{ + if (collapsed) + { + return 0; + } + const int rows = qMax(1, qCeil((qreal)view->numItemsForCategory(this) / (qreal)view->itemsPerRow())); + return view->itemSize().height() * rows; +} +QSize CategorizedView::Category::categoryTotalSize() const +{ + return QSize(view->contentWidth(), contentHeight()); +} + +CategorizedView::CategorizedView(QWidget *parent) + : QListView(parent), m_leftMargin(5), m_rightMargin(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); +} + +CategorizedView::~CategorizedView() +{ + qDeleteAll(m_categories); + m_categories.clear(); +} + +void CategorizedView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) +{ +// if (m_updatesDisabled) +// { +// return; +// } + + QListView::dataChanged(topLeft, bottomRight, roles); + + if (roles.contains(CategoryRole)) + { + updateGeometries(); + update(); + } +} +void CategorizedView::rowsInserted(const QModelIndex &parent, int start, int end) +{ +// if (m_updatesDisabled) +// { +// return; +// } + + QListView::rowsInserted(parent, start, end); + + updateGeometries(); + update(); +} +void CategorizedView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ +// if (m_updatesDisabled) +// { +// return; +// } + + QListView::rowsAboutToBeRemoved(parent, start, end); + + updateGeometries(); + update(); +} + +void CategorizedView::updateGeometries() +{ + QListView::updateGeometries(); + + m_cachedItemSize = QSize(); + + QMap cats; + + for (int i = 0; i < model()->rowCount(); ++i) + { + const QString category = model()->index(i, 0).data(CategoryRole).toString(); + if (!cats.contains(category)) + { + Category *old = this->category(category); + if (old) + { + cats.insert(category, new Category(old)); + } + else + { + cats.insert(category, new Category(category, this)); + } + } + } + + /*if (m_editedCategory) + { + m_editedCategory = cats[m_editedCategory->text]; + }*/ + + qDeleteAll(m_categories); + m_categories = cats.values(); + + update(); +} + +bool CategorizedView::isIndexHidden(const QModelIndex &index) const +{ + Category *cat = category(index); + if (cat) + { + return cat->collapsed; + } + else + { + return false; + } +} + +CategorizedView::Category *CategorizedView::category(const QModelIndex &index) const +{ + return category(index.data(CategoryRole).toString()); +} +CategorizedView::Category *CategorizedView::category(const QString &cat) const +{ + for (int i = 0; i < m_categories.size(); ++i) + { + if (m_categories.at(i)->text == cat) + { + return m_categories.at(i); + } + } + return 0; +} + +int CategorizedView::numItemsForCategory(const CategorizedView::Category *category) const +{ + return itemsForCategory(category).size(); +} +QList CategorizedView::itemsForCategory(const CategorizedView::Category *category) const +{ + QList indices; + for (int i = 0; i < model()->rowCount(); ++i) + { + if (model()->index(i, 0).data(CategoryRole).toString() == category->text) + { + indices += model()->index(i, 0); + } + } + return indices; +} + +int CategorizedView::categoryTop(const CategorizedView::Category *category) const +{ + int res = 0; + const QList cats = sortedCategories(); + for (int i = 0; i < cats.size(); ++i) + { + if (cats.at(i) == category) + { + break; + } + res += cats.at(i)->totalHeight() + m_categoryMargin; + } + return res; +} + +int CategorizedView::itemsPerRow() const +{ + return qFloor((qreal)contentWidth() / (qreal)itemSize().width()); +} +int CategorizedView::contentWidth() const +{ + return width() - m_leftMargin - m_rightMargin; +} + +bool CategorizedView::lessThanCategoryPointer(const CategorizedView::Category *c1, const CategorizedView::Category *c2) +{ + return c1->text < c2->text; +} +QList CategorizedView::sortedCategories() const +{ + QList out = m_categories; + qSort(out.begin(), out.end(), &CategorizedView::lessThanCategoryPointer); + return out; +} + +QSize CategorizedView::itemSize(const QStyleOptionViewItem &option) const +{ + if (!m_cachedItemSize.isValid()) + { + QModelIndex sample = model()->index(model()->rowCount() -1, 0); + const QAbstractItemDelegate *delegate = itemDelegate(); + if (delegate) + { + m_cachedItemSize = delegate->sizeHint(option, sample); + m_cachedItemSize.setWidth(m_cachedItemSize.width() + 20); + m_cachedItemSize.setHeight(m_cachedItemSize.height() + 20); + } + else + { + m_cachedItemSize = QSize(); + } + } + return m_cachedItemSize; +} + +void CategorizedView::mousePressEvent(QMouseEvent *event) +{ + //endCategoryEditor(); + + if (event->buttons() & Qt::LeftButton) + { + foreach (Category *category, m_categories) + { + if (category->iconRect.contains(event->pos())) + { + category->collapsed = !category->collapsed; + updateGeometries(); + viewport()->update(); + event->accept(); + return; + } + } + + for (int i = 0; i < model()->rowCount(); ++i) + { + QModelIndex index = model()->index(i, 0); + if (visualRect(index).contains(event->pos())) + { + selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); + event->accept(); + return; + } + } + } + + QListView::mousePressEvent(event); +} +void CategorizedView::mouseMoveEvent(QMouseEvent *event) +{ + if (event->buttons() & Qt::LeftButton) + { + for (int i = 0; i < model()->rowCount(); ++i) + { + QModelIndex index = model()->index(i, 0); + if (visualRect(index).contains(event->pos())) + { + selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); + event->accept(); + return; + } + } + } + + QListView::mouseMoveEvent(event); +} +void CategorizedView::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->buttons() & Qt::LeftButton) + { + for (int i = 0; i < model()->rowCount(); ++i) + { + QModelIndex index = model()->index(i, 0); + if (visualRect(index).contains(event->pos())) + { + selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); + event->accept(); + return; + } + } + } + + QListView::mouseReleaseEvent(event); +} +void CategorizedView::mouseDoubleClickEvent(QMouseEvent *event) +{ + /*endCategoryEditor(); + + foreach (Category *category, m_categories) + { + if (category->textRect.contains(event->pos()) && m_categoryEditor == 0) + { + startCategoryEditor(category); + event->accept(); + return; + } + }*/ + + QListView::mouseDoubleClickEvent(event); +} +void CategorizedView::paintEvent(QPaintEvent *event) +{ + QPainter painter(this->viewport()); + + int y = 0; + for (int i = 0; i < m_categories.size(); ++i) + { + Category *category = m_categories.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 |= wordWrap() ? QStyleOptionViewItemV2::WrapText : QStyleOptionViewItemV2::None; + if (flags & Qt::ItemIsSelectable) + { + 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); + } +} +void CategorizedView::resizeEvent(QResizeEvent *event) +{ + QListView::resizeEvent(event); + +// if (m_categoryEditor) +// { +// m_categoryEditor->resize(qMax(contentWidth() / 2, m_editedCategory->textRect.width()), m_categoryEditor->height()); +// } + + updateGeometries(); +} + +void CategorizedView::dragEnterEvent(QDragEnterEvent *event) +{ + // TODO +} +void CategorizedView::dragMoveEvent(QDragMoveEvent *event) +{ + // TODO +} +void CategorizedView::dragLeaveEvent(QDragLeaveEvent *event) +{ + // TODO +} +void CategorizedView::dropEvent(QDropEvent *event) +{ + stopAutoScroll(); + setState(NoState); + + if (event->source() != this || !(event->possibleActions() & Qt::MoveAction)) + { + return; + } + + // check that we aren't on a category header and calculate which category we're in + Category *category = 0; + { + int y = 0; + foreach (Category *cat, m_categories) + { + if (event->pos().y() > y && event->pos().y() < (y + cat->headerHeight())) + { + viewport()->update(); + return; + } + y += cat->totalHeight() + m_categoryMargin; + if (event->pos().y() < y) + { + category = cat; + break; + } + } + } + + // calculate the internal column + int internalColumn = -1; + { + const int itemWidth = itemSize().width(); + for (int i = 0, c = 0; + i < contentWidth(); + i += itemWidth, ++c) + { + if (event->pos().x() > (i - itemWidth / 2) && + event->pos().x() < (i + itemWidth / 2)) + { + internalColumn = c; + break; + } + } + if (internalColumn == -1) + { + viewport()->update(); + return; + } + } + + // calculate the internal row + int internalRow = -1; + { + const int itemHeight = itemSize().height(); + const int top = categoryTop(category); + for (int i = top + category->headerHeight(), r = 0; + i < top + category->totalHeight(); + i += itemHeight, ++r) + { + if (event->pos().y() > i && event->pos().y() < (i + itemHeight)) + { + internalRow = r; + break; + } + } + if (internalRow == -1) + { + viewport()->update(); + return; + } + } + + QList indices = itemsForCategory(category); + + // flaten the internalColumn/internalRow to one row + int categoryRow; + { + for (int i = 0; i < internalRow; ++i) + { + if (i == internalRow) + { + break; + } + categoryRow += itemsPerRow(); + } + categoryRow += internalColumn; + } + + int row = indices.at(categoryRow).row(); + if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex())) + { + event->setDropAction(Qt::MoveAction); + event->accept(); + } + updateGeometries(); +} + +bool lessThanQModelIndex(const QModelIndex &i1, const QModelIndex &i2) +{ + return i1.data() < i2.data(); +} +QRect CategorizedView::visualRect(const QModelIndex &index) const +{ + if (!index.isValid() || isIndexHidden(index) || index.column() > 0) + { + return QRect(); + } + + const Category *cat = category(index); + QList indices = itemsForCategory(cat); + qSort(indices.begin(), indices.end(), &lessThanQModelIndex); + 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; + } + } + + QSize size = itemSize(); + + QRect out; + out.setTop(categoryTop(cat) + cat->headerHeight() + 5 + y * size.height()); + out.setLeft(x * size.width()); + out.setSize(size); + + 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(index.model())->setData(index, m_categoryEditor->text(), CategoryRole); + } + m_updatesDisabled = false; + delete m_categoryEditor; + m_categoryEditor = 0; + m_editedCategory = 0; + updateGeometries(); +} +*/ diff --git a/CategorizedView.h b/CategorizedView.h new file mode 100644 index 00000000..1e918496 --- /dev/null +++ b/CategorizedView.h @@ -0,0 +1,96 @@ +#ifndef WIDGET_H +#define WIDGET_H + +#include +#include + +class CategorizedView : public QListView +{ + Q_OBJECT + +public: + CategorizedView(QWidget *parent = 0); + ~CategorizedView(); + + enum + { + CategoryRole = Qt::UserRole + }; + + virtual QRect visualRect(const QModelIndex &index) const; + +protected slots: + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); + virtual void rowsInserted(const QModelIndex &parent, int start, int end); + virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + virtual void updateGeometries(); + +protected: + virtual bool isIndexHidden(const QModelIndex &index) const; + 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; + +private: + struct Category + { + Category(const QString &text, CategorizedView *view); + Category(const Category *other); + CategorizedView *view; + QString text; + bool collapsed; + QRect iconRect; + QRect textRect; + + void drawHeader(QPainter *painter, const int y); + int totalHeight() const; + int headerHeight() const; + int contentHeight() const; + QSize categoryTotalSize() const; + }; + friend struct Category; + + QList m_categories; + + int m_leftMargin; + int m_rightMargin; + int m_categoryMargin; + int m_itemSpacing; + + //bool m_updatesDisabled; + + Category *category(const QModelIndex &index) const; + Category *category(const QString &cat) const; + int numItemsForCategory(const Category *category) const; + QList itemsForCategory(const Category *category) const; + + int categoryTop(const Category *category) const; + + int itemsPerRow() const; + int contentWidth() const; + + static bool lessThanCategoryPointer(const Category *c1, const Category *c2); + QList sortedCategories() const; + +private: + mutable QSize m_cachedItemSize; + QSize itemSize(const QStyleOptionViewItem &option) const; + QSize itemSize() const { return itemSize(viewOptions()); } + + /*QLineEdit *m_categoryEditor; + Category *m_editedCategory; + void startCategoryEditor(Category *category); + +private slots: + void endCategoryEditor();*/ +}; + +#endif // WIDGET_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 00000000..24d7075e --- /dev/null +++ b/main.cpp @@ -0,0 +1,53 @@ +#include "CategorizedView.h" +#include +#include + +#include "CategorizedProxyModel.h" + +QPixmap icon(const Qt::GlobalColor color) +{ + QPixmap p = QPixmap(32, 32); + p.fill(QColor(color)); + 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, CategorizedView::CategoryRole); + item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + return item; +} + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + QStandardItemModel model; + model.setRowCount(10); + model.setColumnCount(1); + + model.setItem(0, createItem(Qt::red, "Red", "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, createItem(Qt::green, "Green", "")); + + CategorizedProxyModel pModel; + pModel.setSourceModel(&model); + + CategorizedView w; + w.setModel(&pModel); + w.resize(640, 480); + w.show(); + + return a.exec(); +} -- cgit From 525f508d94120feae89ee1d1bd960625ab14ed37 Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Thu, 26 Dec 2013 21:16:03 +0100 Subject: Loads of stuff, amongst others d&d and many bug fixes --- CategorizedProxyModel.cpp | 11 +- CategorizedView.cpp | 681 +++++++++++++++++++++++++++++++++++----------- CategorizedView.h | 25 ++ 3 files changed, 555 insertions(+), 162 deletions(-) diff --git a/CategorizedProxyModel.cpp b/CategorizedProxyModel.cpp index 2b54ce1b..e4a7563a 100644 --- a/CategorizedProxyModel.cpp +++ b/CategorizedProxyModel.cpp @@ -8,5 +8,14 @@ CategorizedProxyModel::CategorizedProxyModel(QObject *parent) } bool CategorizedProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { - return left.data(CategorizedView::CategoryRole).toString() < right.data(CategorizedView::CategoryRole).toString(); + const QString leftCategory = left.data(CategorizedView::CategoryRole).toString(); + const QString rightCategory = right.data(CategorizedView::CategoryRole).toString(); + if (leftCategory == rightCategory) + { + return left.row() < right.row(); + } + else + { + return leftCategory < rightCategory; + } } diff --git a/CategorizedView.cpp b/CategorizedView.cpp index 46b1e072..95505fd7 100644 --- a/CategorizedView.cpp +++ b/CategorizedView.cpp @@ -5,6 +5,23 @@ #include #include #include +#include +#include +#include +#include + +template +bool listsIntersect(const QList &l1, const QList t2) +{ + foreach (const T &item, l1) + { + if (t2.contains(item)) + { + return true; + } + } + return false; +} CategorizedView::Category::Category(const QString &text, CategorizedView *view) : view(view), text(text), collapsed(false) @@ -39,7 +56,7 @@ void CategorizedView::Category::drawHeader(QPainter *painter, const int y) // the text int textWidth = painter->fontMetrics().width(text); textRect = QRect(iconRect.right() + 4, y, textWidth, headerHeight()); - painter->drawText(textRect, text, QTextOption(Qt::AlignHCenter | Qt::AlignVCenter)); + view->style()->drawItemText(painter, textRect, Qt::AlignHCenter | Qt::AlignVCenter, view->palette(), true, text); // the line painter->drawLine(textRect.right() + 4, y + headerHeight() / 2, view->contentWidth() - view->m_rightMargin, y + headerHeight() / 2); @@ -77,6 +94,11 @@ CategorizedView::CategorizedView(QWidget *parent) setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); setWordWrap(true); + setDragDropMode(QListView::InternalMove); + setAcceptDrops(true); + + m_cachedCategoryToIndexMapping.setMaxCost(50); + m_cachedVisualRects.setMaxCost(50); } CategorizedView::~CategorizedView() @@ -130,6 +152,8 @@ void CategorizedView::updateGeometries() QListView::updateGeometries(); m_cachedItemSize = QSize(); + m_cachedCategoryToIndexMapping.clear(); + m_cachedVisualRects.clear(); QMap cats; @@ -189,6 +213,17 @@ CategorizedView::Category *CategorizedView::category(const QString &cat) const } return 0; } +CategorizedView::Category *CategorizedView::categoryAt(const QPoint &pos) const +{ + for (int i = 0; i < m_categories.size(); ++i) + { + if (m_categories.at(i)->iconRect.contains(pos)) + { + return m_categories.at(i); + } + } + return 0; +} int CategorizedView::numItemsForCategory(const CategorizedView::Category *category) const { @@ -196,15 +231,47 @@ int CategorizedView::numItemsForCategory(const CategorizedView::Category *catego } QList CategorizedView::itemsForCategory(const CategorizedView::Category *category) const { - QList indices; - for (int i = 0; i < model()->rowCount(); ++i) + if (!m_cachedCategoryToIndexMapping.contains(category)) + { + QList *indices = new QList(); + for (int i = 0; i < model()->rowCount(); ++i) + { + if (model()->index(i, 0).data(CategoryRole).toString() == category->text) + { + indices->append(model()->index(i, 0)); + } + } + m_cachedCategoryToIndexMapping.insert(category, indices, indices->size()); + } + return *m_cachedCategoryToIndexMapping.object(category); +} +QModelIndex CategorizedView::firstItemForCategory(const CategorizedView::Category *category) const +{ + QList indices = itemsForCategory(category); + QModelIndex first; + foreach (const QModelIndex &index, indices) { - if (model()->index(i, 0).data(CategoryRole).toString() == category->text) + if (index.row() < first.row() || !first.isValid()) { - indices += model()->index(i, 0); + first = index; } } - return indices; + + return first; +} +QModelIndex CategorizedView::lastItemForCategory(const CategorizedView::Category *category) const +{ + QList indices = itemsForCategory(category); + QModelIndex last; + foreach (const QModelIndex &index, indices) + { + if (index.row() > last.row() || !last.isValid()) + { + last = index; + } + } + + return last; } int CategorizedView::categoryTop(const CategorizedView::Category *category) const @@ -231,14 +298,10 @@ int CategorizedView::contentWidth() const return width() - m_leftMargin - m_rightMargin; } -bool CategorizedView::lessThanCategoryPointer(const CategorizedView::Category *c1, const CategorizedView::Category *c2) -{ - return c1->text < c2->text; -} QList CategorizedView::sortedCategories() const { QList out = m_categories; - qSort(out.begin(), out.end(), &CategorizedView::lessThanCategoryPointer); + qSort(out.begin(), out.end(), [](const Category *c1, const Category *c2) { return c1->text < c2->text; }); return out; } @@ -266,85 +329,186 @@ void CategorizedView::mousePressEvent(QMouseEvent *event) { //endCategoryEditor(); - if (event->buttons() & Qt::LeftButton) + QPoint pos = event->pos(); + QPersistentModelIndex index = indexAt(pos); + + m_pressedIndex = index; + m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex); + QItemSelectionModel::SelectionFlags command = selectionCommand(index, event); + QPoint offset = QPoint(horizontalOffset(), verticalOffset()); + if (!(command & QItemSelectionModel::Current)) { - foreach (Category *category, m_categories) - { - if (category->iconRect.contains(event->pos())) - { - category->collapsed = !category->collapsed; - updateGeometries(); - viewport()->update(); - event->accept(); - return; - } - } + m_pressedPosition = pos + offset; + } + else if (!indexAt(m_pressedPosition - offset).isValid()) + { + m_pressedPosition = visualRect(currentIndex()).center() + offset; + } - for (int i = 0; i < model()->rowCount(); ++i) + 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 - offset, pos); + if (command.testFlag(QItemSelectionModel::Toggle)) { - QModelIndex index = model()->index(i, 0); - if (visualRect(index).contains(event->pos())) - { - selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); - event->accept(); - return; - } + command &= ~QItemSelectionModel::Toggle; + m_ctrlDragSelectionFlag = selectionModel()->isSelected(index) ? QItemSelectionModel::Deselect : QItemSelectionModel::Select; + command |= m_ctrlDragSelectionFlag; } - } + setSelection(rect, command); + + // signal handlers may change the model + emit pressed(index); - QListView::mousePressEvent(event); + } else { + // Forces a finalize() even if mouse is pressed, but not on a item + selectionModel()->select(QModelIndex(), QItemSelectionModel::Select); + } } void CategorizedView::mouseMoveEvent(QMouseEvent *event) { - if (event->buttons() & Qt::LeftButton) + QPoint topLeft; + QPoint bottomRight = event->pos(); + + if (state() == ExpandingState || state() == CollapsingState) { - for (int i = 0; i < model()->rowCount(); ++i) + return; + } + + if (state() == DraggingState) + { + topLeft = m_pressedPosition - QPoint(horizontalOffset(), verticalOffset()); + if ((topLeft - event->pos()).manhattanLength() > QApplication::startDragDistance()) { - QModelIndex index = model()->index(i, 0); - if (visualRect(index).contains(event->pos())) - { - selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); - event->accept(); - return; - } + m_pressedIndex = QModelIndex(); + startDrag(model()->supportedDragActions()); + setState(NoState); + stopAutoScroll(); } + return; } - QListView::mouseMoveEvent(event); -} -void CategorizedView::mouseReleaseEvent(QMouseEvent *event) -{ - if (event->buttons() & Qt::LeftButton) + QPersistentModelIndex index = indexAt(bottomRight); + + if (selectionMode() != SingleSelection) { - for (int i = 0; i < model()->rowCount(); ++i) + topLeft = m_pressedPosition - QPoint(horizontalOffset(), verticalOffset()); + } + else + { + topLeft = bottomRight; + } + + if (m_pressedIndex.isValid() + && (state() != DragSelectingState) + && (event->buttons() != Qt::NoButton) + && !selectedIndexes().isEmpty()) + { + setState(DraggingState); + return; + } + + if ((event->buttons() & Qt::LeftButton) && selectionModel()) + { + setState(DragSelectingState); + QItemSelectionModel::SelectionFlags command = selectionCommand(index, event); + if (m_ctrlDragSelectionFlag != QItemSelectionModel::NoUpdate && command.testFlag(QItemSelectionModel::Toggle)) { - QModelIndex index = model()->index(i, 0); - if (visualRect(index).contains(event->pos())) - { - selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); - event->accept(); - return; - } + command &= ~QItemSelectionModel::Toggle; + command |= m_ctrlDragSelectionFlag; } - } - QListView::mouseReleaseEvent(event); + // Do the normalize ourselves, since QRect::normalized() is flawed + QRect selectionRect = QRect(topLeft, bottomRight); + setSelection(selectionRect, command); + + // 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 CategorizedView::mouseDoubleClickEvent(QMouseEvent *event) +void CategorizedView::mouseReleaseEvent(QMouseEvent *event) { - /*endCategoryEditor(); + QPoint pos = event->pos(); + QPersistentModelIndex index = indexAt(pos); - foreach (Category *category, m_categories) + bool click = (index == m_pressedIndex && index.isValid()) || (m_pressedCategory && m_pressedCategory == categoryAt(pos)); + + if (click && m_pressedCategory) { - if (category->textRect.contains(event->pos()) && m_categoryEditor == 0) + if (state() == ExpandingState) { - startCategoryEditor(category); + 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); - QListView::mouseDoubleClickEvent(event); + 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 CategorizedView::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 CategorizedView::paintEvent(QPaintEvent *event) { @@ -385,6 +549,33 @@ void CategorizedView::paintEvent(QPaintEvent *event) } itemDelegate()->paint(&painter, option, index); } + + if (!m_lastDragPosition.isNull()) + { + QPair pair = rowDropPos(m_lastDragPosition); + Category *category = pair.first; + int row = pair.second; + if (category) + { + int internalRow = row - firstItemForCategory(category).row(); + qDebug() << internalRow << numItemsForCategory(category) << model()->index(row, 0).data().toString(); + QLine line; + if (internalRow >= numItemsForCategory(category)) + { + QRect toTheRightOfRect = visualRect(lastItemForCategory(category)); + 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(); + } + } } void CategorizedView::resizeEvent(QResizeEvent *event) { @@ -400,18 +591,33 @@ void CategorizedView::resizeEvent(QResizeEvent *event) void CategorizedView::dragEnterEvent(QDragEnterEvent *event) { - // TODO + if (!isDragEventAccepted(event)) + { + return; + } + m_lastDragPosition = event->pos(); + viewport()->update(); + event->accept(); } void CategorizedView::dragMoveEvent(QDragMoveEvent *event) { - // TODO + if (!isDragEventAccepted(event)) + { + return; + } + m_lastDragPosition = event->pos(); + viewport()->update(); + event->accept(); } void CategorizedView::dragLeaveEvent(QDragLeaveEvent *event) { - // TODO + m_lastDragPosition = QPoint(); + viewport()->update(); } void CategorizedView::dropEvent(QDropEvent *event) { + m_lastDragPosition = QPoint(); + stopAutoScroll(); setState(NoState); @@ -420,93 +626,68 @@ void CategorizedView::dropEvent(QDropEvent *event) return; } - // check that we aren't on a category header and calculate which category we're in - Category *category = 0; + QPair dropPos = rowDropPos(event->pos()); + const Category *category = dropPos.first; + const int row = dropPos.second; + + if (row == -1) { - int y = 0; - foreach (Category *cat, m_categories) - { - if (event->pos().y() > y && event->pos().y() < (y + cat->headerHeight())) - { - viewport()->update(); - return; - } - y += cat->totalHeight() + m_categoryMargin; - if (event->pos().y() < y) - { - category = cat; - break; - } - } + viewport()->update(); + return; } - // calculate the internal column - int internalColumn = -1; + const QString categoryText = category->text; + if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex())) { - const int itemWidth = itemSize().width(); - for (int i = 0, c = 0; - i < contentWidth(); - i += itemWidth, ++c) - { - if (event->pos().x() > (i - itemWidth / 2) && - event->pos().x() < (i + itemWidth / 2)) - { - internalColumn = c; - break; - } - } - if (internalColumn == -1) - { - viewport()->update(); - return; - } + model()->setData(model()->index(row, 0), categoryText, CategoryRole); + event->setDropAction(Qt::MoveAction); + event->accept(); } + updateGeometries(); + viewport()->update(); +} - // calculate the internal row - int internalRow = -1; +void CategorizedView::startDrag(Qt::DropActions supportedActions) +{ + QModelIndexList indexes = selectionModel()->selectedIndexes(); + if (indexes.count() > 0) { - const int itemHeight = itemSize().height(); - const int top = categoryTop(category); - for (int i = top + category->headerHeight(), r = 0; - i < top + category->totalHeight(); - i += itemHeight, ++r) + QMimeData *data = model()->mimeData(indexes); + if (!data) { - if (event->pos().y() > i && event->pos().y() < (i + itemHeight)) - { - internalRow = r; - break; - } + return; } - if (internalRow == -1) + QRect rect; + QPixmap pixmap = renderToPixmap(indexes, &rect); + 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())) { - viewport()->update(); - return; + defaultDropAction = this->defaultDropAction(); } - } - - QList indices = itemsForCategory(category); - - // flaten the internalColumn/internalRow to one row - int categoryRow; - { - for (int i = 0; i < internalRow; ++i) + if (drag->exec(supportedActions, defaultDropAction) == Qt::MoveAction) { - if (i == internalRow) - { - break; + 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); } - categoryRow += itemsPerRow(); } - categoryRow += internalColumn; - } - - int row = indices.at(categoryRow).row(); - if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex())) - { - event->setDropAction(Qt::MoveAction); - event->accept(); } - updateGeometries(); } bool lessThanQModelIndex(const QModelIndex &i1, const QModelIndex &i2) @@ -520,34 +701,39 @@ QRect CategorizedView::visualRect(const QModelIndex &index) const return QRect(); } - const Category *cat = category(index); - QList indices = itemsForCategory(cat); - qSort(indices.begin(), indices.end(), &lessThanQModelIndex); - int x = 0; - int y = 0; - const int perRow = itemsPerRow(); - for (int i = 0; i < indices.size(); ++i) + if (!m_cachedVisualRects.contains(index)) { - if (indices.at(i) == index) - { - break; - } - ++x; - if (x == perRow) + const Category *cat = category(index); + QList indices = itemsForCategory(cat); + qSort(indices.begin(), indices.end(), &lessThanQModelIndex); + int x = 0; + int y = 0; + const int perRow = itemsPerRow(); + for (int i = 0; i < indices.size(); ++i) { - x = 0; - ++y; + if (indices.at(i) == index) + { + break; + } + ++x; + if (x == perRow) + { + x = 0; + ++y; + } } - } - QSize size = itemSize(); + QSize size = itemSize(); - QRect out; - out.setTop(categoryTop(cat) + cat->headerHeight() + 5 + y * size.height()); - out.setLeft(x * size.width()); - out.setSize(size); + QRect *out = new QRect; + out->setTop(categoryTop(cat) + cat->headerHeight() + 5 + y * size.height()); + out->setLeft(x * size.width()); + out->setSize(size); - return out; + m_cachedVisualRects.insert(index, out); + } + + return *m_cachedVisualRects.object(index); } /* void CategorizedView::startCategoryEditor(Category *category) @@ -585,3 +771,176 @@ void CategorizedView::endCategoryEditor() updateGeometries(); } */ + +QModelIndex CategorizedView::indexAt(const QPoint &point) const +{ + for (int i = 0; i < model()->rowCount(); ++i) + { + QModelIndex index = model()->index(i, 0); + if (visualRect(index).contains(point)) + { + return index; + } + } + return QModelIndex(); +} +void CategorizedView::setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) +{ + QItemSelection selection; + for (int i = 0; i < model()->rowCount(); ++i) + { + QModelIndex index = model()->index(i, 0); + if (visualRect(index).intersects(rect)) + { + selection.merge(QItemSelection(index, index), QItemSelectionModel::Select); + } + } + selectionModel()->select(selection, commands); +} + +QPixmap CategorizedView::renderToPixmap(const QModelIndexList &indices, QRect *r) const +{ + Q_ASSERT(r); + QList > 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 > CategorizedView::draggablePaintPairs(const QModelIndexList &indices, QRect *r) const +{ + Q_ASSERT(r); + QRect &rect = *r; + const QRect viewportRect = viewport()->rect(); + QList > ret; + for (int i = 0; i < indices.count(); ++i) { + const QModelIndex &index = indices.at(i); + const QRect current = visualRect(index); + if (current.intersects(viewportRect)) { + ret += qMakePair(current, index); + rect |= current; + } + } + rect &= viewportRect; + return ret; +} + +bool CategorizedView::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 CategorizedView::rowDropPos(const QPoint &pos) +{ + // check that we aren't on a category header and calculate which category we're in + Category *category = 0; + { + int y = 0; + foreach (Category *cat, m_categories) + { + 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); + } + } + + // calculate the internal column + int internalColumn = -1; + { + const int itemWidth = itemSize().width(); + for (int i = 0, c = 0; + i < contentWidth(); + i += itemWidth, ++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; + { + const int itemHeight = itemSize().height(); + const int top = categoryTop(category); + for (int i = top + category->headerHeight(), r = 0; + i < top + category->totalHeight(); + i += itemHeight, ++r) + { + if (pos.y() > i && pos.y() < (i + itemHeight)) + { + internalRow = r; + break; + } + } + if (internalRow == -1) + { + return qMakePair(nullptr, -1); + } + } + + QList indices = itemsForCategory(category); + + // flaten the internalColumn/internalRow to one row + int categoryRow = 0; + { + for (int i = 0; i < internalRow; ++i) + { + if ((i + 1) >= internalRow) + { + break; + } + categoryRow += itemsPerRow(); + } + categoryRow += internalColumn; + } + + // this is used if we're past the last item + if (internalColumn >= qMin(itemsPerRow(), indices.size())) + { + return qMakePair(category, indices.last().row() + 1); + } + + return qMakePair(category, indices.at(categoryRow).row()); +} diff --git a/CategorizedView.h b/CategorizedView.h index 1e918496..0756629a 100644 --- a/CategorizedView.h +++ b/CategorizedView.h @@ -3,6 +3,7 @@ #include #include +#include class CategorizedView : public QListView { @@ -18,6 +19,8 @@ public: }; virtual QRect visualRect(const QModelIndex &index) const; + QModelIndex indexAt(const QPoint &point) const; + void setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) override; protected slots: void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); @@ -39,6 +42,8 @@ protected: void dragLeaveEvent(QDragLeaveEvent *event) override; void dropEvent(QDropEvent *event) override; + void startDrag(Qt::DropActions supportedActions) override; + private: struct Category { @@ -59,6 +64,8 @@ private: friend struct Category; QList m_categories; + mutable QCache > m_cachedCategoryToIndexMapping; + mutable QCache m_cachedVisualRects; int m_leftMargin; int m_rightMargin; @@ -69,8 +76,11 @@ private: Category *category(const QModelIndex &index) const; Category *category(const QString &cat) const; + Category *categoryAt(const QPoint &pos) const; int numItemsForCategory(const Category *category) const; QList itemsForCategory(const Category *category) const; + QModelIndex firstItemForCategory(const Category *category) const; + QModelIndex lastItemForCategory(const Category *category) const; int categoryTop(const Category *category) const; @@ -91,6 +101,21 @@ private: private slots: void endCategoryEditor();*/ + +private: + QPoint m_pressedPosition; + QPersistentModelIndex m_pressedIndex; + bool m_pressedAlreadySelected; + Category *m_pressedCategory; + QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag; + QPoint m_lastDragPosition; + + QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const; + QList > draggablePaintPairs(const QModelIndexList &indices, QRect *r) const; + + bool isDragEventAccepted(QDropEvent *event); + + QPair rowDropPos(const QPoint &pos); }; #endif // WIDGET_H -- cgit From c71808446b3e95e4fefb91b69c2cc51e4c4918cc Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Thu, 26 Dec 2013 21:23:21 +0100 Subject: Fix a bug --- CategorizedView.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CategorizedView.cpp b/CategorizedView.cpp index 95505fd7..4d9a4a62 100644 --- a/CategorizedView.cpp +++ b/CategorizedView.cpp @@ -690,10 +690,6 @@ void CategorizedView::startDrag(Qt::DropActions supportedActions) } } -bool lessThanQModelIndex(const QModelIndex &i1, const QModelIndex &i2) -{ - return i1.data() < i2.data(); -} QRect CategorizedView::visualRect(const QModelIndex &index) const { if (!index.isValid() || isIndexHidden(index) || index.column() > 0) @@ -705,7 +701,6 @@ QRect CategorizedView::visualRect(const QModelIndex &index) const { const Category *cat = category(index); QList indices = itemsForCategory(cat); - qSort(indices.begin(), indices.end(), &lessThanQModelIndex); int x = 0; int y = 0; const int perRow = itemsPerRow(); -- cgit From acbbdf319a7378a4029965a52222e7a84c33253f Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Thu, 26 Dec 2013 21:24:42 +0100 Subject: Remove a debug message --- CategorizedView.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/CategorizedView.cpp b/CategorizedView.cpp index 4d9a4a62..780674eb 100644 --- a/CategorizedView.cpp +++ b/CategorizedView.cpp @@ -558,7 +558,6 @@ void CategorizedView::paintEvent(QPaintEvent *event) if (category) { int internalRow = row - firstItemForCategory(category).row(); - qDebug() << internalRow << numItemsForCategory(category) << model()->index(row, 0).data().toString(); QLine line; if (internalRow >= numItemsForCategory(category)) { -- cgit From 53db8edb851917809209e4473eef2a66651d6047 Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Thu, 26 Dec 2013 22:02:25 +0100 Subject: Fixing several d&d bugs --- CategorizedView.cpp | 26 +++++++++++--------------- main.cpp | 27 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/CategorizedView.cpp b/CategorizedView.cpp index 780674eb..1860f095 100644 --- a/CategorizedView.cpp +++ b/CategorizedView.cpp @@ -872,6 +872,8 @@ QPair CategorizedView::rowDropPos(const QPoint } } + QList indices = itemsForCategory(category); + // calculate the internal column int internalColumn = -1; { @@ -912,26 +914,20 @@ QPair CategorizedView::rowDropPos(const QPoint { return qMakePair(nullptr, -1); } - } - - QList indices = itemsForCategory(category); - - // flaten the internalColumn/internalRow to one row - int categoryRow = 0; - { - for (int i = 0; i < internalRow; ++i) + // this happens if we're in the margin between a one category and another + // categories header + if (internalRow > (indices.size() / itemsPerRow())) { - if ((i + 1) >= internalRow) - { - break; - } - categoryRow += itemsPerRow(); + return qMakePair(nullptr, -1); } - categoryRow += internalColumn; } + // flaten the internalColumn/internalRow to one row + int categoryRow = internalRow * itemsPerRow() + internalColumn; + // this is used if we're past the last item - if (internalColumn >= qMin(itemsPerRow(), indices.size())) + int numItemsInLastRow = indices.size() % itemsPerRow(); + if (internalColumn >= numItemsInLastRow) { return qMakePair(category, indices.last().row() + 1); } diff --git a/main.cpp b/main.cpp index 24d7075e..427ae17a 100644 --- a/main.cpp +++ b/main.cpp @@ -1,6 +1,7 @@ #include "CategorizedView.h" #include #include +#include #include "CategorizedProxyModel.h" @@ -10,6 +11,18 @@ QPixmap icon(const Qt::GlobalColor color) 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; @@ -19,6 +32,15 @@ QStandardItem *createItem(const Qt::GlobalColor color, const QString &text, cons item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); 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, CategorizedView::CategoryRole); + item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + return item; +} int main(int argc, char *argv[]) { @@ -41,6 +63,11 @@ int main(int argc, char *argv[]) model.setItem(8, createItem(Qt::darkGreen, "Dark Green", "")); model.setItem(9, createItem(Qt::green, "Green", "")); + for (int i = 0; i < 21; ++i) + { + model.setItem(i + 10, createItem(i+1, "Items 1-20")); + } + CategorizedProxyModel pModel; pModel.setSourceModel(&model); -- cgit From f8d835cd22de89bc130ff0413228cfea0ebfd8ac Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Thu, 26 Dec 2013 22:40:26 +0100 Subject: Fix scrolling --- CategorizedView.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CategorizedView.cpp b/CategorizedView.cpp index 1860f095..6164cde6 100644 --- a/CategorizedView.cpp +++ b/CategorizedView.cpp @@ -9,6 +9,7 @@ #include #include #include +#include template bool listsIntersect(const QList &l1, const QList t2) @@ -182,6 +183,22 @@ void CategorizedView::updateGeometries() qDeleteAll(m_categories); m_categories = cats.values(); + if (m_categories.isEmpty()) + { + verticalScrollBar()->setRange(0, 0); + } + else + { + int totalHeight = 0; + foreach (const Category *category, m_categories) + { + totalHeight += category->totalHeight() + m_categoryMargin; + } + // remove the last margin (we don't want it) + totalHeight -= m_categoryMargin; + verticalScrollBar()->setRange(0, totalHeight- height()); + } + update(); } @@ -513,6 +530,8 @@ void CategorizedView::mouseDoubleClickEvent(QMouseEvent *event) void CategorizedView::paintEvent(QPaintEvent *event) { QPainter painter(this->viewport()); + QPoint offset(horizontalOffset(), verticalOffset()); + painter.translate(-offset); int y = 0; for (int i = 0; i < m_categories.size(); ++i) -- cgit From 01092206783f74ce14f31d328cdac025fd90fe16 Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Fri, 27 Dec 2013 00:03:24 +0100 Subject: Take the spacing into account --- CategorizedView.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CategorizedView.cpp b/CategorizedView.cpp index 6164cde6..c134220f 100644 --- a/CategorizedView.cpp +++ b/CategorizedView.cpp @@ -97,6 +97,7 @@ CategorizedView::CategorizedView(QWidget *parent) setWordWrap(true); setDragDropMode(QListView::InternalMove); setAcceptDrops(true); + setSpacing(10); m_cachedCategoryToIndexMapping.setMaxCost(50); m_cachedVisualRects.setMaxCost(50); @@ -308,7 +309,7 @@ int CategorizedView::categoryTop(const CategorizedView::Category *category) cons int CategorizedView::itemsPerRow() const { - return qFloor((qreal)contentWidth() / (qreal)itemSize().width()); + return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + spacing())); } int CategorizedView::contentWidth() const { @@ -740,7 +741,7 @@ QRect CategorizedView::visualRect(const QModelIndex &index) const QRect *out = new QRect; out->setTop(categoryTop(cat) + cat->headerHeight() + 5 + y * size.height()); - out->setLeft(x * size.width()); + out->setLeft(spacing() + x * itemWidth() + x * spacing()); out->setSize(size); m_cachedVisualRects.insert(index, out); -- cgit From 4662fbd29891ccb9120df82d17a34a7619242827 Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Mon, 30 Dec 2013 18:45:40 +0100 Subject: Make the MultiMC delegate fully usable. Dynamic row heights. --- CMakeLists.txt | 2 + CategorizedView.cpp | 104 ++++++++++++++------- CategorizedView.h | 9 +- InstanceDelegate.cpp | 254 +++++++++++++++++++++++++++++++++++++++++++++++++++ InstanceDelegate.h | 27 ++++++ main.cpp | 4 +- 6 files changed, 363 insertions(+), 37 deletions(-) create mode 100644 InstanceDelegate.cpp create mode 100644 InstanceDelegate.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a246bcf..b94cf53e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,8 @@ set(SOURCES CategorizedView.cpp CategorizedProxyModel.h CategorizedProxyModel.cpp + InstanceDelegate.h + InstanceDelegate.cpp ) add_executable(GroupView ${SOURCES}) diff --git a/CategorizedView.cpp b/CategorizedView.cpp index c134220f..bdb0b222 100644 --- a/CategorizedView.cpp +++ b/CategorizedView.cpp @@ -80,7 +80,22 @@ int CategorizedView::Category::contentHeight() const return 0; } const int rows = qMax(1, qCeil((qreal)view->numItemsForCategory(this) / (qreal)view->itemsPerRow())); - return view->itemSize().height() * rows; + QMap rowToHeightMapping; + foreach (const QModelIndex &index, view->itemsForCategory(this)) + { + int row = view->categoryInternalPosition(index).second; + if (!rowToHeightMapping.contains(row)) + { + rowToHeightMapping.insert(row, view->itemSize(index).height()); + } + } + int result = 0; + for (int i = 0; i < rows; ++i) + { + Q_ASSERT(rowToHeightMapping.contains(i)); + result += rowToHeightMapping[i]; + } + return result; } QSize CategorizedView::Category::categoryTotalSize() const { @@ -153,9 +168,10 @@ void CategorizedView::updateGeometries() { QListView::updateGeometries(); - m_cachedItemSize = QSize(); + m_cachedItemWidth = -1; m_cachedCategoryToIndexMapping.clear(); m_cachedVisualRects.clear(); + m_cachedItemSizes.clear(); QMap cats; @@ -323,24 +339,59 @@ QList CategorizedView::sortedCategories() const return out; } -QSize CategorizedView::itemSize(const QStyleOptionViewItem &option) const +int CategorizedView::itemWidth() const { - if (!m_cachedItemSize.isValid()) + if (m_cachedItemWidth == -1) { - QModelIndex sample = model()->index(model()->rowCount() -1, 0); - const QAbstractItemDelegate *delegate = itemDelegate(); - if (delegate) + m_cachedItemWidth = itemDelegate()->sizeHint(viewOptions(), model()->index(model()->rowCount() -1, 0)).width(); + } + return m_cachedItemWidth; +} + +QSize CategorizedView::itemSize(const QModelIndex &index) const +{ + if (!m_cachedItemSizes.contains(index)) + { + QModelIndexList indices; + int internalRow = categoryInternalPosition(index).second; + foreach (const QModelIndex &i, itemsForCategory(category(index))) + { + if (categoryInternalPosition(i).second == internalRow) + { + indices.append(i); + } + } + + int largestHeight = 0; + foreach (const QModelIndex &i, indices) { - m_cachedItemSize = delegate->sizeHint(option, sample); - m_cachedItemSize.setWidth(m_cachedItemSize.width() + 20); - m_cachedItemSize.setHeight(m_cachedItemSize.height() + 20); + largestHeight = qMax(largestHeight, itemDelegate()->sizeHint(viewOptions(), i).height()); } - else + m_cachedItemSizes.insert(index, new QSize(itemWidth(), largestHeight)); + } + return *m_cachedItemSizes.object(index); +} + +QPair CategorizedView::categoryInternalPosition(const QModelIndex &index) const +{ + QList indices = itemsForCategory(category(index)); + 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) { - m_cachedItemSize = QSize(); + x = 0; + ++y; } } - return m_cachedItemSize; + return qMakePair(x, y); } void CategorizedView::mousePressEvent(QMouseEvent *event) @@ -719,25 +770,11 @@ QRect CategorizedView::visualRect(const QModelIndex &index) const if (!m_cachedVisualRects.contains(index)) { const Category *cat = category(index); - QList indices = itemsForCategory(cat); - 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; - } - } + QPair pos = categoryInternalPosition(index); + int x = pos.first; + int y = pos.second; - QSize size = itemSize(); + QSize size = itemSize(index); QRect *out = new QRect; out->setTop(categoryTop(cat) + cat->headerHeight() + 5 + y * size.height()); @@ -897,7 +934,7 @@ QPair CategorizedView::rowDropPos(const QPoint // calculate the internal column int internalColumn = -1; { - const int itemWidth = itemSize().width(); + const int itemWidth = this->itemWidth(); for (int i = 0, c = 0; i < contentWidth(); i += itemWidth, ++c) @@ -918,7 +955,8 @@ QPair CategorizedView::rowDropPos(const QPoint // calculate the internal row int internalRow = -1; { - const int itemHeight = itemSize().height(); + // FIXME rework the drag and drop code + const int itemHeight = 0; //itemSize().height(); const int top = categoryTop(category); for (int i = top + category->headerHeight(), r = 0; i < top + category->totalHeight(); diff --git a/CategorizedView.h b/CategorizedView.h index 0756629a..e98e7c5e 100644 --- a/CategorizedView.h +++ b/CategorizedView.h @@ -91,9 +91,10 @@ private: QList sortedCategories() const; private: - mutable QSize m_cachedItemSize; - QSize itemSize(const QStyleOptionViewItem &option) const; - QSize itemSize() const { return itemSize(viewOptions()); } + mutable int m_cachedItemWidth; + mutable QCache m_cachedItemSizes; + int itemWidth() const; + QSize itemSize(const QModelIndex &index) const; /*QLineEdit *m_categoryEditor; Category *m_editedCategory; @@ -110,6 +111,8 @@ private: QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag; QPoint m_lastDragPosition; + QPair categoryInternalPosition(const QModelIndex &index) const; + QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const; QList > draggablePaintPairs(const QModelIndexList &indices, QRect *r) const; diff --git a/InstanceDelegate.cpp b/InstanceDelegate.cpp new file mode 100644 index 00000000..5020b8b6 --- /dev/null +++ b/InstanceDelegate.cpp @@ -0,0 +1,254 @@ +/* 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 +#include +#include +#include +#include + +// 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); +} + +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); + } + + 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/InstanceDelegate.h b/InstanceDelegate.h new file mode 100644 index 00000000..6f924405 --- /dev/null +++ b/InstanceDelegate.h @@ -0,0 +1,27 @@ +/* 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 + +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/main.cpp b/main.cpp index 427ae17a..b164b85d 100644 --- a/main.cpp +++ b/main.cpp @@ -4,6 +4,7 @@ #include #include "CategorizedProxyModel.h" +#include "InstanceDelegate.h" QPixmap icon(const Qt::GlobalColor color) { @@ -50,7 +51,7 @@ int main(int argc, char *argv[]) model.setRowCount(10); model.setColumnCount(1); - model.setItem(0, createItem(Qt::red, "Red", "Colorful")); + 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")); @@ -72,6 +73,7 @@ int main(int argc, char *argv[]) pModel.setSourceModel(&model); CategorizedView w; + w.setItemDelegate(new ListViewDelegate); w.setModel(&pModel); w.resize(640, 480); w.show(); -- cgit From 1e1b2342f48a3dd1eff90229ac4005fb0d45d2ba Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Mon, 30 Dec 2013 18:46:12 +0100 Subject: Loads of fixes --- CategorizedView.cpp | 115 +++++++++++++++++++++++++++++++--------------------- CategorizedView.h | 8 +++- main.cpp | 2 +- 3 files changed, 77 insertions(+), 48 deletions(-) diff --git a/CategorizedView.cpp b/CategorizedView.cpp index bdb0b222..f660e8f3 100644 --- a/CategorizedView.cpp +++ b/CategorizedView.cpp @@ -79,7 +79,6 @@ int CategorizedView::Category::contentHeight() const { return 0; } - const int rows = qMax(1, qCeil((qreal)view->numItemsForCategory(this) / (qreal)view->itemsPerRow())); QMap rowToHeightMapping; foreach (const QModelIndex &index, view->itemsForCategory(this)) { @@ -90,20 +89,23 @@ int CategorizedView::Category::contentHeight() const } } int result = 0; - for (int i = 0; i < rows; ++i) + if (!rowToHeightMapping.isEmpty()) { - Q_ASSERT(rowToHeightMapping.contains(i)); - result += rowToHeightMapping[i]; + for (int i = 0; i < numRows(); ++i) + { + Q_ASSERT(rowToHeightMapping.contains(i)); + result += rowToHeightMapping[i]; + } } return result; } -QSize CategorizedView::Category::categoryTotalSize() const +int CategorizedView::Category::numRows() const { - return QSize(view->contentWidth(), contentHeight()); + return qMax(1, qCeil((qreal)view->numItemsForCategory(this) / (qreal)view->itemsPerRow())); } CategorizedView::CategorizedView(QWidget *parent) - : QListView(parent), m_leftMargin(5), m_rightMargin(5), m_categoryMargin(5)//, m_updatesDisabled(false), m_categoryEditor(0), m_editedCategory(0) + : QListView(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); @@ -168,10 +170,7 @@ void CategorizedView::updateGeometries() { QListView::updateGeometries(); - m_cachedItemWidth = -1; - m_cachedCategoryToIndexMapping.clear(); - m_cachedVisualRects.clear(); - m_cachedItemSizes.clear(); + invalidateCaches(); QMap cats; @@ -213,6 +212,7 @@ void CategorizedView::updateGeometries() } // remove the last margin (we don't want it) totalHeight -= m_categoryMargin; + totalHeight += m_bottomMargin; verticalScrollBar()->setRange(0, totalHeight- height()); } @@ -265,7 +265,7 @@ int CategorizedView::numItemsForCategory(const CategorizedView::Category *catego } QList CategorizedView::itemsForCategory(const CategorizedView::Category *category) const { - if (!m_cachedCategoryToIndexMapping.contains(category)) + if (!m_cachedCategoryToIndexMapping.contains(category) || true) { QList *indices = new QList(); for (int i = 0; i < model()->rowCount(); ++i) @@ -393,26 +393,30 @@ QPair CategorizedView::categoryInternalPosition(const QModelIndex &ind } return qMakePair(x, y); } +int CategorizedView::itemHeightForCategoryRow(const CategorizedView::Category *category, const int internalRow) const +{ + foreach (const QModelIndex &i, itemsForCategory(category)) + { + QPair pos = categoryInternalPosition(i); + if (pos.second == internalRow) + { + return itemSize(i).height(); + } + } + return -1; +} void CategorizedView::mousePressEvent(QMouseEvent *event) { //endCategoryEditor(); - QPoint pos = event->pos(); + QPoint pos = event->pos() + offset(); QPersistentModelIndex index = indexAt(pos); m_pressedIndex = index; m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex); QItemSelectionModel::SelectionFlags command = selectionCommand(index, event); - QPoint offset = QPoint(horizontalOffset(), verticalOffset()); - if (!(command & QItemSelectionModel::Current)) - { - m_pressedPosition = pos + offset; - } - else if (!indexAt(m_pressedPosition - offset).isValid()) - { - m_pressedPosition = visualRect(currentIndex()).center() + offset; - } + m_pressedPosition = pos; m_pressedCategory = categoryAt(m_pressedPosition); if (m_pressedCategory) @@ -430,7 +434,7 @@ void CategorizedView::mousePressEvent(QMouseEvent *event) setAutoScroll(false); selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); setAutoScroll(autoScroll); - QRect rect(m_pressedPosition - offset, pos); + QRect rect(m_pressedPosition, pos); if (command.testFlag(QItemSelectionModel::Toggle)) { command &= ~QItemSelectionModel::Toggle; @@ -459,7 +463,7 @@ void CategorizedView::mouseMoveEvent(QMouseEvent *event) if (state() == DraggingState) { - topLeft = m_pressedPosition - QPoint(horizontalOffset(), verticalOffset()); + topLeft = m_pressedPosition - offset(); if ((topLeft - event->pos()).manhattanLength() > QApplication::startDragDistance()) { m_pressedIndex = QModelIndex(); @@ -474,7 +478,7 @@ void CategorizedView::mouseMoveEvent(QMouseEvent *event) if (selectionMode() != SingleSelection) { - topLeft = m_pressedPosition - QPoint(horizontalOffset(), verticalOffset()); + topLeft = m_pressedPosition - offset(); } else { @@ -515,7 +519,7 @@ void CategorizedView::mouseMoveEvent(QMouseEvent *event) } void CategorizedView::mouseReleaseEvent(QMouseEvent *event) { - QPoint pos = event->pos(); + QPoint pos = event->pos() - offset(); QPersistentModelIndex index = indexAt(pos); bool click = (index == m_pressedIndex && index.isValid()) || (m_pressedCategory && m_pressedCategory == categoryAt(pos)); @@ -582,8 +586,10 @@ void CategorizedView::mouseDoubleClickEvent(QMouseEvent *event) void CategorizedView::paintEvent(QPaintEvent *event) { QPainter painter(this->viewport()); - QPoint offset(horizontalOffset(), verticalOffset()); - painter.translate(-offset); + painter.translate(-offset()); + + // FIXME we shouldn't need to do this + invalidateCaches(); int y = 0; for (int i = 0; i < m_categories.size(); ++i) @@ -665,7 +671,7 @@ void CategorizedView::dragEnterEvent(QDragEnterEvent *event) { return; } - m_lastDragPosition = event->pos(); + m_lastDragPosition = event->pos() + offset(); viewport()->update(); event->accept(); } @@ -675,7 +681,7 @@ void CategorizedView::dragMoveEvent(QDragMoveEvent *event) { return; } - m_lastDragPosition = event->pos(); + m_lastDragPosition = event->pos() + offset(); viewport()->update(); event->accept(); } @@ -696,7 +702,7 @@ void CategorizedView::dropEvent(QDropEvent *event) return; } - QPair dropPos = rowDropPos(event->pos()); + QPair dropPos = rowDropPos(event->pos() + offset()); const Category *category = dropPos.first; const int row = dropPos.second; @@ -729,7 +735,8 @@ void CategorizedView::startDrag(Qt::DropActions supportedActions) } QRect rect; QPixmap pixmap = renderToPixmap(indexes, &rect); - rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); + rect.translate(offset()); + //rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); QDrag *drag = new QDrag(this); drag->setPixmap(pixmap); drag->setMimeData(data); @@ -935,15 +942,22 @@ QPair CategorizedView::rowDropPos(const QPoint int internalColumn = -1; { const int itemWidth = this->itemWidth(); - for (int i = 0, c = 0; - i < contentWidth(); - i += itemWidth, ++c) + if (pos.x() >= (itemWidth * itemsPerRow())) { - if (pos.x() > (i - itemWidth / 2) && - pos.x() < (i + itemWidth / 2)) + internalColumn = itemsPerRow(); + } + else + { + for (int i = 0, c = 0; + i < contentWidth(); + i += itemWidth + spacing(), ++c) { - internalColumn = c; - break; + if (pos.x() > (i - itemWidth / 2) && + pos.x() <= (i + itemWidth / 2)) + { + internalColumn = c; + break; + } } } if (internalColumn == -1) @@ -956,13 +970,10 @@ QPair CategorizedView::rowDropPos(const QPoint int internalRow = -1; { // FIXME rework the drag and drop code - const int itemHeight = 0; //itemSize().height(); const int top = categoryTop(category); - for (int i = top + category->headerHeight(), r = 0; - i < top + category->totalHeight(); - i += itemHeight, ++r) + for (int r = 0, h = top; r < category->numRows(); h += itemHeightForCategoryRow(category, r), ++r) { - if (pos.y() > i && pos.y() < (i + itemHeight)) + if (pos.y() > h && pos.y() < (h + itemHeightForCategoryRow(category, r))) { internalRow = r; break; @@ -984,11 +995,23 @@ QPair CategorizedView::rowDropPos(const QPoint int categoryRow = internalRow * itemsPerRow() + internalColumn; // this is used if we're past the last item - int numItemsInLastRow = indices.size() % itemsPerRow(); - if (internalColumn >= numItemsInLastRow) + if (categoryRow >= indices.size()) { return qMakePair(category, indices.last().row() + 1); } return qMakePair(category, indices.at(categoryRow).row()); } + +void CategorizedView::invalidateCaches() +{ + m_cachedItemWidth = -1; + m_cachedCategoryToIndexMapping.clear(); + m_cachedVisualRects.clear(); + m_cachedItemSizes.clear(); +} + +QPoint CategorizedView::offset() const +{ + return QPoint(horizontalOffset(), verticalOffset()); +} diff --git a/CategorizedView.h b/CategorizedView.h index e98e7c5e..8ab9ce87 100644 --- a/CategorizedView.h +++ b/CategorizedView.h @@ -59,7 +59,7 @@ private: int totalHeight() const; int headerHeight() const; int contentHeight() const; - QSize categoryTotalSize() const; + int numRows() const; }; friend struct Category; @@ -69,6 +69,7 @@ private: int m_leftMargin; int m_rightMargin; + int m_bottomMargin; int m_categoryMargin; int m_itemSpacing; @@ -112,6 +113,7 @@ private: QPoint m_lastDragPosition; QPair categoryInternalPosition(const QModelIndex &index) const; + int itemHeightForCategoryRow(const Category *category, const int internalRow) const; QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const; QList > draggablePaintPairs(const QModelIndexList &indices, QRect *r) const; @@ -119,6 +121,10 @@ private: bool isDragEventAccepted(QDropEvent *event); QPair rowDropPos(const QPoint &pos); + + void invalidateCaches(); + + QPoint offset() const; }; #endif // WIDGET_H diff --git a/main.cpp b/main.cpp index b164b85d..58d1c9ba 100644 --- a/main.cpp +++ b/main.cpp @@ -64,7 +64,7 @@ int main(int argc, char *argv[]) model.setItem(8, createItem(Qt::darkGreen, "Dark Green", "")); model.setItem(9, createItem(Qt::green, "Green", "")); - for (int i = 0; i < 21; ++i) + for (int i = 0; i < 20; ++i) { model.setItem(i + 10, createItem(i+1, "Items 1-20")); } -- cgit From e6be883d14a4147ffd58a1c8066bb70879d775fb Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Mon, 30 Dec 2013 23:10:53 +0100 Subject: Fixing more bugs --- CategorizedView.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/CategorizedView.cpp b/CategorizedView.cpp index f660e8f3..27575b59 100644 --- a/CategorizedView.cpp +++ b/CategorizedView.cpp @@ -170,6 +170,8 @@ void CategorizedView::updateGeometries() { QListView::updateGeometries(); + int previousScroll = verticalScrollBar()->value(); + invalidateCaches(); QMap cats; @@ -216,6 +218,8 @@ void CategorizedView::updateGeometries() verticalScrollBar()->setRange(0, totalHeight- height()); } + verticalScrollBar()->setValue(qMin(previousScroll, verticalScrollBar()->maximum())); + update(); } @@ -435,13 +439,7 @@ void CategorizedView::mousePressEvent(QMouseEvent *event) selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); setAutoScroll(autoScroll); QRect rect(m_pressedPosition, pos); - if (command.testFlag(QItemSelectionModel::Toggle)) - { - command &= ~QItemSelectionModel::Toggle; - m_ctrlDragSelectionFlag = selectionModel()->isSelected(index) ? QItemSelectionModel::Deselect : QItemSelectionModel::Select; - command |= m_ctrlDragSelectionFlag; - } - setSelection(rect, command); + setSelection(rect, QItemSelectionModel::ClearAndSelect); // signal handlers may change the model emit pressed(index); @@ -519,7 +517,7 @@ void CategorizedView::mouseMoveEvent(QMouseEvent *event) } void CategorizedView::mouseReleaseEvent(QMouseEvent *event) { - QPoint pos = event->pos() - offset(); + QPoint pos = event->pos() + offset(); QPersistentModelIndex index = indexAt(pos); bool click = (index == m_pressedIndex && index.isValid()) || (m_pressedCategory && m_pressedCategory == categoryAt(pos)); @@ -611,7 +609,7 @@ void CategorizedView::paintEvent(QPaintEvent *event) option.rect = visualRect(index); option.widget = this; option.features |= wordWrap() ? QStyleOptionViewItemV2::WrapText : QStyleOptionViewItemV2::None; - if (flags & Qt::ItemIsSelectable) + if (flags & Qt::ItemIsSelectable && selectionModel()->isSelected(index)) { option.state |= selectionModel()->isSelected(index) ? QStyle::State_Selected : QStyle::State_None; } @@ -844,16 +842,15 @@ QModelIndex CategorizedView::indexAt(const QPoint &point) const } void CategorizedView::setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) { - QItemSelection selection; for (int i = 0; i < model()->rowCount(); ++i) { QModelIndex index = model()->index(i, 0); if (visualRect(index).intersects(rect)) { - selection.merge(QItemSelection(index, index), QItemSelectionModel::Select); + selectionModel()->select(index, commands); } } - selectionModel()->select(selection, commands); + update(); } QPixmap CategorizedView::renderToPixmap(const QModelIndexList &indices, QRect *r) const -- cgit From 8cfd0881ac3fcbb45fd42dedcb1caf0be38eacaf Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Mon, 30 Dec 2013 23:39:10 +0100 Subject: Progress indicators --- CMakeLists.txt | 1 + CategorizedProxyModel.cpp | 4 ++-- CategorizedView.cpp | 10 ++++----- CategorizedView.h | 15 +++++++++----- InstanceDelegate.cpp | 25 ++++++++++++++++++++++ main.cpp | 19 +++++++++++++---- main.h | 53 +++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 111 insertions(+), 16 deletions(-) create mode 100644 main.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b94cf53e..029c90b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ include_directories(${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS} ${Qt5Widgets_ set(SOURCES main.cpp + main.h CategorizedView.h CategorizedView.cpp diff --git a/CategorizedProxyModel.cpp b/CategorizedProxyModel.cpp index e4a7563a..efcf13c8 100644 --- a/CategorizedProxyModel.cpp +++ b/CategorizedProxyModel.cpp @@ -8,8 +8,8 @@ CategorizedProxyModel::CategorizedProxyModel(QObject *parent) } bool CategorizedProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { - const QString leftCategory = left.data(CategorizedView::CategoryRole).toString(); - const QString rightCategory = right.data(CategorizedView::CategoryRole).toString(); + const QString leftCategory = left.data(CategorizedViewRoles::CategoryRole).toString(); + const QString rightCategory = right.data(CategorizedViewRoles::CategoryRole).toString(); if (leftCategory == rightCategory) { return left.row() < right.row(); diff --git a/CategorizedView.cpp b/CategorizedView.cpp index 27575b59..6bf18cbc 100644 --- a/CategorizedView.cpp +++ b/CategorizedView.cpp @@ -135,7 +135,7 @@ void CategorizedView::dataChanged(const QModelIndex &topLeft, const QModelIndex QListView::dataChanged(topLeft, bottomRight, roles); - if (roles.contains(CategoryRole)) + if (roles.contains(CategorizedViewRoles::CategoryRole) || roles.contains(Qt::DisplayRole)) { updateGeometries(); update(); @@ -178,7 +178,7 @@ void CategorizedView::updateGeometries() for (int i = 0; i < model()->rowCount(); ++i) { - const QString category = model()->index(i, 0).data(CategoryRole).toString(); + const QString category = model()->index(i, 0).data(CategorizedViewRoles::CategoryRole).toString(); if (!cats.contains(category)) { Category *old = this->category(category); @@ -238,7 +238,7 @@ bool CategorizedView::isIndexHidden(const QModelIndex &index) const CategorizedView::Category *CategorizedView::category(const QModelIndex &index) const { - return category(index.data(CategoryRole).toString()); + return category(index.data(CategorizedViewRoles::CategoryRole).toString()); } CategorizedView::Category *CategorizedView::category(const QString &cat) const { @@ -274,7 +274,7 @@ QList CategorizedView::itemsForCategory(const CategorizedView::Cate QList *indices = new QList(); for (int i = 0; i < model()->rowCount(); ++i) { - if (model()->index(i, 0).data(CategoryRole).toString() == category->text) + if (model()->index(i, 0).data(CategorizedViewRoles::CategoryRole).toString() == category->text) { indices->append(model()->index(i, 0)); } @@ -713,7 +713,7 @@ void CategorizedView::dropEvent(QDropEvent *event) const QString categoryText = category->text; if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex())) { - model()->setData(model()->index(row, 0), categoryText, CategoryRole); + model()->setData(model()->index(row, 0), categoryText, CategorizedViewRoles::CategoryRole); event->setDropAction(Qt::MoveAction); event->accept(); } diff --git a/CategorizedView.h b/CategorizedView.h index 8ab9ce87..08d43be8 100644 --- a/CategorizedView.h +++ b/CategorizedView.h @@ -5,6 +5,16 @@ #include #include +struct CategorizedViewRoles +{ + enum + { + CategoryRole = Qt::UserRole, + ProgressValueRole, + ProgressMaximumRole + }; +}; + class CategorizedView : public QListView { Q_OBJECT @@ -13,11 +23,6 @@ public: CategorizedView(QWidget *parent = 0); ~CategorizedView(); - enum - { - CategoryRole = Qt::UserRole - }; - virtual QRect visualRect(const QModelIndex &index) const; QModelIndex indexAt(const QPoint &point) const; void setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) override; diff --git a/InstanceDelegate.cpp b/InstanceDelegate.cpp index 5020b8b6..50cead55 100644 --- a/InstanceDelegate.cpp +++ b/InstanceDelegate.cpp @@ -20,6 +20,8 @@ #include #include +#include "CategorizedView.h" + // Origin: Qt static void viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height, qreal &widthUsed) @@ -85,6 +87,26 @@ void drawFocusRect(QPainter *painter, const QStyleOptionViewItemV4 &option, cons 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 * 60); + + painter->restore(); +} + static QSize viewItemTextSize(const QStyleOptionViewItemV4 *option) { QStyle *style = option->widget ? option->widget->style() : QApplication::style(); @@ -229,6 +251,9 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti line.draw(painter, position); } + drawProgressOverlay(painter, opt, index.data(CategorizedViewRoles::ProgressValueRole).toInt(), + index.data(CategorizedViewRoles::ProgressMaximumRole).toInt()); + painter->restore(); } diff --git a/main.cpp b/main.cpp index 58d1c9ba..bd6a44f9 100644 --- a/main.cpp +++ b/main.cpp @@ -1,11 +1,16 @@ -#include "CategorizedView.h" +#include "main.h" + #include #include #include +#include +#include "CategorizedView.h" #include "CategorizedProxyModel.h" #include "InstanceDelegate.h" +Progresser *progresser; + QPixmap icon(const Qt::GlobalColor color) { QPixmap p = QPixmap(32, 32); @@ -29,8 +34,9 @@ QStandardItem *createItem(const Qt::GlobalColor color, const QString &text, cons QStandardItem *item = new QStandardItem; item->setText(text); item->setData(icon(color), Qt::DecorationRole); - item->setData(category, CategorizedView::CategoryRole); + item->setData(category, CategorizedViewRoles::CategoryRole); item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + //progresser->addTrackedIndex(item); return item; } QStandardItem *createItem(const int index, const QString &category) @@ -38,8 +44,9 @@ 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, CategorizedView::CategoryRole); + item->setData(category, CategorizedViewRoles::CategoryRole); item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + //progresser->addTrackedIndex(item); return item; } @@ -47,6 +54,10 @@ 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); @@ -62,7 +73,7 @@ int main(int argc, char *argv[]) model.setItem(7, createItem(Qt::white, "White", "Not Colorful")); model.setItem(8, createItem(Qt::darkGreen, "Dark Green", "")); - model.setItem(9, createItem(Qt::green, "Green", "")); + model.setItem(9, progresser->addTrackedIndex(createItem(Qt::green, "Green", ""))); for (int i = 0; i < 20; ++i) { diff --git a/main.h b/main.h new file mode 100644 index 00000000..f4c7a3f8 --- /dev/null +++ b/main.h @@ -0,0 +1,53 @@ +#ifndef MAIN_H +#define MAIN_H + +#include +#include +#include +#include +#include + +#include "CategorizedView.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, CategorizedViewRoles::ProgressMaximumRole); + m_items.append(item); + return item; + } + +public slots: + void timeout() + { + foreach (QStandardItem *item, m_items) + { + int value = item->data(CategorizedViewRoles::ProgressValueRole).toInt(); + value += qrand() % 3; + if (value >= item->data(CategorizedViewRoles::ProgressMaximumRole).toInt()) + { + item->setData(item->data(CategorizedViewRoles::ProgressMaximumRole).toInt(), + CategorizedViewRoles::ProgressValueRole); + } + else + { + item->setData(value, CategorizedViewRoles::ProgressValueRole); + } + } + } + +private: + QList m_items; +}; + +#endif // MAIN_H -- cgit From c47933d95cae407a99acfbbb941655f7cd52e1ae Mon Sep 17 00:00:00 2001 From: Jan Dalheimer Date: Tue, 31 Dec 2013 17:26:36 +0100 Subject: Loads of changes and some refactorings --- CMakeLists.txt | 2 + CategorizedView.cpp | 296 ++++++++++---------------------------------- CategorizedView.h | 56 ++------- CategorizedViewCategory.cpp | 128 +++++++++++++++++++ CategorizedViewCategory.h | 39 ++++++ 5 files changed, 248 insertions(+), 273 deletions(-) create mode 100644 CategorizedViewCategory.cpp create mode 100644 CategorizedViewCategory.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 029c90b3..44a28c57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,8 @@ set(SOURCES CategorizedView.h CategorizedView.cpp + CategorizedViewCategory.h + CategorizedViewCategory.cpp CategorizedProxyModel.h CategorizedProxyModel.cpp InstanceDelegate.h diff --git a/CategorizedView.cpp b/CategorizedView.cpp index 6bf18cbc..60230661 100644 --- a/CategorizedView.cpp +++ b/CategorizedView.cpp @@ -11,6 +11,8 @@ #include #include +#include "CategorizedViewCategory.h" + template bool listsIntersect(const QList &l1, const QList t2) { @@ -24,86 +26,6 @@ bool listsIntersect(const QList &l1, const QList t2) return false; } -CategorizedView::Category::Category(const QString &text, CategorizedView *view) - : view(view), text(text), collapsed(false) -{ -} -CategorizedView::Category::Category(const CategorizedView::Category *other) : - view(other->view), text(other->text), collapsed(other->collapsed), iconRect(other->iconRect), textRect(other->textRect) -{ -} - -void CategorizedView::Category::drawHeader(QPainter *painter, const int y) -{ - painter->save(); - - int height = headerHeight() - 4; - int collapseSize = height; - - // the icon - iconRect = QRect(view->m_rightMargin + 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); - textRect = QRect(iconRect.right() + 4, y, textWidth, headerHeight()); - view->style()->drawItemText(painter, textRect, Qt::AlignHCenter | Qt::AlignVCenter, view->palette(), true, text); - - // the line - painter->drawLine(textRect.right() + 4, y + headerHeight() / 2, view->contentWidth() - view->m_rightMargin, y + headerHeight() / 2); - - painter->restore(); -} - -int CategorizedView::Category::totalHeight() const -{ - return headerHeight() + 5 + contentHeight(); -} -int CategorizedView::Category::headerHeight() const -{ - return qApp->fontMetrics().height() + 4; -} -int CategorizedView::Category::contentHeight() const -{ - if (collapsed) - { - return 0; - } - QMap rowToHeightMapping; - foreach (const QModelIndex &index, view->itemsForCategory(this)) - { - int row = view->categoryInternalPosition(index).second; - if (!rowToHeightMapping.contains(row)) - { - rowToHeightMapping.insert(row, view->itemSize(index).height()); - } - } - int result = 0; - if (!rowToHeightMapping.isEmpty()) - { - for (int i = 0; i < numRows(); ++i) - { - Q_ASSERT(rowToHeightMapping.contains(i)); - result += rowToHeightMapping[i]; - } - } - return result; -} -int CategorizedView::Category::numRows() const -{ - return qMax(1, qCeil((qreal)view->numItemsForCategory(this) / (qreal)view->itemsPerRow())); -} - CategorizedView::CategorizedView(QWidget *parent) : QListView(parent), m_leftMargin(5), m_rightMargin(5), m_bottomMargin(5), m_categoryMargin(5)//, m_updatesDisabled(false), m_categoryEditor(0), m_editedCategory(0) { @@ -115,9 +37,6 @@ CategorizedView::CategorizedView(QWidget *parent) setDragDropMode(QListView::InternalMove); setAcceptDrops(true); setSpacing(10); - - m_cachedCategoryToIndexMapping.setMaxCost(50); - m_cachedVisualRects.setMaxCost(50); } CategorizedView::~CategorizedView() @@ -172,23 +91,21 @@ void CategorizedView::updateGeometries() int previousScroll = verticalScrollBar()->value(); - invalidateCaches(); - - QMap cats; + QMap cats; for (int i = 0; i < model()->rowCount(); ++i) { const QString category = model()->index(i, 0).data(CategorizedViewRoles::CategoryRole).toString(); if (!cats.contains(category)) { - Category *old = this->category(category); + CategorizedViewCategory *old = this->category(category); if (old) { - cats.insert(category, new Category(old)); + cats.insert(category, new CategorizedViewCategory(old)); } else { - cats.insert(category, new Category(category, this)); + cats.insert(category, new CategorizedViewCategory(category, this)); } } } @@ -201,6 +118,11 @@ void CategorizedView::updateGeometries() qDeleteAll(m_categories); m_categories = cats.values(); + for (auto cat : m_categories) + { + cat->update(); + } + if (m_categories.isEmpty()) { verticalScrollBar()->setRange(0, 0); @@ -208,7 +130,7 @@ void CategorizedView::updateGeometries() else { int totalHeight = 0; - foreach (const Category *category, m_categories) + foreach (const CategorizedViewCategory *category, m_categories) { totalHeight += category->totalHeight() + m_categoryMargin; } @@ -225,7 +147,7 @@ void CategorizedView::updateGeometries() bool CategorizedView::isIndexHidden(const QModelIndex &index) const { - Category *cat = category(index); + CategorizedViewCategory *cat = category(index); if (cat) { return cat->collapsed; @@ -236,11 +158,11 @@ bool CategorizedView::isIndexHidden(const QModelIndex &index) const } } -CategorizedView::Category *CategorizedView::category(const QModelIndex &index) const +CategorizedViewCategory *CategorizedView::category(const QModelIndex &index) const { return category(index.data(CategorizedViewRoles::CategoryRole).toString()); } -CategorizedView::Category *CategorizedView::category(const QString &cat) const +CategorizedViewCategory *CategorizedView::category(const QString &cat) const { for (int i = 0; i < m_categories.size(); ++i) { @@ -251,7 +173,7 @@ CategorizedView::Category *CategorizedView::category(const QString &cat) const } return 0; } -CategorizedView::Category *CategorizedView::categoryAt(const QPoint &pos) const +CategorizedViewCategory *CategorizedView::categoryAt(const QPoint &pos) const { for (int i = 0; i < m_categories.size(); ++i) { @@ -263,70 +185,6 @@ CategorizedView::Category *CategorizedView::categoryAt(const QPoint &pos) const return 0; } -int CategorizedView::numItemsForCategory(const CategorizedView::Category *category) const -{ - return itemsForCategory(category).size(); -} -QList CategorizedView::itemsForCategory(const CategorizedView::Category *category) const -{ - if (!m_cachedCategoryToIndexMapping.contains(category) || true) - { - QList *indices = new QList(); - for (int i = 0; i < model()->rowCount(); ++i) - { - if (model()->index(i, 0).data(CategorizedViewRoles::CategoryRole).toString() == category->text) - { - indices->append(model()->index(i, 0)); - } - } - m_cachedCategoryToIndexMapping.insert(category, indices, indices->size()); - } - return *m_cachedCategoryToIndexMapping.object(category); -} -QModelIndex CategorizedView::firstItemForCategory(const CategorizedView::Category *category) const -{ - QList indices = itemsForCategory(category); - QModelIndex first; - foreach (const QModelIndex &index, indices) - { - if (index.row() < first.row() || !first.isValid()) - { - first = index; - } - } - - return first; -} -QModelIndex CategorizedView::lastItemForCategory(const CategorizedView::Category *category) const -{ - QList indices = itemsForCategory(category); - QModelIndex last; - foreach (const QModelIndex &index, indices) - { - if (index.row() > last.row() || !last.isValid()) - { - last = index; - } - } - - return last; -} - -int CategorizedView::categoryTop(const CategorizedView::Category *category) const -{ - int res = 0; - const QList cats = sortedCategories(); - for (int i = 0; i < cats.size(); ++i) - { - if (cats.at(i) == category) - { - break; - } - res += cats.at(i)->totalHeight() + m_categoryMargin; - } - return res; -} - int CategorizedView::itemsPerRow() const { return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + spacing())); @@ -336,49 +194,34 @@ int CategorizedView::contentWidth() const return width() - m_leftMargin - m_rightMargin; } -QList CategorizedView::sortedCategories() const -{ - QList out = m_categories; - qSort(out.begin(), out.end(), [](const Category *c1, const Category *c2) { return c1->text < c2->text; }); - return out; -} - int CategorizedView::itemWidth() const { - if (m_cachedItemWidth == -1) - { - m_cachedItemWidth = itemDelegate()->sizeHint(viewOptions(), model()->index(model()->rowCount() -1, 0)).width(); - } - return m_cachedItemWidth; + return itemDelegate()->sizeHint(viewOptions(), model()->index(model()->rowCount() -1, 0)).width(); } -QSize CategorizedView::itemSize(const QModelIndex &index) const +int CategorizedView::categoryRowHeight(const QModelIndex &index) const { - if (!m_cachedItemSizes.contains(index)) + QModelIndexList indices; + int internalRow = categoryInternalPosition(index).second; + foreach (const QModelIndex &i, category(index)->items()) { - QModelIndexList indices; - int internalRow = categoryInternalPosition(index).second; - foreach (const QModelIndex &i, itemsForCategory(category(index))) + if (categoryInternalPosition(i).second == internalRow) { - if (categoryInternalPosition(i).second == internalRow) - { - indices.append(i); - } + indices.append(i); } + } - int largestHeight = 0; - foreach (const QModelIndex &i, indices) - { - largestHeight = qMax(largestHeight, itemDelegate()->sizeHint(viewOptions(), i).height()); - } - m_cachedItemSizes.insert(index, new QSize(itemWidth(), largestHeight)); + int largestHeight = 0; + foreach (const QModelIndex &i, indices) + { + largestHeight = qMax(largestHeight, itemDelegate()->sizeHint(viewOptions(), i).height()); } - return *m_cachedItemSizes.object(index); + return largestHeight; } QPair CategorizedView::categoryInternalPosition(const QModelIndex &index) const { - QList indices = itemsForCategory(category(index)); + QList indices = category(index)->items(); int x = 0; int y = 0; const int perRow = itemsPerRow(); @@ -397,14 +240,25 @@ QPair CategorizedView::categoryInternalPosition(const QModelIndex &ind } return qMakePair(x, y); } -int CategorizedView::itemHeightForCategoryRow(const CategorizedView::Category *category, const int internalRow) const +int CategorizedView::categoryInternalRowTop(const QModelIndex &index) const { - foreach (const QModelIndex &i, itemsForCategory(category)) + CategorizedViewCategory *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 CategorizedView::itemHeightForCategoryRow(const CategorizedViewCategory *category, const int internalRow) const +{ + foreach (const QModelIndex &i, category->items()) { QPair pos = categoryInternalPosition(i); if (pos.second == internalRow) { - return itemSize(i).height(); + return categoryRowHeight(i); } } return -1; @@ -586,13 +440,10 @@ void CategorizedView::paintEvent(QPaintEvent *event) QPainter painter(this->viewport()); painter.translate(-offset()); - // FIXME we shouldn't need to do this - invalidateCaches(); - int y = 0; for (int i = 0; i < m_categories.size(); ++i) { - Category *category = m_categories.at(i); + CategorizedViewCategory *category = m_categories.at(i); category->drawHeader(&painter, y); y += category->totalHeight() + m_categoryMargin; } @@ -627,16 +478,16 @@ void CategorizedView::paintEvent(QPaintEvent *event) if (!m_lastDragPosition.isNull()) { - QPair pair = rowDropPos(m_lastDragPosition); - Category *category = pair.first; + QPair pair = rowDropPos(m_lastDragPosition); + CategorizedViewCategory *category = pair.first; int row = pair.second; if (category) { - int internalRow = row - firstItemForCategory(category).row(); + int internalRow = row - category->firstRow; QLine line; - if (internalRow >= numItemsForCategory(category)) + if (internalRow >= category->numItems()) { - QRect toTheRightOfRect = visualRect(lastItemForCategory(category)); + QRect toTheRightOfRect = visualRect(category->lastItem()); line = QLine(toTheRightOfRect.topRight(), toTheRightOfRect.bottomRight()); } else @@ -700,8 +551,8 @@ void CategorizedView::dropEvent(QDropEvent *event) return; } - QPair dropPos = rowDropPos(event->pos() + offset()); - const Category *category = dropPos.first; + QPair dropPos = rowDropPos(event->pos() + offset()); + const CategorizedViewCategory *category = dropPos.first; const int row = dropPos.second; if (row == -1) @@ -772,24 +623,17 @@ QRect CategorizedView::visualRect(const QModelIndex &index) const return QRect(); } - if (!m_cachedVisualRects.contains(index)) - { - const Category *cat = category(index); - QPair pos = categoryInternalPosition(index); - int x = pos.first; - int y = pos.second; - - QSize size = itemSize(index); + const CategorizedViewCategory *cat = category(index); + QPair pos = categoryInternalPosition(index); + int x = pos.first; + int y = pos.second; - QRect *out = new QRect; - out->setTop(categoryTop(cat) + cat->headerHeight() + 5 + y * size.height()); - out->setLeft(spacing() + x * itemWidth() + x * spacing()); - out->setSize(size); + QRect out; + out.setTop(cat->top() + cat->headerHeight() + 5 + categoryInternalRowTop(index)); + out.setLeft(spacing() + x * itemWidth() + x * spacing()); + out.setSize(itemDelegate()->sizeHint(viewOptions(), index)); - m_cachedVisualRects.insert(index, out); - } - - return *m_cachedVisualRects.object(index); + return out; } /* void CategorizedView::startCategoryEditor(Category *category) @@ -908,13 +752,13 @@ bool CategorizedView::isDragEventAccepted(QDropEvent *event) } return true; } -QPair CategorizedView::rowDropPos(const QPoint &pos) +QPair CategorizedView::rowDropPos(const QPoint &pos) { // check that we aren't on a category header and calculate which category we're in - Category *category = 0; + CategorizedViewCategory *category = 0; { int y = 0; - foreach (Category *cat, m_categories) + foreach (CategorizedViewCategory *cat, m_categories) { if (pos.y() > y && pos.y() < (y + cat->headerHeight())) { @@ -933,7 +777,7 @@ QPair CategorizedView::rowDropPos(const QPoint } } - QList indices = itemsForCategory(category); + QList indices = category->items(); // calculate the internal column int internalColumn = -1; @@ -967,7 +811,7 @@ QPair CategorizedView::rowDropPos(const QPoint int internalRow = -1; { // FIXME rework the drag and drop code - const int top = categoryTop(category); + const int top = category->top(); for (int r = 0, h = top; r < category->numRows(); h += itemHeightForCategoryRow(category, r), ++r) { if (pos.y() > h && pos.y() < (h + itemHeightForCategoryRow(category, r))) @@ -1000,14 +844,6 @@ QPair CategorizedView::rowDropPos(const QPoint return qMakePair(category, indices.at(categoryRow).row()); } -void CategorizedView::invalidateCaches() -{ - m_cachedItemWidth = -1; - m_cachedCategoryToIndexMapping.clear(); - m_cachedVisualRects.clear(); - m_cachedItemSizes.clear(); -} - QPoint CategorizedView::offset() const { return QPoint(horizontalOffset(), verticalOffset()); diff --git a/CategorizedView.h b/CategorizedView.h index 08d43be8..0550c7f8 100644 --- a/CategorizedView.h +++ b/CategorizedView.h @@ -3,7 +3,6 @@ #include #include -#include struct CategorizedViewRoles { @@ -15,6 +14,8 @@ struct CategorizedViewRoles }; }; +struct CategorizedViewCategory; + class CategorizedView : public QListView { Q_OBJECT @@ -28,7 +29,7 @@ public: void setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) override; protected slots: - void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); virtual void rowsInserted(const QModelIndex &parent, int start, int end); virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); virtual void updateGeometries(); @@ -50,57 +51,27 @@ protected: void startDrag(Qt::DropActions supportedActions) override; private: - struct Category - { - Category(const QString &text, CategorizedView *view); - Category(const Category *other); - CategorizedView *view; - QString text; - bool collapsed; - QRect iconRect; - QRect textRect; - - void drawHeader(QPainter *painter, const int y); - int totalHeight() const; - int headerHeight() const; - int contentHeight() const; - int numRows() const; - }; - friend struct Category; + friend struct CategorizedViewCategory; - QList m_categories; - mutable QCache > m_cachedCategoryToIndexMapping; - mutable QCache m_cachedVisualRects; + QList m_categories; int m_leftMargin; int m_rightMargin; int m_bottomMargin; int m_categoryMargin; - int m_itemSpacing; //bool m_updatesDisabled; - Category *category(const QModelIndex &index) const; - Category *category(const QString &cat) const; - Category *categoryAt(const QPoint &pos) const; - int numItemsForCategory(const Category *category) const; - QList itemsForCategory(const Category *category) const; - QModelIndex firstItemForCategory(const Category *category) const; - QModelIndex lastItemForCategory(const Category *category) const; - - int categoryTop(const Category *category) const; + CategorizedViewCategory *category(const QModelIndex &index) const; + CategorizedViewCategory *category(const QString &cat) const; + CategorizedViewCategory *categoryAt(const QPoint &pos) const; int itemsPerRow() const; int contentWidth() const; - static bool lessThanCategoryPointer(const Category *c1, const Category *c2); - QList sortedCategories() const; - private: - mutable int m_cachedItemWidth; - mutable QCache m_cachedItemSizes; int itemWidth() const; - QSize itemSize(const QModelIndex &index) const; + int categoryRowHeight(const QModelIndex &index) const; /*QLineEdit *m_categoryEditor; Category *m_editedCategory; @@ -113,21 +84,20 @@ private: QPoint m_pressedPosition; QPersistentModelIndex m_pressedIndex; bool m_pressedAlreadySelected; - Category *m_pressedCategory; + CategorizedViewCategory *m_pressedCategory; QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag; QPoint m_lastDragPosition; QPair categoryInternalPosition(const QModelIndex &index) const; - int itemHeightForCategoryRow(const Category *category, const int internalRow) const; + int categoryInternalRowTop(const QModelIndex &index) const; + int itemHeightForCategoryRow(const CategorizedViewCategory *category, const int internalRow) const; QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const; QList > draggablePaintPairs(const QModelIndexList &indices, QRect *r) const; bool isDragEventAccepted(QDropEvent *event); - QPair rowDropPos(const QPoint &pos); - - void invalidateCaches(); + QPair rowDropPos(const QPoint &pos); QPoint offset() const; }; diff --git a/CategorizedViewCategory.cpp b/CategorizedViewCategory.cpp new file mode 100644 index 00000000..b82ffc96 --- /dev/null +++ b/CategorizedViewCategory.cpp @@ -0,0 +1,128 @@ +#include "CategorizedViewCategory.h" + +#include +#include +#include + +#include "CategorizedView.h" + +CategorizedViewCategory::CategorizedViewCategory(const QString &text, CategorizedView *view) + : view(view), text(text), collapsed(false) +{ +} +CategorizedViewCategory::CategorizedViewCategory(const CategorizedViewCategory *other) : + view(other->view), text(other->text), collapsed(other->collapsed), iconRect(other->iconRect), textRect(other->textRect) +{ +} + +void CategorizedViewCategory::update() +{ + firstRow = firstItem().row(); + + rowHeights = QVector(numRows()); + for (int i = 0; i < numRows(); ++i) + { + rowHeights[i] = view->categoryRowHeight(view->model()->index(i * view->itemsPerRow() + firstRow, 0)); + } +} + +void CategorizedViewCategory::drawHeader(QPainter *painter, const int y) +{ + painter->save(); + + int height = headerHeight() - 4; + int collapseSize = height; + + // the icon + iconRect = QRect(view->m_rightMargin + 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); + 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 CategorizedViewCategory::totalHeight() const +{ + return headerHeight() + 5 + contentHeight(); +} +int CategorizedViewCategory::headerHeight() const +{ + return view->viewport()->fontMetrics().height() + 4; +} +int CategorizedViewCategory::contentHeight() const +{ + if (collapsed) + { + return 0; + } + int result = 0; + for (int i = 0; i < rowHeights.size(); ++i) + { + result += rowHeights[i]; + } + return result; +} +int CategorizedViewCategory::numRows() const +{ + return qMax(1, qCeil((qreal)numItems() / (qreal)view->itemsPerRow())); +} +int CategorizedViewCategory::top() const +{ + int res = 0; + const QList cats = view->m_categories; + 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 CategorizedViewCategory::items() const +{ + QList indices; + for (int i = 0; i < view->model()->rowCount(); ++i) + { + const QModelIndex index = view->model()->index(i, 0); + if (index.data(CategorizedViewRoles::CategoryRole).toString() == text) + { + indices.append(index); + } + } + return indices; +} +int CategorizedViewCategory::numItems() const +{ + return items().size(); +} +QModelIndex CategorizedViewCategory::firstItem() const +{ + QList indices = items(); + return indices.isEmpty() ? QModelIndex() : indices.first(); +} +QModelIndex CategorizedViewCategory::lastItem() const +{ + QList indices = items(); + return indices.isEmpty() ? QModelIndex() : indices.last(); +} diff --git a/CategorizedViewCategory.h b/CategorizedViewCategory.h new file mode 100644 index 00000000..cb6ef8c5 --- /dev/null +++ b/CategorizedViewCategory.h @@ -0,0 +1,39 @@ +#ifndef CATEGORIZEDVIEWROW_H +#define CATEGORIZEDVIEWROW_H + +#include +#include +#include + +class CategorizedView; +class QPainter; +class QModelIndex; + +struct CategorizedViewCategory +{ + CategorizedViewCategory(const QString &text, CategorizedView *view); + CategorizedViewCategory(const CategorizedViewCategory *other); + CategorizedView *view; + QString text; + bool collapsed; + QRect iconRect; + QRect textRect; + QVector rowHeights; + int firstRow; + + void update(); + + void drawHeader(QPainter *painter, const int y); + int totalHeight() const; + int headerHeight() const; + int contentHeight() const; + int numRows() const; + int top() const; + + QList items() const; + int numItems() const; + QModelIndex firstItem() const; + QModelIndex lastItem() const; +}; + +#endif // CATEGORIZEDVIEWROW_H -- cgit From 3fb7a0faf069fdc5a4f14a12dfd76c43819c3378 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Fri, 31 Jan 2014 22:51:45 +0100 Subject: Reformat, Rename, Redo --- .clang-format | 24 ++ CMakeLists.txt | 22 +- CategorizedProxyModel.cpp | 21 -- CategorizedProxyModel.h | 18 - CategorizedView.cpp | 850 ------------------------------------------ CategorizedView.h | 105 ------ CategorizedViewCategory.cpp | 128 ------- CategorizedViewCategory.h | 39 -- Group.cpp | 139 +++++++ Group.h | 36 ++ GroupView.cpp | 881 ++++++++++++++++++++++++++++++++++++++++++++ GroupView.h | 107 ++++++ GroupedProxyModel.cpp | 21 ++ GroupedProxyModel.h | 14 + InstanceDelegate.cpp | 8 +- InstanceDelegate.h | 8 +- main.cpp | 25 +- main.h | 12 +- 18 files changed, 1263 insertions(+), 1195 deletions(-) create mode 100644 .clang-format delete mode 100644 CategorizedProxyModel.cpp delete mode 100644 CategorizedProxyModel.h delete mode 100644 CategorizedView.cpp delete mode 100644 CategorizedView.h delete mode 100644 CategorizedViewCategory.cpp delete mode 100644 CategorizedViewCategory.h create mode 100644 Group.cpp create mode 100644 Group.h create mode 100644 GroupView.cpp create mode 100644 GroupView.h create mode 100644 GroupedProxyModel.cpp create mode 100644 GroupedProxyModel.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..167a8fa7 --- /dev/null +++ b/.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/CMakeLists.txt b/CMakeLists.txt index 44a28c57..e2a85950 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,17 +23,17 @@ find_package(Qt5Widgets REQUIRED) include_directories(${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS}) set(SOURCES - main.cpp - main.h - - CategorizedView.h - CategorizedView.cpp - CategorizedViewCategory.h - CategorizedViewCategory.cpp - CategorizedProxyModel.h - CategorizedProxyModel.cpp - InstanceDelegate.h - InstanceDelegate.cpp + main.cpp + main.h + + GroupView.h + GroupView.cpp + Group.h + Group.cpp + GroupedProxyModel.h + GroupedProxyModel.cpp + InstanceDelegate.h + InstanceDelegate.cpp ) add_executable(GroupView ${SOURCES}) diff --git a/CategorizedProxyModel.cpp b/CategorizedProxyModel.cpp deleted file mode 100644 index efcf13c8..00000000 --- a/CategorizedProxyModel.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "CategorizedProxyModel.h" - -#include "CategorizedView.h" - -CategorizedProxyModel::CategorizedProxyModel(QObject *parent) - : QSortFilterProxyModel(parent) -{ -} -bool CategorizedProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const -{ - const QString leftCategory = left.data(CategorizedViewRoles::CategoryRole).toString(); - const QString rightCategory = right.data(CategorizedViewRoles::CategoryRole).toString(); - if (leftCategory == rightCategory) - { - return left.row() < right.row(); - } - else - { - return leftCategory < rightCategory; - } -} diff --git a/CategorizedProxyModel.h b/CategorizedProxyModel.h deleted file mode 100644 index 6e4f3fdc..00000000 --- a/CategorizedProxyModel.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef CATEGORIZEDPROXYMODEL_H -#define CATEGORIZEDPROXYMODEL_H - -#include - -class CategorizedProxyModel : public QSortFilterProxyModel -{ - Q_OBJECT - -public: - CategorizedProxyModel(QObject *parent = 0); - -protected: - bool lessThan(const QModelIndex &left, const QModelIndex &right) const; -}; - - -#endif // CATEGORIZEDPROXYMODEL_H diff --git a/CategorizedView.cpp b/CategorizedView.cpp deleted file mode 100644 index 60230661..00000000 --- a/CategorizedView.cpp +++ /dev/null @@ -1,850 +0,0 @@ -#include "CategorizedView.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "CategorizedViewCategory.h" - -template -bool listsIntersect(const QList &l1, const QList t2) -{ - foreach (const T &item, l1) - { - if (t2.contains(item)) - { - return true; - } - } - return false; -} - -CategorizedView::CategorizedView(QWidget *parent) - : QListView(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); - setSpacing(10); -} - -CategorizedView::~CategorizedView() -{ - qDeleteAll(m_categories); - m_categories.clear(); -} - -void CategorizedView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) -{ -// if (m_updatesDisabled) -// { -// return; -// } - - QListView::dataChanged(topLeft, bottomRight, roles); - - if (roles.contains(CategorizedViewRoles::CategoryRole) || roles.contains(Qt::DisplayRole)) - { - updateGeometries(); - update(); - } -} -void CategorizedView::rowsInserted(const QModelIndex &parent, int start, int end) -{ -// if (m_updatesDisabled) -// { -// return; -// } - - QListView::rowsInserted(parent, start, end); - - updateGeometries(); - update(); -} -void CategorizedView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) -{ -// if (m_updatesDisabled) -// { -// return; -// } - - QListView::rowsAboutToBeRemoved(parent, start, end); - - updateGeometries(); - update(); -} - -void CategorizedView::updateGeometries() -{ - QListView::updateGeometries(); - - int previousScroll = verticalScrollBar()->value(); - - QMap cats; - - for (int i = 0; i < model()->rowCount(); ++i) - { - const QString category = model()->index(i, 0).data(CategorizedViewRoles::CategoryRole).toString(); - if (!cats.contains(category)) - { - CategorizedViewCategory *old = this->category(category); - if (old) - { - cats.insert(category, new CategorizedViewCategory(old)); - } - else - { - cats.insert(category, new CategorizedViewCategory(category, this)); - } - } - } - - /*if (m_editedCategory) - { - m_editedCategory = cats[m_editedCategory->text]; - }*/ - - qDeleteAll(m_categories); - m_categories = cats.values(); - - for (auto cat : m_categories) - { - cat->update(); - } - - if (m_categories.isEmpty()) - { - verticalScrollBar()->setRange(0, 0); - } - else - { - int totalHeight = 0; - foreach (const CategorizedViewCategory *category, m_categories) - { - 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())); - - update(); -} - -bool CategorizedView::isIndexHidden(const QModelIndex &index) const -{ - CategorizedViewCategory *cat = category(index); - if (cat) - { - return cat->collapsed; - } - else - { - return false; - } -} - -CategorizedViewCategory *CategorizedView::category(const QModelIndex &index) const -{ - return category(index.data(CategorizedViewRoles::CategoryRole).toString()); -} -CategorizedViewCategory *CategorizedView::category(const QString &cat) const -{ - for (int i = 0; i < m_categories.size(); ++i) - { - if (m_categories.at(i)->text == cat) - { - return m_categories.at(i); - } - } - return 0; -} -CategorizedViewCategory *CategorizedView::categoryAt(const QPoint &pos) const -{ - for (int i = 0; i < m_categories.size(); ++i) - { - if (m_categories.at(i)->iconRect.contains(pos)) - { - return m_categories.at(i); - } - } - return 0; -} - -int CategorizedView::itemsPerRow() const -{ - return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + spacing())); -} -int CategorizedView::contentWidth() const -{ - return width() - m_leftMargin - m_rightMargin; -} - -int CategorizedView::itemWidth() const -{ - return itemDelegate()->sizeHint(viewOptions(), model()->index(model()->rowCount() -1, 0)).width(); -} - -int CategorizedView::categoryRowHeight(const QModelIndex &index) const -{ - QModelIndexList indices; - int internalRow = categoryInternalPosition(index).second; - foreach (const QModelIndex &i, category(index)->items()) - { - if (categoryInternalPosition(i).second == internalRow) - { - indices.append(i); - } - } - - int largestHeight = 0; - foreach (const QModelIndex &i, indices) - { - largestHeight = qMax(largestHeight, itemDelegate()->sizeHint(viewOptions(), i).height()); - } - return largestHeight; -} - -QPair CategorizedView::categoryInternalPosition(const QModelIndex &index) const -{ - QList 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 CategorizedView::categoryInternalRowTop(const QModelIndex &index) const -{ - CategorizedViewCategory *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 CategorizedView::itemHeightForCategoryRow(const CategorizedViewCategory *category, const int internalRow) const -{ - foreach (const QModelIndex &i, category->items()) - { - QPair pos = categoryInternalPosition(i); - if (pos.second == internalRow) - { - return categoryRowHeight(i); - } - } - return -1; -} - -void CategorizedView::mousePressEvent(QMouseEvent *event) -{ - //endCategoryEditor(); - - QPoint pos = event->pos() + offset(); - QPersistentModelIndex index = indexAt(pos); - - m_pressedIndex = index; - m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex); - QItemSelectionModel::SelectionFlags command = 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 CategorizedView::mouseMoveEvent(QMouseEvent *event) -{ - QPoint topLeft; - QPoint bottomRight = event->pos(); - - 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; - } - - QPersistentModelIndex index = indexAt(bottomRight); - - if (selectionMode() != SingleSelection) - { - topLeft = m_pressedPosition - offset(); - } - else - { - topLeft = bottomRight; - } - - if (m_pressedIndex.isValid() - && (state() != DragSelectingState) - && (event->buttons() != Qt::NoButton) - && !selectedIndexes().isEmpty()) - { - setState(DraggingState); - return; - } - - if ((event->buttons() & Qt::LeftButton) && selectionModel()) - { - setState(DragSelectingState); - QItemSelectionModel::SelectionFlags command = selectionCommand(index, event); - if (m_ctrlDragSelectionFlag != QItemSelectionModel::NoUpdate && command.testFlag(QItemSelectionModel::Toggle)) - { - command &= ~QItemSelectionModel::Toggle; - command |= m_ctrlDragSelectionFlag; - } - - // Do the normalize ourselves, since QRect::normalized() is flawed - QRect selectionRect = QRect(topLeft, bottomRight); - setSelection(selectionRect, command); - - // 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 CategorizedView::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 CategorizedView::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 CategorizedView::paintEvent(QPaintEvent *event) -{ - QPainter painter(this->viewport()); - painter.translate(-offset()); - - int y = 0; - for (int i = 0; i < m_categories.size(); ++i) - { - CategorizedViewCategory *category = m_categories.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 |= wordWrap() ? QStyleOptionViewItemV2::WrapText : QStyleOptionViewItemV2::None; - 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); - } - - if (!m_lastDragPosition.isNull()) - { - QPair pair = rowDropPos(m_lastDragPosition); - CategorizedViewCategory *category = pair.first; - int row = pair.second; - if (category) - { - int internalRow = row - category->firstRow; - 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(); - } - } -} -void CategorizedView::resizeEvent(QResizeEvent *event) -{ - QListView::resizeEvent(event); - -// if (m_categoryEditor) -// { -// m_categoryEditor->resize(qMax(contentWidth() / 2, m_editedCategory->textRect.width()), m_categoryEditor->height()); -// } - - updateGeometries(); -} - -void CategorizedView::dragEnterEvent(QDragEnterEvent *event) -{ - if (!isDragEventAccepted(event)) - { - return; - } - m_lastDragPosition = event->pos() + offset(); - viewport()->update(); - event->accept(); -} -void CategorizedView::dragMoveEvent(QDragMoveEvent *event) -{ - if (!isDragEventAccepted(event)) - { - return; - } - m_lastDragPosition = event->pos() + offset(); - viewport()->update(); - event->accept(); -} -void CategorizedView::dragLeaveEvent(QDragLeaveEvent *event) -{ - m_lastDragPosition = QPoint(); - viewport()->update(); -} -void CategorizedView::dropEvent(QDropEvent *event) -{ - m_lastDragPosition = QPoint(); - - stopAutoScroll(); - setState(NoState); - - if (event->source() != this || !(event->possibleActions() & Qt::MoveAction)) - { - return; - } - - QPair dropPos = rowDropPos(event->pos() + offset()); - const CategorizedViewCategory *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, CategorizedViewRoles::CategoryRole); - event->setDropAction(Qt::MoveAction); - event->accept(); - } - updateGeometries(); - viewport()->update(); -} - -void CategorizedView::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 CategorizedView::visualRect(const QModelIndex &index) const -{ - if (!index.isValid() || isIndexHidden(index) || index.column() > 0) - { - return QRect(); - } - - const CategorizedViewCategory *cat = category(index); - QPair pos = categoryInternalPosition(index); - int x = pos.first; - int y = pos.second; - - QRect out; - out.setTop(cat->top() + cat->headerHeight() + 5 + categoryInternalRowTop(index)); - out.setLeft(spacing() + x * itemWidth() + x * 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(index.model())->setData(index, m_categoryEditor->text(), CategoryRole); - } - m_updatesDisabled = false; - delete m_categoryEditor; - m_categoryEditor = 0; - m_editedCategory = 0; - updateGeometries(); -} -*/ - -QModelIndex CategorizedView::indexAt(const QPoint &point) const -{ - for (int i = 0; i < model()->rowCount(); ++i) - { - QModelIndex index = model()->index(i, 0); - if (visualRect(index).contains(point)) - { - return index; - } - } - return QModelIndex(); -} -void CategorizedView::setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) -{ - for (int i = 0; i < model()->rowCount(); ++i) - { - QModelIndex index = model()->index(i, 0); - if (visualRect(index).intersects(rect)) - { - selectionModel()->select(index, commands); - } - } - update(); -} - -QPixmap CategorizedView::renderToPixmap(const QModelIndexList &indices, QRect *r) const -{ - Q_ASSERT(r); - QList > 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 > CategorizedView::draggablePaintPairs(const QModelIndexList &indices, QRect *r) const -{ - Q_ASSERT(r); - QRect &rect = *r; - const QRect viewportRect = viewport()->rect(); - QList > ret; - for (int i = 0; i < indices.count(); ++i) { - const QModelIndex &index = indices.at(i); - const QRect current = visualRect(index); - if (current.intersects(viewportRect)) { - ret += qMakePair(current, index); - rect |= current; - } - } - rect &= viewportRect; - return ret; -} - -bool CategorizedView::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 CategorizedView::rowDropPos(const QPoint &pos) -{ - // check that we aren't on a category header and calculate which category we're in - CategorizedViewCategory *category = 0; - { - int y = 0; - foreach (CategorizedViewCategory *cat, m_categories) - { - 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 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 + 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->top(); - 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 CategorizedView::offset() const -{ - return QPoint(horizontalOffset(), verticalOffset()); -} diff --git a/CategorizedView.h b/CategorizedView.h deleted file mode 100644 index 0550c7f8..00000000 --- a/CategorizedView.h +++ /dev/null @@ -1,105 +0,0 @@ -#ifndef WIDGET_H -#define WIDGET_H - -#include -#include - -struct CategorizedViewRoles -{ - enum - { - CategoryRole = Qt::UserRole, - ProgressValueRole, - ProgressMaximumRole - }; -}; - -struct CategorizedViewCategory; - -class CategorizedView : public QListView -{ - Q_OBJECT - -public: - CategorizedView(QWidget *parent = 0); - ~CategorizedView(); - - virtual QRect visualRect(const QModelIndex &index) const; - QModelIndex indexAt(const QPoint &point) const; - void setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) override; - -protected slots: - void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); - virtual void rowsInserted(const QModelIndex &parent, int start, int end); - virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); - virtual void updateGeometries(); - -protected: - virtual bool isIndexHidden(const QModelIndex &index) const; - 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 CategorizedViewCategory; - - QList m_categories; - - int m_leftMargin; - int m_rightMargin; - int m_bottomMargin; - int m_categoryMargin; - - //bool m_updatesDisabled; - - CategorizedViewCategory *category(const QModelIndex &index) const; - CategorizedViewCategory *category(const QString &cat) const; - CategorizedViewCategory *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: - QPoint m_pressedPosition; - QPersistentModelIndex m_pressedIndex; - bool m_pressedAlreadySelected; - CategorizedViewCategory *m_pressedCategory; - QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag; - QPoint m_lastDragPosition; - - QPair categoryInternalPosition(const QModelIndex &index) const; - int categoryInternalRowTop(const QModelIndex &index) const; - int itemHeightForCategoryRow(const CategorizedViewCategory *category, const int internalRow) const; - - QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const; - QList > draggablePaintPairs(const QModelIndexList &indices, QRect *r) const; - - bool isDragEventAccepted(QDropEvent *event); - - QPair rowDropPos(const QPoint &pos); - - QPoint offset() const; -}; - -#endif // WIDGET_H diff --git a/CategorizedViewCategory.cpp b/CategorizedViewCategory.cpp deleted file mode 100644 index b82ffc96..00000000 --- a/CategorizedViewCategory.cpp +++ /dev/null @@ -1,128 +0,0 @@ -#include "CategorizedViewCategory.h" - -#include -#include -#include - -#include "CategorizedView.h" - -CategorizedViewCategory::CategorizedViewCategory(const QString &text, CategorizedView *view) - : view(view), text(text), collapsed(false) -{ -} -CategorizedViewCategory::CategorizedViewCategory(const CategorizedViewCategory *other) : - view(other->view), text(other->text), collapsed(other->collapsed), iconRect(other->iconRect), textRect(other->textRect) -{ -} - -void CategorizedViewCategory::update() -{ - firstRow = firstItem().row(); - - rowHeights = QVector(numRows()); - for (int i = 0; i < numRows(); ++i) - { - rowHeights[i] = view->categoryRowHeight(view->model()->index(i * view->itemsPerRow() + firstRow, 0)); - } -} - -void CategorizedViewCategory::drawHeader(QPainter *painter, const int y) -{ - painter->save(); - - int height = headerHeight() - 4; - int collapseSize = height; - - // the icon - iconRect = QRect(view->m_rightMargin + 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); - 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 CategorizedViewCategory::totalHeight() const -{ - return headerHeight() + 5 + contentHeight(); -} -int CategorizedViewCategory::headerHeight() const -{ - return view->viewport()->fontMetrics().height() + 4; -} -int CategorizedViewCategory::contentHeight() const -{ - if (collapsed) - { - return 0; - } - int result = 0; - for (int i = 0; i < rowHeights.size(); ++i) - { - result += rowHeights[i]; - } - return result; -} -int CategorizedViewCategory::numRows() const -{ - return qMax(1, qCeil((qreal)numItems() / (qreal)view->itemsPerRow())); -} -int CategorizedViewCategory::top() const -{ - int res = 0; - const QList cats = view->m_categories; - 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 CategorizedViewCategory::items() const -{ - QList indices; - for (int i = 0; i < view->model()->rowCount(); ++i) - { - const QModelIndex index = view->model()->index(i, 0); - if (index.data(CategorizedViewRoles::CategoryRole).toString() == text) - { - indices.append(index); - } - } - return indices; -} -int CategorizedViewCategory::numItems() const -{ - return items().size(); -} -QModelIndex CategorizedViewCategory::firstItem() const -{ - QList indices = items(); - return indices.isEmpty() ? QModelIndex() : indices.first(); -} -QModelIndex CategorizedViewCategory::lastItem() const -{ - QList indices = items(); - return indices.isEmpty() ? QModelIndex() : indices.last(); -} diff --git a/CategorizedViewCategory.h b/CategorizedViewCategory.h deleted file mode 100644 index cb6ef8c5..00000000 --- a/CategorizedViewCategory.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef CATEGORIZEDVIEWROW_H -#define CATEGORIZEDVIEWROW_H - -#include -#include -#include - -class CategorizedView; -class QPainter; -class QModelIndex; - -struct CategorizedViewCategory -{ - CategorizedViewCategory(const QString &text, CategorizedView *view); - CategorizedViewCategory(const CategorizedViewCategory *other); - CategorizedView *view; - QString text; - bool collapsed; - QRect iconRect; - QRect textRect; - QVector rowHeights; - int firstRow; - - void update(); - - void drawHeader(QPainter *painter, const int y); - int totalHeight() const; - int headerHeight() const; - int contentHeight() const; - int numRows() const; - int top() const; - - QList items() const; - int numItems() const; - QModelIndex firstItem() const; - QModelIndex lastItem() const; -}; - -#endif // CATEGORIZEDVIEWROW_H diff --git a/Group.cpp b/Group.cpp new file mode 100644 index 00000000..f23066c5 --- /dev/null +++ b/Group.cpp @@ -0,0 +1,139 @@ +#include "Group.h" + +#include +#include +#include + +#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), + iconRect(other->iconRect), textRect(other->textRect) +{ +} + +void Group::update() +{ + firstRow = firstItem().row(); + + rowHeights = QVector(numRows()); + for (int i = 0; i < numRows(); ++i) + { + rowHeights[i] = view->categoryRowHeight( + view->model()->index(i * view->itemsPerRow() + firstRow, 0)); + } +} + +void Group::drawHeader(QPainter *painter, const int y) +{ + painter->save(); + + int height = headerHeight() - 4; + int collapseSize = height; + + // the icon + iconRect = QRect(view->m_rightMargin + 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); + 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(); +} + +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::top() const +{ + int res = 0; + const QList cats = view->m_categories; + 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 Group::items() const +{ + QList indices; + for (int i = 0; i < view->model()->rowCount(); ++i) + { + const QModelIndex index = view->model()->index(i, 0); + if (index.data(CategorizedViewRoles::CategoryRole).toString() == text) + { + indices.append(index); + } + } + return indices; +} + +int Group::numItems() const +{ + return items().size(); +} + +QModelIndex Group::firstItem() const +{ + QList indices = items(); + return indices.isEmpty() ? QModelIndex() : indices.first(); +} + +QModelIndex Group::lastItem() const +{ + QList indices = items(); + return indices.isEmpty() ? QModelIndex() : indices.last(); +} diff --git a/Group.h b/Group.h new file mode 100644 index 00000000..6a8fadeb --- /dev/null +++ b/Group.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + +class GroupView; +class QPainter; +class QModelIndex; + +struct Group +{ + Group(const QString &text, GroupView *view); + Group(const Group *other); + GroupView *view; + QString text; + bool collapsed; + QRect iconRect; + QRect textRect; + QVector rowHeights; + int firstRow; + + void update(); + + void drawHeader(QPainter *painter, const int y); + int totalHeight() const; + int headerHeight() const; + int contentHeight() const; + int numRows() const; + int top() const; + + QList items() const; + int numItems() const; + QModelIndex firstItem() const; + QModelIndex lastItem() const; +}; diff --git a/GroupView.cpp b/GroupView.cpp new file mode 100644 index 00000000..e3bc1055 --- /dev/null +++ b/GroupView.cpp @@ -0,0 +1,881 @@ +#include "GroupView.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Group.h" + +template bool listsIntersect(const QList &l1, const QList t2) +{ + for (auto &item : l1) + { + if (t2.contains(item)) + { + return true; + } + } + return false; +} + +GroupView::GroupView(QWidget *parent) + : QListView(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); + setSpacing(10); +} + +GroupView::~GroupView() +{ + qDeleteAll(m_categories); + m_categories.clear(); +} + +void GroupView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, + const QVector &roles) +{ + // if (m_updatesDisabled) + // { + // return; + // } + + QListView::dataChanged(topLeft, bottomRight, roles); + + if (roles.contains(CategorizedViewRoles::CategoryRole) || roles.contains(Qt::DisplayRole)) + { + updateGeometries(); + update(); + } +} +void GroupView::rowsInserted(const QModelIndex &parent, int start, int end) +{ + // if (m_updatesDisabled) + // { + // return; + // } + + QListView::rowsInserted(parent, start, end); + + updateGeometries(); + update(); +} + +void GroupView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + // if (m_updatesDisabled) + // { + // return; + // } + + QListView::rowsAboutToBeRemoved(parent, start, end); + + updateGeometries(); + update(); +} + +void GroupView::updateGeometries() +{ + QListView::updateGeometries(); + + int previousScroll = verticalScrollBar()->value(); + + QMap cats; + + for (int i = 0; i < model()->rowCount(); ++i) + { + const QString category = + model()->index(i, 0).data(CategorizedViewRoles::CategoryRole).toString(); + if (!cats.contains(category)) + { + Group *old = this->category(category); + if (old) + { + cats.insert(category, new Group(old)); + } + else + { + cats.insert(category, new Group(category, this)); + } + } + } + + /*if (m_editedCategory) + { + m_editedCategory = cats[m_editedCategory->text]; + }*/ + + qDeleteAll(m_categories); + m_categories = cats.values(); + + for (auto cat : m_categories) + { + cat->update(); + } + + if (m_categories.isEmpty()) + { + verticalScrollBar()->setRange(0, 0); + } + else + { + int totalHeight = 0; + for (auto category : m_categories) + { + 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())); + + 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(CategorizedViewRoles::CategoryRole).toString()); +} + +Group *GroupView::category(const QString &cat) const +{ + for (int i = 0; i < m_categories.size(); ++i) + { + if (m_categories.at(i)->text == cat) + { + return m_categories.at(i); + } + } + return 0; +} + +Group *GroupView::categoryAt(const QPoint &pos) const +{ + for (int i = 0; i < m_categories.size(); ++i) + { + if (m_categories.at(i)->iconRect.contains(pos)) + { + return m_categories.at(i); + } + } + return 0; +} + +int GroupView::itemsPerRow() const +{ + return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + /* spacing */ 10)); +} + +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 GroupView::categoryInternalPosition(const QModelIndex &index) const +{ + QList 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 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 command = 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 bottomRight = event->pos(); + + 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; + } + + QPersistentModelIndex index = indexAt(bottomRight); + + if (selectionMode() != SingleSelection) + { + topLeft = m_pressedPosition - offset(); + } + else + { + topLeft = bottomRight; + } + + if (m_pressedIndex.isValid() && (state() != DragSelectingState) && + (event->buttons() != Qt::NoButton) && !selectedIndexes().isEmpty()) + { + setState(DraggingState); + return; + } + + if ((event->buttons() & Qt::LeftButton) && selectionModel()) + { + setState(DragSelectingState); + QItemSelectionModel::SelectionFlags command = selectionCommand(index, event); + if (m_ctrlDragSelectionFlag != QItemSelectionModel::NoUpdate && + command.testFlag(QItemSelectionModel::Toggle)) + { + command &= ~QItemSelectionModel::Toggle; + command |= m_ctrlDragSelectionFlag; + } + + // Do the normalize ourselves, since QRect::normalized() is flawed + QRect selectionRect = QRect(topLeft, bottomRight); + setSelection(selectionRect, command); + + // 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()); + painter.translate(-offset()); + + int y = 0; + for (int i = 0; i < m_categories.size(); ++i) + { + Group *category = m_categories.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); + } + + if (!m_lastDragPosition.isNull()) + { + QPair pair = rowDropPos(m_lastDragPosition); + Group *category = pair.first; + int row = pair.second; + if (category) + { + int internalRow = row - category->firstRow; + 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(); + } + } +} + +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 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, + CategorizedViewRoles::CategoryRole); + 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 +{ + if (!index.isValid() || isIndexHidden(index) || index.column() > 0) + { + return QRect(); + } + + const Group *cat = category(index); + QPair pos = categoryInternalPosition(index); + int x = pos.first; + int y = pos.second; + + QRect out; + out.setTop(cat->top() + cat->headerHeight() + 5 + categoryInternalRowTop(index)); + out.setLeft(/*spacing*/ 10 + x * itemWidth() + x * /*spacing()*/ 10); + 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(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 (visualRect(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); + if (visualRect(index).intersects(rect)) + { + selectionModel()->select(index, commands); + } + } + update(); +} + +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> +GroupView::draggablePaintPairs(const QModelIndexList &indices, QRect *r) const +{ + Q_ASSERT(r); + QRect &rect = *r; + const QRect viewportRect = viewport()->rect(); + QList> ret; + for (int i = 0; i < indices.count(); ++i) + { + const QModelIndex &index = indices.at(i); + const QRect current = visualRect(index); + if (current.intersects(viewportRect)) + { + ret += qMakePair(current, index); + rect |= current; + } + } + rect &= viewportRect; + 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 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; + foreach(Group * cat, m_categories) + { + 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 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->top(); + 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()); +} diff --git a/GroupView.h b/GroupView.h new file mode 100644 index 00000000..e949d892 --- /dev/null +++ b/GroupView.h @@ -0,0 +1,107 @@ +#pragma once + +#include +#include + +struct CategorizedViewRoles +{ + enum + { + CategoryRole = Qt::UserRole, + ProgressValueRole, + ProgressMaximumRole + }; +}; + +struct Group; + +class GroupView : public QListView +{ + Q_OBJECT + +public: + GroupView(QWidget *parent = 0); + ~GroupView(); + + virtual QRect visualRect(const QModelIndex &index) const; + QModelIndex indexAt(const QPoint &point) const; + void setSelection(const QRect &rect, + const QItemSelectionModel::SelectionFlags commands) override; + +protected +slots: + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, + const QVector &roles); + virtual void rowsInserted(const QModelIndex &parent, int start, int end); + virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + virtual void updateGeometries(); + +protected: + virtual bool isIndexHidden(const QModelIndex &index) const; + 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 m_categories; + + 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: + QPoint m_pressedPosition; + QPersistentModelIndex m_pressedIndex; + bool m_pressedAlreadySelected; + Group *m_pressedCategory; + QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag; + QPoint m_lastDragPosition; + + QPair 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> draggablePaintPairs(const QModelIndexList &indices, + QRect *r) const; + + bool isDragEventAccepted(QDropEvent *event); + + QPair rowDropPos(const QPoint &pos); + + QPoint offset() const; +}; diff --git a/GroupedProxyModel.cpp b/GroupedProxyModel.cpp new file mode 100644 index 00000000..ab00a412 --- /dev/null +++ b/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(CategorizedViewRoles::CategoryRole).toString(); + const QString rightCategory = right.data(CategorizedViewRoles::CategoryRole).toString(); + if (leftCategory == rightCategory) + { + return left.row() < right.row(); + } + else + { + return leftCategory < rightCategory; + } +} diff --git a/GroupedProxyModel.h b/GroupedProxyModel.h new file mode 100644 index 00000000..cae87ecd --- /dev/null +++ b/GroupedProxyModel.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +class GroupedProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + GroupedProxyModel(QObject *parent = 0); + +protected: + bool lessThan(const QModelIndex &left, const QModelIndex &right) const; +}; diff --git a/InstanceDelegate.cpp b/InstanceDelegate.cpp index 50cead55..056db99d 100644 --- a/InstanceDelegate.cpp +++ b/InstanceDelegate.cpp @@ -20,7 +20,7 @@ #include #include -#include "CategorizedView.h" +#include "GroupView.h" // Origin: Qt static void viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height, @@ -88,7 +88,8 @@ void drawFocusRect(QPainter *painter, const QStyleOptionViewItemV4 &option, cons } // TODO this can be made a lot prettier -void drawProgressOverlay(QPainter *painter, const QStyleOptionViewItemV4 &option, const int value, const int maximum) +void drawProgressOverlay(QPainter *painter, const QStyleOptionViewItemV4 &option, + const int value, const int maximum) { if (maximum == 0 || value == maximum) { @@ -251,7 +252,8 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti line.draw(painter, position); } - drawProgressOverlay(painter, opt, index.data(CategorizedViewRoles::ProgressValueRole).toInt(), + drawProgressOverlay(painter, opt, + index.data(CategorizedViewRoles::ProgressValueRole).toInt(), index.data(CategorizedViewRoles::ProgressMaximumRole).toInt()); painter->restore(); diff --git a/InstanceDelegate.h b/InstanceDelegate.h index 6f924405..de2f429b 100644 --- a/InstanceDelegate.h +++ b/InstanceDelegate.h @@ -20,8 +20,10 @@ class ListViewDelegate : public QStyledItemDelegate { public: - explicit ListViewDelegate ( QObject* parent = 0 ); + 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; + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; }; diff --git a/main.cpp b/main.cpp index bd6a44f9..b9ecde8f 100644 --- a/main.cpp +++ b/main.cpp @@ -5,8 +5,8 @@ #include #include -#include "CategorizedView.h" -#include "CategorizedProxyModel.h" +#include "GroupView.h" +#include "GroupedProxyModel.h" #include "InstanceDelegate.h" Progresser *progresser; @@ -25,18 +25,20 @@ QPixmap icon(const int number) 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.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 *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, CategorizedViewRoles::CategoryRole); item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); - //progresser->addTrackedIndex(item); + // progresser->addTrackedIndex(item); return item; } QStandardItem *createItem(const int index, const QString &category) @@ -46,7 +48,7 @@ QStandardItem *createItem(const int index, const QString &category) item->setData(icon(index), Qt::DecorationRole); item->setData(category, CategorizedViewRoles::CategoryRole); item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); - //progresser->addTrackedIndex(item); + // progresser->addTrackedIndex(item); return item; } @@ -62,7 +64,10 @@ int main(int argc, char *argv[]) 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( + 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")); @@ -77,13 +82,13 @@ int main(int argc, char *argv[]) for (int i = 0; i < 20; ++i) { - model.setItem(i + 10, createItem(i+1, "Items 1-20")); + model.setItem(i + 10, createItem(i + 1, "Items 1-20")); } - CategorizedProxyModel pModel; + GroupedProxyModel pModel; pModel.setSourceModel(&model); - CategorizedView w; + GroupView w; w.setItemDelegate(new ListViewDelegate); w.setModel(&pModel); w.resize(640, 480); diff --git a/main.h b/main.h index f4c7a3f8..a1e7f432 100644 --- a/main.h +++ b/main.h @@ -1,5 +1,4 @@ -#ifndef MAIN_H -#define MAIN_H +#pragma once #include #include @@ -7,7 +6,7 @@ #include #include -#include "CategorizedView.h" +#include "GroupView.h" class Progresser : public QObject { @@ -27,10 +26,11 @@ public: return item; } -public slots: +public +slots: void timeout() { - foreach (QStandardItem *item, m_items) + foreach(QStandardItem * item, m_items) { int value = item->data(CategorizedViewRoles::ProgressValueRole).toInt(); value += qrand() % 3; @@ -49,5 +49,3 @@ public slots: private: QList m_items; }; - -#endif // MAIN_H -- cgit From 179451d5911ccce569492717b1f0b0186e25cab9 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Fri, 31 Jan 2014 22:58:57 +0100 Subject: More reformat. --- Group.cpp | 3 +-- GroupView.cpp | 25 +++++++++++++------------ GroupView.h | 3 +-- main.h | 2 +- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/Group.cpp b/Group.cpp index f23066c5..a62f592b 100644 --- a/Group.cpp +++ b/Group.cpp @@ -6,8 +6,7 @@ #include "GroupView.h" -Group::Group(const QString &text, GroupView *view) - : view(view), text(text), collapsed(false) +Group::Group(const QString &text, GroupView *view) : view(view), text(text), collapsed(false) { } Group::Group(const Group *other) diff --git a/GroupView.cpp b/GroupView.cpp index e3bc1055..94682df8 100644 --- a/GroupView.cpp +++ b/GroupView.cpp @@ -30,11 +30,11 @@ GroupView::GroupView(QWidget *parent) m_categoryMargin(5) //, m_updatesDisabled(false), m_categoryEditor(0), m_editedCategory(0) { setViewMode(IconMode); - //setMovement(Snap); + // setMovement(Snap); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); setWordWrap(true); - //setDragDropMode(QListView::InternalMove); + // setDragDropMode(QListView::InternalMove); setAcceptDrops(true); setSpacing(10); } @@ -46,7 +46,7 @@ GroupView::~GroupView() } void GroupView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, - const QVector &roles) + const QVector &roles) { // if (m_updatesDisabled) // { @@ -262,8 +262,7 @@ int GroupView::categoryInternalRowTop(const QModelIndex &index) const return result; } -int GroupView::itemHeightForCategoryRow(const Group *category, - const int internalRow) const +int GroupView::itemHeightForCategoryRow(const Group *category, const int internalRow) const { for (auto &i : category->items()) { @@ -473,7 +472,8 @@ void GroupView::paintEvent(QPaintEvent *event) QStyleOptionViewItemV4 option(viewOptions()); option.rect = visualRect(index); option.widget = this; - option.features |= QStyleOptionViewItemV2::WrapText; // FIXME: what is the meaning of this anyway? + 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 @@ -524,8 +524,9 @@ void GroupView::resizeEvent(QResizeEvent *event) // if (m_categoryEditor) // { - // m_categoryEditor->resize(qMax(contentWidth() / 2, m_editedCategory->textRect.width()), - //m_categoryEditor->height()); + // m_categoryEditor->resize(qMax(contentWidth() / 2, + //m_editedCategory->textRect.width()), + // m_categoryEditor->height()); // } updateGeometries(); @@ -711,7 +712,7 @@ QModelIndex GroupView::indexAt(const QPoint &point) const } void GroupView::setSelection(const QRect &rect, - const QItemSelectionModel::SelectionFlags commands) + const QItemSelectionModel::SelectionFlags commands) { for (int i = 0; i < model()->rowCount(); ++i) { @@ -746,8 +747,8 @@ QPixmap GroupView::renderToPixmap(const QModelIndexList &indices, QRect *r) cons return pixmap; } -QList> -GroupView::draggablePaintPairs(const QModelIndexList &indices, QRect *r) const +QList> GroupView::draggablePaintPairs(const QModelIndexList &indices, + QRect *r) const { Q_ASSERT(r); QRect &rect = *r; @@ -791,7 +792,7 @@ QPair GroupView::rowDropPos(const QPoint &pos) Group *category = 0; { int y = 0; - foreach(Group * cat, m_categories) + for (auto cat : m_categories) { if (pos.y() > y && pos.y() < (y + cat->headerHeight())) { diff --git a/GroupView.h b/GroupView.h index e949d892..6d55a462 100644 --- a/GroupView.h +++ b/GroupView.h @@ -92,8 +92,7 @@ private: QPair categoryInternalPosition(const QModelIndex &index) const; int categoryInternalRowTop(const QModelIndex &index) const; - int itemHeightForCategoryRow(const Group *category, - const int internalRow) const; + int itemHeightForCategoryRow(const Group *category, const int internalRow) const; QPixmap renderToPixmap(const QModelIndexList &indices, QRect *r) const; QList> draggablePaintPairs(const QModelIndexList &indices, diff --git a/main.h b/main.h index a1e7f432..47377f7a 100644 --- a/main.h +++ b/main.h @@ -30,7 +30,7 @@ public slots: void timeout() { - foreach(QStandardItem * item, m_items) + for (auto item : m_items) { int value = item->data(CategorizedViewRoles::ProgressValueRole).toInt(); value += qrand() % 3; -- cgit From b2bf50a6d75d32ac483bb53d5c5948b353cd2d16 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Sun, 2 Feb 2014 10:26:38 +0100 Subject: Small tweaks --- GroupView.cpp | 41 +++++++++-------------------------------- GroupView.h | 36 +++++++++++++++++++++++++++++++++++- InstanceDelegate.cpp | 2 +- main.h | 19 +++++++++++-------- 4 files changed, 56 insertions(+), 42 deletions(-) diff --git a/GroupView.cpp b/GroupView.cpp index 94682df8..50d19f52 100644 --- a/GroupView.cpp +++ b/GroupView.cpp @@ -26,17 +26,17 @@ template bool listsIntersect(const QList &l1, const QList t2) } GroupView::GroupView(QWidget *parent) - : QListView(parent), m_leftMargin(5), m_rightMargin(5), m_bottomMargin(5), + : 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); + // setViewMode(IconMode); // setMovement(Snap); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - setWordWrap(true); + // setWordWrap(true); // setDragDropMode(QListView::InternalMove); setAcceptDrops(true); - setSpacing(10); + // setSpacing(10); } GroupView::~GroupView() @@ -48,49 +48,26 @@ GroupView::~GroupView() void GroupView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { - // if (m_updatesDisabled) - // { - // return; - // } - - QListView::dataChanged(topLeft, bottomRight, roles); - if (roles.contains(CategorizedViewRoles::CategoryRole) || roles.contains(Qt::DisplayRole)) { updateGeometries(); - update(); } + viewport()->update(); } void GroupView::rowsInserted(const QModelIndex &parent, int start, int end) { - // if (m_updatesDisabled) - // { - // return; - // } - - QListView::rowsInserted(parent, start, end); - updateGeometries(); - update(); + viewport()->update(); } void GroupView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) { - // if (m_updatesDisabled) - // { - // return; - // } - - QListView::rowsAboutToBeRemoved(parent, start, end); - updateGeometries(); - update(); + viewport()->update(); } void GroupView::updateGeometries() { - QListView::updateGeometries(); - int previousScroll = verticalScrollBar()->value(); QMap cats; @@ -145,7 +122,7 @@ void GroupView::updateGeometries() verticalScrollBar()->setValue(qMin(previousScroll, verticalScrollBar()->maximum())); - update(); + viewport()->update(); } bool GroupView::isIndexHidden(const QModelIndex &index) const @@ -520,7 +497,7 @@ void GroupView::paintEvent(QPaintEvent *event) void GroupView::resizeEvent(QResizeEvent *event) { - QListView::resizeEvent(event); + // QListView::resizeEvent(event); // if (m_categoryEditor) // { diff --git a/GroupView.h b/GroupView.h index 6d55a462..bf911794 100644 --- a/GroupView.h +++ b/GroupView.h @@ -15,7 +15,7 @@ struct CategorizedViewRoles struct Group; -class GroupView : public QListView +class GroupView : public QAbstractItemView { Q_OBJECT @@ -28,6 +28,40 @@ public: void setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) override; + /* + * BS + */ + + virtual int horizontalOffset() const override + { + return 0; + } + + virtual int verticalOffset() const override + { + return 0; + } + + virtual void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) override + { + return; + } + + virtual QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) + override + { + return QModelIndex(); + } + + virtual QRegion visualRegionForSelection(const QItemSelection &) const override + { + return QRegion(); + } + + /* + * End of BS + */ + protected slots: void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, diff --git a/InstanceDelegate.cpp b/InstanceDelegate.cpp index 056db99d..944cfd76 100644 --- a/InstanceDelegate.cpp +++ b/InstanceDelegate.cpp @@ -103,7 +103,7 @@ void drawProgressOverlay(QPainter *painter, const QStyleOptionViewItemV4 &option color.setAlphaF(0.70f); painter->setBrush(color); painter->setPen(QPen(QBrush(), 0)); - painter->drawPie(option.rect, 90 * 16, -percent * 360 * 60); + painter->drawPie(option.rect, 90 * 16, -percent * 360 * 16); painter->restore(); } diff --git a/main.h b/main.h index 47377f7a..6883e98e 100644 --- a/main.h +++ b/main.h @@ -30,20 +30,23 @@ public slots: void timeout() { + QList toRemove; for (auto item : m_items) { + int maximum = item->data(CategorizedViewRoles::ProgressMaximumRole).toInt(); int value = item->data(CategorizedViewRoles::ProgressValueRole).toInt(); - value += qrand() % 3; - if (value >= item->data(CategorizedViewRoles::ProgressMaximumRole).toInt()) - { - item->setData(item->data(CategorizedViewRoles::ProgressMaximumRole).toInt(), - CategorizedViewRoles::ProgressValueRole); - } - else + int newvalue = std::min(value + 3, maximum); + item->setData(newvalue, CategorizedViewRoles::ProgressValueRole); + + if(newvalue >= maximum) { - item->setData(value, CategorizedViewRoles::ProgressValueRole); + toRemove.append(item); } } + for(auto remove : toRemove) + { + m_items.removeAll(remove); + } } private: -- cgit From eb0ed082d877156f543324736cbf4ab85a9ec3f8 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Sun, 2 Feb 2014 14:27:43 +0100 Subject: Fix group mouse interaction issues --- Group.cpp | 39 ++++++++++++++++++++++++++++++++---- Group.h | 16 +++++++++++++-- GroupView.cpp | 64 +++++++++++++++++++++++++++++++++++++++++------------------ GroupView.h | 23 +++++++++++++-------- 4 files changed, 109 insertions(+), 33 deletions(-) diff --git a/Group.cpp b/Group.cpp index a62f592b..c92132ca 100644 --- a/Group.cpp +++ b/Group.cpp @@ -9,9 +9,9 @@ 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), - iconRect(other->iconRect), textRect(other->textRect) + : view(other->view), text(other->text), collapsed(other->collapsed) { } @@ -27,6 +27,37 @@ void Group::update() } } +Group::HitResults Group::pointIntersect(const QPoint &pos) const +{ + Group::HitResults results = Group::NoHit; + int y_start = top(); + 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(); @@ -35,7 +66,7 @@ void Group::drawHeader(QPainter *painter, const int y) int collapseSize = height; // the icon - iconRect = QRect(view->m_rightMargin + 2, 2 + y, collapseSize, collapseSize); + 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; @@ -50,7 +81,7 @@ void Group::drawHeader(QPainter *painter, const int y) // the text int textWidth = painter->fontMetrics().width(text); - textRect = QRect(iconRect.right() + 4, y, textWidth, headerHeight()); + 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); diff --git a/Group.h b/Group.h index 6a8fadeb..51e0470d 100644 --- a/Group.h +++ b/Group.h @@ -15,8 +15,6 @@ struct Group GroupView *view; QString text; bool collapsed; - QRect iconRect; - QRect textRect; QVector rowHeights; int firstRow; @@ -29,8 +27,22 @@ struct Group int numRows() const; int top() const; + enum HitResult + { + NoHit = 0x0, + TextHit = 0x1, + CheckboxHit = 0x2, + HeaderHit = 0x4, + BodyHit = 0x8 + }; + Q_DECLARE_FLAGS(HitResults, HitResult) + + HitResults pointIntersect (const QPoint &pos) const; + QList items() const; int numItems() const; QModelIndex firstItem() const; QModelIndex lastItem() const; }; + +Q_DECLARE_OPERATORS_FOR_FLAGS(Group::HitResults) diff --git a/GroupView.cpp b/GroupView.cpp index 50d19f52..1f0a51e7 100644 --- a/GroupView.cpp +++ b/GroupView.cpp @@ -36,7 +36,7 @@ GroupView::GroupView(QWidget *parent) // setWordWrap(true); // setDragDropMode(QListView::InternalMove); setAcceptDrops(true); - // setSpacing(10); + m_spacing = 5; } GroupView::~GroupView() @@ -145,31 +145,31 @@ Group *GroupView::category(const QModelIndex &index) const Group *GroupView::category(const QString &cat) const { - for (int i = 0; i < m_categories.size(); ++i) + for (auto group : m_categories) { - if (m_categories.at(i)->text == cat) + if (group->text == cat) { - return m_categories.at(i); + return group; } } - return 0; + return nullptr; } Group *GroupView::categoryAt(const QPoint &pos) const { - for (int i = 0; i < m_categories.size(); ++i) + for (auto group : m_categories) { - if (m_categories.at(i)->iconRect.contains(pos)) + if(group->pointIntersect(pos) & Group::CheckboxHit) { - return m_categories.at(i); + return group; } } - return 0; + return nullptr; } int GroupView::itemsPerRow() const { - return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + /* spacing */ 10)); + return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + m_spacing)); } int GroupView::contentWidth() const @@ -261,7 +261,7 @@ void GroupView::mousePressEvent(QMouseEvent *event) m_pressedIndex = index; m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex); - QItemSelectionModel::SelectionFlags command = selectionCommand(index, event); + QItemSelectionModel::SelectionFlags selection_flags = selectionCommand(index, event); m_pressedPosition = pos; m_pressedCategory = categoryAt(m_pressedPosition); @@ -428,9 +428,8 @@ void GroupView::mouseDoubleClickEvent(QMouseEvent *event) void GroupView::paintEvent(QPaintEvent *event) { QPainter painter(this->viewport()); - painter.translate(-offset()); - int y = 0; + int y = -verticalOffset(); for (int i = 0; i < m_categories.size(); ++i) { Group *category = m_categories.at(i); @@ -502,7 +501,7 @@ void GroupView::resizeEvent(QResizeEvent *event) // if (m_categoryEditor) // { // m_categoryEditor->resize(qMax(contentWidth() / 2, - //m_editedCategory->textRect.width()), + // m_editedCategory->textRect.width()), // m_categoryEditor->height()); // } @@ -618,6 +617,11 @@ void GroupView::startDrag(Qt::DropActions supportedActions) } 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) { @@ -627,15 +631,16 @@ QRect GroupView::visualRect(const QModelIndex &index) const const Group *cat = category(index); QPair pos = categoryInternalPosition(index); int x = pos.first; - int y = pos.second; + // int y = pos.second; QRect out; out.setTop(cat->top() + cat->headerHeight() + 5 + categoryInternalRowTop(index)); - out.setLeft(/*spacing*/ 10 + x * itemWidth() + x * /*spacing()*/ 10); + out.setLeft(m_spacing + x * (itemWidth() + m_spacing)); out.setSize(itemDelegate()->sizeHint(viewOptions(), index)); return out; } + /* void CategorizedView::startCategoryEditor(Category *category) { @@ -680,7 +685,7 @@ QModelIndex GroupView::indexAt(const QPoint &point) const for (int i = 0; i < model()->rowCount(); ++i) { QModelIndex index = model()->index(i, 0); - if (visualRect(index).contains(point)) + if (geometryRect(index).contains(point)) { return index; } @@ -694,7 +699,7 @@ void GroupView::setSelection(const QRect &rect, for (int i = 0; i < model()->rowCount(); ++i) { QModelIndex index = model()->index(i, 0); - if (visualRect(index).intersects(rect)) + if (geometryRect(index).intersects(rect)) { selectionModel()->select(index, commands); } @@ -734,7 +739,7 @@ QList> GroupView::draggablePaintPairs(const QModelInde for (int i = 0; i < indices.count(); ++i) { const QModelIndex &index = indices.at(i); - const QRect current = visualRect(index); + const QRect current = geometryRect(index); if (current.intersects(viewportRect)) { ret += qMakePair(current, index); @@ -857,3 +862,24 @@ 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; +} diff --git a/GroupView.h b/GroupView.h index bf911794..2c60f0e9 100644 --- a/GroupView.h +++ b/GroupView.h @@ -2,6 +2,7 @@ #include #include +#include struct CategorizedViewRoles { @@ -23,7 +24,8 @@ public: GroupView(QWidget *parent = 0); ~GroupView(); - virtual QRect visualRect(const QModelIndex &index) const; + virtual 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; @@ -34,12 +36,18 @@ public: virtual int horizontalOffset() const override { - return 0; + return horizontalScrollBar()->value(); } virtual int verticalOffset() const override { - return 0; + return verticalScrollBar()->value(); + } + + virtual void scrollContentsBy(int dx, int dy) override + { + scrollDirtyRegion(dx, dy); + viewport()->scroll(dx, dy); } virtual void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) override @@ -53,10 +61,7 @@ public: return QModelIndex(); } - virtual QRegion visualRegionForSelection(const QItemSelection &) const override - { - return QRegion(); - } + virtual QRegion visualRegionForSelection(const QItemSelection &selection) const override; /* * End of BS @@ -116,14 +121,16 @@ private: private slots: void endCategoryEditor();*/ -private: +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 categoryInternalPosition(const QModelIndex &index) const; int categoryInternalRowTop(const QModelIndex &index) const; int itemHeightForCategoryRow(const Group *category, const int internalRow) const; -- cgit From 2cd9b0647640ee595113ceca128db1131bebc19a Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Sun, 2 Feb 2014 16:57:00 +0100 Subject: Fix drag&drop pixmap rendering --- GroupView.cpp | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/GroupView.cpp b/GroupView.cpp index 1f0a51e7..b47cf93f 100644 --- a/GroupView.cpp +++ b/GroupView.cpp @@ -296,7 +296,7 @@ void GroupView::mousePressEvent(QMouseEvent *event) void GroupView::mouseMoveEvent(QMouseEvent *event) { QPoint topLeft; - QPoint bottomRight = event->pos(); + QPoint pos = event->pos() + offset(); if (state() == ExpandingState || state() == CollapsingState) { @@ -316,15 +316,13 @@ void GroupView::mouseMoveEvent(QMouseEvent *event) return; } - QPersistentModelIndex index = indexAt(bottomRight); - if (selectionMode() != SingleSelection) { topLeft = m_pressedPosition - offset(); } else { - topLeft = bottomRight; + topLeft = pos; } if (m_pressedIndex.isValid() && (state() != DragSelectingState) && @@ -337,17 +335,9 @@ void GroupView::mouseMoveEvent(QMouseEvent *event) if ((event->buttons() & Qt::LeftButton) && selectionModel()) { setState(DragSelectingState); - QItemSelectionModel::SelectionFlags command = selectionCommand(index, event); - if (m_ctrlDragSelectionFlag != QItemSelectionModel::NoUpdate && - command.testFlag(QItemSelectionModel::Toggle)) - { - command &= ~QItemSelectionModel::Toggle; - command |= m_ctrlDragSelectionFlag; - } - // Do the normalize ourselves, since QRect::normalized() is flawed - QRect selectionRect = QRect(topLeft, bottomRight); - setSelection(selectionRect, command); + 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()) && @@ -582,7 +572,7 @@ void GroupView::startDrag(Qt::DropActions supportedActions) } QRect rect; QPixmap pixmap = renderToPixmap(indexes, &rect); - rect.translate(offset()); + //rect.translate(offset()); // rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); QDrag *drag = new QDrag(this); drag->setPixmap(pixmap); @@ -699,12 +689,14 @@ void GroupView::setSelection(const QRect &rect, for (int i = 0; i < model()->rowCount(); ++i) { QModelIndex index = model()->index(i, 0); - if (geometryRect(index).intersects(rect)) + QRect itemRect = geometryRect(index); + if (itemRect.intersects(rect)) { selectionModel()->select(index, commands); + update(itemRect.translated(-offset())); } } - update(); + } QPixmap GroupView::renderToPixmap(const QModelIndexList &indices, QRect *r) const @@ -725,6 +717,7 @@ QPixmap GroupView::renderToPixmap(const QModelIndexList &indices, QRect *r) cons option.rect = paintPairs.at(j).first.translated(-r->topLeft()); const QModelIndex ¤t = paintPairs.at(j).second; itemDelegate()->paint(&painter, option, current); + painter.drawLine(0,0, r->width(), r->height()); } return pixmap; } @@ -740,13 +733,13 @@ QList> GroupView::draggablePaintPairs(const QModelInde { const QModelIndex &index = indices.at(i); const QRect current = geometryRect(index); - if (current.intersects(viewportRect)) - { + //if (current.intersects(viewportRect)) + //{ ret += qMakePair(current, index); rect |= current; - } + //} } - rect &= viewportRect; + //rect &= viewportRect; return ret; } -- cgit From ddedd077b66f2bf45cd157d760eebdad15bcb289 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Sun, 2 Feb 2014 18:30:52 +0100 Subject: Oh my. --- GroupView.cpp | 40 +++++++++++++++++++++++++++++++++------- GroupView.h | 15 ++++----------- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/GroupView.cpp b/GroupView.cpp index b47cf93f..3296ccfb 100644 --- a/GroupView.cpp +++ b/GroupView.cpp @@ -727,19 +727,14 @@ QList> GroupView::draggablePaintPairs(const QModelInde { Q_ASSERT(r); QRect &rect = *r; - const QRect viewportRect = viewport()->rect(); QList> ret; for (int i = 0; i < indices.count(); ++i) { const QModelIndex &index = indices.at(i); const QRect current = geometryRect(index); - //if (current.intersects(viewportRect)) - //{ - ret += qMakePair(current, index); - rect |= current; - //} + ret += qMakePair(current, index); + rect |= current; } - //rect &= viewportRect; return ret; } @@ -876,3 +871,34 @@ QRegion GroupView::visualRegionForSelection(const QItemSelection &selection) con } 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_categories.indexOf(cat); + if(i >= 0) + { + // this is a pile of something foul + auto real_cat = m_categories[i]; + int beginning_row = 0; + for(auto catt: m_categories) + { + if(catt == real_cat) + break; + beginning_row += catt->numRows(); + } + qDebug() << "category: " << real_cat->text; + QPair pos = categoryInternalPosition(current); + int row = beginning_row + pos.second; + qDebug() << "row: " << row; + qDebug() << "column: " << pos.first; + } + return current; +} diff --git a/GroupView.h b/GroupView.h index 2c60f0e9..a7ed8e9d 100644 --- a/GroupView.h +++ b/GroupView.h @@ -30,10 +30,6 @@ public: void setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) override; - /* - * BS - */ - virtual int horizontalOffset() const override { return horizontalScrollBar()->value(); @@ -50,22 +46,19 @@ public: 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 - { - return QModelIndex(); - } + override; virtual QRegion visualRegionForSelection(const QItemSelection &selection) const override; - /* - * End of BS - */ protected slots: -- cgit From 946d49675cb4725c31ab49a51f3bcae302f89a19 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Tue, 4 Feb 2014 00:40:10 +0100 Subject: Swap things around -- rename refactors, moving towards non-derpy design. Maybe. --- Group.cpp | 16 ++++++------ Group.h | 25 ++++++++++++++++--- GroupView.cpp | 68 +++++++++++++++++++++++++++------------------------ GroupView.h | 25 +++++++++---------- GroupedProxyModel.cpp | 4 +-- InstanceDelegate.cpp | 4 +-- main.cpp | 4 +-- main.h | 8 +++--- 8 files changed, 88 insertions(+), 66 deletions(-) diff --git a/Group.cpp b/Group.cpp index c92132ca..f216cc6e 100644 --- a/Group.cpp +++ b/Group.cpp @@ -17,20 +17,20 @@ Group::Group(const Group *other) void Group::update() { - firstRow = firstItem().row(); + firstItemIndex = firstItem().row(); rowHeights = QVector(numRows()); for (int i = 0; i < numRows(); ++i) { rowHeights[i] = view->categoryRowHeight( - view->model()->index(i * view->itemsPerRow() + firstRow, 0)); + view->model()->index(i * view->itemsPerRow() + firstItemIndex, 0)); } } -Group::HitResults Group::pointIntersect(const QPoint &pos) const +Group::HitResults Group::hitScan(const QPoint &pos) const { Group::HitResults results = Group::NoHit; - int y_start = top(); + 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(); @@ -95,7 +95,7 @@ void Group::drawHeader(QPainter *painter, const int y) int Group::totalHeight() const { - return headerHeight() + 5 + contentHeight(); + return headerHeight() + 5 + contentHeight(); // FIXME: wtf is that '5'? } int Group::headerHeight() const @@ -122,10 +122,10 @@ int Group::numRows() const return qMax(1, qCeil((qreal)numItems() / (qreal)view->itemsPerRow())); } -int Group::top() const +int Group::verticalPosition() const { int res = 0; - const QList cats = view->m_categories; + const QList cats = view->m_groups; for (int i = 0; i < cats.size(); ++i) { if (cats.at(i) == this) @@ -143,7 +143,7 @@ QList Group::items() const for (int i = 0; i < view->model()->rowCount(); ++i) { const QModelIndex index = view->model()->index(i, 0); - if (index.data(CategorizedViewRoles::CategoryRole).toString() == text) + if (index.data(GroupViewRoles::GroupRole).toString() == text) { indices.append(index); } diff --git a/Group.h b/Group.h index 51e0470d..455ee1a8 100644 --- a/Group.h +++ b/Group.h @@ -10,22 +10,38 @@ class QModelIndex; struct Group { +/* constructors */ Group(const QString &text, GroupView *view); Group(const Group *other); + +/* data */ GroupView *view; QString text; bool collapsed; QVector rowHeights; - int firstRow; + 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; - int top() const; + + /// the height at which this group starts, in pixels + int verticalPosition() const; enum HitResult { @@ -37,9 +53,12 @@ struct Group }; Q_DECLARE_FLAGS(HitResults, HitResult) - HitResults pointIntersect (const QPoint &pos) const; + /// shoot! BANG! what did we hit? + HitResults hitScan (const QPoint &pos) const; + /// super derpy thing. QList items() const; + /// I don't even int numItems() const; QModelIndex firstItem() const; QModelIndex lastItem() const; diff --git a/GroupView.cpp b/GroupView.cpp index 3296ccfb..89e3e223 100644 --- a/GroupView.cpp +++ b/GroupView.cpp @@ -41,14 +41,14 @@ GroupView::GroupView(QWidget *parent) GroupView::~GroupView() { - qDeleteAll(m_categories); - m_categories.clear(); + qDeleteAll(m_groups); + m_groups.clear(); } void GroupView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { - if (roles.contains(CategorizedViewRoles::CategoryRole) || roles.contains(Qt::DisplayRole)) + if (roles.contains(GroupViewRoles::GroupRole) || roles.contains(Qt::DisplayRole)) { updateGeometries(); } @@ -74,18 +74,18 @@ void GroupView::updateGeometries() for (int i = 0; i < model()->rowCount(); ++i) { - const QString category = - model()->index(i, 0).data(CategorizedViewRoles::CategoryRole).toString(); - if (!cats.contains(category)) + const QString groupName = + model()->index(i, 0).data(GroupViewRoles::GroupRole).toString(); + if (!cats.contains(groupName)) { - Group *old = this->category(category); + Group *old = this->category(groupName); if (old) { - cats.insert(category, new Group(old)); + cats.insert(groupName, new Group(old)); } else { - cats.insert(category, new Group(category, this)); + cats.insert(groupName, new Group(groupName, this)); } } } @@ -95,22 +95,22 @@ void GroupView::updateGeometries() m_editedCategory = cats[m_editedCategory->text]; }*/ - qDeleteAll(m_categories); - m_categories = cats.values(); + qDeleteAll(m_groups); + m_groups = cats.values(); - for (auto cat : m_categories) + for (auto cat : m_groups) { cat->update(); } - if (m_categories.isEmpty()) + if (m_groups.isEmpty()) { verticalScrollBar()->setRange(0, 0); } else { int totalHeight = 0; - for (auto category : m_categories) + for (auto category : m_groups) { totalHeight += category->totalHeight() + m_categoryMargin; } @@ -140,12 +140,12 @@ bool GroupView::isIndexHidden(const QModelIndex &index) const Group *GroupView::category(const QModelIndex &index) const { - return category(index.data(CategorizedViewRoles::CategoryRole).toString()); + return category(index.data(GroupViewRoles::GroupRole).toString()); } Group *GroupView::category(const QString &cat) const { - for (auto group : m_categories) + for (auto group : m_groups) { if (group->text == cat) { @@ -157,9 +157,9 @@ Group *GroupView::category(const QString &cat) const Group *GroupView::categoryAt(const QPoint &pos) const { - for (auto group : m_categories) + for (auto group : m_groups) { - if(group->pointIntersect(pos) & Group::CheckboxHit) + if(group->hitScan(pos) & Group::CheckboxHit) { return group; } @@ -420,9 +420,9 @@ void GroupView::paintEvent(QPaintEvent *event) QPainter painter(this->viewport()); int y = -verticalOffset(); - for (int i = 0; i < m_categories.size(); ++i) + for (int i = 0; i < m_groups.size(); ++i) { - Group *category = m_categories.at(i); + Group *category = m_groups.at(i); category->drawHeader(&painter, y); y += category->totalHeight() + m_categoryMargin; } @@ -457,6 +457,10 @@ void GroupView::paintEvent(QPaintEvent *event) itemDelegate()->paint(&painter, option, index); } + /* + * Drop indicators for manual reordering... + */ +#if 0 if (!m_lastDragPosition.isNull()) { QPair pair = rowDropPos(m_lastDragPosition); @@ -464,7 +468,7 @@ void GroupView::paintEvent(QPaintEvent *event) int row = pair.second; if (category) { - int internalRow = row - category->firstRow; + int internalRow = row - category->firstItemIndex; QLine line; if (internalRow >= category->numItems()) { @@ -482,6 +486,7 @@ void GroupView::paintEvent(QPaintEvent *event) painter.restore(); } } +#endif } void GroupView::resizeEvent(QResizeEvent *event) @@ -552,7 +557,7 @@ void GroupView::dropEvent(QDropEvent *event) if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex())) { model()->setData(model()->index(row, 0), categoryText, - CategorizedViewRoles::CategoryRole); + GroupViewRoles::GroupRole); event->setDropAction(Qt::MoveAction); event->accept(); } @@ -624,7 +629,7 @@ QRect GroupView::geometryRect(const QModelIndex &index) const // int y = pos.second; QRect out; - out.setTop(cat->top() + cat->headerHeight() + 5 + categoryInternalRowTop(index)); + out.setTop(cat->verticalPosition() + cat->headerHeight() + 5 + categoryInternalRowTop(index)); out.setLeft(m_spacing + x * (itemWidth() + m_spacing)); out.setSize(itemDelegate()->sizeHint(viewOptions(), index)); @@ -717,7 +722,6 @@ QPixmap GroupView::renderToPixmap(const QModelIndexList &indices, QRect *r) cons option.rect = paintPairs.at(j).first.translated(-r->topLeft()); const QModelIndex ¤t = paintPairs.at(j).second; itemDelegate()->paint(&painter, option, current); - painter.drawLine(0,0, r->width(), r->height()); } return pixmap; } @@ -762,7 +766,7 @@ QPair GroupView::rowDropPos(const QPoint &pos) Group *category = 0; { int y = 0; - for (auto cat : m_categories) + for (auto cat : m_groups) { if (pos.y() > y && pos.y() < (y + cat->headerHeight())) { @@ -812,7 +816,7 @@ QPair GroupView::rowDropPos(const QPoint &pos) int internalRow = -1; { // FIXME rework the drag and drop code - const int top = category->top(); + const int top = category->verticalPosition(); for (int r = 0, h = top; r < category->numRows(); h += itemHeightForCategoryRow(category, r), ++r) { @@ -882,19 +886,19 @@ QModelIndex GroupView::moveCursor(QAbstractItemView::CursorAction cursorAction, } qDebug() << "model row: " << current.row(); auto cat = category(current); - int i = m_categories.indexOf(cat); + int i = m_groups.indexOf(cat); if(i >= 0) { // this is a pile of something foul - auto real_cat = m_categories[i]; + auto real_group = m_groups[i]; int beginning_row = 0; - for(auto catt: m_categories) + for(auto group: m_groups) { - if(catt == real_cat) + if(group == real_group) break; - beginning_row += catt->numRows(); + beginning_row += group->numRows(); } - qDebug() << "category: " << real_cat->text; + qDebug() << "category: " << real_group->text; QPair pos = categoryInternalPosition(current); int row = beginning_row + pos.second; qDebug() << "row: " << row; diff --git a/GroupView.h b/GroupView.h index a7ed8e9d..329a3503 100644 --- a/GroupView.h +++ b/GroupView.h @@ -4,11 +4,11 @@ #include #include -struct CategorizedViewRoles +struct GroupViewRoles { enum { - CategoryRole = Qt::UserRole, + GroupRole = Qt::UserRole, ProgressValueRole, ProgressMaximumRole }; @@ -24,7 +24,7 @@ public: GroupView(QWidget *parent = 0); ~GroupView(); - virtual QRect geometryRect(const QModelIndex &index) const; + 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, @@ -54,22 +54,21 @@ public: return; } - virtual QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) - override; + virtual QModelIndex moveCursor(CursorAction cursorAction, + Qt::KeyboardModifiers modifiers) override; virtual QRegion visualRegionForSelection(const QItemSelection &selection) const override; - protected slots: - void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, - const QVector &roles); - virtual void rowsInserted(const QModelIndex &parent, int start, int end); - virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); - virtual void updateGeometries(); + virtual void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, + const QVector &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; + virtual bool isIndexHidden(const QModelIndex &index) const override; void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; @@ -87,7 +86,7 @@ protected: private: friend struct Group; - QList m_categories; + QList m_groups; int m_leftMargin; int m_rightMargin; diff --git a/GroupedProxyModel.cpp b/GroupedProxyModel.cpp index ab00a412..57d7ff5c 100644 --- a/GroupedProxyModel.cpp +++ b/GroupedProxyModel.cpp @@ -8,8 +8,8 @@ GroupedProxyModel::GroupedProxyModel(QObject *parent) : QSortFilterProxyModel(pa bool GroupedProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { - const QString leftCategory = left.data(CategorizedViewRoles::CategoryRole).toString(); - const QString rightCategory = right.data(CategorizedViewRoles::CategoryRole).toString(); + const QString leftCategory = left.data(GroupViewRoles::GroupRole).toString(); + const QString rightCategory = right.data(GroupViewRoles::GroupRole).toString(); if (leftCategory == rightCategory) { return left.row() < right.row(); diff --git a/InstanceDelegate.cpp b/InstanceDelegate.cpp index 944cfd76..8527e2bc 100644 --- a/InstanceDelegate.cpp +++ b/InstanceDelegate.cpp @@ -253,8 +253,8 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti } drawProgressOverlay(painter, opt, - index.data(CategorizedViewRoles::ProgressValueRole).toInt(), - index.data(CategorizedViewRoles::ProgressMaximumRole).toInt()); + index.data(GroupViewRoles::ProgressValueRole).toInt(), + index.data(GroupViewRoles::ProgressMaximumRole).toInt()); painter->restore(); } diff --git a/main.cpp b/main.cpp index b9ecde8f..df14e3bd 100644 --- a/main.cpp +++ b/main.cpp @@ -36,7 +36,7 @@ QStandardItem *createItem(const Qt::GlobalColor color, const QString &text, QStandardItem *item = new QStandardItem; item->setText(text); item->setData(icon(color), Qt::DecorationRole); - item->setData(category, CategorizedViewRoles::CategoryRole); + item->setData(category, GroupViewRoles::GroupRole); item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); // progresser->addTrackedIndex(item); return item; @@ -46,7 +46,7 @@ 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, CategorizedViewRoles::CategoryRole); + item->setData(category, GroupViewRoles::GroupRole); item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); // progresser->addTrackedIndex(item); return item; diff --git a/main.h b/main.h index 6883e98e..2a358329 100644 --- a/main.h +++ b/main.h @@ -21,7 +21,7 @@ public: QStandardItem *addTrackedIndex(QStandardItem *item) { - item->setData(1000, CategorizedViewRoles::ProgressMaximumRole); + item->setData(1000, GroupViewRoles::ProgressMaximumRole); m_items.append(item); return item; } @@ -33,10 +33,10 @@ slots: QList toRemove; for (auto item : m_items) { - int maximum = item->data(CategorizedViewRoles::ProgressMaximumRole).toInt(); - int value = item->data(CategorizedViewRoles::ProgressValueRole).toInt(); + int maximum = item->data(GroupViewRoles::ProgressMaximumRole).toInt(); + int value = item->data(GroupViewRoles::ProgressValueRole).toInt(); int newvalue = std::min(value + 3, maximum); - item->setData(newvalue, CategorizedViewRoles::ProgressValueRole); + item->setData(newvalue, GroupViewRoles::ProgressValueRole); if(newvalue >= maximum) { -- cgit From a17caba2c9c2aa5960581db01e4b66472a9c019c Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Tue, 4 Feb 2014 00:43:18 +0100 Subject: Nuke. --- depends/groupview/CMakeLists.txt | 46 - .../include/categorizedsortfilterproxymodel.h | 175 -- depends/groupview/include/categorizedview.h | 332 ---- depends/groupview/include/categorydrawer.h | 179 --- depends/groupview/include/groupview_config.h | 28 - .../src/categorizedsortfilterproxymodel.cpp | 168 -- .../src/categorizedsortfilterproxymodel_p.h | 48 - depends/groupview/src/categorizedview.cpp | 1692 -------------------- depends/groupview/src/categorizedview_p.h | 160 -- depends/groupview/src/categorydrawer.cpp | 231 --- 10 files changed, 3059 deletions(-) delete mode 100644 depends/groupview/CMakeLists.txt delete mode 100644 depends/groupview/include/categorizedsortfilterproxymodel.h delete mode 100644 depends/groupview/include/categorizedview.h delete mode 100644 depends/groupview/include/categorydrawer.h delete mode 100644 depends/groupview/include/groupview_config.h delete mode 100644 depends/groupview/src/categorizedsortfilterproxymodel.cpp delete mode 100644 depends/groupview/src/categorizedsortfilterproxymodel_p.h delete mode 100644 depends/groupview/src/categorizedview.cpp delete mode 100644 depends/groupview/src/categorizedview_p.h delete mode 100644 depends/groupview/src/categorydrawer.cpp diff --git a/depends/groupview/CMakeLists.txt b/depends/groupview/CMakeLists.txt deleted file mode 100644 index 417b1d3a..00000000 --- a/depends/groupview/CMakeLists.txt +++ /dev/null @@ -1,46 +0,0 @@ -project(libGroupView) - -set(CMAKE_AUTOMOC ON) - -# Find Qt -find_package(Qt5Core REQUIRED) -find_package(Qt5Widgets REQUIRED) - -# Include Qt headers. -include_directories(${Qt5Base_INCLUDE_DIRS}) - -SET(LIBGROUPVIEW_HEADERS -include/groupview_config.h - -# Public headers -include/categorizedsortfilterproxymodel.h -include/categorizedview.h -include/categorydrawer.h - -# Private headers -src/categorizedsortfilterproxymodel_p.h -src/categorizedview_p.h -) - -SET(LIBGROUPVIEW_SOURCES -src/categorizedsortfilterproxymodel.cpp -src/categorizedview.cpp -src/categorydrawer.cpp -) - -# Set the include dir path. -SET(LIBGROUPVIEW_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include" PARENT_SCOPE) - -# Include self. -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) -include_directories(${CMAKE_BINARY_DIR}/include) - -# Static link! -ADD_DEFINITIONS(-DLIBGROUPVIEW_STATIC) - -add_definitions(-DLIBGROUPVIEW_LIBRARY) - -set(CMAKE_POSITION_INDEPENDENT_CODE ON) - -add_library(libGroupView STATIC ${LIBGROUPVIEW_SOURCES} ${LIBGROUPVIEW_HEADERS}) -qt5_use_modules(libGroupView Core Widgets) diff --git a/depends/groupview/include/categorizedsortfilterproxymodel.h b/depends/groupview/include/categorizedsortfilterproxymodel.h deleted file mode 100644 index d90fb254..00000000 --- a/depends/groupview/include/categorizedsortfilterproxymodel.h +++ /dev/null @@ -1,175 +0,0 @@ -/* - * This file is part of the KDE project - * Copyright (C) 2007 Rafael Fernández López - * Copyright (C) 2007 John Tapsell - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef KCATEGORIZEDSORTFILTERPROXYMODEL_H -#define KCATEGORIZEDSORTFILTERPROXYMODEL_H - -#include - -#include - -class QItemSelection; - - -/** - * This class lets you categorize a view. It is meant to be used along with - * KCategorizedView class. - * - * In general terms all you need to do is to reimplement subSortLessThan() and - * compareCategories() methods. In order to make categorization work, you need - * to also call setCategorizedModel() class to enable it, since the categorization - * is disabled by default. - * - * @see KCategorizedView - * - * @author Rafael Fernández López - */ -class LIBGROUPVIEW_EXPORT KCategorizedSortFilterProxyModel - : public QSortFilterProxyModel -{ -public: - enum AdditionalRoles - { - // Note: use printf "0x%08X\n" $(($RANDOM*$RANDOM)) - // to define additional roles. - CategoryDisplayRole = 0x17CE990A, ///< This role is used for asking the category to a given index - - CategorySortRole = 0x27857E60 ///< This role is used for sorting categories. You can return a - ///< string or a long long value. Strings will be sorted alphabetically - ///< while long long will be sorted by their value. Please note that this - ///< value won't be shown on the view, is only for sorting purposes. What will - ///< be shown as "Category" on the view will be asked with the role - ///< CategoryDisplayRole. - }; - - KCategorizedSortFilterProxyModel ( QObject *parent = 0 ); - virtual ~KCategorizedSortFilterProxyModel(); - - /** - * Overridden from QSortFilterProxyModel. Sorts the source model using - * @p column for the given @p order. - */ - virtual void sort ( int column, Qt::SortOrder order = Qt::AscendingOrder ); - - /** - * @return whether the model is categorized or not. Disabled by default. - */ - bool isCategorizedModel() const; - - /** - * Enables or disables the categorization feature. - * - * @param categorizedModel whether to enable or disable the categorization feature. - */ - void setCategorizedModel ( bool categorizedModel ); - - /** - * @return the column being used for sorting. - */ - int sortColumn() const; - - /** - * @return the sort order being used for sorting. - */ - Qt::SortOrder sortOrder() const; - - /** - * Set if the sorting using CategorySortRole will use a natural comparison - * in the case that strings were returned. If enabled, QString::localeAwareCompare - * will be used for sorting. - * - * @param sortCategoriesByNaturalComparison whether to sort using a natural comparison or not. - */ - void setSortCategoriesByNaturalComparison ( bool sortCategoriesByNaturalComparison ); - - /** - * @return whether it is being used a natural comparison for sorting. Enabled by default. - */ - bool sortCategoriesByNaturalComparison() const; - -protected: - /** - * Overridden from QSortFilterProxyModel. If you are subclassing - * KCategorizedSortFilterProxyModel, you will probably not need to reimplement this - * method. - * - * It calls compareCategories() to sort by category. If the both items are in the - * same category (i.e. compareCategories returns 0), then subSortLessThan is called. - * - * @return Returns true if the item @p left is less than the item @p right when sorting. - * - * @warning You usually won't need to reimplement this method when subclassing - * from KCategorizedSortFilterProxyModel. - */ - virtual bool lessThan ( const QModelIndex &left, const QModelIndex &right ) const; - - /** - * This method has a similar purpose as lessThan() has on QSortFilterProxyModel. - * It is used for sorting items that are in the same category. - * - * @return Returns true if the item @p left is less than the item @p right when sorting. - */ - virtual bool subSortLessThan ( const QModelIndex &left, const QModelIndex &right ) const; - - /** - * This method compares the category of the @p left index with the category - * of the @p right index. - * - * Internally and if not reimplemented, this method will ask for @p left and - * @p right models for role CategorySortRole. In order to correctly sort - * categories, the data() metod of the model should return a qlonglong (or numeric) value, or - * a QString object. QString objects will be sorted with QString::localeAwareCompare if - * sortCategoriesByNaturalComparison() is true. - * - * @note Please have present that: - * QString(QChar(QChar::ObjectReplacementCharacter)) > - * QString(QChar(QChar::ReplacementCharacter)) > - * [ all possible strings ] > - * QString(); - * - * This means that QString() will be sorted the first one, while - * QString(QChar(QChar::ObjectReplacementCharacter)) and - * QString(QChar(QChar::ReplacementCharacter)) will be sorted in last - * position. - * - * @warning Please note that data() method of the model should return always - * information of the same type. If you return a QString for an index, - * you should return always QStrings for all indexes for role CategorySortRole - * in order to correctly sort categories. You can't mix by returning - * a QString for one index, and a qlonglong for other. - * - * @note If you need a more complex layout, you will have to reimplement this - * method. - * - * @return A negative value if the category of @p left should be placed before the - * category of @p right. 0 if @p left and @p right are on the same category, and - * a positive value if the category of @p left should be placed after the - * category of @p right. - */ - virtual int compareCategories ( const QModelIndex &left, const QModelIndex &right ) const; - -private: - class Private; - Private *const d; -}; - - -#endif // KCATEGORIZEDSORTFILTERPROXYMODEL_H diff --git a/depends/groupview/include/categorizedview.h b/depends/groupview/include/categorizedview.h deleted file mode 100644 index 81b1dbb1..00000000 --- a/depends/groupview/include/categorizedview.h +++ /dev/null @@ -1,332 +0,0 @@ -/** - * This file is part of the KDE project - * Copyright (C) 2007, 2009 Rafael Fernández López - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef KCATEGORIZEDVIEW_H -#define KCATEGORIZEDVIEW_H - -#include - -#include - -class KCategoryDrawer; - -/** - * @short Item view for listing items in a categorized fashion optionally - * - * KCategorizedView basically has the same functionality as QListView, only that it also lets you - * layout items in a way that they are categorized visually. - * - * For it to work you will need to set a KCategorizedSortFilterProxyModel and a KCategoryDrawer - * with methods setModel() and setCategoryDrawer() respectively. Also, the model will need to be - * flagged as categorized with KCategorizedSortFilterProxyModel::setCategorizedModel(true). - * - * The way it works (if categorization enabled): - * - * - When sorting, it does more things than QListView does. It will ask the model for the - * special role CategorySortRole (@see KCategorizedSortFilterProxyModel). This can return - * a QString or an int in order to tell the view the order of categories. In this sense, for - * instance, if we are sorting by name ascending, "A" would be before than "B". If we are - * sorting by size ascending, 512 bytes would be before 1024 bytes. This way categories are - * also sorted. - * - * - When the view has to paint, it will ask the model with the role CategoryDisplayRole - * (@see KCategorizedSortFilterProxyModel). It will for instance return "F" for "foo.pdf" if - * we are sorting by name ascending, or "Small" if a certain item has 100 bytes, for example. - * - * For drawing categories, KCategoryDrawer will be used. You can inherit this class to do your own - * drawing. - * - * @note All examples cited before talk about filesystems and such, but have present that this - * is a completely generic class, and it can be used for whatever your purpose is. For - * instance when talking about animals, you can separate them by "Mammal" and "Oviparous". In - * this very case, for example, the CategorySortRole and the CategoryDisplayRole could be the - * same ("Mammal" and "Oviparous"). - * - * @note There is a really performance boost if CategorySortRole returns an int instead of a QString. - * Have present that this role is asked (n * log n) times when sorting and compared. Comparing - * ints is always faster than comparing strings, whithout mattering how fast the string - * comparison is. Consider thinking of a way of returning ints instead of QStrings if your - * model can contain a high number of items. - * - * @warning Note that for really drawing items in blocks you will need some things to be done: - * - The model set to this view has to be (or inherit if you want to do special stuff - * in it) KCategorizedSortFilterProxyModel. - * - This model needs to be set setCategorizedModel to true. - * - Set a category drawer by calling setCategoryDrawer. - * - * @see KCategorizedSortFilterProxyModel, KCategoryDrawer - * - * @author Rafael Fernández López - */ -class LIBGROUPVIEW_EXPORT KCategorizedView - : public QListView -{ - Q_OBJECT - Q_PROPERTY ( int categorySpacing READ categorySpacing WRITE setCategorySpacing ) - Q_PROPERTY ( bool alternatingBlockColors READ alternatingBlockColors WRITE setAlternatingBlockColors ) - Q_PROPERTY ( bool collapsibleBlocks READ collapsibleBlocks WRITE setCollapsibleBlocks ) - -public: - KCategorizedView ( QWidget *parent = 0 ); - - ~KCategorizedView(); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void setModel ( QAbstractItemModel *model ); - - /** - * Calls to setGridSizeOwn(). - */ - void setGridSize ( const QSize &size ); - - /** - * @warning note that setGridSize is not virtual in the base class (QListView), so if you are - * calling to this method, make sure you have a KCategorizedView pointer around. This - * means that something like: - * @code - * QListView *lv = new KCategorizedView(); - * lv->setGridSize(mySize); - * @endcode - * - * will not call to the expected setGridSize method. Instead do something like this: - * - * @code - * QListView *lv; - * ... - * KCategorizedView *cv = qobject_cast(lv); - * if (cv) { - * cv->setGridSizeOwn(mySize); - * } else { - * lv->setGridSize(mySize); - * } - * @endcode - * - * @note this method will call to QListView::setGridSize among other operations. - * - * @since 4.4 - */ - void setGridSizeOwn ( const QSize &size ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual QRect visualRect ( const QModelIndex &index ) const; - - /** - * Returns the current category drawer. - */ - KCategoryDrawer *categoryDrawer() const; - - /** - * The category drawer that will be used for drawing categories. - */ - void setCategoryDrawer ( KCategoryDrawer *categoryDrawer ); - - /** - * @return Category spacing. The spacing between categories. - * - * @since 4.4 - */ - int categorySpacing() const; - - /** - * Stablishes the category spacing. This is the spacing between categories. - * - * @since 4.4 - */ - void setCategorySpacing ( int categorySpacing ); - - /** - * @return Whether blocks should be drawn with alternating colors. - * - * @since 4.4 - */ - bool alternatingBlockColors() const; - - /** - * Sets whether blocks should be drawn with alternating colors. - * - * @since 4.4 - */ - void setAlternatingBlockColors ( bool enable ); - - /** - * @return Whether blocks can be collapsed or not. - * - * @since 4.4 - */ - bool collapsibleBlocks() const; - - /** - * Sets whether blocks can be collapsed or not. - * - * @since 4.4 - */ - void setCollapsibleBlocks ( bool enable ); - - /** - * @return Block of indexes that are into @p category. - * - * @since 4.5 - */ - QModelIndexList block ( const QString &category ); - - /** - * @return Block of indexes that are represented by @p representative. - * - * @since 4.5 - */ - QModelIndexList block ( const QModelIndex &representative ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual QModelIndex indexAt ( const QPoint &point ) const; - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void reset(); - - /** - * Signify that all item delegates size hints return the same fixed size - */ - void setUniformItemWidths(bool enable); - - /** - * Do all item delegate size hints return the same fixed size? - */ - bool uniformItemWidths() const; - -protected: - /** - * Reimplemented from QWidget. - */ - virtual void paintEvent ( QPaintEvent *event ); - - /** - * Reimplemented from QWidget. - */ - virtual void resizeEvent ( QResizeEvent *event ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void setSelection ( const QRect &rect, - QItemSelectionModel::SelectionFlags flags ); - - /** - * Reimplemented from QWidget. - */ - virtual void mouseMoveEvent ( QMouseEvent *event ); - - /** - * Reimplemented from QWidget. - */ - virtual void mousePressEvent ( QMouseEvent *event ); - - /** - * Reimplemented from QWidget. - */ - virtual void mouseReleaseEvent ( QMouseEvent *event ); - - /** - * Reimplemented from QWidget. - */ - virtual void leaveEvent ( QEvent *event ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void startDrag ( Qt::DropActions supportedActions ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void dragMoveEvent ( QDragMoveEvent *event ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void dragEnterEvent ( QDragEnterEvent *event ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void dragLeaveEvent ( QDragLeaveEvent *event ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void dropEvent ( QDropEvent *event ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual QModelIndex moveCursor ( CursorAction cursorAction, - Qt::KeyboardModifiers modifiers ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void rowsAboutToBeRemoved ( const QModelIndex &parent, - int start, - int end ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void updateGeometries(); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void currentChanged ( const QModelIndex ¤t, - const QModelIndex &previous ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void dataChanged ( const QModelIndex &topLeft, - const QModelIndex &bottomRight ); - - /** - * Reimplemented from QAbstractItemView. - */ - virtual void rowsInserted ( const QModelIndex &parent, - int start, - int end ); - -protected Q_SLOTS: - /** - * @internal - * Reposition items as needed. - */ - virtual void slotLayoutChanged(); - virtual void slotCollapseOrExpandClicked ( QModelIndex ); - -private: - class Private; - Private *const d; -}; - -#endif // KCATEGORIZEDVIEW_H diff --git a/depends/groupview/include/categorydrawer.h b/depends/groupview/include/categorydrawer.h deleted file mode 100644 index f37422ec..00000000 --- a/depends/groupview/include/categorydrawer.h +++ /dev/null @@ -1,179 +0,0 @@ -/** - * This file is part of the KDE project - * Copyright (C) 2007, 2009 Rafael Fernández López - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef KCATEGORYDRAWER_H -#define KCATEGORYDRAWER_H - -#include - -#include -#include - -class QPainter; -class QModelIndex; -class QStyleOption; -class KCategorizedView; - - -/** - * @since 4.5 - */ -class LIBGROUPVIEW_EXPORT KCategoryDrawer - : public QObject -{ - friend class KCategorizedView; - Q_OBJECT - - -public: - KCategoryDrawer ( KCategorizedView *view ); - virtual ~KCategoryDrawer(); - - /** - * @return The view this category drawer is associated with. - */ - KCategorizedView *view() const; - - /** - * This method purpose is to draw a category represented by the given - * @param index with the given @param sortRole sorting role - * - * @note This method will be called one time per category, always with the - * first element in that category - */ - virtual void drawCategory ( const QModelIndex &index, - int sortRole, - const QStyleOption &option, - QPainter *painter ) const; - - /** - * @return The category height for the category representated by index @p index with - * style options @p option. - */ - virtual int categoryHeight ( const QModelIndex &index, const QStyleOption &option ) const; - - //TODO KDE5: make virtual as leftMargin - /** - * @note 0 by default - * - * @since 4.4 - */ - int leftMargin() const; - - /** - * @note call to this method on the KCategoryDrawer constructor to set the left margin - * - * @since 4.4 - */ - void setLeftMargin ( int leftMargin ); - - //TODO KDE5: make virtual as rightMargin - /** - * @note 0 by default - * - * @since 4.4 - */ - int rightMargin() const; - - /** - * @note call to this method on the KCategoryDrawer constructor to set the right margin - * - * @since 4.4 - */ - void setRightMargin ( int rightMargin ); - - KCategoryDrawer &operator= ( const KCategoryDrawer &cd ); -protected: - /** - * Method called when the mouse button has been pressed. - * - * @param index The representative index of the block of items. - * @param blockRect The rect occupied by the block of items. - * @param event The mouse event. - * - * @warning You explicitly have to determine whether the event has been accepted or not. You - * have to call event->accept() or event->ignore() at all possible case branches in - * your code. - */ - virtual void mouseButtonPressed ( const QModelIndex &index, const QRect &blockRect, QMouseEvent *event ); - - /** - * Method called when the mouse button has been released. - * - * @param index The representative index of the block of items. - * @param blockRect The rect occupied by the block of items. - * @param event The mouse event. - * - * @warning You explicitly have to determine whether the event has been accepted or not. You - * have to call event->accept() or event->ignore() at all possible case branches in - * your code. - */ - virtual void mouseButtonReleased ( const QModelIndex &index, const QRect &blockRect, QMouseEvent *event ); - - /** - * Method called when the mouse has been moved. - * - * @param index The representative index of the block of items. - * @param blockRect The rect occupied by the block of items. - * @param event The mouse event. - */ - virtual void mouseMoved ( const QModelIndex &index, const QRect &blockRect, QMouseEvent *event ); - - /** - * Method called when the mouse button has been double clicked. - * - * @param index The representative index of the block of items. - * @param blockRect The rect occupied by the block of items. - * @param event The mouse event. - * - * @warning You explicitly have to determine whether the event has been accepted or not. You - * have to call event->accept() or event->ignore() at all possible case branches in - * your code. - */ - virtual void mouseButtonDoubleClicked ( const QModelIndex &index, const QRect &blockRect, QMouseEvent *event ); - - /** - * Method called when the mouse button has left this block. - * - * @param index The representative index of the block of items. - * @param blockRect The rect occupied by the block of items. - */ - virtual void mouseLeft ( const QModelIndex &index, const QRect &blockRect ); - -private: - class Private; - Private *const d; -Q_SIGNALS: - /** - * This signal becomes emitted when collapse or expand has been clicked. - */ - void collapseOrExpandClicked ( const QModelIndex &index ); - - /** - * Emit this signal on your subclass implementation to notify that something happened. Usually - * this will be triggered when you have received an event, and its position matched some "hot spot". - * - * You give this action the integer you want, and having connected this signal to your code, - * the connected slot can perform the needed changes (view, model, selection model, delegate...) - */ - void actionRequested ( int action, const QModelIndex &index ); -}; - -#endif // KCATEGORYDRAWER_H diff --git a/depends/groupview/include/groupview_config.h b/depends/groupview/include/groupview_config.h deleted file mode 100644 index c63acbde..00000000 --- a/depends/groupview/include/groupview_config.h +++ /dev/null @@ -1,28 +0,0 @@ -/* 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 - -#ifdef LIBGROUPVIEW_STATIC - #define LIBGROUPVIEW_EXPORT -#else - #ifdef LIBGROUPVIEW_LIBRARY - #define LIBGROUPVIEW_EXPORT Q_DECL_EXPORT - #else - #define LIBGROUPVIEW_EXPORT Q_DECL_IMPORT - #endif -#endif diff --git a/depends/groupview/src/categorizedsortfilterproxymodel.cpp b/depends/groupview/src/categorizedsortfilterproxymodel.cpp deleted file mode 100644 index 09da9dd3..00000000 --- a/depends/groupview/src/categorizedsortfilterproxymodel.cpp +++ /dev/null @@ -1,168 +0,0 @@ -/** - * This file is part of the KDE project - * Copyright (C) 2007 Rafael Fernández López - * Copyright (C) 2007 John Tapsell - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include "categorizedsortfilterproxymodel.h" -#include "categorizedsortfilterproxymodel_p.h" - -#include - -#include -#include -#include - -KCategorizedSortFilterProxyModel::KCategorizedSortFilterProxyModel ( QObject *parent ) - : QSortFilterProxyModel ( parent ) - , d ( new Private() ) -{ -} - -KCategorizedSortFilterProxyModel::~KCategorizedSortFilterProxyModel() -{ - delete d; -} - -void KCategorizedSortFilterProxyModel::sort ( int column, Qt::SortOrder order ) -{ - d->sortColumn = column; - d->sortOrder = order; - - QSortFilterProxyModel::sort ( column, order ); -} - -bool KCategorizedSortFilterProxyModel::isCategorizedModel() const -{ - return d->categorizedModel; -} - -void KCategorizedSortFilterProxyModel::setCategorizedModel ( bool categorizedModel ) -{ - if ( categorizedModel == d->categorizedModel ) - { - return; - } - - d->categorizedModel = categorizedModel; - - invalidate(); -} - -int KCategorizedSortFilterProxyModel::sortColumn() const -{ - return d->sortColumn; -} - -Qt::SortOrder KCategorizedSortFilterProxyModel::sortOrder() const -{ - return d->sortOrder; -} - -void KCategorizedSortFilterProxyModel::setSortCategoriesByNaturalComparison ( bool sortCategoriesByNaturalComparison ) -{ - if ( sortCategoriesByNaturalComparison == d->sortCategoriesByNaturalComparison ) - { - return; - } - - d->sortCategoriesByNaturalComparison = sortCategoriesByNaturalComparison; - - invalidate(); -} - -bool KCategorizedSortFilterProxyModel::sortCategoriesByNaturalComparison() const -{ - return d->sortCategoriesByNaturalComparison; -} - -bool KCategorizedSortFilterProxyModel::lessThan ( const QModelIndex &left, const QModelIndex &right ) const -{ - if ( d->categorizedModel ) - { - int compare = compareCategories ( left, right ); - - if ( compare > 0 ) // left is greater than right - { - return false; - } - else if ( compare < 0 ) // left is less than right - { - return true; - } - } - - return subSortLessThan ( left, right ); -} - -bool KCategorizedSortFilterProxyModel::subSortLessThan ( const QModelIndex &left, const QModelIndex &right ) const -{ - return QSortFilterProxyModel::lessThan ( left, right ); -} - -int KCategorizedSortFilterProxyModel::compareCategories ( const QModelIndex &left, const QModelIndex &right ) const -{ - QVariant l = ( left.model() ? left.model()->data ( left, CategorySortRole ) : QVariant() ); - QVariant r = ( right.model() ? right.model()->data ( right, CategorySortRole ) : QVariant() ); - - Q_ASSERT ( l.isValid() ); - Q_ASSERT ( r.isValid() ); - Q_ASSERT ( l.type() == r.type() ); - - if ( l.type() == QVariant::String ) - { - QString lstr = l.toString(); - QString rstr = r.toString(); - - /* - if ( d->sortCategoriesByNaturalComparison ) - { - return KStringHandler::naturalCompare ( lstr, rstr ); - } - else - { - */ - if ( lstr < rstr ) - { - return -1; - } - - if ( lstr > rstr ) - { - return 1; - } - - return 0; - //} - } - - qlonglong lint = l.toLongLong(); - qlonglong rint = r.toLongLong(); - - if ( lint < rint ) - { - return -1; - } - - if ( lint > rint ) - { - return 1; - } - - return 0; -} diff --git a/depends/groupview/src/categorizedsortfilterproxymodel_p.h b/depends/groupview/src/categorizedsortfilterproxymodel_p.h deleted file mode 100644 index d7e7c9a0..00000000 --- a/depends/groupview/src/categorizedsortfilterproxymodel_p.h +++ /dev/null @@ -1,48 +0,0 @@ -/** - * This file is part of the KDE project - * Copyright (C) 2007 Rafael Fernández López - * Copyright (C) 2007 John Tapsell - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef KCATEGORIZEDSORTFILTERPROXYMODEL_P_H -#define KCATEGORIZEDSORTFILTERPROXYMODEL_P_H - -class KCategorizedSortFilterProxyModel; - -class KCategorizedSortFilterProxyModel::Private -{ -public: - Private() - : sortColumn ( 0 ) - , sortOrder ( Qt::AscendingOrder ) - , categorizedModel ( false ) - , sortCategoriesByNaturalComparison ( true ) - { - } - - ~Private() - { - } - - int sortColumn; - Qt::SortOrder sortOrder; - bool categorizedModel; - bool sortCategoriesByNaturalComparison; -}; - -#endif diff --git a/depends/groupview/src/categorizedview.cpp b/depends/groupview/src/categorizedview.cpp deleted file mode 100644 index 1345205c..00000000 --- a/depends/groupview/src/categorizedview.cpp +++ /dev/null @@ -1,1692 +0,0 @@ -/** - * This file is part of the KDE project - * Copyright (C) 2007, 2009 Rafael Fernández López - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -/** - * IMPLEMENTATION NOTES: - * - * QListView::setRowHidden() and QListView::isRowHidden() are not taken into - * account. This methods should actually not exist. This effect should be handled - * by an hypothetical QSortFilterProxyModel which filters out the desired rows. - * - * In case this needs to be implemented, contact me, but I consider this a faulty - * design. - */ - -#include "categorizedview.h" -#include "categorizedview_p.h" - -#include // trunc on C99 compliant systems -//#include // trunc for not C99 compliant systems - -#include -#include -#include - -#include "categorydrawer.h" -#include "categorizedsortfilterproxymodel.h" - -//BEGIN: Private part - -struct KCategorizedView::Private::Item -{ - Item() - : topLeft ( QPoint() ) - , size ( QSize() ) - { - } - - QPoint topLeft; - QSize size; -}; - -struct KCategorizedView::Private::Block -{ - bool operator!= ( const Block &rhs ) const - { - return firstIndex != rhs.firstIndex; - } - - static bool lessThan ( const Block &left, const Block &right ) - { - Q_ASSERT ( left.firstIndex.isValid() ); - Q_ASSERT ( right.firstIndex.isValid() ); - return left.firstIndex.row() < right.firstIndex.row(); - } - - QPoint topLeft; - int height = -1; - QPersistentModelIndex firstIndex; - // if we have n elements on this block, and we inserted an element at position i. The quarantine - // will start at index (i, column, parent). This means that for all elements j where i <= j <= n, the - // visual rect position of item j will have to be recomputed (cannot use the cached point). The quarantine - // will only affect the current block, since the rest of blocks can be affected only in the way - // that the whole block will have different offset, but items will keep the same relative position - // in terms of their parent blocks. - QPersistentModelIndex quarantineStart; - QList items; - - // this affects the whole block, not items separately. items contain the topLeft point relative - // to the block. Because of insertions or removals a whole block can be moved, so the whole block - // will enter in quarantine, what is faster than moving all items in absolute terms. - bool outOfQuarantine = false; - - // should we alternate its color ? is just a hint, could not be used - bool alternate = false; - bool collapsed = false; -}; - -KCategorizedView::Private::Private ( KCategorizedView *q ) - : q ( q ) - , hoveredBlock ( new Block() ) -{ -} - -KCategorizedView::Private::~Private() -{ - delete hoveredBlock; -} - -bool KCategorizedView::Private::isCategorized() const -{ - return proxyModel && categoryDrawer && proxyModel->isCategorizedModel(); -} - -QStyleOptionViewItemV4 KCategorizedView::Private::blockRect ( const QModelIndex &representative ) -{ - QStyleOptionViewItemV4 option ( q->viewOptions() ); - const int height = categoryDrawer->categoryHeight ( representative, option ); - const QString categoryDisplay = representative.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString(); - QPoint pos = blockPosition ( categoryDisplay ); - pos.ry() -= height; - option.rect.setTopLeft ( pos ); - option.rect.setWidth ( viewportWidth() + categoryDrawer->leftMargin() + categoryDrawer->rightMargin() ); - option.rect.setHeight ( height + blockHeight ( categoryDisplay ) ); - option.rect = mapToViewport ( option.rect ); - - return option; -} - -QPair KCategorizedView::Private::intersectingIndexesWithRect ( const QRect &_rect ) const -{ - const int rowCount = proxyModel->rowCount(); - - const QRect rect = _rect.normalized(); - - // binary search to find out the top border - int bottom = 0; - int top = rowCount - 1; - while ( bottom <= top ) - { - const int middle = ( bottom + top ) / 2; - const QModelIndex index = proxyModel->index ( middle, q->modelColumn(), q->rootIndex() ); - QRect itemRect = q->visualRect ( index ); - const int verticalOff = q->verticalOffset(); - const int horizontalOff = q->horizontalOffset(); - itemRect.topLeft().ry() += verticalOff; - itemRect.topLeft().rx() += horizontalOff; - itemRect.bottomRight().ry() += verticalOff; - itemRect.bottomRight().rx() += horizontalOff; - if ( itemRect.bottomRight().y() <= rect.topLeft().y() ) - { - bottom = middle + 1; - } - else - { - top = middle - 1; - } - } - - const QModelIndex bottomIndex = proxyModel->index ( bottom, q->modelColumn(), q->rootIndex() ); - - // binary search to find out the bottom border - bottom = 0; - top = rowCount - 1; - while ( bottom <= top ) - { - const int middle = ( bottom + top ) / 2; - const QModelIndex index = proxyModel->index ( middle, q->modelColumn(), q->rootIndex() ); - QRect itemRect = q->visualRect ( index ); - const int verticalOff = q->verticalOffset(); - const int horizontalOff = q->horizontalOffset(); - itemRect.topLeft().ry() += verticalOff; - itemRect.topLeft().rx() += horizontalOff; - itemRect.bottomRight().ry() += verticalOff; - itemRect.bottomRight().rx() += horizontalOff; - if ( itemRect.topLeft().y() <= rect.bottomRight().y() ) - { - bottom = middle + 1; - } - else - { - top = middle - 1; - } - } - - const QModelIndex topIndex = proxyModel->index ( top, q->modelColumn(), q->rootIndex() ); - - return qMakePair ( bottomIndex, topIndex ); -} - -QPoint KCategorizedView::Private::blockPosition ( const QString &category ) -{ - Block &block = blocks[category]; - - if ( block.outOfQuarantine && !block.topLeft.isNull() ) - { - return block.topLeft; - } - - QPoint res ( categorySpacing, 0 ); - - const QModelIndex index = block.firstIndex; - - for ( QHash::Iterator it = blocks.begin(); it != blocks.end(); ++it ) - { - Block &block = *it; - const QModelIndex categoryIndex = block.firstIndex; - if ( index.row() < categoryIndex.row() ) - { - continue; - } - res.ry() += categoryDrawer->categoryHeight ( categoryIndex, q->viewOptions() ) + categorySpacing; - if ( index.row() == categoryIndex.row() ) - { - continue; - } - res.ry() += blockHeight ( it.key() ); - } - - block.outOfQuarantine = true; - block.topLeft = res; - - return res; -} - -int KCategorizedView::Private::blockHeight ( const QString &category ) -{ - Block &block = blocks[category]; - - if ( block.collapsed ) - { - return 0; - } - - if ( block.height > -1 ) - { - return block.height; - } - - const QModelIndex firstIndex = block.firstIndex; - const QModelIndex lastIndex = proxyModel->index ( firstIndex.row() + block.items.count() - 1, q->modelColumn(), q->rootIndex() ); - const QRect topLeft = q->visualRect ( firstIndex ); - QRect bottomRight = q->visualRect ( lastIndex ); - - if ( hasGrid() ) - { - bottomRight.setHeight ( qMax ( bottomRight.height(), q->gridSize().height() ) ); - } - else - { - if ( !q->uniformItemSizes() ) - { - bottomRight.setHeight ( highestElementInLastRow ( block ) + q->spacing() * 2 ); - } - } - - const int height = bottomRight.bottomRight().y() - topLeft.topLeft().y() + 1; - block.height = height; - - return height; -} - -int KCategorizedView::Private::viewportWidth() const -{ - return q->viewport()->width() - categorySpacing * 2 - categoryDrawer->leftMargin() - categoryDrawer->rightMargin(); -} - -void KCategorizedView::Private::regenerateAllElements() -{ - for ( QHash::Iterator it = blocks.begin(); it != blocks.end(); ++it ) - { - Block &block = *it; - block.outOfQuarantine = false; - block.quarantineStart = block.firstIndex; - block.height = -1; - } -} - -void KCategorizedView::Private::rowsInserted ( const QModelIndex &parent, int start, int end ) -{ - if ( !isCategorized() ) - { - return; - } - - for ( int i = start; i <= end; ++i ) - { - const QModelIndex index = proxyModel->index ( i, q->modelColumn(), parent ); - - Q_ASSERT ( index.isValid() ); - - const QString category = categoryForIndex ( index ); - - Block &block = blocks[category]; - - //BEGIN: update firstIndex - // save as firstIndex in block if - // - it forced the category creation (first element on this category) - // - it is before the first row on that category - const QModelIndex firstIndex = block.firstIndex; - if ( !firstIndex.isValid() || index.row() < firstIndex.row() ) - { - block.firstIndex = index; - } - //END: update firstIndex - - Q_ASSERT ( block.firstIndex.isValid() ); - - const int firstIndexRow = block.firstIndex.row(); - - block.items.insert ( index.row() - firstIndexRow, Private::Item() ); - block.height = -1; - - q->visualRect ( index ); - q->viewport()->update(); - } - - //BEGIN: update the items that are in quarantine in affected categories - { - const QModelIndex lastIndex = proxyModel->index ( end, q->modelColumn(), parent ); - const QString category = categoryForIndex ( lastIndex ); - Private::Block &block = blocks[category]; - block.quarantineStart = block.firstIndex; - } - //END: update the items that are in quarantine in affected categories - - //BEGIN: mark as in quarantine those categories that are under the affected ones - { - const QModelIndex firstIndex = proxyModel->index ( start, q->modelColumn(), parent ); - const QString category = categoryForIndex ( firstIndex ); - const QModelIndex firstAffectedCategory = blocks[category].firstIndex; - //BEGIN: order for marking as alternate those blocks that are alternate - QList blockList = blocks.values(); - qSort ( blockList.begin(), blockList.end(), Block::lessThan ); - QList firstIndexesRows; - foreach ( const Block &block, blockList ) - { - firstIndexesRows << block.firstIndex.row(); - } - //END: order for marking as alternate those blocks that are alternate - for ( QHash::Iterator it = blocks.begin(); it != blocks.end(); ++it ) - { - Private::Block &block = *it; - if ( block.firstIndex.row() > firstAffectedCategory.row() ) - { - block.outOfQuarantine = false; - block.alternate = firstIndexesRows.indexOf ( block.firstIndex.row() ) % 2; - } - else if ( block.firstIndex.row() == firstAffectedCategory.row() ) - { - block.alternate = firstIndexesRows.indexOf ( block.firstIndex.row() ) % 2; - } - } - } - //END: mark as in quarantine those categories that are under the affected ones -} - -QRect KCategorizedView::Private::mapToViewport ( const QRect &rect ) const -{ - const int dx = -q->horizontalOffset(); - const int dy = -q->verticalOffset(); - return rect.adjusted ( dx, dy, dx, dy ); -} - -QRect KCategorizedView::Private::mapFromViewport ( const QRect &rect ) const -{ - const int dx = q->horizontalOffset(); - const int dy = q->verticalOffset(); - return rect.adjusted ( dx, dy, dx, dy ); -} - -int KCategorizedView::Private::highestElementInLastRow ( const Block &block ) const -{ - //Find the highest element in the last row - const QModelIndex lastIndex = proxyModel->index ( block.firstIndex.row() + block.items.count() - 1, q->modelColumn(), q->rootIndex() ); - const QRect prevRect = q->visualRect ( lastIndex ); - int res = prevRect.height(); - QModelIndex prevIndex = proxyModel->index ( lastIndex.row() - 1, q->modelColumn(), q->rootIndex() ); - if ( !prevIndex.isValid() ) - { - return res; - } - Q_FOREVER - { - const QRect tempRect = q->visualRect ( prevIndex ); - if ( tempRect.topLeft().y() < prevRect.topLeft().y() ) - { - break; - } - res = qMax ( res, tempRect.height() ); - if ( prevIndex == block.firstIndex ) - { - break; - } - prevIndex = proxyModel->index ( prevIndex.row() - 1, q->modelColumn(), q->rootIndex() ); - } - - return res; -} - -bool KCategorizedView::Private::hasGrid() const -{ - const QSize gridSize = q->gridSize(); - return gridSize.isValid() && !gridSize.isNull(); -} - -QString KCategorizedView::Private::categoryForIndex ( const QModelIndex &index ) const -{ - const QModelIndex categoryIndex = index.model()->index ( index.row(), proxyModel->sortColumn(), index.parent() ); - return categoryIndex.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString(); -} - -void KCategorizedView::Private::leftToRightVisualRect ( const QModelIndex &index, Item &item, - const Block &block, const QPoint &blockPos ) const -{ - const int firstIndexRow = block.firstIndex.row(); - - if ( hasGrid() ) - { - const int relativeRow = index.row() - firstIndexRow; - const int maxItemsPerRow = qMax ( viewportWidth() / q->gridSize().width(), 1 ); - if ( q->layoutDirection() == Qt::LeftToRight ) - { - item.topLeft.rx() = ( relativeRow % maxItemsPerRow ) * q->gridSize().width() + blockPos.x() + categoryDrawer->leftMargin(); - } - else - { - item.topLeft.rx() = viewportWidth() - ( ( relativeRow % maxItemsPerRow ) + 1 ) * q->gridSize().width() + categoryDrawer->leftMargin() + categorySpacing; - } - item.topLeft.ry() = ( relativeRow / maxItemsPerRow ) * q->gridSize().height(); - } - else - { - if ( q->uniformItemSizes() /*|| q->uniformItemWidths()*/ ) - { - const int relativeRow = index.row() - firstIndexRow; - const QSize itemSize = q->sizeHintForIndex ( index ); - //HACK: Why is the -2 needed? - const int maxItemsPerRow = qMax ( ( viewportWidth() - q->spacing() - 2 ) / ( itemSize.width() + q->spacing() ), 1 ); - if ( q->layoutDirection() == Qt::LeftToRight ) - { - item.topLeft.rx() = ( relativeRow % maxItemsPerRow ) * itemSize.width() + blockPos.x() + categoryDrawer->leftMargin(); - } - else - { - item.topLeft.rx() = viewportWidth() - ( relativeRow % maxItemsPerRow ) * itemSize.width() + categoryDrawer->leftMargin() + categorySpacing; - } - item.topLeft.ry() = ( relativeRow / maxItemsPerRow ) * itemSize.height(); - } - else - { - const QSize currSize = q->sizeHintForIndex ( index ); - if ( index != block.firstIndex ) - { - const int viewportW = viewportWidth() - q->spacing(); - QModelIndex prevIndex = proxyModel->index ( index.row() - 1, q->modelColumn(), q->rootIndex() ); - QRect prevRect = q->visualRect ( prevIndex ); - prevRect = mapFromViewport ( prevRect ); - if ( ( prevRect.bottomRight().x() + 1 ) + currSize.width() - blockPos.x() + q->spacing() > viewportW ) - { - // we have to check the whole previous row, and see which one was the - // highest. - Q_FOREVER - { - prevIndex = proxyModel->index ( prevIndex.row() - 1, q->modelColumn(), q->rootIndex() ); - QRect tempRect = q->visualRect ( prevIndex ); - tempRect = mapFromViewport ( tempRect ); - if ( tempRect.topLeft().y() < prevRect.topLeft().y() ) - { - break; - } - if ( tempRect.bottomRight().y() > prevRect.bottomRight().y() ) - { - prevRect = tempRect; - } - if ( prevIndex == block.firstIndex ) - { - break; - } - } - if ( q->layoutDirection() == Qt::LeftToRight ) - { - item.topLeft.rx() = categoryDrawer->leftMargin() + blockPos.x() + q->spacing(); - } - else - { - item.topLeft.rx() = viewportWidth() - currSize.width() + categoryDrawer->leftMargin() + categorySpacing; - } - item.topLeft.ry() = ( prevRect.bottomRight().y() + 1 ) + q->spacing() - blockPos.y(); - } - else - { - if ( q->layoutDirection() == Qt::LeftToRight ) - { - item.topLeft.rx() = ( prevRect.bottomRight().x() + 1 ) + q->spacing(); - } - else - { - item.topLeft.rx() = ( prevRect.bottomLeft().x() - 1 ) - q->spacing() - item.size.width() + categoryDrawer->leftMargin() + categorySpacing; - } - item.topLeft.ry() = prevRect.topLeft().y() - blockPos.y(); - } - } - else - { - if ( q->layoutDirection() == Qt::LeftToRight ) - { - item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing(); - } - else - { - item.topLeft.rx() = viewportWidth() - currSize.width() + categoryDrawer->leftMargin() + categorySpacing; - } - item.topLeft.ry() = q->spacing(); - } - } - } - item.size = q->sizeHintForIndex ( index ); -} - -void KCategorizedView::Private::topToBottomVisualRect ( const QModelIndex &index, Item &item, - const Block &block, const QPoint &blockPos ) const -{ - const int firstIndexRow = block.firstIndex.row(); - - if ( hasGrid() ) - { - const int relativeRow = index.row() - firstIndexRow; - item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin(); - item.topLeft.ry() = relativeRow * q->gridSize().height(); - } - else - { - if ( q->uniformItemSizes() ) - { - const int relativeRow = index.row() - firstIndexRow; - const QSize itemSize = q->sizeHintForIndex ( index ); - item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin(); - item.topLeft.ry() = relativeRow * itemSize.height(); - } - else - { - if ( index != block.firstIndex ) - { - QModelIndex prevIndex = proxyModel->index ( index.row() - 1, q->modelColumn(), q->rootIndex() ); - QRect prevRect = q->visualRect ( prevIndex ); - prevRect = mapFromViewport ( prevRect ); - item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing(); - item.topLeft.ry() = ( prevRect.bottomRight().y() + 1 ) + q->spacing() - blockPos.y(); - } - else - { - item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing(); - item.topLeft.ry() = q->spacing(); - } - } - } - item.size = q->sizeHintForIndex ( index ); - item.size.setWidth ( viewportWidth() ); -} - -void KCategorizedView::Private::_k_slotCollapseOrExpandClicked ( QModelIndex ) -{ -} - -//END: Private part - -//BEGIN: Public part - -KCategorizedView::KCategorizedView ( QWidget *parent ) - : QListView ( parent ) - , d ( new Private ( this ) ) -{ -} - -KCategorizedView::~KCategorizedView() -{ - delete d; -} - -void KCategorizedView::setModel ( QAbstractItemModel *model ) -{ - if ( d->proxyModel == model ) - { - return; - } - - d->blocks.clear(); - - if ( d->proxyModel ) - { - disconnect ( d->proxyModel, SIGNAL ( layoutChanged() ), this, SLOT ( slotLayoutChanged() ) ); - } - - d->proxyModel = dynamic_cast ( model ); - - if ( d->proxyModel ) - { - connect ( d->proxyModel, SIGNAL ( layoutChanged() ), this, SLOT ( slotLayoutChanged() ) ); - } - - QListView::setModel ( model ); - - // if the model already had information inserted, update our data structures to it - if ( model->rowCount() ) - { - slotLayoutChanged(); - } -} - - -void KCategorizedView::setUniformItemWidths(bool enable) -{ - d->constantItemWidth = enable; -} - - -bool KCategorizedView::uniformItemWidths() const -{ - return d->constantItemWidth; -} - -void KCategorizedView::setGridSize ( const QSize &size ) -{ - setGridSizeOwn ( size ); -} - -void KCategorizedView::setGridSizeOwn ( const QSize &size ) -{ - d->regenerateAllElements(); - QListView::setGridSize ( size ); -} - -QRect KCategorizedView::visualRect ( const QModelIndex &index ) const -{ - if ( !d->isCategorized() ) - { - return QListView::visualRect ( index ); - } - - if ( !index.isValid() ) - { - return QRect(); - } - - const QString category = d->categoryForIndex ( index ); - - if ( !d->blocks.contains ( category ) ) - { - return QRect(); - } - - Private::Block &block = d->blocks[category]; - const int firstIndexRow = block.firstIndex.row(); - - Q_ASSERT ( block.firstIndex.isValid() ); - - if ( index.row() - firstIndexRow < 0 || index.row() - firstIndexRow >= block.items.count() ) - { - return QRect(); - } - - const QPoint blockPos = d->blockPosition ( category ); - - Private::Item &ritem = block.items[index.row() - firstIndexRow]; - - if ( ritem.topLeft.isNull() || ( block.quarantineStart.isValid() && - index.row() >= block.quarantineStart.row() ) ) - { - if ( flow() == LeftToRight ) - { - d->leftToRightVisualRect ( index, ritem, block, blockPos ); - } - else - { - d->topToBottomVisualRect ( index, ritem, block, blockPos ); - } - - //BEGIN: update the quarantine start - const bool wasLastIndex = ( index.row() == ( block.firstIndex.row() + block.items.count() - 1 ) ); - if ( index.row() == block.quarantineStart.row() ) - { - if ( wasLastIndex ) - { - block.quarantineStart = QModelIndex(); - } - else - { - const QModelIndex nextIndex = d->proxyModel->index ( index.row() + 1, modelColumn(), rootIndex() ); - block.quarantineStart = nextIndex; - } - } - //END: update the quarantine start - } - - // we get now the absolute position through the relative position of the parent block. do not - // save this on ritem, since this would override the item relative position in block terms. - Private::Item item ( ritem ); - item.topLeft.ry() += blockPos.y(); - - const QSize sizeHint = item.size; - - if ( d->hasGrid() ) - { - const QSize sizeGrid = gridSize(); - const QSize resultingSize = sizeHint.boundedTo ( sizeGrid ); - QRect res ( item.topLeft.x() + ( ( sizeGrid.width() - resultingSize.width() ) / 2 ), - item.topLeft.y(), resultingSize.width(), resultingSize.height() ); - if ( block.collapsed ) - { - // we can still do binary search, while we "hide" items. We move those items in collapsed - // blocks to the left and set a 0 height. - res.setLeft ( -resultingSize.width() ); - res.setHeight ( 0 ); - } - return d->mapToViewport ( res ); - } - - QRect res ( item.topLeft.x(), item.topLeft.y(), sizeHint.width(), sizeHint.height() ); - if ( block.collapsed ) - { - // we can still do binary search, while we "hide" items. We move those items in collapsed - // blocks to the left and set a 0 height. - res.setLeft ( -sizeHint.width() ); - res.setHeight ( 0 ); - } - return d->mapToViewport ( res ); -} - -KCategoryDrawer *KCategorizedView::categoryDrawer() const -{ - return d->categoryDrawer; -} - -void KCategorizedView::setCategoryDrawer ( KCategoryDrawer *categoryDrawer ) -{ - disconnect ( d->categoryDrawer, SIGNAL ( collapseOrExpandClicked ( QModelIndex ) ), - this, SLOT ( slotCollapseOrExpandClicked ( QModelIndex ) ) ); - d->categoryDrawer = categoryDrawer; - - connect ( d->categoryDrawer, SIGNAL ( collapseOrExpandClicked ( QModelIndex ) ), - this, SLOT ( slotCollapseOrExpandClicked ( QModelIndex ) ) ); -} - -int KCategorizedView::categorySpacing() const -{ - return d->categorySpacing; -} - -void KCategorizedView::setCategorySpacing ( int categorySpacing ) -{ - if ( d->categorySpacing == categorySpacing ) - { - return; - } - - d->categorySpacing = categorySpacing; - - for ( QHash::Iterator it = d->blocks.begin(); it != d->blocks.end(); ++it ) - { - Private::Block &block = *it; - block.outOfQuarantine = false; - } -} - -bool KCategorizedView::alternatingBlockColors() const -{ - return d->alternatingBlockColors; -} - -void KCategorizedView::setAlternatingBlockColors ( bool enable ) -{ - d->alternatingBlockColors = enable; -} - -bool KCategorizedView::collapsibleBlocks() const -{ - return d->collapsibleBlocks; -} - -void KCategorizedView::setCollapsibleBlocks ( bool enable ) -{ - d->collapsibleBlocks = enable; -} - -QModelIndexList KCategorizedView::block ( const QString &category ) -{ - QModelIndexList res; - const Private::Block &block = d->blocks[category]; - if ( block.height == -1 ) - { - return res; - } - QModelIndex current = block.firstIndex; - const int first = current.row(); - for ( int i = 1; i <= block.items.count(); ++i ) - { - if ( current.isValid() ) - { - res << current; - } - current = d->proxyModel->index ( first + i, modelColumn(), rootIndex() ); - } - return res; -} - -QModelIndexList KCategorizedView::block ( const QModelIndex &representative ) -{ - return block ( representative.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString() ); -} - -QModelIndex KCategorizedView::indexAt ( const QPoint &point ) const -{ - if ( !d->isCategorized() ) - { - return QListView::indexAt ( point ); - } - - const int rowCount = d->proxyModel->rowCount(); - if ( !rowCount ) - { - return QModelIndex(); - } - - // Binary search that will try to spot if there is an index under point - int bottom = 0; - int top = rowCount - 1; - while ( bottom <= top ) - { - const int middle = ( bottom + top ) / 2; - const QModelIndex index = d->proxyModel->index ( middle, modelColumn(), rootIndex() ); - QRect rect = visualRect ( index ); - const int verticalOff = verticalOffset(); - int horizontalOff = horizontalOffset(); - if ( layoutDirection() == Qt::RightToLeft ) - { - horizontalOff *= -1; - } - rect.topLeft().ry() += verticalOff; - rect.topLeft().rx() += horizontalOff; - rect.bottomRight().ry() += verticalOff; - rect.bottomRight().rx() += horizontalOff; - if ( rect.contains ( point ) ) - { - if ( index.model()->flags ( index ) & Qt::ItemIsEnabled ) - { - return index; - } - return QModelIndex(); - } - bool directionCondition; - if ( layoutDirection() == Qt::LeftToRight ) - { - directionCondition = point.x() > rect.bottomRight().x(); - } - else - { - directionCondition = point.x() < rect.bottomLeft().x(); - } - if ( point.y() > rect.bottomRight().y() || - ( point.y() > rect.topLeft().y() && point.y() < rect.bottomRight().y() && directionCondition ) ) - { - bottom = middle + 1; - } - else - { - top = middle - 1; - } - } - return QModelIndex(); -} - -void KCategorizedView::reset() -{ - d->blocks.clear(); - QListView::reset(); -} - -void KCategorizedView::paintEvent ( QPaintEvent *event ) -{ - if ( !d->isCategorized() ) - { - QListView::paintEvent ( event ); - return; - } - - const QPair intersecting = d->intersectingIndexesWithRect ( viewport()->rect().intersected ( event->rect() ) ); - - QPainter p ( viewport() ); - p.save(); - - Q_ASSERT ( selectionModel()->model() == d->proxyModel ); - - //BEGIN: draw categories - QHash::ConstIterator it ( d->blocks.constBegin() ); - while ( it != d->blocks.constEnd() ) - { - const Private::Block &block = *it; - const QModelIndex categoryIndex = d->proxyModel->index ( block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); - QStyleOptionViewItemV4 option ( viewOptions() ); - option.features |= d->alternatingBlockColors && block.alternate ? QStyleOptionViewItemV4::Alternate - : QStyleOptionViewItemV4::None; - option.state |= !d->collapsibleBlocks || !block.collapsed ? QStyle::State_Open - : QStyle::State_None; - const int height = d->categoryDrawer->categoryHeight ( categoryIndex, option ); - QPoint pos = d->blockPosition ( it.key() ); - pos.ry() -= height; - option.rect.setTopLeft ( pos ); - option.rect.setWidth ( d->viewportWidth() + d->categoryDrawer->leftMargin() + d->categoryDrawer->rightMargin() ); - option.rect.setHeight ( height + d->blockHeight ( it.key() ) ); - option.rect = d->mapToViewport ( option.rect ); - if ( !option.rect.intersects ( viewport()->rect() ) ) - { - ++it; - continue; - } - d->categoryDrawer->drawCategory ( categoryIndex, d->proxyModel->sortRole(), option, &p ); - ++it; - } - //END: draw categories - - if ( intersecting.first.isValid() && intersecting.second.isValid() ) - { - //BEGIN: draw items - int i = intersecting.first.row(); - int indexToCheckIfBlockCollapsed = i; - QModelIndex categoryIndex; - QString category; - Private::Block *block = 0; - while ( i <= intersecting.second.row() ) - { - //BEGIN: first check if the block is collapsed. if so, we have to skip the item painting - if ( i == indexToCheckIfBlockCollapsed ) - { - categoryIndex = d->proxyModel->index ( i, d->proxyModel->sortColumn(), rootIndex() ); - category = categoryIndex.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString(); - block = &d->blocks[category]; - indexToCheckIfBlockCollapsed = block->firstIndex.row() + block->items.count(); - if ( block->collapsed ) - { - i = indexToCheckIfBlockCollapsed; - continue; - } - } - //END: first check if the block is collapsed. if so, we have to skip the item painting - - Q_ASSERT ( block ); - - const bool alternateItem = ( i - block->firstIndex.row() ) % 2; - - const QModelIndex index = d->proxyModel->index ( i, modelColumn(), rootIndex() ); - const Qt::ItemFlags flags = d->proxyModel->flags ( index ); - QStyleOptionViewItemV4 option ( viewOptions() ); - option.rect = visualRect ( index ); - option.widget = this; - option.features |= wordWrap() ? QStyleOptionViewItemV2::WrapText - : QStyleOptionViewItemV2::None; - option.features |= alternatingRowColors() && alternateItem ? QStyleOptionViewItemV4::Alternate - : QStyleOptionViewItemV4::None; - if ( flags & Qt::ItemIsSelectable ) - { - 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; - } - else - { - option.state |= ( index == d->hoveredIndex ) ? QStyle::State_MouseOver - : QStyle::State_None; - } - - itemDelegate ( index )->paint ( &p, option, index ); - ++i; - } - //END: draw items - } - - //BEGIN: draw selection rect - if ( isSelectionRectVisible() && d->rubberBandRect.isValid() ) - { - QStyleOptionRubberBand opt; - opt.initFrom ( this ); - opt.shape = QRubberBand::Rectangle; - opt.opaque = false; - opt.rect = d->mapToViewport ( d->rubberBandRect ).intersected ( viewport()->rect().adjusted ( -16, -16, 16, 16 ) ); - p.save(); - style()->drawControl ( QStyle::CE_RubberBand, &opt, &p ); - p.restore(); - } - //END: draw selection rect - - p.restore(); -} - -void KCategorizedView::resizeEvent ( QResizeEvent *event ) -{ - d->regenerateAllElements(); - QListView::resizeEvent ( event ); -} - -void KCategorizedView::setSelection ( const QRect &rect, - QItemSelectionModel::SelectionFlags flags ) -{ - if ( !d->isCategorized() ) - { - QListView::setSelection ( rect, flags ); - return; - } - - if ( rect.topLeft() == rect.bottomRight() ) - { - const QModelIndex index = indexAt ( rect.topLeft() ); - selectionModel()->select ( index, flags ); - return; - } - - const QPair intersecting = d->intersectingIndexesWithRect ( rect ); - - QItemSelection selection; - - //TODO: think of a faster implementation - QModelIndex firstIndex; - QModelIndex lastIndex; - for ( int i = intersecting.first.row(); i <= intersecting.second.row(); ++i ) - { - const QModelIndex index = d->proxyModel->index ( i, modelColumn(), rootIndex() ); - const bool visualRectIntersects = visualRect ( index ).intersects ( rect ); - if ( firstIndex.isValid() ) - { - if ( visualRectIntersects ) - { - lastIndex = index; - } - else - { - selection << QItemSelectionRange ( firstIndex, lastIndex ); - firstIndex = QModelIndex(); - } - } - else if ( visualRectIntersects ) - { - firstIndex = index; - lastIndex = index; - } - } - - if ( firstIndex.isValid() ) - { - selection << QItemSelectionRange ( firstIndex, lastIndex ); - } - - selectionModel()->select ( selection, flags ); -} - -void KCategorizedView::mouseMoveEvent ( QMouseEvent *event ) -{ - QListView::mouseMoveEvent ( event ); - d->hoveredIndex = indexAt ( event->pos() ); - const SelectionMode itemViewSelectionMode = selectionMode(); - if ( state() == DragSelectingState && isSelectionRectVisible() && itemViewSelectionMode != SingleSelection - && itemViewSelectionMode != NoSelection ) - { - QRect rect ( d->pressedPosition, event->pos() + QPoint ( horizontalOffset(), verticalOffset() ) ); - rect = rect.normalized(); - update ( rect.united ( d->rubberBandRect ) ); - d->rubberBandRect = rect; - } - QHash::ConstIterator it ( d->blocks.constBegin() ); - while ( it != d->blocks.constEnd() ) - { - const Private::Block &block = *it; - const QModelIndex categoryIndex = d->proxyModel->index ( block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); - QStyleOptionViewItemV4 option ( viewOptions() ); - const int height = d->categoryDrawer->categoryHeight ( categoryIndex, option ); - QPoint pos = d->blockPosition ( it.key() ); - pos.ry() -= height; - option.rect.setTopLeft ( pos ); - option.rect.setWidth ( d->viewportWidth() + d->categoryDrawer->leftMargin() + d->categoryDrawer->rightMargin() ); - option.rect.setHeight ( height + d->blockHeight ( it.key() ) ); - option.rect = d->mapToViewport ( option.rect ); - const QPoint mousePos = viewport()->mapFromGlobal ( QCursor::pos() ); - if ( option.rect.contains ( mousePos ) ) - { - if ( d->categoryDrawer && d->hoveredBlock->height != -1 && *d->hoveredBlock != block ) - { - const QModelIndex categoryIndex = d->proxyModel->index ( d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); - const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); - d->categoryDrawer->mouseLeft ( categoryIndex, option.rect ); - *d->hoveredBlock = block; - d->hoveredCategory = it.key(); - viewport()->update ( option.rect ); - } - else if ( d->hoveredBlock->height == -1 ) - { - *d->hoveredBlock = block; - d->hoveredCategory = it.key(); - } - else if ( d->categoryDrawer ) - { - d->categoryDrawer->mouseMoved ( categoryIndex, option.rect, event ); - } - viewport()->update ( option.rect ); - return; - } - ++it; - } - if ( d->categoryDrawer && d->hoveredBlock->height != -1 ) - { - const QModelIndex categoryIndex = d->proxyModel->index ( d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); - const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); - d->categoryDrawer->mouseLeft ( categoryIndex, option.rect ); - *d->hoveredBlock = Private::Block(); - d->hoveredCategory = QString(); - viewport()->update ( option.rect ); - } -} - -void KCategorizedView::mousePressEvent ( QMouseEvent *event ) -{ - if ( event->button() == Qt::LeftButton ) - { - d->pressedPosition = event->pos(); - d->pressedPosition.rx() += horizontalOffset(); - d->pressedPosition.ry() += verticalOffset(); - } - if ( !d->categoryDrawer ) - { - QListView::mousePressEvent ( event ); - return; - } - QHash::ConstIterator it ( d->blocks.constBegin() ); - while ( it != d->blocks.constEnd() ) - { - const Private::Block &block = *it; - const QModelIndex categoryIndex = d->proxyModel->index ( block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); - const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); - const QPoint mousePos = viewport()->mapFromGlobal ( QCursor::pos() ); - if ( option.rect.contains ( mousePos ) ) - { - if ( d->categoryDrawer ) - { - d->categoryDrawer->mouseButtonPressed ( categoryIndex, option.rect, event ); - } - viewport()->update ( option.rect ); - if ( !event->isAccepted() ) - { - QListView::mousePressEvent ( event ); - } - return; - } - ++it; - } - QListView::mousePressEvent ( event ); -} - -void KCategorizedView::mouseReleaseEvent ( QMouseEvent *event ) -{ - d->pressedPosition = QPoint(); - d->rubberBandRect = QRect(); - QHash::ConstIterator it ( d->blocks.constBegin() ); - while ( it != d->blocks.constEnd() ) - { - const Private::Block &block = *it; - const QModelIndex categoryIndex = d->proxyModel->index ( block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); - const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); - const QPoint mousePos = viewport()->mapFromGlobal ( QCursor::pos() ); - if ( option.rect.contains ( mousePos ) ) - { - if ( d->categoryDrawer ) - { - d->categoryDrawer->mouseButtonReleased ( categoryIndex, option.rect, event ); - } - viewport()->update ( option.rect ); - if ( !event->isAccepted() ) - { - QListView::mouseReleaseEvent ( event ); - } - return; - } - ++it; - } - QListView::mouseReleaseEvent ( event ); -} - -void KCategorizedView::leaveEvent ( QEvent *event ) -{ - QListView::leaveEvent ( event ); - if ( d->hoveredIndex.isValid() ) - { - viewport()->update ( visualRect ( d->hoveredIndex ) ); - d->hoveredIndex = QModelIndex(); - } - if ( d->categoryDrawer && d->hoveredBlock->height != -1 ) - { - const QModelIndex categoryIndex = d->proxyModel->index ( d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); - const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); - d->categoryDrawer->mouseLeft ( categoryIndex, option.rect ); - *d->hoveredBlock = Private::Block(); - d->hoveredCategory = QString(); - viewport()->update ( option.rect ); - } -} - -void KCategorizedView::startDrag ( Qt::DropActions supportedActions ) -{ - QListView::startDrag ( supportedActions ); -} - -void KCategorizedView::dragMoveEvent ( QDragMoveEvent *event ) -{ - QListView::dragMoveEvent ( event ); - d->hoveredIndex = indexAt ( event->pos() ); -} - -void KCategorizedView::dragEnterEvent ( QDragEnterEvent *event ) -{ - QListView::dragEnterEvent ( event ); -} - -void KCategorizedView::dragLeaveEvent ( QDragLeaveEvent *event ) -{ - QListView::dragLeaveEvent ( event ); -} - -void KCategorizedView::dropEvent ( QDropEvent *event ) -{ - QListView::dropEvent ( event ); -} - -//TODO: improve se we take into account collapsed blocks -//TODO: take into account when there is no grid and no uniformItemSizes -QModelIndex KCategorizedView::moveCursor ( CursorAction cursorAction, - Qt::KeyboardModifiers modifiers ) -{ - if ( !d->isCategorized() ) - { - return QListView::moveCursor ( cursorAction, modifiers ); - } - - const QModelIndex current = currentIndex(); - const QRect currentRect = visualRect ( current ); - if ( !current.isValid() ) - { - const int rowCount = d->proxyModel->rowCount ( rootIndex() ); - if ( !rowCount ) - { - return QModelIndex(); - } - return d->proxyModel->index ( 0, modelColumn(), rootIndex() ); - } - - switch ( cursorAction ) - { - case MoveLeft: - { - if ( !current.row() ) - { - return QModelIndex(); - } - const QModelIndex previous = d->proxyModel->index ( current.row() - 1, modelColumn(), rootIndex() ); - const QRect previousRect = visualRect ( previous ); - if ( previousRect.top() == currentRect.top() ) - { - return previous; - } - - return QModelIndex(); - } - case MoveRight: - { - if ( current.row() == d->proxyModel->rowCount() - 1 ) - { - return QModelIndex(); - } - const QModelIndex next = d->proxyModel->index ( current.row() + 1, modelColumn(), rootIndex() ); - const QRect nextRect = visualRect ( next ); - if ( nextRect.top() == currentRect.top() ) - { - return next; - } - - return QModelIndex(); - } - case MoveDown: - { - if ( d->hasGrid() || uniformItemSizes() || uniformItemWidths() ) - { - const QModelIndex current = currentIndex(); - const QSize itemSize = d->hasGrid() ? gridSize() - : sizeHintForIndex ( current ); - const Private::Block &block = d->blocks[d->categoryForIndex ( current )]; - //HACK: Why is the -2 needed? - const int maxItemsPerRow = qMax ( ( d->viewportWidth() - spacing() - 2 ) / ( itemSize.width() + spacing() ), 1 ); - const bool canMove = current.row() + maxItemsPerRow < block.firstIndex.row() + - block.items.count(); - - if ( canMove ) - { - return d->proxyModel->index ( current.row() + maxItemsPerRow, modelColumn(), rootIndex() ); - } - - const int currentRelativePos = ( current.row() - block.firstIndex.row() ) % maxItemsPerRow; - const QModelIndex nextIndex = d->proxyModel->index ( block.firstIndex.row() + block.items.count(), modelColumn(), rootIndex() ); - - if ( !nextIndex.isValid() ) - { - return QModelIndex(); - } - - const Private::Block &nextBlock = d->blocks[d->categoryForIndex ( nextIndex )]; - - if ( nextBlock.items.count() <= currentRelativePos ) - { - return QModelIndex(); - } - - if ( currentRelativePos < ( block.items.count() % maxItemsPerRow ) ) - { - return d->proxyModel->index ( nextBlock.firstIndex.row() + currentRelativePos, modelColumn(), rootIndex() ); - } - - return QModelIndex(); - } - } - case MoveUp: - { - if ( d->hasGrid() || uniformItemSizes() || uniformItemWidths() ) - { - const QModelIndex current = currentIndex(); - const QSize itemSize = d->hasGrid() ? gridSize() - : sizeHintForIndex ( current ); - const Private::Block &block = d->blocks[d->categoryForIndex ( current )]; - //HACK: Why is the -2 needed? - const int maxItemsPerRow = qMax ( ( d->viewportWidth() - spacing() - 2 ) / ( itemSize.width() + spacing() ), 1 ); - const bool canMove = current.row() - maxItemsPerRow >= block.firstIndex.row(); - - if ( canMove ) - { - return d->proxyModel->index ( current.row() - maxItemsPerRow, modelColumn(), rootIndex() ); - } - - const int currentRelativePos = ( current.row() - block.firstIndex.row() ) % maxItemsPerRow; - const QModelIndex prevIndex = d->proxyModel->index ( block.firstIndex.row() - 1, modelColumn(), rootIndex() ); - - if ( !prevIndex.isValid() ) - { - return QModelIndex(); - } - - const Private::Block &prevBlock = d->blocks[d->categoryForIndex ( prevIndex )]; - - if ( prevBlock.items.count() <= currentRelativePos ) - { - return QModelIndex(); - } - - const int remainder = prevBlock.items.count() % maxItemsPerRow; - if ( currentRelativePos < remainder ) - { - return d->proxyModel->index ( prevBlock.firstIndex.row() + prevBlock.items.count() - remainder + currentRelativePos, modelColumn(), rootIndex() ); - } - - return QModelIndex(); - } - } - default: - break; - } - - return QModelIndex(); -} - -void KCategorizedView::rowsAboutToBeRemoved ( const QModelIndex &parent, - int start, - int end ) -{ - if ( !d->isCategorized() ) - { - QListView::rowsAboutToBeRemoved ( parent, start, end ); - return; - } - - *d->hoveredBlock = Private::Block(); - d->hoveredCategory = QString(); - - if ( end - start + 1 == d->proxyModel->rowCount() ) - { - d->blocks.clear(); - QListView::rowsAboutToBeRemoved ( parent, start, end ); - return; - } - - // Removal feels a bit more complicated than insertion. Basically we can consider there are - // 3 different cases when going to remove items. (*) represents an item, Items between ([) and - // (]) are the ones which are marked for removal. - // - // - 1st case: - // ... * * * * * * [ * * * ... - // - // The items marked for removal are the last part of this category. No need to mark any item - // of this category as in quarantine, because no special offset will be pushed to items at - // the right because of any changes (since the removed items are those on the right most part - // of the category). - // - // - 2nd case: - // ... * * * * * * ] * * * ... - // - // The items marked for removal are the first part of this category. We have to mark as in - // quarantine all items in this category. Absolutely all. All items will have to be moved to - // the left (or moving up, because rows got a different offset). - // - // - 3rd case: - // ... * * [ * * * * ] * * ... - // - // The items marked for removal are in between of this category. We have to mark as in - // quarantine only those items that are at the right of the end of the removal interval, - // (starting on "]"). - // - // It hasn't been explicitly said, but when we remove, we have to mark all blocks that are - // located under the top most affected category as in quarantine (the block itself, as a whole), - // because such a change can force it to have a different offset (note that items themselves - // contain relative positions to the block, so marking the block as in quarantine is enough). - // - // Also note that removal implicitly means that we have to update correctly firstIndex of each - // block, and in general keep updated the internal information of elements. - - QStringList listOfCategoriesMarkedForRemoval; - - QString lastCategory; - int alreadyRemoved = 0; - for ( int i = start; i <= end; ++i ) - { - const QModelIndex index = d->proxyModel->index ( i, modelColumn(), parent ); - - Q_ASSERT ( index.isValid() ); - - const QString category = d->categoryForIndex ( index ); - - if ( lastCategory != category ) - { - lastCategory = category; - alreadyRemoved = 0; - } - - Private::Block &block = d->blocks[category]; - block.items.removeAt ( i - block.firstIndex.row() - alreadyRemoved ); - ++alreadyRemoved; - - if ( !block.items.count() ) - { - listOfCategoriesMarkedForRemoval << category; - } - - block.height = -1; - - viewport()->update(); - } - - //BEGIN: update the items that are in quarantine in affected categories - { - const QModelIndex lastIndex = d->proxyModel->index ( end, modelColumn(), parent ); - const QString category = d->categoryForIndex ( lastIndex ); - Private::Block &block = d->blocks[category]; - if ( block.items.count() && start <= block.firstIndex.row() && end >= block.firstIndex.row() ) - { - block.firstIndex = d->proxyModel->index ( end + 1, modelColumn(), parent ); - } - block.quarantineStart = block.firstIndex; - } - //END: update the items that are in quarantine in affected categories - - Q_FOREACH ( const QString &category, listOfCategoriesMarkedForRemoval ) - { - d->blocks.remove ( category ); - } - - //BEGIN: mark as in quarantine those categories that are under the affected ones - { - //BEGIN: order for marking as alternate those blocks that are alternate - QList blockList = d->blocks.values(); - qSort ( blockList.begin(), blockList.end(), Private::Block::lessThan ); - QList firstIndexesRows; - foreach ( const Private::Block &block, blockList ) - { - firstIndexesRows << block.firstIndex.row(); - } - //END: order for marking as alternate those blocks that are alternate - for ( QHash::Iterator it = d->blocks.begin(); it != d->blocks.end(); ++it ) - { - Private::Block &block = *it; - if ( block.firstIndex.row() > start ) - { - block.outOfQuarantine = false; - block.alternate = firstIndexesRows.indexOf ( block.firstIndex.row() ) % 2; - } - else if ( block.firstIndex.row() == start ) - { - block.alternate = firstIndexesRows.indexOf ( block.firstIndex.row() ) % 2; - } - } - } - //END: mark as in quarantine those categories that are under the affected ones - - QListView::rowsAboutToBeRemoved ( parent, start, end ); -} - -void KCategorizedView::updateGeometries() -{ - const int oldVerticalOffset = verticalOffset(); - const Qt::ScrollBarPolicy verticalP = verticalScrollBarPolicy(), horizontalP = horizontalScrollBarPolicy(); - - //BEGIN bugs 213068, 287847 ------------------------------------------------------------ - /* - * QListView::updateGeometries() has it's own opinion on whether the scrollbars should be visible (valid range) or not - * and triggers a (sometimes additionally timered) resize through ::layoutChildren() - * http://qt.gitorious.org/qt/qt/blobs/4.7/src/gui/itemviews/qlistview.cpp#line1499 - * (the comment above the main block isn't all accurate, layoutChldren is called regardless of the policy) - * - * As a result QListView and KCategorizedView occasionally started a race on the scrollbar visibility, effectively blocking the UI - * So we prevent QListView from having an own opinion on the scrollbar visibility by - * fixing it before calling the baseclass QListView::updateGeometries() - * - * Since the implicit show/hide by the followin range setting will cause further resizes if the policy is Qt::ScrollBarAsNeeded - * we keep it static until we're done, then restore the original value and ultimately change the scrollbar visibility ourself. - */ - if ( d->isCategorized() ) // important! - otherwise we'd pollute the setting if the view is initially not categorized - { - setVerticalScrollBarPolicy ( ( verticalP == Qt::ScrollBarAlwaysOn || verticalScrollBar()->isVisibleTo ( this ) ) ? - Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff ); - setHorizontalScrollBarPolicy ( ( horizontalP == Qt::ScrollBarAlwaysOn || horizontalScrollBar()->isVisibleTo ( this ) ) ? - Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff ); - } - //END bugs 213068, 287847 -------------------------------------------------------------- - - QListView::updateGeometries(); - - if ( !d->isCategorized() ) - { - return; - } - - const int rowCount = d->proxyModel->rowCount(); - if ( !rowCount ) - { - verticalScrollBar()->setRange ( 0, 0 ); - // unconditional, see function end todo - horizontalScrollBar()->setRange ( 0, 0 ); - return; - } - - const QModelIndex lastIndex = d->proxyModel->index ( rowCount - 1, modelColumn(), rootIndex() ); - Q_ASSERT ( lastIndex.isValid() ); - QRect lastItemRect = visualRect ( lastIndex ); - - if ( d->hasGrid() ) - { - lastItemRect.setSize ( lastItemRect.size().expandedTo ( gridSize() ) ); - } - else - { - if ( uniformItemSizes() ) - { - QSize itemSize = sizeHintForIndex ( lastIndex ); - itemSize.setHeight ( itemSize.height() + spacing() ); - lastItemRect.setSize ( itemSize ); - } - else - { - QSize itemSize = sizeHintForIndex ( lastIndex ); - const QString category = d->categoryForIndex ( lastIndex ); - itemSize.setHeight ( d->highestElementInLastRow ( d->blocks[category] ) + spacing() ); - lastItemRect.setSize ( itemSize ); - } - } - - const int bottomRange = lastItemRect.bottomRight().y() + verticalOffset() - viewport()->height(); - - if ( verticalScrollMode() == ScrollPerItem ) - { - verticalScrollBar()->setSingleStep ( lastItemRect.height() ); - const int rowsPerPage = qMax ( viewport()->height() / lastItemRect.height(), 1 ); - verticalScrollBar()->setPageStep ( rowsPerPage * lastItemRect.height() ); - } - - verticalScrollBar()->setRange ( 0, bottomRange ); - verticalScrollBar()->setValue ( oldVerticalOffset ); - - //TODO: also consider working with the horizontal scroll bar. since at this level I am not still - // supporting "top to bottom" flow, there is no real problem. If I support that someday - // (think how to draw categories), we would have to take care of the horizontal scroll bar too. - // In theory, as KCategorizedView has been designed, there is no need of horizontal scroll bar. - horizontalScrollBar()->setRange ( 0, 0 ); - - //BEGIN bugs 213068, 287847 ------------------------------------------------------------ - // restoring values from above ... - setVerticalScrollBarPolicy ( verticalP ); - setHorizontalScrollBarPolicy ( horizontalP ); - // ... and correct the visibility - bool validRange = verticalScrollBar()->maximum() != verticalScrollBar()->minimum(); - if ( verticalP == Qt::ScrollBarAsNeeded && ( verticalScrollBar()->isVisibleTo ( this ) != validRange ) ) - verticalScrollBar()->setVisible ( validRange ); - validRange = horizontalScrollBar()->maximum() > horizontalScrollBar()->minimum(); - if ( horizontalP == Qt::ScrollBarAsNeeded && ( horizontalScrollBar()->isVisibleTo ( this ) != validRange ) ) - horizontalScrollBar()->setVisible ( validRange ); - //END bugs 213068, 287847 -------------------------------------------------------------- -} - -void KCategorizedView::currentChanged ( const QModelIndex ¤t, - const QModelIndex &previous ) -{ - QListView::currentChanged ( current, previous ); -} - -void KCategorizedView::dataChanged ( const QModelIndex &topLeft, - const QModelIndex &bottomRight ) -{ - QListView::dataChanged ( topLeft, bottomRight ); - if ( !d->isCategorized() ) - { - return; - } - - *d->hoveredBlock = Private::Block(); - d->hoveredCategory = QString(); - - //BEGIN: since the model changed data, we need to reconsider item sizes - int i = topLeft.row(); - int indexToCheck = i; - QModelIndex categoryIndex; - QString category; - Private::Block *block; - while ( i <= bottomRight.row() ) - { - const QModelIndex currIndex = d->proxyModel->index ( i, modelColumn(), rootIndex() ); - if ( i == indexToCheck ) - { - categoryIndex = d->proxyModel->index ( i, d->proxyModel->sortColumn(), rootIndex() ); - category = categoryIndex.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString(); - block = &d->blocks[category]; - block->quarantineStart = currIndex; - indexToCheck = block->firstIndex.row() + block->items.count(); - } - visualRect ( currIndex ); - ++i; - } - //END: since the model changed data, we need to reconsider item sizes -} - -void KCategorizedView::rowsInserted ( const QModelIndex &parent, - int start, - int end ) -{ - QListView::rowsInserted ( parent, start, end ); - if ( !d->isCategorized() ) - { - return; - } - - *d->hoveredBlock = Private::Block(); - d->hoveredCategory = QString(); - d->rowsInserted ( parent, start, end ); -} - -void KCategorizedView::slotLayoutChanged() -{ - if ( !d->isCategorized() ) - { - return; - } - - d->blocks.clear(); - *d->hoveredBlock = Private::Block(); - d->hoveredCategory = QString(); - if ( d->proxyModel->rowCount() ) - { - d->rowsInserted ( rootIndex(), 0, d->proxyModel->rowCount() - 1 ); - } -} -//END: Public part - -void KCategorizedView::slotCollapseOrExpandClicked ( QModelIndex idx ) -{ - d->_k_slotCollapseOrExpandClicked ( idx ); -} - - -#include "categorizedview.moc" diff --git a/depends/groupview/src/categorizedview_p.h b/depends/groupview/src/categorizedview_p.h deleted file mode 100644 index 524bba3a..00000000 --- a/depends/groupview/src/categorizedview_p.h +++ /dev/null @@ -1,160 +0,0 @@ -/** - * This file is part of the KDE project - * Copyright (C) 2007, 2009 Rafael Fernández López - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef KCATEGORIZEDVIEW_P_H -#define KCATEGORIZEDVIEW_P_H - -class KCategorizedSortFilterProxyModel; -class KCategoryDrawer; -class KCategoryDrawerV2; -class KCategoryDrawerV3; - -/** - * @internal - */ -class KCategorizedView::Private -{ -public: - struct Block; - struct Item; - - Private(KCategorizedView *q); - ~Private(); - - /** - * @return whether this view has all required elements to be categorized. - */ - bool isCategorized() const; - - /** - * @return the block rect for the representative @p representative. - */ - QStyleOptionViewItemV4 blockRect(const QModelIndex &representative); - - /** - * Returns the first and last element that intersects with rect. - * - * @note see that here we cannot take out items between first and last (as we could - * do with the rubberband). - * - * Complexity: O(log(n)) where n is model()->rowCount(). - */ - QPair intersectingIndexesWithRect(const QRect &rect) const; - - /** - * Returns the position of the block of @p category. - * - * Complexity: O(n) where n is the number of different categories when the block has been - * marked as in quarantine. O(1) the rest of the times (the vast majority). - */ - QPoint blockPosition(const QString &category); - - /** - * Returns the height of the block determined by @p category. - */ - int blockHeight(const QString &category); - - /** - * Returns the actual viewport width. - */ - int viewportWidth() const; - - /** - * Marks all elements as in quarantine. - * - * Complexity: O(n) where n is model()->rowCount(). - * - * @warning this is an expensive operation - */ - void regenerateAllElements(); - - /** - * Update internal information, and keep sync with the real information that the model contains. - */ - void rowsInserted(const QModelIndex &parent, int start, int end); - - /** - * Returns @p rect in viewport terms, taking in count horizontal and vertical offsets. - */ - QRect mapToViewport(const QRect &rect) const; - - /** - * Returns @p rect in absolute terms, converted from viewport position. - */ - QRect mapFromViewport(const QRect &rect) const; - - /** - * Returns the height of the highest element in last row. This is only applicable if there is - * no grid set and uniformItemSizes is false. - * - * @param block in which block are we searching. Necessary to stop the search if we hit the - * first item in this block. - */ - int highestElementInLastRow(const Block &block) const; - - /** - * Returns whether the view has a valid grid size. - */ - bool hasGrid() const; - - /** - * Returns the category for the given index. - */ - QString categoryForIndex(const QModelIndex &index) const; - - /** - * Updates the visual rect for item when flow is LeftToRight. - */ - void leftToRightVisualRect(const QModelIndex &index, Item &item, - const Block &block, const QPoint &blockPos) const; - - /** - * Updates the visual rect for item when flow is TopToBottom. - * @note we only support viewMode == ListMode in this case. - */ - void topToBottomVisualRect(const QModelIndex &index, Item &item, - const Block &block, const QPoint &blockPos) const; - - /** - * Called when expand or collapse has been clicked on the category drawer. - */ - void _k_slotCollapseOrExpandClicked(QModelIndex); - - KCategorizedView *q = nullptr; - KCategorizedSortFilterProxyModel *proxyModel = nullptr; - KCategoryDrawer *categoryDrawer = nullptr; - int categorySpacing = 5; - bool alternatingBlockColors = false; - bool collapsibleBlocks = false; - bool constantItemWidth = false; - - // FIXME: this is some really weird logic. Investigate. - Block *hoveredBlock; - QString hoveredCategory; - QModelIndex hoveredIndex; - - QPoint pressedPosition; - QRect rubberBandRect; - - QHash blocks; -}; - -#endif // KCATEGORIZEDVIEW_P_H - diff --git a/depends/groupview/src/categorydrawer.cpp b/depends/groupview/src/categorydrawer.cpp deleted file mode 100644 index 214ce3b2..00000000 --- a/depends/groupview/src/categorydrawer.cpp +++ /dev/null @@ -1,231 +0,0 @@ -/** - * This file is part of the KDE project - * Copyright (C) 2007, 2009 Rafael Fernández López - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#include "categorydrawer.h" - -#include -#include -#include - -#include -#include - -#define HORIZONTAL_HINT 3 - -class KCategoryDrawer::Private -{ -public: - Private(KCategorizedView *view) - : view(view) - , leftMargin(0) - , rightMargin(0) - { - } - - ~Private() - { - } - KCategorizedView *view; - int leftMargin; - int rightMargin; -}; - -KCategoryDrawer::KCategoryDrawer(KCategorizedView *view) - : QObject(view) - , d(new Private(view)) -{ - setLeftMargin(2); - setRightMargin(2); -} - -KCategoryDrawer::~KCategoryDrawer() -{ - delete d; -} - - -void KCategoryDrawer::drawCategory(const QModelIndex &index, - int /*sortRole*/, - const QStyleOption &option, - QPainter *painter) const -{ - painter->setRenderHint(QPainter::Antialiasing); - - const QString category = index.model()->data(index, KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString(); - const QRect optRect = option.rect; - QFont font(QApplication::font()); - font.setBold(true); - const QFontMetrics fontMetrics = QFontMetrics(font); - - QColor outlineColor = option.palette.text().color(); - outlineColor.setAlphaF(0.35); - - //BEGIN: top left corner - { - painter->save(); - painter->setPen(outlineColor); - const QPointF topLeft(optRect.topLeft()); - QRectF arc(topLeft, QSizeF(4, 4)); - arc.translate(0.5, 0.5); - painter->drawArc(arc, 1440, 1440); - painter->restore(); - } - //END: top left corner - - //BEGIN: left vertical line - { - QPoint start(optRect.topLeft()); - start.ry() += 3; - QPoint verticalGradBottom(optRect.topLeft()); - verticalGradBottom.ry() += fontMetrics.height() + 5; - QLinearGradient gradient(start, verticalGradBottom); - gradient.setColorAt(0, outlineColor); - gradient.setColorAt(1, Qt::transparent); - painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); - } - //END: left vertical line - - //BEGIN: horizontal line - { - QPoint start(optRect.topLeft()); - start.rx() += 3; - QPoint horizontalGradTop(optRect.topLeft()); - horizontalGradTop.rx() += optRect.width() - 6; - painter->fillRect(QRect(start, QSize(optRect.width() - 6, 1)), outlineColor); - } - //END: horizontal line - - //BEGIN: top right corner - { - painter->save(); - painter->setPen(outlineColor); - QPointF topRight(optRect.topRight()); - topRight.rx() -= 4; - QRectF arc(topRight, QSizeF(4, 4)); - arc.translate(0.5, 0.5); - painter->drawArc(arc, 0, 1440); - painter->restore(); - } - //END: top right corner - - //BEGIN: right vertical line - { - QPoint start(optRect.topRight()); - start.ry() += 3; - QPoint verticalGradBottom(optRect.topRight()); - verticalGradBottom.ry() += fontMetrics.height() + 5; - QLinearGradient gradient(start, verticalGradBottom); - gradient.setColorAt(0, outlineColor); - gradient.setColorAt(1, Qt::transparent); - painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); - } - //END: right vertical line - - //BEGIN: text - { - QRect textRect(option.rect); - textRect.setTop(textRect.top() + 7); - textRect.setLeft(textRect.left() + 7); - textRect.setHeight(fontMetrics.height()); - textRect.setRight(textRect.right() - 7); - - painter->save(); - painter->setFont(font); - QColor penColor(option.palette.text().color()); - penColor.setAlphaF(0.6); - painter->setPen(penColor); - painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, category); - painter->restore(); - } - //END: text -} - -int KCategoryDrawer::categoryHeight(const QModelIndex &index, const QStyleOption &option) const -{ - Q_UNUSED(index); - Q_UNUSED(option) - - QFont font(QApplication::font()); - font.setBold(true); - QFontMetrics fontMetrics(font); - - const int height = fontMetrics.height() + 1 /* 1 pixel-width gradient */ - + 11 /* top and bottom separation */; - return height; -} - -int KCategoryDrawer::leftMargin() const -{ - return d->leftMargin; -} - -void KCategoryDrawer::setLeftMargin(int leftMargin) -{ - d->leftMargin = leftMargin; -} - -int KCategoryDrawer::rightMargin() const -{ - return d->rightMargin; -} - -void KCategoryDrawer::setRightMargin(int rightMargin) -{ - d->rightMargin = rightMargin; -} - -KCategoryDrawer &KCategoryDrawer::operator=(const KCategoryDrawer &cd) -{ - d->leftMargin = cd.d->leftMargin; - d->rightMargin = cd.d->rightMargin; - d->view = cd.d->view; - return *this; -} - -KCategorizedView *KCategoryDrawer::view() const -{ - return d->view; -} - -void KCategoryDrawer::mouseButtonPressed(const QModelIndex&, const QRect&, QMouseEvent *event) -{ - event->ignore(); -} - -void KCategoryDrawer::mouseButtonReleased(const QModelIndex&, const QRect&, QMouseEvent *event) -{ - event->ignore(); -} - -void KCategoryDrawer::mouseMoved(const QModelIndex&, const QRect&, QMouseEvent *event) -{ - event->ignore(); -} - -void KCategoryDrawer::mouseButtonDoubleClicked(const QModelIndex&, const QRect&, QMouseEvent *event) -{ - event->ignore(); -} - -void KCategoryDrawer::mouseLeft(const QModelIndex&, const QRect&) -{ -} - -#include "categorydrawer.moc" -- cgit From 7839c4ecc01aad4c5d34a6d06b2dfc4afdc58458 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Tue, 4 Feb 2014 01:40:51 +0100 Subject: Pave. --- CMakeLists.txt | 19 +- depends/groupview/.clang-format | 24 - depends/groupview/.gitignore | 2 - depends/groupview/CMakeLists.txt | 40 -- depends/groupview/Group.cpp | 169 ------ depends/groupview/Group.h | 67 --- depends/groupview/GroupView.cpp | 908 -------------------------------- depends/groupview/GroupView.h | 139 ----- depends/groupview/GroupedProxyModel.cpp | 21 - depends/groupview/GroupedProxyModel.h | 14 - depends/groupview/InstanceDelegate.cpp | 281 ---------- depends/groupview/InstanceDelegate.h | 29 - depends/groupview/main.cpp | 98 ---- depends/groupview/main.h | 54 -- gui/MainWindow.cpp | 30 +- gui/MainWindow.h | 6 +- gui/dialogs/IconPickerDialog.cpp | 2 +- gui/groupview/Group.cpp | 169 ++++++ gui/groupview/Group.h | 67 +++ gui/groupview/GroupView.cpp | 908 ++++++++++++++++++++++++++++++++ gui/groupview/GroupView.h | 139 +++++ gui/groupview/GroupedProxyModel.cpp | 26 + gui/groupview/GroupedProxyModel.h | 15 + gui/groupview/InstanceDelegate.cpp | 281 ++++++++++ gui/groupview/InstanceDelegate.h | 29 + gui/widgets/InstanceDelegate.cpp | 231 -------- gui/widgets/InstanceDelegate.h | 27 - logic/lists/InstanceList.cpp | 8 +- logic/lists/InstanceList.h | 4 +- 29 files changed, 1667 insertions(+), 2140 deletions(-) delete mode 100644 depends/groupview/.clang-format delete mode 100644 depends/groupview/.gitignore delete mode 100644 depends/groupview/CMakeLists.txt delete mode 100644 depends/groupview/Group.cpp delete mode 100644 depends/groupview/Group.h delete mode 100644 depends/groupview/GroupView.cpp delete mode 100644 depends/groupview/GroupView.h delete mode 100644 depends/groupview/GroupedProxyModel.cpp delete mode 100644 depends/groupview/GroupedProxyModel.h delete mode 100644 depends/groupview/InstanceDelegate.cpp delete mode 100644 depends/groupview/InstanceDelegate.h delete mode 100644 depends/groupview/main.cpp delete mode 100644 depends/groupview/main.h create mode 100644 gui/groupview/Group.cpp create mode 100644 gui/groupview/Group.h create mode 100644 gui/groupview/GroupView.cpp create mode 100644 gui/groupview/GroupView.h create mode 100644 gui/groupview/GroupedProxyModel.cpp create mode 100644 gui/groupview/GroupedProxyModel.h create mode 100644 gui/groupview/InstanceDelegate.cpp create mode 100644 gui/groupview/InstanceDelegate.h delete mode 100644 gui/widgets/InstanceDelegate.cpp delete mode 100644 gui/widgets/InstanceDelegate.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c34f363f..a8adfec3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -186,7 +186,6 @@ configure_file("${PROJECT_SOURCE_DIR}/config.h.in" "${PROJECT_BINARY_DIR}/includ ADD_DEFINITIONS(-DQUAZIP_STATIC) ADD_DEFINITIONS(-DLIBSETTINGS_STATIC) ADD_DEFINITIONS(-DLIBUTIL_STATIC) -ADD_DEFINITIONS(-DLIBGROUPVIEW_STATIC) ######## Packaging/install paths setup ######## @@ -247,10 +246,6 @@ include_directories(${LIBUTIL_INCLUDE_DIR}) add_subdirectory(depends/settings) include_directories(${LIBSETTINGS_INCLUDE_DIR}) -# Add the group view library. -add_subdirectory(depends/groupview) -include_directories(${LIBGROUPVIEW_INCLUDE_DIR}) - # Add the updater add_subdirectory(mmc_updater) @@ -317,8 +312,6 @@ gui/dialogs/UpdateDialog.cpp # GUI - widgets gui/widgets/Common.h gui/widgets/Common.cpp -gui/widgets/InstanceDelegate.h -gui/widgets/InstanceDelegate.cpp gui/widgets/ModListView.h gui/widgets/ModListView.cpp gui/widgets/VersionListView.h @@ -328,6 +321,16 @@ gui/widgets/LabeledToolButton.cpp gui/widgets/MCModInfoFrame.h gui/widgets/MCModInfoFrame.cpp +# GUI - instance group view +gui/groupview/Group.cpp +gui/groupview/Group.h +gui/groupview/GroupedProxyModel.cpp +gui/groupview/GroupedProxyModel.h +gui/groupview/GroupView.cpp +gui/groupview/GroupView.h +gui/groupview/InstanceDelegate.cpp +gui/groupview/InstanceDelegate.h + # Base classes and infrastructure logic/BaseVersion.h logic/MinecraftVersion.h @@ -584,7 +587,7 @@ ADD_EXECUTABLE(MultiMC MACOSX_BUNDLE WIN32 main.cpp ${MULTIMC_RCS}) # Link TARGET_LINK_LIBRARIES(MultiMC MultiMC_common) -TARGET_LINK_LIBRARIES(MultiMC_common xz-embedded unpack200 quazip libUtil libSettings libGroupView ${MultiMC_LINK_ADDITIONAL_LIBS}) +TARGET_LINK_LIBRARIES(MultiMC_common xz-embedded unpack200 quazip libUtil libSettings ${MultiMC_LINK_ADDITIONAL_LIBS}) QT5_USE_MODULES(MultiMC Core Widgets Network Xml Concurrent ${MultiMC_QT_ADDITIONAL_MODULES}) QT5_USE_MODULES(MultiMC_common Core Widgets Network Xml Concurrent ${MultiMC_QT_ADDITIONAL_MODULES}) diff --git a/depends/groupview/.clang-format b/depends/groupview/.clang-format deleted file mode 100644 index 167a8fa7..00000000 --- a/depends/groupview/.clang-format +++ /dev/null @@ -1,24 +0,0 @@ -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 deleted file mode 100644 index a5d18fa3..00000000 --- a/depends/groupview/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -build/ -*.user* diff --git a/depends/groupview/CMakeLists.txt b/depends/groupview/CMakeLists.txt deleted file mode 100644 index e2a85950..00000000 --- a/depends/groupview/CMakeLists.txt +++ /dev/null @@ -1,40 +0,0 @@ -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 deleted file mode 100644 index f216cc6e..00000000 --- a/depends/groupview/Group.cpp +++ /dev/null @@ -1,169 +0,0 @@ -#include "Group.h" - -#include -#include -#include - -#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(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 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 Group::items() const -{ - QList 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 indices = items(); - return indices.isEmpty() ? QModelIndex() : indices.first(); -} - -QModelIndex Group::lastItem() const -{ - QList indices = items(); - return indices.isEmpty() ? QModelIndex() : indices.last(); -} diff --git a/depends/groupview/Group.h b/depends/groupview/Group.h deleted file mode 100644 index 455ee1a8..00000000 --- a/depends/groupview/Group.h +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#include -#include -#include - -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 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 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 deleted file mode 100644 index 89e3e223..00000000 --- a/depends/groupview/GroupView.cpp +++ /dev/null @@ -1,908 +0,0 @@ -#include "GroupView.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Group.h" - -template bool listsIntersect(const QList &l1, const QList 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 &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 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 GroupView::categoryInternalPosition(const QModelIndex &index) const -{ - QList 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 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 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 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 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(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> GroupView::draggablePaintPairs(const QModelIndexList &indices, - QRect *r) const -{ - Q_ASSERT(r); - QRect &rect = *r; - QList> 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 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 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 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 deleted file mode 100644 index 329a3503..00000000 --- a/depends/groupview/GroupView.h +++ /dev/null @@ -1,139 +0,0 @@ -#pragma once - -#include -#include -#include - -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 &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 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 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> draggablePaintPairs(const QModelIndexList &indices, - QRect *r) const; - - bool isDragEventAccepted(QDropEvent *event); - - QPair rowDropPos(const QPoint &pos); - - QPoint offset() const; -}; diff --git a/depends/groupview/GroupedProxyModel.cpp b/depends/groupview/GroupedProxyModel.cpp deleted file mode 100644 index 57d7ff5c..00000000 --- a/depends/groupview/GroupedProxyModel.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#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 deleted file mode 100644 index cae87ecd..00000000 --- a/depends/groupview/GroupedProxyModel.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include - -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 deleted file mode 100644 index 8527e2bc..00000000 --- a/depends/groupview/InstanceDelegate.cpp +++ /dev/null @@ -1,281 +0,0 @@ -/* 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 -#include -#include -#include -#include - -#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 deleted file mode 100644 index de2f429b..00000000 --- a/depends/groupview/InstanceDelegate.h +++ /dev/null @@ -1,29 +0,0 @@ -/* 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 - -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 deleted file mode 100644 index df14e3bd..00000000 --- a/depends/groupview/main.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include "main.h" - -#include -#include -#include -#include - -#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 deleted file mode 100644 index 2a358329..00000000 --- a/depends/groupview/main.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#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 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 m_items; -}; diff --git a/gui/MainWindow.cpp b/gui/MainWindow.cpp index 9977dc75..cd8ec926 100644 --- a/gui/MainWindow.cpp +++ b/gui/MainWindow.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -37,12 +38,12 @@ #include "userutils.h" #include "pathutils.h" -#include "categorizedview.h" -#include "categorydrawer.h" +#include "gui/groupview/GroupView.h" +#include "gui/groupview/InstanceDelegate.h" #include "gui/Platform.h" -#include "gui/widgets/InstanceDelegate.h" + #include "gui/widgets/LabeledToolButton.h" #include "gui/dialogs/SettingsDialog.h" @@ -140,21 +141,21 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi // Create the instance list widget { - view = new KCategorizedView(ui->centralWidget); - drawer = new KCategoryDrawer(view); + view = new GroupView(ui->centralWidget); + view->setSelectionMode(QAbstractItemView::SingleSelection); - view->setCategoryDrawer(drawer); - view->setCollapsibleBlocks(true); - view->setViewMode(QListView::IconMode); - view->setFlow(QListView::LeftToRight); - view->setWordWrap(true); - view->setMouseTracking(true); - view->viewport()->setAttribute(Qt::WA_Hover); + // view->setCategoryDrawer(drawer); + // view->setCollapsibleBlocks(true); + // view->setViewMode(QListView::IconMode); + // view->setFlow(QListView::LeftToRight); + // view->setWordWrap(true); + // view->setMouseTracking(true); + // view->viewport()->setAttribute(Qt::WA_Hover); auto delegate = new ListViewDelegate(); view->setItemDelegate(delegate); - view->setSpacing(10); - view->setUniformItemWidths(true); + // view->setSpacing(10); + // view->setUniformItemWidths(true); // do not show ugly blue border on the mac view->setAttribute(Qt::WA_MacShowFocusRect, false); @@ -331,7 +332,6 @@ MainWindow::~MainWindow() { delete ui; delete proxymodel; - delete drawer; } void MainWindow::showInstanceContextMenu(const QPoint &pos) diff --git a/gui/MainWindow.h b/gui/MainWindow.h index eeba2c26..4d9e165d 100644 --- a/gui/MainWindow.h +++ b/gui/MainWindow.h @@ -27,9 +27,6 @@ class QToolButton; class LabeledToolButton; class QLabel; -class InstanceProxyModel; -class KCategorizedView; -class KCategoryDrawer; class MinecraftProcess; class ConsoleWindow; @@ -185,8 +182,7 @@ protected: private: Ui::MainWindow *ui; - KCategoryDrawer *drawer; - KCategorizedView *view; + class GroupView *view; InstanceProxyModel *proxymodel; MinecraftProcess *proc; ConsoleWindow *console; diff --git a/gui/dialogs/IconPickerDialog.cpp b/gui/dialogs/IconPickerDialog.cpp index f7970b37..9b1c26ff 100644 --- a/gui/dialogs/IconPickerDialog.cpp +++ b/gui/dialogs/IconPickerDialog.cpp @@ -23,7 +23,7 @@ #include "ui_IconPickerDialog.h" #include "gui/Platform.h" -#include "gui/widgets/InstanceDelegate.h" +#include "gui/groupview/InstanceDelegate.h" #include "logic/icons/IconList.h" diff --git a/gui/groupview/Group.cpp b/gui/groupview/Group.cpp new file mode 100644 index 00000000..f216cc6e --- /dev/null +++ b/gui/groupview/Group.cpp @@ -0,0 +1,169 @@ +#include "Group.h" + +#include +#include +#include + +#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(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 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 Group::items() const +{ + QList 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 indices = items(); + return indices.isEmpty() ? QModelIndex() : indices.first(); +} + +QModelIndex Group::lastItem() const +{ + QList indices = items(); + return indices.isEmpty() ? QModelIndex() : indices.last(); +} diff --git a/gui/groupview/Group.h b/gui/groupview/Group.h new file mode 100644 index 00000000..455ee1a8 --- /dev/null +++ b/gui/groupview/Group.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include + +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 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 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/gui/groupview/GroupView.cpp b/gui/groupview/GroupView.cpp new file mode 100644 index 00000000..89e3e223 --- /dev/null +++ b/gui/groupview/GroupView.cpp @@ -0,0 +1,908 @@ +#include "GroupView.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Group.h" + +template bool listsIntersect(const QList &l1, const QList 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 &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 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 GroupView::categoryInternalPosition(const QModelIndex &index) const +{ + QList 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 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 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 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 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(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> GroupView::draggablePaintPairs(const QModelIndexList &indices, + QRect *r) const +{ + Q_ASSERT(r); + QRect &rect = *r; + QList> 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 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 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 pos = categoryInternalPosition(current); + int row = beginning_row + pos.second; + qDebug() << "row: " << row; + qDebug() << "column: " << pos.first; + } + return current; +} diff --git a/gui/groupview/GroupView.h b/gui/groupview/GroupView.h new file mode 100644 index 00000000..329a3503 --- /dev/null +++ b/gui/groupview/GroupView.h @@ -0,0 +1,139 @@ +#pragma once + +#include +#include +#include + +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 &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 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 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> draggablePaintPairs(const QModelIndexList &indices, + QRect *r) const; + + bool isDragEventAccepted(QDropEvent *event); + + QPair rowDropPos(const QPoint &pos); + + QPoint offset() const; +}; diff --git a/gui/groupview/GroupedProxyModel.cpp b/gui/groupview/GroupedProxyModel.cpp new file mode 100644 index 00000000..d9d6ac78 --- /dev/null +++ b/gui/groupview/GroupedProxyModel.cpp @@ -0,0 +1,26 @@ +#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 subSortLessThan(left, right); + } + else + { + return leftCategory < rightCategory; + } +} + +bool GroupedProxyModel::subSortLessThan(const QModelIndex &left, const QModelIndex &right) const +{ + return left.row() < right.row(); +} diff --git a/gui/groupview/GroupedProxyModel.h b/gui/groupview/GroupedProxyModel.h new file mode 100644 index 00000000..12edee0f --- /dev/null +++ b/gui/groupview/GroupedProxyModel.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +class GroupedProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + GroupedProxyModel(QObject *parent = 0); + +protected: + virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + virtual bool subSortLessThan(const QModelIndex &left, const QModelIndex &right) const; +}; diff --git a/gui/groupview/InstanceDelegate.cpp b/gui/groupview/InstanceDelegate.cpp new file mode 100644 index 00000000..8527e2bc --- /dev/null +++ b/gui/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 +#include +#include +#include +#include + +#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/gui/groupview/InstanceDelegate.h b/gui/groupview/InstanceDelegate.h new file mode 100644 index 00000000..de2f429b --- /dev/null +++ b/gui/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 + +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/gui/widgets/InstanceDelegate.cpp b/gui/widgets/InstanceDelegate.cpp deleted file mode 100644 index 33da7130..00000000 --- a/gui/widgets/InstanceDelegate.cpp +++ /dev/null @@ -1,231 +0,0 @@ -/* 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 -#include -#include -#include -#include -#include "Common.h" -#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); -} - -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); - } - - 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/gui/widgets/InstanceDelegate.h b/gui/widgets/InstanceDelegate.h deleted file mode 100644 index 6f924405..00000000 --- a/gui/widgets/InstanceDelegate.h +++ /dev/null @@ -1,27 +0,0 @@ -/* 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 - -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/logic/lists/InstanceList.cpp b/logic/lists/InstanceList.cpp index 0d4eab95..935f9fd3 100644 --- a/logic/lists/InstanceList.cpp +++ b/logic/lists/InstanceList.cpp @@ -33,6 +33,7 @@ #include "logic/BaseInstance.h" #include "logic/InstanceFactory.h" #include "logger/QsLog.h" +#include const static int GROUP_FILE_FORMAT_VERSION = 1; @@ -96,8 +97,7 @@ QVariant InstanceList::data(const QModelIndex &index, int role) const return MMC->icons()->getIcon(key); } // for now. - case KCategorizedSortFilterProxyModel::CategorySortRole: - case KCategorizedSortFilterProxyModel::CategoryDisplayRole: + case GroupViewRoles::GroupRole: { return pdata->group(); } @@ -585,10 +585,8 @@ void InstanceList::propertiesChanged(BaseInstance *inst) } InstanceProxyModel::InstanceProxyModel(QObject *parent) - : KCategorizedSortFilterProxyModel(parent) + : GroupedProxyModel(parent) { - // disable since by default we are globally sorting by date: - setCategorizedModel(true); } bool InstanceProxyModel::subSortLessThan(const QModelIndex &left, diff --git a/logic/lists/InstanceList.h b/logic/lists/InstanceList.h index 0ce808e5..cda51a03 100644 --- a/logic/lists/InstanceList.h +++ b/logic/lists/InstanceList.h @@ -18,7 +18,7 @@ #include #include #include -#include "categorizedsortfilterproxymodel.h" +#include #include #include "logic/BaseInstance.h" @@ -129,7 +129,7 @@ protected: QSet m_groups; }; -class InstanceProxyModel : public KCategorizedSortFilterProxyModel +class InstanceProxyModel : public GroupedProxyModel { public: explicit InstanceProxyModel(QObject *parent = 0); -- cgit From 6206a241ea51864b22d493d5b7b0a9282c754571 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Tue, 4 Feb 2014 02:01:11 +0100 Subject: A try at fixing the instance delegate. A bit. Maybe. --- gui/groupview/InstanceDelegate.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gui/groupview/InstanceDelegate.cpp b/gui/groupview/InstanceDelegate.cpp index 8527e2bc..8a273758 100644 --- a/gui/groupview/InstanceDelegate.cpp +++ b/gui/groupview/InstanceDelegate.cpp @@ -173,6 +173,7 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti opt2.palette.setCurrentColorGroup(cg); // fill in background, if any + if (opt.backgroundBrush.style() != Qt::NoBrush) { QPointF oldBO = painter->brushOrigin(); @@ -181,6 +182,9 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti painter->setBrushOrigin(oldBO); } + drawSelectionRect(painter, opt2, textHighlightRect); + + /* if (opt.showDecorationSelected) { drawSelectionRect(painter, opt2, opt.rect); @@ -200,6 +204,7 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti drawFocusRect(painter, opt2, textHighlightRect); } } + */ } // draw the icon -- cgit From c84c51860d4a39f9219998826c093e4e60b7bf0a Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Tue, 4 Feb 2014 21:18:02 +0100 Subject: Fix crash bug related to data changes in new group view. --- gui/groupview/GroupView.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gui/groupview/GroupView.cpp b/gui/groupview/GroupView.cpp index 89e3e223..fb4b45f4 100644 --- a/gui/groupview/GroupView.cpp +++ b/gui/groupview/GroupView.cpp @@ -48,10 +48,14 @@ GroupView::~GroupView() void GroupView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { + /* if (roles.contains(GroupViewRoles::GroupRole) || roles.contains(Qt::DisplayRole)) { + */ updateGeometries(); + /* } + */ viewport()->update(); } void GroupView::rowsInserted(const QModelIndex &parent, int start, int end) -- cgit From 573d4c8050fa6548e4680fd74c1a0872e9a62c34 Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Wed, 5 Feb 2014 01:34:50 +0100 Subject: Paint the headers nicer. --- gui/groupview/Group.cpp | 66 +++++++++++++++++++++++++++++---------------- gui/groupview/Group.h | 3 ++- gui/groupview/GroupView.cpp | 9 ++++--- 3 files changed, 50 insertions(+), 28 deletions(-) diff --git a/gui/groupview/Group.cpp b/gui/groupview/Group.cpp index f216cc6e..7cce674a 100644 --- a/gui/groupview/Group.cpp +++ b/gui/groupview/Group.cpp @@ -35,18 +35,18 @@ Group::HitResults Group::hitScan(const QPoint &pos) const int body_end = body_start + contentHeight() + 5; // FIXME: wtf is this 5? int y = pos.y(); // int x = pos.x(); - if(y < y_start) + if (y < y_start) { results = Group::NoHit; } - else if(y < body_start) + 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)) + if (iconRect.contains(pos)) { results |= Group::CheckboxHit; } @@ -58,37 +58,53 @@ Group::HitResults Group::hitScan(const QPoint &pos) const return results; } -void Group::drawHeader(QPainter *painter, const int y) +void Group::drawHeader(QPainter *painter, const QStyleOptionViewItem &option, const int y) { + QStyleOptionViewItemV4 opt = option; 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; + static const int spacing = 10; + int height = headerHeight(); + int text_height = height - 2 * margin; + + // set the text colors + QPalette::ColorGroup cg = QPalette::Normal; + painter->setPen(opt.palette.color(cg, QPalette::Text)); + + // set up geometry + QRect iconRect = QRect(view->m_leftMargin + margin, y + margin, text_height - 1, text_height - 1); QRect iconSubrect = iconRect.adjusted(margin, margin, -margin, -margin); + QRect smallRect = iconSubrect.adjusted(margin, margin, -margin, -margin); int midX = iconSubrect.center().x(); int midY = iconSubrect.center().y(); - if (collapsed) + + // checkboxy thingy { - painter->drawLine(midX, iconSubrect.top(), midX, iconSubrect.bottom()); + painter->drawRect(iconSubrect); + + painter->setBrush(opt.palette.text()); + painter->drawRect(smallRect.x(), midY, smallRect.width(), 2); + if(collapsed) + { + painter->drawRect(midX, smallRect.y(), 2, smallRect.height()); + } } - 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); + int x_left = iconRect.right(); + if(text.length()) + { + // the text + int text_width = painter->fontMetrics().width(text); + QRect textRect = QRect(x_left + spacing, y + margin, text_width, text_height); + x_left = textRect.right(); + view->style()->drawItemText(painter, textRect, Qt::AlignHCenter | Qt::AlignVCenter, + opt.palette, true, text); + } // the line - painter->drawLine(textRect.right() + 4, y + headerHeight() / 2, - view->contentWidth() - view->m_rightMargin, y + headerHeight() / 2); + painter->drawLine(x_left + spacing, midY + 1, view->contentWidth() - view->m_rightMargin, + midY + 1); painter->restore(); } @@ -100,7 +116,11 @@ int Group::totalHeight() const int Group::headerHeight() const { - return view->viewport()->fontMetrics().height() + 4; + int raw = view->viewport()->fontMetrics().height() + 4; + // add english. maybe. depends on font height. + if(raw % 2 == 0) + raw++; + return std::min( raw , 25 ); } int Group::contentHeight() const diff --git a/gui/groupview/Group.h b/gui/groupview/Group.h index 455ee1a8..46a12f63 100644 --- a/gui/groupview/Group.h +++ b/gui/groupview/Group.h @@ -3,6 +3,7 @@ #include #include #include +#include class GroupView; class QPainter; @@ -26,7 +27,7 @@ struct Group void update(); /// draw the header at y-position. - void drawHeader(QPainter *painter, const int y); + void drawHeader(QPainter *painter, const QStyleOptionViewItem &option, const int y); /// height of the group, in total. includes a small bit of padding. int totalHeight() const; diff --git a/gui/groupview/GroupView.cpp b/gui/groupview/GroupView.cpp index fb4b45f4..9e186854 100644 --- a/gui/groupview/GroupView.cpp +++ b/gui/groupview/GroupView.cpp @@ -206,7 +206,7 @@ int GroupView::categoryRowHeight(const QModelIndex &index) const largestHeight = qMax(largestHeight, itemDelegate()->sizeHint(viewOptions(), i).height()); } - return largestHeight; + return largestHeight + m_spacing; } QPair GroupView::categoryInternalPosition(const QModelIndex &index) const @@ -423,11 +423,14 @@ void GroupView::paintEvent(QPaintEvent *event) { QPainter painter(this->viewport()); + QStyleOptionViewItemV4 option(viewOptions()); + option.widget = this; + int y = -verticalOffset(); for (int i = 0; i < m_groups.size(); ++i) { Group *category = m_groups.at(i); - category->drawHeader(&painter, y); + category->drawHeader(&painter, option, y); y += category->totalHeight() + m_categoryMargin; } @@ -439,9 +442,7 @@ void GroupView::paintEvent(QPaintEvent *event) 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)) -- cgit From aa41b891f06a629099d108ab1f4df94d1bbc828c Mon Sep 17 00:00:00 2001 From: Petr Mrázek Date: Sat, 8 Feb 2014 21:46:29 +0100 Subject: Group View: Use painting code from the previous group headers, small optimizations --- gui/groupview/Group.cpp | 105 +++++++++++++++++++++++++++++++++++++++----- gui/groupview/Group.h | 9 ++-- gui/groupview/GroupView.cpp | 22 ++++++++-- 3 files changed, 117 insertions(+), 19 deletions(-) diff --git a/gui/groupview/Group.cpp b/gui/groupview/Group.cpp index 7cce674a..f3319dc3 100644 --- a/gui/groupview/Group.cpp +++ b/gui/groupview/Group.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "GroupView.h" @@ -58,8 +59,9 @@ Group::HitResults Group::hitScan(const QPoint &pos) const return results; } -void Group::drawHeader(QPainter *painter, const QStyleOptionViewItem &option, const int y) +void Group::drawHeader(QPainter *painter, const QStyleOptionViewItem &option) { + /* QStyleOptionViewItemV4 opt = option; painter->save(); @@ -107,6 +109,95 @@ void Group::drawHeader(QPainter *painter, const QStyleOptionViewItem &option, co midY + 1); painter->restore(); + */ + painter->setRenderHint(QPainter::Antialiasing); + + const QRect optRect = option.rect; + QFont font(QApplication::font()); + font.setBold(true); + const QFontMetrics fontMetrics = QFontMetrics(font); + + QColor outlineColor = option.palette.text().color(); + outlineColor.setAlphaF(0.35); + + //BEGIN: top left corner + { + painter->save(); + painter->setPen(outlineColor); + const QPointF topLeft(optRect.topLeft()); + QRectF arc(topLeft, QSizeF(4, 4)); + arc.translate(0.5, 0.5); + painter->drawArc(arc, 1440, 1440); + painter->restore(); + } + //END: top left corner + + //BEGIN: left vertical line + { + QPoint start(optRect.topLeft()); + start.ry() += 3; + QPoint verticalGradBottom(optRect.topLeft()); + verticalGradBottom.ry() += fontMetrics.height() + 5; + QLinearGradient gradient(start, verticalGradBottom); + gradient.setColorAt(0, outlineColor); + gradient.setColorAt(1, Qt::transparent); + painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); + } + //END: left vertical line + + //BEGIN: horizontal line + { + QPoint start(optRect.topLeft()); + start.rx() += 3; + QPoint horizontalGradTop(optRect.topLeft()); + horizontalGradTop.rx() += optRect.width() - 6; + painter->fillRect(QRect(start, QSize(optRect.width() - 6, 1)), outlineColor); + } + //END: horizontal line + + //BEGIN: top right corner + { + painter->save(); + painter->setPen(outlineColor); + QPointF topRight(optRect.topRight()); + topRight.rx() -= 4; + QRectF arc(topRight, QSizeF(4, 4)); + arc.translate(0.5, 0.5); + painter->drawArc(arc, 0, 1440); + painter->restore(); + } + //END: top right corner + + //BEGIN: right vertical line + { + QPoint start(optRect.topRight()); + start.ry() += 3; + QPoint verticalGradBottom(optRect.topRight()); + verticalGradBottom.ry() += fontMetrics.height() + 5; + QLinearGradient gradient(start, verticalGradBottom); + gradient.setColorAt(0, outlineColor); + gradient.setColorAt(1, Qt::transparent); + painter->fillRect(QRect(start, QSize(1, fontMetrics.height() + 5)), gradient); + } + //END: right vertical line + + //BEGIN: text + { + QRect textRect(option.rect); + textRect.setTop(textRect.top() + 7); + textRect.setLeft(textRect.left() + 7); + textRect.setHeight(fontMetrics.height()); + textRect.setRight(textRect.right() - 7); + + painter->save(); + painter->setFont(font); + QColor penColor(option.palette.text().color()); + penColor.setAlphaF(0.6); + painter->setPen(penColor); + painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, text); + painter->restore(); + } + //END: text } int Group::totalHeight() const @@ -144,17 +235,7 @@ int Group::numRows() const int Group::verticalPosition() const { - int res = 0; - const QList 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; + return m_verticalPosition; } QList Group::items() const diff --git a/gui/groupview/Group.h b/gui/groupview/Group.h index 46a12f63..3b797f4c 100644 --- a/gui/groupview/Group.h +++ b/gui/groupview/Group.h @@ -16,18 +16,19 @@ struct Group Group(const Group *other); /* data */ - GroupView *view; + GroupView *view = nullptr; QString text; - bool collapsed; + bool collapsed = false; QVector rowHeights; - int firstItemIndex; + int firstItemIndex = 0; + int m_verticalPosition = 0; /* logic */ /// do stuff. and things. TODO: redo. void update(); /// draw the header at y-position. - void drawHeader(QPainter *painter, const QStyleOptionViewItem &option, const int y); + void drawHeader(QPainter *painter, const QStyleOptionViewItem &option); /// height of the group, in total. includes a small bit of padding. int totalHeight() const; diff --git a/gui/groupview/GroupView.cpp b/gui/groupview/GroupView.cpp index 9e186854..9954d743 100644 --- a/gui/groupview/GroupView.cpp +++ b/gui/groupview/GroupView.cpp @@ -114,12 +114,19 @@ void GroupView::updateGeometries() else { int totalHeight = 0; + // top margin + totalHeight += m_categoryMargin; for (auto category : m_groups) { + category->m_verticalPosition = totalHeight; totalHeight += category->totalHeight() + m_categoryMargin; } + /* // remove the last margin (we don't want it) totalHeight -= m_categoryMargin; + // add a margin on top... + totalHeight += m_categoryMargin; + */ totalHeight += m_bottomMargin; verticalScrollBar()->setRange(0, totalHeight - height()); } @@ -426,12 +433,22 @@ void GroupView::paintEvent(QPaintEvent *event) QStyleOptionViewItemV4 option(viewOptions()); option.widget = this; - int y = -verticalOffset(); + int wpWidth = viewport()->width(); + option.rect.setWidth(wpWidth); for (int i = 0; i < m_groups.size(); ++i) { Group *category = m_groups.at(i); - category->drawHeader(&painter, option, y); + int y = category->verticalPosition(); + y -= verticalOffset(); + QRect backup = option.rect; + int height = category->totalHeight(); + option.rect.setTop(y); + option.rect.setHeight(height); + option.rect.setLeft(m_leftMargin); + option.rect.setRight(wpWidth - m_rightMargin); + category->drawHeader(&painter, option); y += category->totalHeight() + m_categoryMargin; + option.rect = backup; } for (int i = 0; i < model()->rowCount(); ++i) @@ -587,7 +604,6 @@ void GroupView::startDrag(Qt::DropActions supportedActions) 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())) -- cgit