From cc6968e9a3059451c4c1a5296ea5da7457ca831f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sat, 12 Jul 2014 21:13:23 +0200 Subject: Group view gets keyboard navigation back. And a bunch of fixes. --- gui/groupview/Group.cpp | 269 ------------------------ gui/groupview/Group.h | 69 ------- gui/groupview/GroupView.cpp | 463 +++++++++++++++++++++--------------------- gui/groupview/GroupView.h | 101 ++++----- gui/groupview/VisualGroup.cpp | 301 +++++++++++++++++++++++++++ gui/groupview/VisualGroup.h | 91 +++++++++ 6 files changed, 664 insertions(+), 630 deletions(-) delete mode 100644 gui/groupview/Group.cpp delete mode 100644 gui/groupview/Group.h create mode 100644 gui/groupview/VisualGroup.cpp create mode 100644 gui/groupview/VisualGroup.h (limited to 'gui/groupview') diff --git a/gui/groupview/Group.cpp b/gui/groupview/Group.cpp deleted file mode 100644 index 51aa6658..00000000 --- a/gui/groupview/Group.cpp +++ /dev/null @@ -1,269 +0,0 @@ -#include "Group.h" - -#include -#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 QStyleOptionViewItem &option) -{ - 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: checkboxy thing - { - painter->save(); - painter->setRenderHint(QPainter::Antialiasing, false); - painter->setFont(font); - QColor penColor(option.palette.text().color()); - penColor.setAlphaF(0.6); - painter->setPen(penColor); - QRect iconSubRect(option.rect); - iconSubRect.setTop(iconSubRect.top() + 7); - iconSubRect.setLeft(iconSubRect.left() + 7); - - int sizing = fontMetrics.height(); - int even = ( (sizing - 1) % 2 ); - - iconSubRect.setHeight(sizing - even); - iconSubRect.setWidth(sizing - even); - painter->drawRect(iconSubRect); - - - /* - if(collapsed) - painter->drawText(iconSubRect, Qt::AlignHCenter | Qt::AlignVCenter, "+"); - else - painter->drawText(iconSubRect, Qt::AlignHCenter | Qt::AlignVCenter, "-"); - */ - painter->setBrush(option.palette.text()); - painter->fillRect(iconSubRect.x(), iconSubRect.y() + iconSubRect.height() / 2, - iconSubRect.width(), 2, penColor); - if (collapsed) - { - painter->fillRect(iconSubRect.x() + iconSubRect.width() / 2, iconSubRect.y(), 2, - iconSubRect.height(), penColor); - } - - painter->restore(); - } - //END: checkboxy thing - - //BEGIN: text - { - QRect textRect(option.rect); - textRect.setTop(textRect.top() + 7); - textRect.setLeft(textRect.left() + 7 + fontMetrics.height() + 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 -{ - return headerHeight() + 5 + contentHeight(); // FIXME: wtf is that '5'? -} - -int Group::headerHeight() const -{ - 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 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 -{ - 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 -{ - return m_verticalPosition; -} - -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 deleted file mode 100644 index 3b797f4c..00000000 --- a/gui/groupview/Group.h +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -#include -#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 = nullptr; - QString text; - bool collapsed = false; - QVector rowHeights; - 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); - - /// 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 index 6f6f0f8e..3f85c6e9 100644 --- a/gui/groupview/GroupView.cpp +++ b/gui/groupview/GroupView.cpp @@ -11,7 +11,7 @@ #include #include -#include "Group.h" +#include "VisualGroup.h" #include "logger/QsLog.h" template bool listsIntersect(const QList &l1, const QList t2) @@ -27,17 +27,12 @@ template bool listsIntersect(const QList &l1, const QList t2) } 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) + : QAbstractItemView(parent) { - // setViewMode(IconMode); - // setMovement(Snap); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - // setWordWrap(true); - // setDragDropMode(QListView::InternalMove); setAcceptDrops(true); - m_spacing = 5; + setAutoScroll(true); } GroupView::~GroupView() @@ -72,7 +67,7 @@ void GroupView::updateGeometries() geometryCache.clear(); int previousScroll = verticalScrollBar()->value(); - QMap cats; + QMap cats; for (int i = 0; i < model()->rowCount(); ++i) { @@ -80,14 +75,14 @@ void GroupView::updateGeometries() model()->index(i, 0).data(GroupViewRoles::GroupRole).toString(); if (!cats.contains(groupName)) { - Group *old = this->category(groupName); + VisualGroup *old = this->category(groupName); if (old) { - cats.insert(groupName, new Group(old)); + cats.insert(groupName, new VisualGroup(old)); } else { - cats.insert(groupName, new Group(groupName, this)); + cats.insert(groupName, new VisualGroup(groupName, this)); } } } @@ -149,7 +144,7 @@ void GroupView::modelReset() bool GroupView::isIndexHidden(const QModelIndex &index) const { - Group *cat = category(index); + VisualGroup *cat = category(index); if (cat) { return cat->collapsed; @@ -160,12 +155,12 @@ bool GroupView::isIndexHidden(const QModelIndex &index) const } } -Group *GroupView::category(const QModelIndex &index) const +VisualGroup *GroupView::category(const QModelIndex &index) const { return category(index.data(GroupViewRoles::GroupRole).toString()); } -Group *GroupView::category(const QString &cat) const +VisualGroup *GroupView::category(const QString &cat) const { for (auto group : m_groups) { @@ -177,11 +172,11 @@ Group *GroupView::category(const QString &cat) const return nullptr; } -Group *GroupView::categoryAt(const QPoint &pos) const +VisualGroup *GroupView::categoryAt(const QPoint &pos) const { for (auto group : m_groups) { - if(group->hitScan(pos) & Group::CheckboxHit) + if(group->hitScan(pos) & VisualGroup::CheckboxHit) { return group; } @@ -189,7 +184,7 @@ Group *GroupView::categoryAt(const QPoint &pos) const return nullptr; } -int GroupView::itemsPerRow() const +int GroupView::calculateItemsPerRow() const { return qFloor((qreal)(contentWidth()) / (qreal)(itemWidth() + m_spacing)); } @@ -201,77 +196,7 @@ int GroupView::contentWidth() const 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 + m_spacing; -} - -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; + return m_itemWidth; } void GroupView::mousePressEvent(QMouseEvent *event) @@ -297,13 +222,19 @@ void GroupView::mousePressEvent(QMouseEvent *event) if (index.isValid() && (index.flags() & Qt::ItemIsEnabled)) { + if(index != currentIndex()) + { + // FIXME: better! + m_currentCursorColumn = -1; + } // 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(geometryPos, geometryPos); + QRect rect(visualPos, visualPos); setSelection(rect, QItemSelectionModel::ClearAndSelect); // signal handlers may change the model @@ -360,7 +291,7 @@ void GroupView::mouseMoveEvent(QMouseEvent *event) { setState(DragSelectingState); - setSelection(QRect(geometryPos, geometryPos), QItemSelectionModel::ClearAndSelect); + setSelection(QRect(visualPos, visualPos), QItemSelectionModel::ClearAndSelect); QModelIndex index = indexAt(visualPos); // set at the end because it might scroll the view @@ -453,7 +384,7 @@ void GroupView::paintEvent(QPaintEvent *event) option.rect.setWidth(wpWidth); for (int i = 0; i < m_groups.size(); ++i) { - Group *category = m_groups.at(i); + VisualGroup *category = m_groups.at(i); int y = category->verticalPosition(); y -= verticalOffset(); QRect backup = option.rect; @@ -529,16 +460,12 @@ void GroupView::paintEvent(QPaintEvent *event) 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(); + int newItemsPerRow = calculateItemsPerRow(); + if(newItemsPerRow != m_currentItemsPerRow) + { + m_currentItemsPerRow = newItemsPerRow; + updateGeometries(); + } } void GroupView::dragEnterEvent(QDragEnterEvent *event) @@ -581,8 +508,8 @@ void GroupView::dropEvent(QDropEvent *event) return; } - QPair dropPos = rowDropPos(event->pos() + offset()); - const Group *category = dropPos.first; + QPair dropPos = rowDropPos(event->pos() + offset()); + const VisualGroup *category = dropPos.first; const int row = dropPos.second; if (row == -1) @@ -606,44 +533,44 @@ void GroupView::dropEvent(QDropEvent *event) void GroupView::startDrag(Qt::DropActions supportedActions) { QModelIndexList indexes = selectionModel()->selectedIndexes(); - if (indexes.count() > 0) + if(indexes.count() == 0) + return; + + QMimeData *data = model()->mimeData(indexes); + if (!data) { - 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); - 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(); + 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); + 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) + for (auto it = selection.constBegin(); it != selection.constEnd(); ++it) + { + QModelIndex parent = (*it).parent(); + if ((*it).left() != 0) { - 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); + continue; + } + if ((*it).right() != (model()->columnCount(parent) - 1)) + { + continue; } + int count = (*it).bottom() - (*it).top() + 1; + model()->removeRows((*it).top(), count, parent); } } } @@ -665,60 +592,19 @@ QRect GroupView::geometryRect(const QModelIndex &index) const { return *geometryCache[row]; } - else - { - 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)); - const_cast&>(geometryCache).insert(row, new QRect(out)); - 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); -} + const VisualGroup *cat = category(index); + QPair pos = cat->positionOf(index); + int x = pos.first; + // int y = pos.second; -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(); + QRect out; + out.setTop(cat->verticalPosition() + cat->headerHeight() + 5 + cat->rowTopOf(index)); + out.setLeft(m_spacing + x * (itemWidth() + m_spacing)); + out.setSize(itemDelegate()->sizeHint(viewOptions(), index)); + geometryCache.insert(row, new QRect(out)); + return out; } -*/ QModelIndex GroupView::indexAt(const QPoint &point) const { @@ -733,21 +619,19 @@ QModelIndex GroupView::indexAt(const QPoint &point) const return QModelIndex(); } -// FIXME: is rect supposed to be geometry or visual coords? 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); + QRect itemRect = visualRect(index); if (itemRect.intersects(rect)) { selectionModel()->select(index, commands); update(itemRect.translated(-offset())); } } - } QPixmap GroupView::renderToPixmap(const QModelIndexList &indices, QRect *r) const @@ -790,33 +674,23 @@ QList> GroupView::draggablePaintPairs(const QModelInde 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; + return false; } -QPair GroupView::rowDropPos(const QPoint &pos) +QPair GroupView::rowDropPos(const QPoint &pos) { + return qMakePair(nullptr, -1); + // FIXME: PIXIE DUST. + /* // check that we aren't on a category header and calculate which category we're in - Group *category = 0; + VisualGroup *category = 0; { int y = 0; for (auto cat : m_groups) { if (pos.y() > y && pos.y() < (y + cat->headerHeight())) { - return qMakePair(nullptr, -1); + return qMakePair(nullptr, -1); } y += cat->totalHeight() + m_categoryMargin; if (pos.y() < y) @@ -827,7 +701,7 @@ QPair GroupView::rowDropPos(const QPoint &pos) } if (category == 0) { - return qMakePair(nullptr, -1); + return qMakePair(nullptr, -1); } } @@ -843,7 +717,7 @@ QPair GroupView::rowDropPos(const QPoint &pos) } else { - for (int i = 0, c = 0; i < contentWidth(); i += itemWidth + 10 /*spacing()*/, ++c) + for (int i = 0, c = 0; i < contentWidth(); i += itemWidth + 10 , ++c) { if (pos.x() > (i - itemWidth / 2) && pos.x() <= (i + itemWidth / 2)) { @@ -854,7 +728,7 @@ QPair GroupView::rowDropPos(const QPoint &pos) } if (internalColumn == -1) { - return qMakePair(nullptr, -1); + return qMakePair(nullptr, -1); } } @@ -874,13 +748,13 @@ QPair GroupView::rowDropPos(const QPoint &pos) } if (internalRow == -1) { - return qMakePair(nullptr, -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); + return qMakePair(nullptr, -1); } } @@ -894,6 +768,7 @@ QPair GroupView::rowDropPos(const QPoint &pos) } return qMakePair(category, indices.at(categoryRow).row()); + */ } QPoint GroupView::offset() const @@ -921,34 +796,162 @@ QRegion GroupView::visualRegionForSelection(const QItemSelection &selection) con } return region; } + QModelIndex GroupView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers) { auto current = currentIndex(); if(!current.isValid()) { - QLOG_DEBUG() << "model row: invalid"; return current; } - QLOG_DEBUG() << "model row: " << current.row(); auto cat = category(current); - int i = m_groups.indexOf(cat); - if(i >= 0) + int group_index = m_groups.indexOf(cat); + if(group_index < 0) + return current; + + auto real_group = m_groups[group_index]; + int beginning_row = 0; + for(auto group: m_groups) + { + if(group == real_group) + break; + beginning_row += group->numRows(); + } + + QPair pos = cat->positionOf(current); + int column = pos.first; + int row = pos.second; + if(m_currentCursorColumn < 0) { - // this is a pile of something foul - auto real_group = m_groups[i]; - int beginning_row = 0; - for(auto group: m_groups) + m_currentCursorColumn = column; + } + switch(cursorAction) + { + case MoveUp: { - if(group == real_group) - break; - beginning_row += group->numRows(); + if(row == 0) + { + if(group_index == 0) + return current; + auto prevgroup = m_groups[group_index-1]; + int newRow = prevgroup->numRows() - 1; + int newRowSize = prevgroup->rows[newRow].size(); + int newColumn = m_currentCursorColumn; + if (m_currentCursorColumn >= newRowSize) + { + newColumn = newRowSize - 1; + } + return prevgroup->rows[newRow][newColumn]; + } + else + { + int newRow = row - 1; + int newRowSize = cat->rows[newRow].size(); + int newColumn = m_currentCursorColumn; + if (m_currentCursorColumn >= newRowSize) + { + newColumn = newRowSize - 1; + } + return cat->rows[newRow][newColumn]; + } } - QLOG_DEBUG() << "category: " << real_group->text; - QPair pos = categoryInternalPosition(current); - int row = beginning_row + pos.second; - QLOG_DEBUG() << "row: " << row; - QLOG_DEBUG() << "column: " << pos.first; + case MoveDown: + { + if(row == cat->rows.size() - 1) + { + if(group_index == m_groups.size() - 1) + return current; + auto nextgroup = m_groups[group_index+1]; + int newRowSize = nextgroup->rows[0].size(); + int newColumn = m_currentCursorColumn; + if (m_currentCursorColumn >= newRowSize) + { + newColumn = newRowSize - 1; + } + return nextgroup->rows[0][newColumn]; + } + else + { + int newRow = row + 1; + int newRowSize = cat->rows[newRow].size(); + int newColumn = m_currentCursorColumn; + if (m_currentCursorColumn >= newRowSize) + { + newColumn = newRowSize - 1; + } + return cat->rows[newRow][newColumn]; + } + } + case MoveLeft: + { + if(column > 0) + { + m_currentCursorColumn = column - 1; + return cat->rows[row][column - 1]; + } + return current; + } + case MoveRight: + { + if(column < cat->rows[row].size() - 1) + { + m_currentCursorColumn = column + 1; + return cat->rows[row][column + 1]; + } + return current; + } + default: + break; } return current; } + +int GroupView::horizontalOffset() const +{ + return horizontalScrollBar()->value(); +} + +int GroupView::verticalOffset() const +{ + return verticalScrollBar()->value(); +} + +void GroupView::scrollContentsBy(int dx, int dy) +{ + scrollDirtyRegion(dx, dy); + viewport()->scroll(dx, dy); +} + +void GroupView::scrollTo(const QModelIndex &index, ScrollHint hint) +{ + if (!index.isValid()) + return; + + const QRect rect = visualRect(index); + if (hint == EnsureVisible && viewport()->rect().contains(rect)) + { + viewport()->update(rect); + return; + } + + verticalScrollBar()->setValue(verticalScrollToValue(index, rect, hint)); +} + +int GroupView::verticalScrollToValue(const QModelIndex &index, const QRect &rect, + QListView::ScrollHint hint) const +{ + const QRect area = viewport()->rect(); + const bool above = (hint == QListView::EnsureVisible && rect.top() < area.top()); + const bool below = (hint == QListView::EnsureVisible && rect.bottom() > area.bottom()); + + int verticalValue = verticalScrollBar()->value(); + QRect adjusted = rect.adjusted(-spacing(), -spacing(), spacing(), spacing()); + if (hint == QListView::PositionAtTop || above) + verticalValue += adjusted.top(); + else if (hint == QListView::PositionAtBottom || below) + verticalValue += qMin(adjusted.top(), adjusted.bottom() - area.height() + 1); + else if (hint == QListView::PositionAtCenter) + verticalValue += adjusted.top() - ((area.height() - adjusted.height()) / 2); + return verticalValue; +} diff --git a/gui/groupview/GroupView.h b/gui/groupview/GroupView.h index 736bfbeb..93e45ed7 100644 --- a/gui/groupview/GroupView.h +++ b/gui/groupview/GroupView.h @@ -15,7 +15,7 @@ struct GroupViewRoles }; }; -struct Group; +struct VisualGroup; class GroupView : public QAbstractItemView { @@ -36,35 +36,20 @@ public: 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 int horizontalOffset() const override; + virtual int verticalOffset() const override; + virtual void scrollContentsBy(int dx, int dy) override; + virtual void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) override; virtual QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override; virtual QRegion visualRegionForSelection(const QItemSelection &selection) const override; + int spacing() const + { + return m_spacing; + }; protected slots: virtual void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, @@ -91,58 +76,50 @@ protected: 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();*/ + friend struct VisualGroup; + QList m_groups; + + // geometry + int m_leftMargin = 5; + int m_rightMargin = 5; + int m_bottomMargin = 5; + int m_categoryMargin = 5; + int m_spacing = 5; + int m_itemWidth = 100; + int m_currentItemsPerRow = -1; + int m_currentCursorColumn= -1; + mutable QCache geometryCache; -private: /* variables */ - /// point where the currently active mouse action started in geometry coordinates + // point where the currently active mouse action started in geometry coordinates QPoint m_pressedPosition; QPersistentModelIndex m_pressedIndex; bool m_pressedAlreadySelected; - Group *m_pressedCategory; + VisualGroup *m_pressedCategory; QItemSelectionModel::SelectionFlag m_ctrlDragSelectionFlag; QPoint m_lastDragPosition; - int m_spacing = 5; - QCache geometryCache; -private: /* methods */ - QPair categoryInternalPosition(const QModelIndex &index) const; - int categoryInternalRowTop(const QModelIndex &index) const; - int itemHeightForCategoryRow(const Group *category, const int internalRow) const; + VisualGroup *category(const QModelIndex &index) const; + VisualGroup *category(const QString &cat) const; + VisualGroup *categoryAt(const QPoint &pos) const; + int itemsPerRow() const + { + return m_currentItemsPerRow; + }; + int contentWidth() const; + +private: /* methods */ + int itemWidth() const; + int calculateItemsPerRow() const; + int verticalScrollToValue(const QModelIndex &index, const QRect &rect, + QListView::ScrollHint hint) 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); + QPair rowDropPos(const QPoint &pos); QPoint offset() const; }; diff --git a/gui/groupview/VisualGroup.cpp b/gui/groupview/VisualGroup.cpp new file mode 100644 index 00000000..560fc9ca --- /dev/null +++ b/gui/groupview/VisualGroup.cpp @@ -0,0 +1,301 @@ +#include "VisualGroup.h" + +#include +#include +#include +#include + +#include "GroupView.h" + +VisualGroup::VisualGroup(const QString &text, GroupView *view) : view(view), text(text), collapsed(false) +{ +} + +VisualGroup::VisualGroup(const VisualGroup *other) + : view(other->view), text(other->text), collapsed(other->collapsed) +{ +} + +void VisualGroup::update() +{ + auto temp_items = items(); + auto itemsPerRow = view->itemsPerRow(); + + int numRows = qMax(1, qCeil((qreal)temp_items.size() / (qreal)itemsPerRow)); + rows = QVector(numRows); + + int maxRowHeight = 0; + int positionInRow = 0; + int currentRow = 0; + int offsetFromTop = 0; + for (auto item: temp_items) + { + if(positionInRow == itemsPerRow) + { + rows[currentRow].height = maxRowHeight; + rows[currentRow].top = offsetFromTop; + currentRow ++; + offsetFromTop += maxRowHeight + 5; + positionInRow = 0; + maxRowHeight = 0; + } + auto itemHeight = view->itemDelegate()->sizeHint(view->viewOptions(), item).height(); + if(itemHeight > maxRowHeight) + { + maxRowHeight = itemHeight; + } + rows[currentRow].items.append(item); + positionInRow++; + } + rows[currentRow].height = maxRowHeight; + rows[currentRow].top = offsetFromTop; +} + +QPair VisualGroup::positionOf(const QModelIndex &index) const +{ + int x = 0; + int y = 0; + for (auto & row: rows) + { + for(auto x = 0; x < row.items.size(); x++) + { + if(row.items[x] == index) + { + return qMakePair(x,y); + } + } + y++; + } + return qMakePair(x, y); +} + +int VisualGroup::rowTopOf(const QModelIndex &index) const +{ + auto position = positionOf(index); + return rows[position.second].top; +} + +int VisualGroup::rowHeightOf(const QModelIndex &index) const +{ + auto position = positionOf(index); + return rows[position.second].height; +} + +VisualGroup::HitResults VisualGroup::hitScan(const QPoint &pos) const +{ + VisualGroup::HitResults results = VisualGroup::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 = VisualGroup::NoHit; + } + else if (y < body_start) + { + results = VisualGroup::HeaderHit; + int collapseSize = headerHeight() - 4; + + // the icon + QRect iconRect = QRect(view->m_leftMargin + 2, 2 + y_start, collapseSize, collapseSize); + if (iconRect.contains(pos)) + { + results |= VisualGroup::CheckboxHit; + } + } + else if (y < body_end) + { + results |= VisualGroup::BodyHit; + } + return results; +} + +void VisualGroup::drawHeader(QPainter *painter, const QStyleOptionViewItem &option) +{ + 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: checkboxy thing + { + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, false); + painter->setFont(font); + QColor penColor(option.palette.text().color()); + penColor.setAlphaF(0.6); + painter->setPen(penColor); + QRect iconSubRect(option.rect); + iconSubRect.setTop(iconSubRect.top() + 7); + iconSubRect.setLeft(iconSubRect.left() + 7); + + int sizing = fontMetrics.height(); + int even = ( (sizing - 1) % 2 ); + + iconSubRect.setHeight(sizing - even); + iconSubRect.setWidth(sizing - even); + painter->drawRect(iconSubRect); + + + /* + if(collapsed) + painter->drawText(iconSubRect, Qt::AlignHCenter | Qt::AlignVCenter, "+"); + else + painter->drawText(iconSubRect, Qt::AlignHCenter | Qt::AlignVCenter, "-"); + */ + painter->setBrush(option.palette.text()); + painter->fillRect(iconSubRect.x(), iconSubRect.y() + iconSubRect.height() / 2, + iconSubRect.width(), 2, penColor); + if (collapsed) + { + painter->fillRect(iconSubRect.x() + iconSubRect.width() / 2, iconSubRect.y(), 2, + iconSubRect.height(), penColor); + } + + painter->restore(); + } + //END: checkboxy thing + + //BEGIN: text + { + QRect textRect(option.rect); + textRect.setTop(textRect.top() + 7); + textRect.setLeft(textRect.left() + 7 + fontMetrics.height() + 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 VisualGroup::totalHeight() const +{ + return headerHeight() + 5 + contentHeight(); // FIXME: wtf is that '5'? +} + +int VisualGroup::headerHeight() const +{ + 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 raw = view->viewport()->fontMetrics().height() + 4; + // add english. maybe. depends on font height. + if (raw % 2 == 0) + raw++; + return std::min(raw, 25); + */ +} + +int VisualGroup::contentHeight() const +{ + if (collapsed) + { + return 0; + } + auto last = rows[numRows() - 1]; + return last.top + last.height; +} + +int VisualGroup::numRows() const +{ + return rows.size(); +} + +int VisualGroup::verticalPosition() const +{ + return m_verticalPosition; +} + +QList VisualGroup::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; +} diff --git a/gui/groupview/VisualGroup.h b/gui/groupview/VisualGroup.h new file mode 100644 index 00000000..d8d1f145 --- /dev/null +++ b/gui/groupview/VisualGroup.h @@ -0,0 +1,91 @@ +#pragma once + +#include +#include +#include +#include + +class GroupView; +class QPainter; +class QModelIndex; + +struct VisualRow +{ + QList items; + int height = 0; + int top = 0; + inline int size() const + { + return items.size(); + } + inline QModelIndex &operator[](int i) + { + return items[i]; + } +}; + +struct VisualGroup +{ +/* constructors */ + VisualGroup(const QString &text, GroupView *view); + VisualGroup(const VisualGroup *other); + +/* data */ + GroupView *view = nullptr; + QString text; + bool collapsed = false; + QVector rows; + int firstItemIndex = 0; + int m_verticalPosition = 0; + +/* logic */ + /// update the internal list of items and flow them into the rows. + void update(); + + /// draw the header at y-position. + void drawHeader(QPainter *painter, const QStyleOptionViewItem &option); + + /// 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; + + /// actually calculate the above value + int calculateNumRows() const; + + /// the height at which this group starts, in pixels + int verticalPosition() const; + + /// relative geometry - top of the row of the given item + int rowTopOf(const QModelIndex &index) const; + + /// height of the row of the given item + int rowHeightOf(const QModelIndex &index) const; + + /// x/y position of the given item inside the group (in items!) + QPair positionOf(const QModelIndex &index) 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; + + QList items() const; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(VisualGroup::HitResults) -- cgit v1.2.3