summaryrefslogtreecommitdiffstats
path: root/gui/groupview/GroupView.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gui/groupview/GroupView.cpp')
-rw-r--r--gui/groupview/GroupView.cpp908
1 files changed, 908 insertions, 0 deletions
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 <QPainter>
+#include <QApplication>
+#include <QtMath>
+#include <QDebug>
+#include <QMouseEvent>
+#include <QListView>
+#include <QPersistentModelIndex>
+#include <QDrag>
+#include <QMimeData>
+#include <QScrollBar>
+
+#include "Group.h"
+
+template <typename T> bool listsIntersect(const QList<T> &l1, const QList<T> t2)
+{
+ for (auto &item : l1)
+ {
+ if (t2.contains(item))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+GroupView::GroupView(QWidget *parent)
+ : QAbstractItemView(parent), m_leftMargin(5), m_rightMargin(5), m_bottomMargin(5),
+ m_categoryMargin(5) //, m_updatesDisabled(false), m_categoryEditor(0), m_editedCategory(0)
+{
+ // setViewMode(IconMode);
+ // setMovement(Snap);
+ setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+ // setWordWrap(true);
+ // setDragDropMode(QListView::InternalMove);
+ setAcceptDrops(true);
+ m_spacing = 5;
+}
+
+GroupView::~GroupView()
+{
+ qDeleteAll(m_groups);
+ m_groups.clear();
+}
+
+void GroupView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
+ const QVector<int> &roles)
+{
+ if (roles.contains(GroupViewRoles::GroupRole) || roles.contains(Qt::DisplayRole))
+ {
+ updateGeometries();
+ }
+ viewport()->update();
+}
+void GroupView::rowsInserted(const QModelIndex &parent, int start, int end)
+{
+ updateGeometries();
+ viewport()->update();
+}
+
+void GroupView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
+{
+ updateGeometries();
+ viewport()->update();
+}
+
+void GroupView::updateGeometries()
+{
+ int previousScroll = verticalScrollBar()->value();
+
+ QMap<QString, Group *> cats;
+
+ for (int i = 0; i < model()->rowCount(); ++i)
+ {
+ const QString groupName =
+ model()->index(i, 0).data(GroupViewRoles::GroupRole).toString();
+ if (!cats.contains(groupName))
+ {
+ Group *old = this->category(groupName);
+ if (old)
+ {
+ cats.insert(groupName, new Group(old));
+ }
+ else
+ {
+ cats.insert(groupName, new Group(groupName, this));
+ }
+ }
+ }
+
+ /*if (m_editedCategory)
+ {
+ m_editedCategory = cats[m_editedCategory->text];
+ }*/
+
+ qDeleteAll(m_groups);
+ m_groups = cats.values();
+
+ for (auto cat : m_groups)
+ {
+ cat->update();
+ }
+
+ if (m_groups.isEmpty())
+ {
+ verticalScrollBar()->setRange(0, 0);
+ }
+ else
+ {
+ int totalHeight = 0;
+ for (auto category : m_groups)
+ {
+ totalHeight += category->totalHeight() + m_categoryMargin;
+ }
+ // remove the last margin (we don't want it)
+ totalHeight -= m_categoryMargin;
+ totalHeight += m_bottomMargin;
+ verticalScrollBar()->setRange(0, totalHeight - height());
+ }
+
+ verticalScrollBar()->setValue(qMin(previousScroll, verticalScrollBar()->maximum()));
+
+ viewport()->update();
+}
+
+bool GroupView::isIndexHidden(const QModelIndex &index) const
+{
+ Group *cat = category(index);
+ if (cat)
+ {
+ return cat->collapsed;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+Group *GroupView::category(const QModelIndex &index) const
+{
+ return category(index.data(GroupViewRoles::GroupRole).toString());
+}
+
+Group *GroupView::category(const QString &cat) const
+{
+ for (auto group : m_groups)
+ {
+ if (group->text == cat)
+ {
+ return group;
+ }
+ }
+ return nullptr;
+}
+
+Group *GroupView::categoryAt(const QPoint &pos) const
+{
+ for (auto group : m_groups)
+ {
+ if(group->hitScan(pos) & Group::CheckboxHit)
+ {
+ return group;
+ }
+ }
+ return nullptr;
+}
+
+int GroupView::itemsPerRow() const
+{
+ return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + m_spacing));
+}
+
+int GroupView::contentWidth() const
+{
+ return width() - m_leftMargin - m_rightMargin;
+}
+
+int GroupView::itemWidth() const
+{
+ return itemDelegate()
+ ->sizeHint(viewOptions(), model()->index(model()->rowCount() - 1, 0))
+ .width();
+}
+
+int GroupView::categoryRowHeight(const QModelIndex &index) const
+{
+ QModelIndexList indices;
+ int internalRow = categoryInternalPosition(index).second;
+ for (auto &i : category(index)->items())
+ {
+ if (categoryInternalPosition(i).second == internalRow)
+ {
+ indices.append(i);
+ }
+ }
+
+ int largestHeight = 0;
+ for (auto &i : indices)
+ {
+ largestHeight =
+ qMax(largestHeight, itemDelegate()->sizeHint(viewOptions(), i).height());
+ }
+ return largestHeight;
+}
+
+QPair<int, int> GroupView::categoryInternalPosition(const QModelIndex &index) const
+{
+ QList<QModelIndex> indices = category(index)->items();
+ int x = 0;
+ int y = 0;
+ const int perRow = itemsPerRow();
+ for (int i = 0; i < indices.size(); ++i)
+ {
+ if (indices.at(i) == index)
+ {
+ break;
+ }
+ ++x;
+ if (x == perRow)
+ {
+ x = 0;
+ ++y;
+ }
+ }
+ return qMakePair(x, y);
+}
+
+int GroupView::categoryInternalRowTop(const QModelIndex &index) const
+{
+ Group *cat = category(index);
+ int categoryInternalRow = categoryInternalPosition(index).second;
+ int result = 0;
+ for (int i = 0; i < categoryInternalRow; ++i)
+ {
+ result += cat->rowHeights.at(i);
+ }
+ return result;
+}
+
+int GroupView::itemHeightForCategoryRow(const Group *category, const int internalRow) const
+{
+ for (auto &i : category->items())
+ {
+ QPair<int, int> pos = categoryInternalPosition(i);
+ if (pos.second == internalRow)
+ {
+ return categoryRowHeight(i);
+ }
+ }
+ return -1;
+}
+
+void GroupView::mousePressEvent(QMouseEvent *event)
+{
+ // endCategoryEditor();
+
+ QPoint pos = event->pos() + offset();
+ QPersistentModelIndex index = indexAt(pos);
+
+ m_pressedIndex = index;
+ m_pressedAlreadySelected = selectionModel()->isSelected(m_pressedIndex);
+ QItemSelectionModel::SelectionFlags selection_flags = selectionCommand(index, event);
+ m_pressedPosition = pos;
+
+ m_pressedCategory = categoryAt(m_pressedPosition);
+ if (m_pressedCategory)
+ {
+ setState(m_pressedCategory->collapsed ? ExpandingState : CollapsingState);
+ event->accept();
+ return;
+ }
+
+ if (index.isValid() && (index.flags() & Qt::ItemIsEnabled))
+ {
+ // we disable scrollTo for mouse press so the item doesn't change position
+ // when the user is interacting with it (ie. clicking on it)
+ bool autoScroll = hasAutoScroll();
+ setAutoScroll(false);
+ selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
+ setAutoScroll(autoScroll);
+ QRect rect(m_pressedPosition, pos);
+ setSelection(rect, QItemSelectionModel::ClearAndSelect);
+
+ // signal handlers may change the model
+ emit pressed(index);
+ }
+ else
+ {
+ // Forces a finalize() even if mouse is pressed, but not on a item
+ selectionModel()->select(QModelIndex(), QItemSelectionModel::Select);
+ }
+}
+
+void GroupView::mouseMoveEvent(QMouseEvent *event)
+{
+ QPoint topLeft;
+ QPoint pos = event->pos() + offset();
+
+ if (state() == ExpandingState || state() == CollapsingState)
+ {
+ return;
+ }
+
+ if (state() == DraggingState)
+ {
+ topLeft = m_pressedPosition - offset();
+ if ((topLeft - event->pos()).manhattanLength() > QApplication::startDragDistance())
+ {
+ m_pressedIndex = QModelIndex();
+ startDrag(model()->supportedDragActions());
+ setState(NoState);
+ stopAutoScroll();
+ }
+ return;
+ }
+
+ if (selectionMode() != SingleSelection)
+ {
+ topLeft = m_pressedPosition - offset();
+ }
+ else
+ {
+ topLeft = pos;
+ }
+
+ if (m_pressedIndex.isValid() && (state() != DragSelectingState) &&
+ (event->buttons() != Qt::NoButton) && !selectedIndexes().isEmpty())
+ {
+ setState(DraggingState);
+ return;
+ }
+
+ if ((event->buttons() & Qt::LeftButton) && selectionModel())
+ {
+ setState(DragSelectingState);
+
+ setSelection(QRect(pos, pos), QItemSelectionModel::ClearAndSelect);
+ QModelIndex index = indexAt(pos);
+
+ // set at the end because it might scroll the view
+ if (index.isValid() && (index != selectionModel()->currentIndex()) &&
+ (index.flags() & Qt::ItemIsEnabled))
+ {
+ selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
+ }
+ }
+}
+
+void GroupView::mouseReleaseEvent(QMouseEvent *event)
+{
+ QPoint pos = event->pos() + offset();
+ QPersistentModelIndex index = indexAt(pos);
+
+ bool click = (index == m_pressedIndex && index.isValid()) ||
+ (m_pressedCategory && m_pressedCategory == categoryAt(pos));
+
+ if (click && m_pressedCategory)
+ {
+ if (state() == ExpandingState)
+ {
+ m_pressedCategory->collapsed = false;
+ updateGeometries();
+ viewport()->update();
+ event->accept();
+ return;
+ }
+ else if (state() == CollapsingState)
+ {
+ m_pressedCategory->collapsed = true;
+ updateGeometries();
+ viewport()->update();
+ event->accept();
+ return;
+ }
+ }
+
+ m_ctrlDragSelectionFlag = QItemSelectionModel::NoUpdate;
+
+ setState(NoState);
+
+ if (click)
+ {
+ if (event->button() == Qt::LeftButton)
+ {
+ emit clicked(index);
+ }
+ QStyleOptionViewItem option = viewOptions();
+ if (m_pressedAlreadySelected)
+ {
+ option.state |= QStyle::State_Selected;
+ }
+ if ((model()->flags(index) & Qt::ItemIsEnabled) &&
+ style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this))
+ {
+ emit activated(index);
+ }
+ }
+}
+
+void GroupView::mouseDoubleClickEvent(QMouseEvent *event)
+{
+ QModelIndex index = indexAt(event->pos());
+ if (!index.isValid() || !(index.flags() & Qt::ItemIsEnabled) || (m_pressedIndex != index))
+ {
+ QMouseEvent me(QEvent::MouseButtonPress, event->localPos(), event->windowPos(),
+ event->screenPos(), event->button(), event->buttons(),
+ event->modifiers());
+ mousePressEvent(&me);
+ return;
+ }
+ // signal handlers may change the model
+ QPersistentModelIndex persistent = index;
+ emit doubleClicked(persistent);
+}
+
+void GroupView::paintEvent(QPaintEvent *event)
+{
+ QPainter painter(this->viewport());
+
+ int y = -verticalOffset();
+ for (int i = 0; i < m_groups.size(); ++i)
+ {
+ Group *category = m_groups.at(i);
+ category->drawHeader(&painter, y);
+ y += category->totalHeight() + m_categoryMargin;
+ }
+
+ for (int i = 0; i < model()->rowCount(); ++i)
+ {
+ const QModelIndex index = model()->index(i, 0);
+ if (isIndexHidden(index))
+ {
+ continue;
+ }
+ Qt::ItemFlags flags = index.flags();
+ QStyleOptionViewItemV4 option(viewOptions());
+ option.rect = visualRect(index);
+ option.widget = this;
+ option.features |=
+ QStyleOptionViewItemV2::WrapText; // FIXME: what is the meaning of this anyway?
+ if (flags & Qt::ItemIsSelectable && selectionModel()->isSelected(index))
+ {
+ option.state |= selectionModel()->isSelected(index) ? QStyle::State_Selected
+ : QStyle::State_None;
+ }
+ else
+ {
+ option.state &= ~QStyle::State_Selected;
+ }
+ option.state |= (index == currentIndex()) ? QStyle::State_HasFocus : QStyle::State_None;
+ if (!(flags & Qt::ItemIsEnabled))
+ {
+ option.state &= ~QStyle::State_Enabled;
+ }
+ itemDelegate()->paint(&painter, option, index);
+ }
+
+ /*
+ * Drop indicators for manual reordering...
+ */
+#if 0
+ if (!m_lastDragPosition.isNull())
+ {
+ QPair<Group *, int> pair = rowDropPos(m_lastDragPosition);
+ Group *category = pair.first;
+ int row = pair.second;
+ if (category)
+ {
+ int internalRow = row - category->firstItemIndex;
+ QLine line;
+ if (internalRow >= category->numItems())
+ {
+ QRect toTheRightOfRect = visualRect(category->lastItem());
+ line = QLine(toTheRightOfRect.topRight(), toTheRightOfRect.bottomRight());
+ }
+ else
+ {
+ QRect toTheLeftOfRect = visualRect(model()->index(row, 0));
+ line = QLine(toTheLeftOfRect.topLeft(), toTheLeftOfRect.bottomLeft());
+ }
+ painter.save();
+ painter.setPen(QPen(Qt::black, 3));
+ painter.drawLine(line);
+ painter.restore();
+ }
+ }
+#endif
+}
+
+void GroupView::resizeEvent(QResizeEvent *event)
+{
+ // QListView::resizeEvent(event);
+
+ // if (m_categoryEditor)
+ // {
+ // m_categoryEditor->resize(qMax(contentWidth() / 2,
+ // m_editedCategory->textRect.width()),
+ // m_categoryEditor->height());
+ // }
+
+ updateGeometries();
+}
+
+void GroupView::dragEnterEvent(QDragEnterEvent *event)
+{
+ if (!isDragEventAccepted(event))
+ {
+ return;
+ }
+ m_lastDragPosition = event->pos() + offset();
+ viewport()->update();
+ event->accept();
+}
+
+void GroupView::dragMoveEvent(QDragMoveEvent *event)
+{
+ if (!isDragEventAccepted(event))
+ {
+ return;
+ }
+ m_lastDragPosition = event->pos() + offset();
+ viewport()->update();
+ event->accept();
+}
+
+void GroupView::dragLeaveEvent(QDragLeaveEvent *event)
+{
+ m_lastDragPosition = QPoint();
+ viewport()->update();
+}
+
+void GroupView::dropEvent(QDropEvent *event)
+{
+ m_lastDragPosition = QPoint();
+
+ stopAutoScroll();
+ setState(NoState);
+
+ if (event->source() != this || !(event->possibleActions() & Qt::MoveAction))
+ {
+ return;
+ }
+
+ QPair<Group *, int> dropPos = rowDropPos(event->pos() + offset());
+ const Group *category = dropPos.first;
+ const int row = dropPos.second;
+
+ if (row == -1)
+ {
+ viewport()->update();
+ return;
+ }
+
+ const QString categoryText = category->text;
+ if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex()))
+ {
+ model()->setData(model()->index(row, 0), categoryText,
+ GroupViewRoles::GroupRole);
+ event->setDropAction(Qt::MoveAction);
+ event->accept();
+ }
+ updateGeometries();
+ viewport()->update();
+}
+
+void GroupView::startDrag(Qt::DropActions supportedActions)
+{
+ QModelIndexList indexes = selectionModel()->selectedIndexes();
+ if (indexes.count() > 0)
+ {
+ QMimeData *data = model()->mimeData(indexes);
+ if (!data)
+ {
+ return;
+ }
+ QRect rect;
+ QPixmap pixmap = renderToPixmap(indexes, &rect);
+ //rect.translate(offset());
+ // rect.adjust(horizontalOffset(), verticalOffset(), 0, 0);
+ QDrag *drag = new QDrag(this);
+ drag->setPixmap(pixmap);
+ drag->setMimeData(data);
+ drag->setHotSpot(m_pressedPosition - rect.topLeft());
+ Qt::DropAction defaultDropAction = Qt::IgnoreAction;
+ if (this->defaultDropAction() != Qt::IgnoreAction &&
+ (supportedActions & this->defaultDropAction()))
+ {
+ defaultDropAction = this->defaultDropAction();
+ }
+ if (drag->exec(supportedActions, defaultDropAction) == Qt::MoveAction)
+ {
+ const QItemSelection selection = selectionModel()->selection();
+
+ for (auto it = selection.constBegin(); it != selection.constEnd(); ++it)
+ {
+ QModelIndex parent = (*it).parent();
+ if ((*it).left() != 0)
+ {
+ continue;
+ }
+ if ((*it).right() != (model()->columnCount(parent) - 1))
+ {
+ continue;
+ }
+ int count = (*it).bottom() - (*it).top() + 1;
+ model()->removeRows((*it).top(), count, parent);
+ }
+ }
+ }
+}
+
+QRect GroupView::visualRect(const QModelIndex &index) const
+{
+ return geometryRect(index).translated(-offset());
+}
+
+QRect GroupView::geometryRect(const QModelIndex &index) const
+{
+ if (!index.isValid() || isIndexHidden(index) || index.column() > 0)
+ {
+ return QRect();
+ }
+
+ const Group *cat = category(index);
+ QPair<int, int> pos = categoryInternalPosition(index);
+ int x = pos.first;
+ // int y = pos.second;
+
+ QRect out;
+ out.setTop(cat->verticalPosition() + cat->headerHeight() + 5 + categoryInternalRowTop(index));
+ out.setLeft(m_spacing + x * (itemWidth() + m_spacing));
+ out.setSize(itemDelegate()->sizeHint(viewOptions(), index));
+
+ return out;
+}
+
+/*
+void CategorizedView::startCategoryEditor(Category *category)
+{
+ if (m_categoryEditor != 0)
+ {
+ return;
+ }
+ m_editedCategory = category;
+ m_categoryEditor = new QLineEdit(m_editedCategory->text, this);
+ QRect rect = m_editedCategory->textRect;
+ rect.setWidth(qMax(contentWidth() / 2, rect.width()));
+ m_categoryEditor->setGeometry(rect);
+ m_categoryEditor->show();
+ m_categoryEditor->setFocus();
+ connect(m_categoryEditor, &QLineEdit::returnPressed, this,
+&CategorizedView::endCategoryEditor);
+}
+
+void CategorizedView::endCategoryEditor()
+{
+ if (m_categoryEditor == 0)
+ {
+ return;
+ }
+ m_editedCategory->text = m_categoryEditor->text();
+ m_updatesDisabled = true;
+ foreach (const QModelIndex &index, itemsForCategory(m_editedCategory))
+ {
+ const_cast<QAbstractItemModel *>(index.model())->setData(index,
+m_categoryEditor->text(), CategoryRole);
+ }
+ m_updatesDisabled = false;
+ delete m_categoryEditor;
+ m_categoryEditor = 0;
+ m_editedCategory = 0;
+ updateGeometries();
+}
+*/
+
+QModelIndex GroupView::indexAt(const QPoint &point) const
+{
+ for (int i = 0; i < model()->rowCount(); ++i)
+ {
+ QModelIndex index = model()->index(i, 0);
+ if (geometryRect(index).contains(point))
+ {
+ return index;
+ }
+ }
+ return QModelIndex();
+}
+
+void GroupView::setSelection(const QRect &rect,
+ const QItemSelectionModel::SelectionFlags commands)
+{
+ for (int i = 0; i < model()->rowCount(); ++i)
+ {
+ QModelIndex index = model()->index(i, 0);
+ QRect itemRect = geometryRect(index);
+ if (itemRect.intersects(rect))
+ {
+ selectionModel()->select(index, commands);
+ update(itemRect.translated(-offset()));
+ }
+ }
+
+}
+
+QPixmap GroupView::renderToPixmap(const QModelIndexList &indices, QRect *r) const
+{
+ Q_ASSERT(r);
+ auto paintPairs = draggablePaintPairs(indices, r);
+ if (paintPairs.isEmpty())
+ {
+ return QPixmap();
+ }
+ QPixmap pixmap(r->size());
+ pixmap.fill(Qt::transparent);
+ QPainter painter(&pixmap);
+ QStyleOptionViewItem option = viewOptions();
+ option.state |= QStyle::State_Selected;
+ for (int j = 0; j < paintPairs.count(); ++j)
+ {
+ option.rect = paintPairs.at(j).first.translated(-r->topLeft());
+ const QModelIndex &current = paintPairs.at(j).second;
+ itemDelegate()->paint(&painter, option, current);
+ }
+ return pixmap;
+}
+
+QList<QPair<QRect, QModelIndex>> GroupView::draggablePaintPairs(const QModelIndexList &indices,
+ QRect *r) const
+{
+ Q_ASSERT(r);
+ QRect &rect = *r;
+ QList<QPair<QRect, QModelIndex>> ret;
+ for (int i = 0; i < indices.count(); ++i)
+ {
+ const QModelIndex &index = indices.at(i);
+ const QRect current = geometryRect(index);
+ ret += qMakePair(current, index);
+ rect |= current;
+ }
+ return ret;
+}
+
+bool GroupView::isDragEventAccepted(QDropEvent *event)
+{
+ if (event->source() != this)
+ {
+ return false;
+ }
+ if (!listsIntersect(event->mimeData()->formats(), model()->mimeTypes()))
+ {
+ return false;
+ }
+ if (!model()->canDropMimeData(event->mimeData(), event->dropAction(),
+ rowDropPos(event->pos()).second, 0, QModelIndex()))
+ {
+ return false;
+ }
+ return true;
+}
+
+QPair<Group *, int> GroupView::rowDropPos(const QPoint &pos)
+{
+ // check that we aren't on a category header and calculate which category we're in
+ Group *category = 0;
+ {
+ int y = 0;
+ for (auto cat : m_groups)
+ {
+ if (pos.y() > y && pos.y() < (y + cat->headerHeight()))
+ {
+ return qMakePair(nullptr, -1);
+ }
+ y += cat->totalHeight() + m_categoryMargin;
+ if (pos.y() < y)
+ {
+ category = cat;
+ break;
+ }
+ }
+ if (category == 0)
+ {
+ return qMakePair(nullptr, -1);
+ }
+ }
+
+ QList<QModelIndex> indices = category->items();
+
+ // calculate the internal column
+ int internalColumn = -1;
+ {
+ const int itemWidth = this->itemWidth();
+ if (pos.x() >= (itemWidth * itemsPerRow()))
+ {
+ internalColumn = itemsPerRow();
+ }
+ else
+ {
+ for (int i = 0, c = 0; i < contentWidth(); i += itemWidth + 10 /*spacing()*/, ++c)
+ {
+ if (pos.x() > (i - itemWidth / 2) && pos.x() <= (i + itemWidth / 2))
+ {
+ internalColumn = c;
+ break;
+ }
+ }
+ }
+ if (internalColumn == -1)
+ {
+ return qMakePair(nullptr, -1);
+ }
+ }
+
+ // calculate the internal row
+ int internalRow = -1;
+ {
+ // FIXME rework the drag and drop code
+ const int top = category->verticalPosition();
+ for (int r = 0, h = top; r < category->numRows();
+ h += itemHeightForCategoryRow(category, r), ++r)
+ {
+ if (pos.y() > h && pos.y() < (h + itemHeightForCategoryRow(category, r)))
+ {
+ internalRow = r;
+ break;
+ }
+ }
+ if (internalRow == -1)
+ {
+ return qMakePair(nullptr, -1);
+ }
+ // this happens if we're in the margin between a one category and another
+ // categories header
+ if (internalRow > (indices.size() / itemsPerRow()))
+ {
+ return qMakePair(nullptr, -1);
+ }
+ }
+
+ // flaten the internalColumn/internalRow to one row
+ int categoryRow = internalRow * itemsPerRow() + internalColumn;
+
+ // this is used if we're past the last item
+ if (categoryRow >= indices.size())
+ {
+ return qMakePair(category, indices.last().row() + 1);
+ }
+
+ return qMakePair(category, indices.at(categoryRow).row());
+}
+
+QPoint GroupView::offset() const
+{
+ return QPoint(horizontalOffset(), verticalOffset());
+}
+
+QRegion GroupView::visualRegionForSelection(const QItemSelection &selection) const
+{
+ QRegion region;
+ for (auto &range : selection)
+ {
+ int start_row = range.top();
+ int end_row = range.bottom();
+ for (int row = start_row; row <= end_row; ++row)
+ {
+ int start_column = range.left();
+ int end_column = range.right();
+ for (int column = start_column; column <= end_column; ++column)
+ {
+ QModelIndex index = model()->index(row, column, rootIndex());
+ region += visualRect(index); // OK
+ }
+ }
+ }
+ return region;
+}
+QModelIndex GroupView::moveCursor(QAbstractItemView::CursorAction cursorAction,
+ Qt::KeyboardModifiers modifiers)
+{
+ auto current = currentIndex();
+ if(!current.isValid())
+ {
+ qDebug() << "model row: invalid";
+ return current;
+ }
+ qDebug() << "model row: " << current.row();
+ auto cat = category(current);
+ int i = m_groups.indexOf(cat);
+ if(i >= 0)
+ {
+ // this is a pile of something foul
+ auto real_group = m_groups[i];
+ int beginning_row = 0;
+ for(auto group: m_groups)
+ {
+ if(group == real_group)
+ break;
+ beginning_row += group->numRows();
+ }
+ qDebug() << "category: " << real_group->text;
+ QPair<int, int> pos = categoryInternalPosition(current);
+ int row = beginning_row + pos.second;
+ qDebug() << "row: " << row;
+ qDebug() << "column: " << pos.first;
+ }
+ return current;
+}