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,
152 const QEvent *event) const
153{
164 if (event &&
165 (event->type() == QEvent::MouseButtonPress ||
166 event->type() == QEvent::MouseButtonRelease) &&
167 index.isValid()) {
168
169 const QMouseEvent *mevent = static_cast<const QMouseEvent*>(event);
170
171 if (mevent->button() == Qt::RightButton &&
172 selectionModel()->selectedIndexes().contains(index)) {
173
174 // Allow calling context menu for multiple layers
175 return QItemSelectionModel::NoUpdate;
176 }
177
178 if (event->type() == QEvent::MouseButtonPress &&
179 (mevent->modifiers() & Qt::ControlModifier)) {
180
181 return QItemSelectionModel::NoUpdate;
182 }
183
184 if (event->type() == QEvent::MouseButtonRelease &&
185 (mevent->modifiers() & Qt::ControlModifier)) {
186
187 // Select the entire row, otherwise we only get updates for DEFAULT_COL and then we have to
188 // manually sync its state with SELECTED_COL.
189 return QItemSelectionModel::Toggle | QItemSelectionModel::Rows;
190 }
191 }
192
198 Qt::KeyboardModifiers globalModifiers = QApplication::keyboardModifiers();
199 if (!event && globalModifiers != Qt::NoModifier) {
200 return QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows;
201 }
202
203 return QAbstractItemView::selectionCommand(index, event);
204}
205
206QModelIndex NodeView::indexAt(const QPoint &point) const
207{
209
210 QModelIndex index = QTreeView::indexAt(point);
211 if (!index.isValid()) {
212 // Middle is a good position for both LTR and RTL layouts
213 // First reset x, then get the x in the middle
214 index = QTreeView::indexAt(point - QPoint(point.x(), 0) + QPoint(width() / 2, 0));
215 }
216
217 return index;
218}
219
221{
222 if (model()) {
223 switch(e->type()) {
224 case QEvent::MouseButtonPress: {
226
227 const QPoint pos = static_cast<QMouseEvent*>(e)->pos();
228 d->lastPos = pos;
229
230 if (!indexAt(pos).isValid()) {
231 return QTreeView::viewportEvent(e);
232 }
233 QModelIndex index = model()->buddy(indexAt(pos));
234 if (d->delegate.editorEvent(e, model(), optionForIndex(index), index)) {
235 return true;
236 }
237 } break;
238 case QEvent::Leave: {
239 QEvent e(QEvent::Leave);
240 d->delegate.editorEvent(&e, model(), optionForIndex(d->hovered), d->hovered);
241 d->hovered = QModelIndex();
242 } break;
243 case QEvent::MouseMove: {
244#ifdef DRAG_WHILE_DRAG_WORKAROUND
245 if (d->isDragging) {
246 return false;
247 }
248#endif
249
250 const QPoint pos = static_cast<QMouseEvent*>(e)->pos();
251 QModelIndex hovered = indexAt(pos);
252 if (hovered != d->hovered) {
253 if (d->hovered.isValid()) {
254 QEvent e(QEvent::Leave);
255 d->delegate.editorEvent(&e, model(), optionForIndex(d->hovered), d->hovered);
256 }
257 if (hovered.isValid()) {
258 QEvent e(QEvent::Enter);
259 d->delegate.editorEvent(&e, model(), optionForIndex(hovered), hovered);
260 }
261 d->hovered = hovered;
262 }
263 /* This is a workaround for a bug in QTreeView that immediately begins a dragging action
264 when the mouse lands on the decoration/icon of a different index and moves 1 pixel or more */
265 Qt::MouseButtons buttons = static_cast<QMouseEvent*>(e)->buttons();
266 if ((Qt::LeftButton | Qt::MiddleButton) & buttons) {
267 if ((pos - d->lastPos).manhattanLength() > qApp->startDragDistance()) {
268 return QTreeView::viewportEvent(e);
269 }
270 return true;
271 }
272 } break;
273 case QEvent::ToolTip: {
274 const QPoint pos = static_cast<QHelpEvent*>(e)->pos();
275 if (!indexAt(pos).isValid()) {
276 return QTreeView::viewportEvent(e);
277 }
278 QModelIndex index = model()->buddy(indexAt(pos));
279 return d->delegate.editorEvent(e, model(), optionForIndex(index), index);
280 } break;
281 case QEvent::Resize: {
282 scheduleDelayedItemsLayout();
283 break;
284 }
285 default: break;
286 }
287 }
288 return QTreeView::viewportEvent(e);
289}
290
291void NodeView::contextMenuEvent(QContextMenuEvent *e)
292{
293 QTreeView::contextMenuEvent(e);
294 QModelIndex i = indexAt(e->pos());
295 if (model())
296 i = model()->buddy(i);
297 showContextMenu(e->globalPos(), i);
298}
299
300void NodeView::showContextMenu(const QPoint &globalPos, const QModelIndex &index)
301{
302 Q_EMIT contextMenuRequested(globalPos, index);
303}
304
305void NodeView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
306{
307 QTreeView::currentChanged(current, previous);
308 if (current != previous) {
309 Q_ASSERT(!current.isValid() || current.model() == model());
310 KisSignalsBlocker blocker(this);
311 model()->setData(current, true, KisNodeModel::ActiveRole);
312 }
313}
314
315void NodeView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &/*roles*/)
316{
317 QTreeView::dataChanged(topLeft, bottomRight);
318
319 for (int x = topLeft.row(); x <= bottomRight.row(); ++x) {
320 for (int y = topLeft.column(); y <= bottomRight.column(); ++y) {
321 QModelIndex index = topLeft.sibling(x, y);
322 if (index.data(KisNodeModel::ActiveRole).toBool()) {
323 if (currentIndex() != index) {
324 setCurrentIndex(index);
325 }
326
327 return;
328 }
329 }
330 }
331}
332
333void NodeView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
334{
335 QTreeView::selectionChanged(selected, deselected);
336 // XXX: selectedIndexes() does not include hidden (collapsed) items, is this really intended?
337 Q_EMIT selectionChanged(selectedIndexes());
338}
339
340void NodeView::slotActionToggled(bool on, const QPersistentModelIndex &index, int num)
341{
343 list[num].state = on;
344 const_cast<QAbstractItemModel*>(index.model())->setData(index, QVariant::fromValue(list), KisNodeModel::PropertiesRole);
345}
346
347QStyleOptionViewItem NodeView::optionForIndex(const QModelIndex &index) const
348{
349#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
350 QStyleOptionViewItem option = viewOptions();
351#else
352 QStyleOptionViewItem option;
353 initViewItemOption(&option);
354#endif
355 option.rect = visualRect(index);
356 if (index == currentIndex())
357 option.state |= QStyle::State_HasFocus;
358 return option;
359}
360
361void NodeView::startDrag(Qt::DropActions supportedActions)
362{
364 QTreeView::startDrag(supportedActions);
365}
366
368{
369 const QModelIndexList selectedIndexes = selectionModel()->selectedIndexes();
370 Q_ASSERT(!selectedIndexes.isEmpty());
371
372 const int itemCount = selectedIndexes.count();
373
374 // If more than one item is dragged, align the items inside a
375 // rectangular grid. The maximum grid size is limited to 4 x 4 items.
376 int xCount = 2;
377 int size = 96;
378 if (itemCount > 9) {
379 xCount = 4;
381 }
382 else if (itemCount > 4) {
383 xCount = 3;
385 }
386 else if (itemCount < xCount) {
387 xCount = itemCount;
388 }
389
390 int yCount = itemCount / xCount;
391 if (itemCount % xCount != 0) {
392 ++yCount;
393 }
394
395 if (yCount > xCount) {
396 yCount = xCount;
397 }
398
399 // Draw the selected items into the grid cells
400 QPixmap dragPixmap(xCount * size + xCount - 1, yCount * size + yCount - 1);
401 dragPixmap.fill(Qt::transparent);
402
403 QPainter painter(&dragPixmap);
404 int x = 0;
405 int y = 0;
406 Q_FOREACH (const QModelIndex &selectedIndex, selectedIndexes) {
407 const QImage img = selectedIndex.data(int(KisNodeModel::BeginThumbnailRole) + size).value<QImage>();
408 painter.drawPixmap(x, y, QPixmap::fromImage(img.scaled(QSize(size, size), Qt::KeepAspectRatio, Qt::SmoothTransformation)));
409
410 x += size + 1;
411 if (x >= dragPixmap.width()) {
412 x = 0;
413 y += size + 1;
414 }
415 if (y >= dragPixmap.height()) {
416 break;
417 }
418 }
419
420 return dragPixmap;
421}
422
423void NodeView::resizeEvent(QResizeEvent * event)
424{
426 header()->setStretchLastSection(false);
427
428 int otherColumnsWidth = scm.visibilityColumnWidth();
429
430 // if layer box is enabled subtract its width from the "Default col".
431 if (KisConfig(false).useLayerSelectionCheckbox()) {
432 otherColumnsWidth += scm.selectedButtonColumnWidth();
433 }
434 header()->resizeSection(DEFAULT_COL, event->size().width() - otherColumnsWidth);
435 header()->resizeSection(SELECTED_COL, scm.selectedButtonColumnWidth());
436 header()->resizeSection(VISIBILITY_COL, scm.visibilityColumnWidth());
437
438 setIndentation(scm.indentation());
439 QTreeView::resizeEvent(event);
440}
441
442void NodeView::paintEvent(QPaintEvent *event)
443{
444 event->accept();
445 QTreeView::paintEvent(event);
446}
447
448void NodeView::drawBranches(QPainter *painter, const QRect &rect,
449 const QModelIndex &index) const
450{
451#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
452 QStyleOptionViewItem option = viewOptions();
453#else
454 QStyleOptionViewItem option;
455 initViewItemOption(&option);
456#endif
457 option.rect = rect;
458 // This is not really a job for an item delegate, but the logic was already there
459 d->delegate.drawBranches(painter, option, index);
460}
461
462void NodeView::dropEvent(QDropEvent *ev)
463{
464 QTreeView::dropEvent(ev);
466}
467
469{
470 QSize size(visualRect(model()->index(0, 0, QModelIndex())).width(), visualRect(model()->index(0, 0, QModelIndex())).height());
471 int scrollBarValue = verticalScrollBar()->value();
472
473 QPoint cursorPosition = QWidget::mapFromGlobal(QCursor::pos());
474
475 int numberRow = (cursorPosition.y() + scrollBarValue) / size.height();
476
477 //If cursor is at the half button of the page then the move action is performed after the slide, otherwise it is
478 //performed before the page
479 if (abs((cursorPosition.y() + scrollBarValue) - size.height()*numberRow) > (size.height()/2)) {
480 numberRow++;
481 }
482
483 if (numberRow > model()->rowCount(QModelIndex())) {
484 numberRow = model()->rowCount(QModelIndex());
485 }
486
487 return numberRow;
488}
489
490void NodeView::dragEnterEvent(QDragEnterEvent *ev)
491{
493
494 QVariant data = QVariant::fromValue(
495 static_cast<void*>(const_cast<QMimeData*>(ev->mimeData())));
496 model()->setData(QModelIndex(), data, KisNodeModel::DropEnabled);
497
498 QTreeView::dragEnterEvent(ev);
499}
500
501void NodeView::dragMoveEvent(QDragMoveEvent *ev)
502{
504 QTreeView::dragMoveEvent(ev);
505}
506
507void NodeView::dragLeaveEvent(QDragLeaveEvent *e)
508{
509 QTreeView::dragLeaveEvent(e);
511}
512
514{
515 return m_draggingFlag;
516}
517
519{
520 m_draggingFlag = flag;
521}
522
524{
525 d->delegate.slotUpdateIcon();
526}
527
528void NodeView::slotScrollerStateChanged(QScroller::State state){
530}
531
533{
534 setIndentation(KisNodeViewColorScheme::instance()->indentation());
536 d->delegate.slotConfigChanged();
537}
538
540{
541 KisConfig cfg(false);
542 if (cfg.useLayerSelectionCheckbox() == !header()->isSectionHidden(SELECTED_COL)) {
543 return;
544 }
545 header()->setSectionHidden(SELECTED_COL, !cfg.useLayerSelectionCheckbox());
546 // add/subtract width based on SELECTED_COL section's visibility
547 header()->resizeSection(DEFAULT_COL,
548 size().width()
549 + (cfg.useLayerSelectionCheckbox() ? header()->sectionSize(SELECTED_COL)
550 : -header()->sectionSize(SELECTED_COL)));
551}
#define DRAG_WHILE_DRAG_WORKAROUND_START()
Definition NodeView.cpp:45
#define DRAG_WHILE_DRAG_WORKAROUND_STOP()
Definition NodeView.cpp:46
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
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:518
virtual void showContextMenu(const QPoint &globalPos, const QModelIndex &index)
Definition NodeView.cpp:300
void contextMenuRequested(const QPoint &globalPos, const QModelIndex &index)
void paintEvent(QPaintEvent *event) override
Definition NodeView.cpp:442
Private *const d
Definition NodeView.h:154
void selectionChanged(const QModelIndexList &)
void dragMoveEvent(QDragMoveEvent *ev) override
Definition NodeView.cpp:501
Private(NodeView *_q)
Definition NodeView.cpp:53
void updateNode(const QModelIndex &index)
Definition NodeView.cpp:142
void resizeEvent(QResizeEvent *event) override
Definition NodeView.cpp:423
bool viewportEvent(QEvent *event) override
Definition NodeView.cpp:220
int cursorPageIndex() const
Definition NodeView.cpp:468
void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const override
Definition NodeView.cpp:448
NodeView(QWidget *parent=0)
Definition NodeView.cpp:70
void slotScrollerStateChanged(QScroller::State state)
Definition NodeView.cpp:528
NodeDelegate delegate
Definition NodeView.cpp:60
void toggleSolo(const QModelIndex &index)
Definition NodeView.cpp:147
void slotConfigurationChanged()
Definition NodeView.cpp:532
void slotUpdateIcons()
called with a theme change to refresh icon colors
Definition NodeView.cpp:523
void dropEvent(QDropEvent *ev) override
Definition NodeView.cpp:462
bool m_draggingFlag
Definition NodeView.h:148
@ 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:315
QStyleOptionViewItem optionForIndex(const QModelIndex &index) const
Definition NodeView.cpp:347
~NodeView() override
Definition NodeView.cpp:99
void slotActionToggled(bool on, const QPersistentModelIndex &index, int property)
Definition NodeView.cpp:340
QPixmap createDragPixmap() const
Definition NodeView.cpp:367
QPoint lastPos
Definition NodeView.cpp:62
QPersistentModelIndex hovered
Definition NodeView.cpp:61
void dragEnterEvent(QDragEnterEvent *e) override
Definition NodeView.cpp:490
void addPropertyActions(QMenu *menu, const QModelIndex &index)
Definition NodeView.cpp:129
void startDrag(Qt::DropActions supportedActions) override
Definition NodeView.cpp:361
bool isDragging() const
Definition NodeView.cpp:513
void setModel(QAbstractItemModel *model) override
Definition NodeView.cpp:104
void dragLeaveEvent(QDragLeaveEvent *e) override
Definition NodeView.cpp:507
void updateSelectedCheckboxColumn()
Definition NodeView.cpp:539
void contextMenuEvent(QContextMenuEvent *event) override
Definition NodeView.cpp:291
void currentChanged(const QModelIndex &current, const QModelIndex &previous) override
Definition NodeView.cpp:305
QModelIndex indexAt(const QPoint &point) const override
Definition NodeView.cpp:206
QString buttons(const T &ev)
KRITAWIDGETUTILS_EXPORT void updateCursor(QWidget *source, QScroller::State state)
KRITAWIDGETUTILS_EXPORT QScroller * createPreconfiguredScroller(QAbstractScrollArea *target)