Krita Source Code Documentation
Loading...
Searching...
No Matches
NodeView.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2006 Gábor Lehel <illissius@gmail.com>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6#include "NodeView.h"
8#include "NodeDelegate.h"
10#include "kis_node_model.h"
11#include "kis_signals_blocker.h"
12
13
14#include <kconfig.h>
15#include <kconfiggroup.h>
16#include <kis_config.h>
17#include <kis_icon.h>
18#include <ksharedconfig.h>
19#include <KisKineticScroller.h>
20
21#include <QtDebug>
22#include <QContextMenuEvent>
23#include <QHeaderView>
24#include <QHelpEvent>
25#include <QMenu>
26#include <QDrag>
27#include <QMouseEvent>
28#include <QPersistentModelIndex>
29#include <QApplication>
30#include <QPainter>
31#include <QScrollBar>
32#include <QScroller>
33
35
36
37#ifdef HAVE_X11
38#define DRAG_WHILE_DRAG_WORKAROUND
39#endif
40
41#ifdef DRAG_WHILE_DRAG_WORKAROUND
42#define DRAG_WHILE_DRAG_WORKAROUND_START() d->isDragging = true
43#define DRAG_WHILE_DRAG_WORKAROUND_STOP() d->isDragging = false
44#else
45#define DRAG_WHILE_DRAG_WORKAROUND_START()
46#define DRAG_WHILE_DRAG_WORKAROUND_STOP()
47#endif
48
49
50class Q_DECL_HIDDEN NodeView::Private
51{
52public:
54 : delegate(_q, _q)
55#ifdef DRAG_WHILE_DRAG_WORKAROUND
56 , isDragging(false)
57#endif
58 {
59 }
61 QPersistentModelIndex hovered;
62 QPoint lastPos;
63
64#ifdef DRAG_WHILE_DRAG_WORKAROUND
65 bool isDragging;
66#endif
67};
68
69
70NodeView::NodeView(QWidget *parent)
71 : QTreeView(parent)
72 , m_draggingFlag(false)
73 , d(new Private(this))
74{
75 setItemDelegate(&d->delegate);
76
77 setMouseTracking(true);
78 setSelectionBehavior(SelectRows);
79 setDefaultDropAction(Qt::MoveAction);
80 setVerticalScrollMode(QAbstractItemView::ScrollPerItem);
81 setSelectionMode(QAbstractItemView::ExtendedSelection);
82 setRootIsDecorated(false);
83
84 header()->hide();
85 setDragEnabled(true);
86 setDragDropMode(QAbstractItemView::DragDrop);
87 setAcceptDrops(true);
88 setDropIndicatorShown(true);
89
90 {
91 QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(this);
92 if (scroller) {
93 connect(scroller, SIGNAL(stateChanged(QScroller::State)),
94 this, SLOT(slotScrollerStateChanged(QScroller::State)));
95 }
96 }
97}
98
100{
101 delete d;
102}
103
104void NodeView::setModel(QAbstractItemModel *model)
105{
106 QTreeView::setModel(model);
107
108 if (!this->model()->inherits("KisNodeModel") && !this->model()->inherits("KisNodeFilterProxyModel")) {
109 qWarning() << "NodeView may not work with" << model->metaObject()->className();
110 }
111 if (this->model()->columnCount() != 3) {
112 qWarning() << "NodeView: expected 2 model columns, got " << this->model()->columnCount();
113 }
114
115 if (header()->sectionPosition(VISIBILITY_COL) != 0 || header()->sectionPosition(SELECTED_COL) != 1) {
116 header()->moveSection(VISIBILITY_COL, 0);
117 header()->moveSection(SELECTED_COL, 1);
118 }
119
120 KisConfig cfg(true);
121 if (!cfg.useLayerSelectionCheckbox()) {
122 header()->hideSection(SELECTED_COL);
123 }
124
125 // the default may be too large for our visibility icon
126 header()->setMinimumSectionSize(KisNodeViewColorScheme::instance()->visibilityColumnWidth());
127}
128
129void NodeView::addPropertyActions(QMenu *menu, const QModelIndex &index)
130{
132 for (int i = 0, n = list.count(); i < n; ++i) {
133 if (list.at(i).isMutable) {
134 PropertyAction *a = new PropertyAction(i, list.at(i), index, menu);
135 connect(a, SIGNAL(toggled(bool,QPersistentModelIndex,int)),
136 this, SLOT(slotActionToggled(bool,QPersistentModelIndex,int)));
137 menu->addAction(a);
138 }
139 }
140}
141
142void NodeView::updateNode(const QModelIndex &index)
143{
144 dataChanged(index, index);
145}
146
147void NodeView::toggleSolo(const QModelIndex &index) {
148 d->delegate.toggleSolo(index);
149}
150
151QItemSelectionModel::SelectionFlags NodeView::selectionCommand(const QModelIndex &index, const QEvent *event) const
152{
153 //Block selection on press if ctrl is held
154 //This is to allow for a more consistent behaviour with ctrl+dnd
155 if (event &&
156 event->type() == QEvent::MouseButtonPress ) {
157 const QMouseEvent *mevent = static_cast<const QMouseEvent*>(event);
158 if (mevent->modifiers() & Qt::ControlModifier)
159 return QItemSelectionModel::NoUpdate;
160 }
161
162 return QTreeView::selectionCommand(index, event);
163}
164
165QModelIndex NodeView::indexAt(const QPoint &point) const
166{
168
169 QModelIndex index = QTreeView::indexAt(point);
170 if (!index.isValid()) {
171 // Middle is a good position for both LTR and RTL layouts
172 // First reset x, then get the x in the middle
173 index = QTreeView::indexAt(point - QPoint(point.x(), 0) + QPoint(width() / 2, 0));
174 }
175
176 return index;
177}
178
180{
181 if (model()) {
182 switch(e->type()) {
183 case QEvent::MouseButtonPress: {
185
186 const QPoint pos = static_cast<QMouseEvent*>(e)->pos();
187 d->lastPos = pos;
188
189 if (!indexAt(pos).isValid()) {
190 return QTreeView::viewportEvent(e);
191 }
192 QModelIndex index = model()->buddy(indexAt(pos));
193 if (d->delegate.editorEvent(e, model(), optionForIndex(index), index)) {
194 return true;
195 }
196 } break;
197 case QEvent::Leave: {
198 QEvent e(QEvent::Leave);
199 d->delegate.editorEvent(&e, model(), optionForIndex(d->hovered), d->hovered);
200 d->hovered = QModelIndex();
201 } break;
202 case QEvent::MouseMove: {
203#ifdef DRAG_WHILE_DRAG_WORKAROUND
204 if (d->isDragging) {
205 return false;
206 }
207#endif
208
209 const QPoint pos = static_cast<QMouseEvent*>(e)->pos();
210 QModelIndex hovered = indexAt(pos);
211 if (hovered != d->hovered) {
212 if (d->hovered.isValid()) {
213 QEvent e(QEvent::Leave);
214 d->delegate.editorEvent(&e, model(), optionForIndex(d->hovered), d->hovered);
215 }
216 if (hovered.isValid()) {
217 QEvent e(QEvent::Enter);
218 d->delegate.editorEvent(&e, model(), optionForIndex(hovered), hovered);
219 }
220 d->hovered = hovered;
221 }
222 /* This is a workaround for a bug in QTreeView that immediately begins a dragging action
223 when the mouse lands on the decoration/icon of a different index and moves 1 pixel or more */
224 Qt::MouseButtons buttons = static_cast<QMouseEvent*>(e)->buttons();
225 if ((Qt::LeftButton | Qt::MiddleButton) & buttons) {
226 if ((pos - d->lastPos).manhattanLength() > qApp->startDragDistance()) {
227 return QTreeView::viewportEvent(e);
228 }
229 return true;
230 }
231 } break;
232 case QEvent::ToolTip: {
233 const QPoint pos = static_cast<QHelpEvent*>(e)->pos();
234 if (!indexAt(pos).isValid()) {
235 return QTreeView::viewportEvent(e);
236 }
237 QModelIndex index = model()->buddy(indexAt(pos));
238 return d->delegate.editorEvent(e, model(), optionForIndex(index), index);
239 } break;
240 case QEvent::Resize: {
241 scheduleDelayedItemsLayout();
242 break;
243 }
244 default: break;
245 }
246 }
247 return QTreeView::viewportEvent(e);
248}
249
250void NodeView::contextMenuEvent(QContextMenuEvent *e)
251{
252 QTreeView::contextMenuEvent(e);
253 QModelIndex i = indexAt(e->pos());
254 if (model())
255 i = model()->buddy(i);
256 showContextMenu(e->globalPos(), i);
257}
258
259void NodeView::showContextMenu(const QPoint &globalPos, const QModelIndex &index)
260{
261 Q_EMIT contextMenuRequested(globalPos, index);
262}
263
264void NodeView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
265{
266 QTreeView::currentChanged(current, previous);
267 if (current != previous) {
268 Q_ASSERT(!current.isValid() || current.model() == model());
269 KisSignalsBlocker blocker(this);
270 model()->setData(current, true, KisNodeModel::ActiveRole);
271 }
272}
273
274void NodeView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &/*roles*/)
275{
276 QTreeView::dataChanged(topLeft, bottomRight);
277
278 for (int x = topLeft.row(); x <= bottomRight.row(); ++x) {
279 for (int y = topLeft.column(); y <= bottomRight.column(); ++y) {
280 QModelIndex index = topLeft.sibling(x, y);
281 if (index.data(KisNodeModel::ActiveRole).toBool()) {
282 if (currentIndex() != index) {
283 setCurrentIndex(index);
284 }
285
286 return;
287 }
288 }
289 }
290}
291
292void NodeView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
293{
294 QTreeView::selectionChanged(selected, deselected);
295 // XXX: selectedIndexes() does not include hidden (collapsed) items, is this really intended?
296 Q_EMIT selectionChanged(selectedIndexes());
297}
298
299void NodeView::slotActionToggled(bool on, const QPersistentModelIndex &index, int num)
300{
302 list[num].state = on;
303 const_cast<QAbstractItemModel*>(index.model())->setData(index, QVariant::fromValue(list), KisNodeModel::PropertiesRole);
304}
305
306QStyleOptionViewItem NodeView::optionForIndex(const QModelIndex &index) const
307{
308#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
309 QStyleOptionViewItem option = viewOptions();
310#else
311 QStyleOptionViewItem option;
312 initViewItemOption(&option);
313#endif
314 option.rect = visualRect(index);
315 if (index == currentIndex())
316 option.state |= QStyle::State_HasFocus;
317 return option;
318}
319
320void NodeView::startDrag(Qt::DropActions supportedActions)
321{
323 QTreeView::startDrag(supportedActions);
324}
325
327{
328 const QModelIndexList selectedIndexes = selectionModel()->selectedIndexes();
329 Q_ASSERT(!selectedIndexes.isEmpty());
330
331 const int itemCount = selectedIndexes.count();
332
333 // If more than one item is dragged, align the items inside a
334 // rectangular grid. The maximum grid size is limited to 4 x 4 items.
335 int xCount = 2;
336 int size = 96;
337 if (itemCount > 9) {
338 xCount = 4;
340 }
341 else if (itemCount > 4) {
342 xCount = 3;
344 }
345 else if (itemCount < xCount) {
346 xCount = itemCount;
347 }
348
349 int yCount = itemCount / xCount;
350 if (itemCount % xCount != 0) {
351 ++yCount;
352 }
353
354 if (yCount > xCount) {
355 yCount = xCount;
356 }
357
358 // Draw the selected items into the grid cells
359 QPixmap dragPixmap(xCount * size + xCount - 1, yCount * size + yCount - 1);
360 dragPixmap.fill(Qt::transparent);
361
362 QPainter painter(&dragPixmap);
363 int x = 0;
364 int y = 0;
365 Q_FOREACH (const QModelIndex &selectedIndex, selectedIndexes) {
366 const QImage img = selectedIndex.data(int(KisNodeModel::BeginThumbnailRole) + size).value<QImage>();
367 painter.drawPixmap(x, y, QPixmap::fromImage(img.scaled(QSize(size, size), Qt::KeepAspectRatio, Qt::SmoothTransformation)));
368
369 x += size + 1;
370 if (x >= dragPixmap.width()) {
371 x = 0;
372 y += size + 1;
373 }
374 if (y >= dragPixmap.height()) {
375 break;
376 }
377 }
378
379 return dragPixmap;
380}
381
382void NodeView::resizeEvent(QResizeEvent * event)
383{
385 header()->setStretchLastSection(false);
386
387 int otherColumnsWidth = scm.visibilityColumnWidth();
388
389 // if layer box is enabled subtract its width from the "Default col".
390 if (KisConfig(false).useLayerSelectionCheckbox()) {
391 otherColumnsWidth += scm.selectedButtonColumnWidth();
392 }
393 header()->resizeSection(DEFAULT_COL, event->size().width() - otherColumnsWidth);
394 header()->resizeSection(SELECTED_COL, scm.selectedButtonColumnWidth());
395 header()->resizeSection(VISIBILITY_COL, scm.visibilityColumnWidth());
396
397 setIndentation(scm.indentation());
398 QTreeView::resizeEvent(event);
399}
400
401void NodeView::paintEvent(QPaintEvent *event)
402{
403 event->accept();
404 QTreeView::paintEvent(event);
405}
406
407void NodeView::drawBranches(QPainter *painter, const QRect &rect,
408 const QModelIndex &index) const
409{
410#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
411 QStyleOptionViewItem option = viewOptions();
412#else
413 QStyleOptionViewItem option;
414 initViewItemOption(&option);
415#endif
416 option.rect = rect;
417 // This is not really a job for an item delegate, but the logic was already there
418 d->delegate.drawBranches(painter, option, index);
419}
420
421void NodeView::dropEvent(QDropEvent *ev)
422{
423 QTreeView::dropEvent(ev);
425}
426
428{
429 QSize size(visualRect(model()->index(0, 0, QModelIndex())).width(), visualRect(model()->index(0, 0, QModelIndex())).height());
430 int scrollBarValue = verticalScrollBar()->value();
431
432 QPoint cursorPosition = QWidget::mapFromGlobal(QCursor::pos());
433
434 int numberRow = (cursorPosition.y() + scrollBarValue) / size.height();
435
436 //If cursor is at the half button of the page then the move action is performed after the slide, otherwise it is
437 //performed before the page
438 if (abs((cursorPosition.y() + scrollBarValue) - size.height()*numberRow) > (size.height()/2)) {
439 numberRow++;
440 }
441
442 if (numberRow > model()->rowCount(QModelIndex())) {
443 numberRow = model()->rowCount(QModelIndex());
444 }
445
446 return numberRow;
447}
448
449void NodeView::dragEnterEvent(QDragEnterEvent *ev)
450{
452
453 QVariant data = QVariant::fromValue(
454 static_cast<void*>(const_cast<QMimeData*>(ev->mimeData())));
455 model()->setData(QModelIndex(), data, KisNodeModel::DropEnabled);
456
457 QTreeView::dragEnterEvent(ev);
458}
459
460void NodeView::dragMoveEvent(QDragMoveEvent *ev)
461{
463 QTreeView::dragMoveEvent(ev);
464}
465
466void NodeView::dragLeaveEvent(QDragLeaveEvent *e)
467{
468 QTreeView::dragLeaveEvent(e);
470}
471
473{
474 return m_draggingFlag;
475}
476
478{
479 m_draggingFlag = flag;
480}
481
483{
484 d->delegate.slotUpdateIcon();
485}
486
487void NodeView::slotScrollerStateChanged(QScroller::State state){
489}
490
492{
493 setIndentation(KisNodeViewColorScheme::instance()->indentation());
495 d->delegate.slotConfigChanged();
496}
497
499{
500 KisConfig cfg(false);
501 if (cfg.useLayerSelectionCheckbox() == !header()->isSectionHidden(SELECTED_COL)) {
502 return;
503 }
504 header()->setSectionHidden(SELECTED_COL, !cfg.useLayerSelectionCheckbox());
505 // add/subtract width based on SELECTED_COL section's visibility
506 header()->resizeSection(DEFAULT_COL,
507 size().width()
508 + (cfg.useLayerSelectionCheckbox() ? header()->sectionSize(SELECTED_COL)
509 : -header()->sectionSize(SELECTED_COL)));
510}
#define DRAG_WHILE_DRAG_WORKAROUND_START()
Definition NodeView.cpp:45
#define DRAG_WHILE_DRAG_WORKAROUND_STOP()
Definition NodeView.cpp:46
bool useLayerSelectionCheckbox(bool defaultValue=false) const
@ PropertiesRole
A list of properties the part has.
@ ActiveRole
Whether the section is the active one.
static KisNodeViewColorScheme * instance()
QItemSelectionModel::SelectionFlags selectionCommand(const QModelIndex &index, const QEvent *event) const override
Definition NodeView.cpp:151
void setDraggingFlag(bool flag=true)
Definition NodeView.cpp:477
virtual void showContextMenu(const QPoint &globalPos, const QModelIndex &index)
Definition NodeView.cpp:259
void contextMenuRequested(const QPoint &globalPos, const QModelIndex &index)
void paintEvent(QPaintEvent *event) override
Definition NodeView.cpp:401
Private *const d
Definition NodeView.h:152
void selectionChanged(const QModelIndexList &)
void dragMoveEvent(QDragMoveEvent *ev) override
Definition NodeView.cpp:460
Private(NodeView *_q)
Definition NodeView.cpp:53
void updateNode(const QModelIndex &index)
Definition NodeView.cpp:142
void resizeEvent(QResizeEvent *event) override
Definition NodeView.cpp:382
bool viewportEvent(QEvent *event) override
Definition NodeView.cpp:179
int cursorPageIndex() const
Definition NodeView.cpp:427
void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const override
Definition NodeView.cpp:407
NodeView(QWidget *parent=0)
Definition NodeView.cpp:70
void slotScrollerStateChanged(QScroller::State state)
Definition NodeView.cpp:487
NodeDelegate delegate
Definition NodeView.cpp:60
void toggleSolo(const QModelIndex &index)
Definition NodeView.cpp:147
void slotConfigurationChanged()
Definition NodeView.cpp:491
void slotUpdateIcons()
called with a theme change to refresh icon colors
Definition NodeView.cpp:482
void dropEvent(QDropEvent *ev) override
Definition NodeView.cpp:421
bool m_draggingFlag
Definition NodeView.h:146
@ SELECTED_COL
Definition NodeView.h:44
@ VISIBILITY_COL
Definition NodeView.h:43
@ DEFAULT_COL
Definition NodeView.h:42
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector< int > &roles=QVector< int >()) override
Definition NodeView.cpp:274
QStyleOptionViewItem optionForIndex(const QModelIndex &index) const
Definition NodeView.cpp:306
~NodeView() override
Definition NodeView.cpp:99
void slotActionToggled(bool on, const QPersistentModelIndex &index, int property)
Definition NodeView.cpp:299
QPixmap createDragPixmap() const
Definition NodeView.cpp:326
QPoint lastPos
Definition NodeView.cpp:62
QPersistentModelIndex hovered
Definition NodeView.cpp:61
void dragEnterEvent(QDragEnterEvent *e) override
Definition NodeView.cpp:449
void addPropertyActions(QMenu *menu, const QModelIndex &index)
Definition NodeView.cpp:129
void startDrag(Qt::DropActions supportedActions) override
Definition NodeView.cpp:320
bool isDragging() const
Definition NodeView.cpp:472
void setModel(QAbstractItemModel *model) override
Definition NodeView.cpp:104
void dragLeaveEvent(QDragLeaveEvent *e) override
Definition NodeView.cpp:466
void updateSelectedCheckboxColumn()
Definition NodeView.cpp:498
void contextMenuEvent(QContextMenuEvent *event) override
Definition NodeView.cpp:250
void currentChanged(const QModelIndex &current, const QModelIndex &previous) override
Definition NodeView.cpp:264
QModelIndex indexAt(const QPoint &point) const override
Definition NodeView.cpp:165
QString buttons(const T &ev)
KRITAWIDGETUTILS_EXPORT void updateCursor(QWidget *source, QScroller::State state)
KRITAWIDGETUTILS_EXPORT QScroller * createPreconfiguredScroller(QAbstractScrollArea *target)