From 3fb7a0faf069fdc5a4f14a12dfd76c43819c3378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= 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 v1.2.3