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#include <KoConfig.h>
21
22#include <QtDebug>
23#include <QContextMenuEvent>
24#include <QHeaderView>
25#include <QHelpEvent>
26#include <QMenu>
27#include <QDrag>
28#include <QMouseEvent>
29#include <QPersistentModelIndex>
30#include <QApplication>
31#include <QPainter>
32#include <QScrollBar>
33#include <QScroller>
34
36
37#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
38#define QT6_SHIFT_SELECTION_WORKAROUND
39#endif
40
41#ifdef HAVE_X11
42#define DRAG_WHILE_DRAG_WORKAROUND
43#endif
44
45#ifdef DRAG_WHILE_DRAG_WORKAROUND
46#define DRAG_WHILE_DRAG_WORKAROUND_START() d->isDragging = true
47#define DRAG_WHILE_DRAG_WORKAROUND_STOP() d->isDragging = false
48#else
49#define DRAG_WHILE_DRAG_WORKAROUND_START()
50#define DRAG_WHILE_DRAG_WORKAROUND_STOP()
51#endif
52
53
54class Q_DECL_HIDDEN NodeView::Private
55{
56public:
58 : delegate(_q, _q)
59#ifdef DRAG_WHILE_DRAG_WORKAROUND
60 , isDragging(false)
61#endif
63 , shiftClickFix(false)
64#endif
65 {
66 }
68 QPersistentModelIndex hovered;
69 QPoint lastPos;
70
71#ifdef DRAG_WHILE_DRAG_WORKAROUND
72 bool isDragging;
73#endif
74#ifdef QT6_SHIFT_SELECTION_WORKAROUND
76#endif
77 bool ignoreDataChanged = false;
78};
79
80
81NodeView::NodeView(QWidget *parent)
82 : QTreeView(parent)
83 , m_draggingFlag(false)
84 , d(new Private(this))
85{
86 setItemDelegate(&d->delegate);
87
88 setMouseTracking(true);
89 setSelectionBehavior(SelectRows);
90 setDefaultDropAction(Qt::MoveAction);
91 setVerticalScrollMode(QAbstractItemView::ScrollPerItem);
92 setSelectionMode(QAbstractItemView::ExtendedSelection);
93 setRootIsDecorated(false);
94
95 header()->hide();
96 setDragEnabled(true);
97 setDragDropMode(QAbstractItemView::DragDrop);
98 setAcceptDrops(true);
99 setDropIndicatorShown(true);
100
101 {
102 QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(this);
103 if (scroller) {
104 connect(scroller, SIGNAL(stateChanged(QScroller::State)),
105 this, SLOT(slotScrollerStateChanged(QScroller::State)));
106 }
107 }
108}
109
111{
112 delete d;
113}
114
115void NodeView::setModel(QAbstractItemModel *model)
116{
117 QTreeView::setModel(model);
118
119 if (!this->model()->inherits("KisNodeModel") && !this->model()->inherits("KisNodeFilterProxyModel")) {
120 qWarning() << "NodeView may not work with" << model->metaObject()->className();
121 }
122 if (this->model()->columnCount() != 3) {
123 qWarning() << "NodeView: expected 2 model columns, got " << this->model()->columnCount();
124 }
125
126 if (header()->sectionPosition(VISIBILITY_COL) != 0 || header()->sectionPosition(SELECTED_COL) != 1) {
127 header()->moveSection(VISIBILITY_COL, 0);
128 header()->moveSection(SELECTED_COL, 1);
129 }
130
131 KisConfig cfg(true);
132 if (!cfg.useLayerSelectionCheckbox()) {
133 header()->hideSection(SELECTED_COL);
134 }
135
136 // the default may be too large for our visibility icon
137 header()->setMinimumSectionSize(KisNodeViewColorScheme::instance()->visibilityColumnWidth());
138}
139
140void NodeView::addPropertyActions(QMenu *menu, const QModelIndex &index)
141{
143 for (int i = 0, n = list.count(); i < n; ++i) {
144 if (list.at(i).isMutable) {
145 PropertyAction *a = new PropertyAction(i, list.at(i), index, menu);
146 connect(a, SIGNAL(toggled(bool,QPersistentModelIndex,int)),
147 this, SLOT(slotActionToggled(bool,QPersistentModelIndex,int)));
148 menu->addAction(a);
149 }
150 }
151}
152
153void NodeView::updateNode(const QModelIndex &index)
154{
155 dataChanged(index, index);
156}
157
158void NodeView::toggleSolo(const QModelIndex &index) {
159 d->delegate.toggleSolo(index);
160}
161
162QItemSelectionModel::SelectionFlags NodeView::selectionCommand(const QModelIndex &index, const QEvent *event) const {
163 //When adding a layer with Shift or Ctrl is held, the selection will expand to add that new layer,
164 //So to avoid that we explicitly select the new layer.
165 if (!event && QApplication::keyboardModifiers() != Qt::NoModifier
167 && !d->shiftClickFix
168#endif
169 ) {
170#ifdef QT6_SHIFT_SELECTION_WORKAROUND
171 d->shiftClickFix = false;
172#endif
173 return QItemSelectionModel::ClearAndSelect;
174 }
175
176#ifdef QT6_SHIFT_SELECTION_WORKAROUND
177 //Clear this just in case
178 d->shiftClickFix = false;
179
180 //Qt6 has a bug/feature? where after you do a shift selection it sends a second selection event with the event argument being null
181 //This triggers the logic above, so we explicitly avoid the next null event
182 if (event &&
183 (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::KeyPress) &&
184 QApplication::keyboardModifiers() & Qt::ShiftModifier)
185 d->shiftClickFix = true;
186#endif
187
188 //We still get ctrl click events so we need to ignore those
189 if (event &&
190 event->type() == QEvent::MouseButtonPress && (static_cast<const QMouseEvent*>(event)->modifiers() & Qt::ControlModifier))
191 return QItemSelectionModel::NoUpdate;
192
193 return QTreeView::selectionCommand(index, event);
194}
195
196QModelIndex NodeView::indexAt(const QPoint &point) const
197{
199
200 QModelIndex index = QTreeView::indexAt(point);
201 if (!index.isValid()) {
202 // Middle is a good position for both LTR and RTL layouts
203 // First reset x, then get the x in the middle
204 index = QTreeView::indexAt(point - QPoint(point.x(), 0) + QPoint(width() / 2, 0));
205 }
206
207 return index;
208}
209
211{
212 if (model()) {
213 switch(e->type()) {
214 case QEvent::MouseButtonPress: {
216
217 const QPoint pos = static_cast<QMouseEvent*>(e)->pos();
218 d->lastPos = pos;
219
220 if (!indexAt(pos).isValid()) {
221 return QTreeView::viewportEvent(e);
222 }
223 QModelIndex index = model()->buddy(indexAt(pos));
224 if (d->delegate.editorEvent(e, model(), optionForIndex(index), index)) {
225 return true;
226 }
227 } break;
228 case QEvent::MouseButtonRelease: {
229 const QPoint pos = static_cast<QMouseEvent*>(e)->pos();
230 QModelIndex index = model()->buddy(indexAt(pos));
231 if (!indexAt(pos).isValid()) {
232 return QTreeView::viewportEvent(e);
233 }
234 if (d->delegate.editorEvent(e, model(), optionForIndex(index), index)) {
235 //Sometimes 2 items become active, to prevent that we explicitly turn off all but the first active item
236 std::optional<QModelIndex> active_ind = getActiveItem();
237 if (active_ind.has_value()) {
238 setExclusiveActiveItem(active_ind.value());
239 }
240 return true;
241 }
242 } break;
243 case QEvent::Leave: {
244 QEvent e(QEvent::Leave);
245 d->delegate.editorEvent(&e, model(), optionForIndex(d->hovered), d->hovered);
246 d->hovered = QModelIndex();
247 } break;
248 case QEvent::MouseMove: {
249#ifdef DRAG_WHILE_DRAG_WORKAROUND
250 if (d->isDragging) {
251 return false;
252 }
253#endif
254
255 const QPoint pos = static_cast<QMouseEvent*>(e)->pos();
256 QModelIndex hovered = indexAt(pos);
257 if (hovered != d->hovered) {
258 if (d->hovered.isValid()) {
259 QEvent e(QEvent::Leave);
260 d->delegate.editorEvent(&e, model(), optionForIndex(d->hovered), d->hovered);
261 }
262 if (hovered.isValid()) {
263 QEvent e(QEvent::Enter);
264 d->delegate.editorEvent(&e, model(), optionForIndex(hovered), hovered);
265 }
266 d->hovered = hovered;
267 }
268 /* This is a workaround for a bug in QTreeView that immediately begins a dragging action
269 when the mouse lands on the decoration/icon of a different index and moves 1 pixel or more */
270 Qt::MouseButtons buttons = static_cast<QMouseEvent*>(e)->buttons();
271 if ((Qt::LeftButton | Qt::MiddleButton) & buttons) {
272 if ((pos - d->lastPos).manhattanLength() > qApp->startDragDistance()) {
273 return QTreeView::viewportEvent(e);
274 }
275 return true;
276 }
277 } break;
278 case QEvent::ToolTip: {
279 const QPoint pos = static_cast<QHelpEvent*>(e)->pos();
280 if (!indexAt(pos).isValid()) {
281 return QTreeView::viewportEvent(e);
282 }
283 QModelIndex index = model()->buddy(indexAt(pos));
284 return d->delegate.editorEvent(e, model(), optionForIndex(index), index);
285 } break;
286 case QEvent::Resize: {
287 scheduleDelayedItemsLayout();
288 break;
289 }
290 default: break;
291 }
292 }
293
294 return QTreeView::viewportEvent(e);
295}
296
297void NodeView::contextMenuEvent(QContextMenuEvent *e)
298{
299 QTreeView::contextMenuEvent(e);
300 QModelIndex i = indexAt(e->pos());
301 if (model())
302 i = model()->buddy(i);
303 showContextMenu(e->globalPos(), i);
304}
305
306void NodeView::showContextMenu(const QPoint &globalPos, const QModelIndex &index)
307{
308 Q_EMIT contextMenuRequested(globalPos, index);
309}
310
311void NodeView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
312{
313 QTreeView::currentChanged(current, previous);
314 if (current != previous) {
315 Q_ASSERT(!current.isValid() || current.model() == model());
316 KisSignalsBlocker blocker(this);
317 model()->setData(current, true, KisNodeModel::ActiveRole);
318 }
319}
320
321void NodeView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &/*roles*/)
322{
323 QTreeView::dataChanged(topLeft, bottomRight);
324
325 if (d->ignoreDataChanged){
326 d->ignoreDataChanged = false;
327 return;
328 }
329
330 for (int x = topLeft.row(); x <= bottomRight.row(); ++x) {
331 for (int y = topLeft.column(); y <= bottomRight.column(); ++y) {
332 QModelIndex index = topLeft.sibling(x, y);
333 if (index.data(KisNodeModel::ActiveRole).toBool()) {
334 if (currentIndex() != index) {
335 setCurrentIndex(index);
336 }
337
338 return;
339 }
340 }
341 }
342}
343
344void NodeView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
345{
346 QTreeView::selectionChanged(selected, deselected);
347 // XXX: selectedIndexes() does not include hidden (collapsed) items, is this really intended?
348 Q_EMIT selectionChanged(selectedIndexes());
349}
350
351void NodeView::slotActionToggled(bool on, const QPersistentModelIndex &index, int num)
352{
354 list[num].state = on;
355 const_cast<QAbstractItemModel*>(index.model())->setData(index, QVariant::fromValue(list), KisNodeModel::PropertiesRole);
356}
357
358QStyleOptionViewItem NodeView::optionForIndex(const QModelIndex &index) const
359{
360#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
361 QStyleOptionViewItem option = viewOptions();
362#else
363 QStyleOptionViewItem option;
364 initViewItemOption(&option);
365#endif
366 option.rect = visualRect(index);
367 if (index == currentIndex())
368 option.state |= QStyle::State_HasFocus;
369 return option;
370}
371
372void NodeView::startDrag(Qt::DropActions supportedActions)
373{
375 QTreeView::startDrag(supportedActions);
376}
377
379{
380 const QModelIndexList selectedIndexes = selectionModel()->selectedIndexes();
381 Q_ASSERT(!selectedIndexes.isEmpty());
382
383 const int itemCount = selectedIndexes.count();
384
385 // If more than one item is dragged, align the items inside a
386 // rectangular grid. The maximum grid size is limited to 4 x 4 items.
387 int xCount = 2;
388 int size = 96;
389 if (itemCount > 9) {
390 xCount = 4;
392 }
393 else if (itemCount > 4) {
394 xCount = 3;
396 }
397 else if (itemCount < xCount) {
398 xCount = itemCount;
399 }
400
401 int yCount = itemCount / xCount;
402 if (itemCount % xCount != 0) {
403 ++yCount;
404 }
405
406 if (yCount > xCount) {
407 yCount = xCount;
408 }
409
410 // Draw the selected items into the grid cells
411 QPixmap dragPixmap(xCount * size + xCount - 1, yCount * size + yCount - 1);
412 dragPixmap.fill(Qt::transparent);
413
414 QPainter painter(&dragPixmap);
415 int x = 0;
416 int y = 0;
417 Q_FOREACH (const QModelIndex &selectedIndex, selectedIndexes) {
418 const QImage img = selectedIndex.data(int(KisNodeModel::BeginThumbnailRole) + size).value<QImage>();
419 painter.drawPixmap(x, y, QPixmap::fromImage(img.scaled(QSize(size, size), Qt::KeepAspectRatio, Qt::SmoothTransformation)));
420
421 x += size + 1;
422 if (x >= dragPixmap.width()) {
423 x = 0;
424 y += size + 1;
425 }
426 if (y >= dragPixmap.height()) {
427 break;
428 }
429 }
430
431 return dragPixmap;
432}
433
434void NodeView::resizeEvent(QResizeEvent * event)
435{
437 header()->setStretchLastSection(false);
438
439 int otherColumnsWidth = scm.visibilityColumnWidth();
440
441 // if layer box is enabled subtract its width from the "Default col".
442 if (KisConfig(false).useLayerSelectionCheckbox()) {
443 otherColumnsWidth += scm.selectedButtonColumnWidth();
444 }
445 header()->resizeSection(DEFAULT_COL, event->size().width() - otherColumnsWidth);
446 header()->resizeSection(SELECTED_COL, scm.selectedButtonColumnWidth());
447 header()->resizeSection(VISIBILITY_COL, scm.visibilityColumnWidth());
448
449 setIndentation(scm.indentation());
450 QTreeView::resizeEvent(event);
451}
452
453void NodeView::paintEvent(QPaintEvent *event)
454{
455 event->accept();
456 QTreeView::paintEvent(event);
457}
458
459void NodeView::drawBranches(QPainter *painter, const QRect &rect,
460 const QModelIndex &index) const
461{
462#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
463 QStyleOptionViewItem option = viewOptions();
464#else
465 QStyleOptionViewItem option;
466 initViewItemOption(&option);
467#endif
468 option.rect = rect;
469 // This is not really a job for an item delegate, but the logic was already there
470 d->delegate.drawBranches(painter, option, index);
471}
472
473void NodeView::dropEvent(QDropEvent *ev)
474{
475 QTreeView::dropEvent(ev);
477}
478
480{
481 QSize size(visualRect(model()->index(0, 0, QModelIndex())).width(), visualRect(model()->index(0, 0, QModelIndex())).height());
482 int scrollBarValue = verticalScrollBar()->value();
483
484 QPoint cursorPosition = QWidget::mapFromGlobal(QCursor::pos());
485
486 int numberRow = (cursorPosition.y() + scrollBarValue) / size.height();
487
488 //If cursor is at the half button of the page then the move action is performed after the slide, otherwise it is
489 //performed before the page
490 if (abs((cursorPosition.y() + scrollBarValue) - size.height()*numberRow) > (size.height()/2)) {
491 numberRow++;
492 }
493
494 if (numberRow > model()->rowCount(QModelIndex())) {
495 numberRow = model()->rowCount(QModelIndex());
496 }
497
498 return numberRow;
499}
500
501void NodeView::dragEnterEvent(QDragEnterEvent *ev)
502{
504
505 QVariant data = QVariant::fromValue(
506 static_cast<void*>(const_cast<QMimeData*>(ev->mimeData())));
507 model()->setData(QModelIndex(), data, KisNodeModel::DropEnabled);
508
509 QTreeView::dragEnterEvent(ev);
510}
511
512void NodeView::dragMoveEvent(QDragMoveEvent *ev)
513{
515 QTreeView::dragMoveEvent(ev);
516}
517
518void NodeView::dragLeaveEvent(QDragLeaveEvent *e)
519{
520 QTreeView::dragLeaveEvent(e);
522}
523
524void NodeView::mousePressEvent(QMouseEvent *e) {
525 if (!(e->modifiers() & Qt::ControlModifier)) {
526 QTreeView::mousePressEvent(e);
527 return;
528 }
529
530 std::optional<QModelIndex> ind = getActiveItem();
531 QTreeView::mousePressEvent(e);
532
533 if (ind.has_value()) {
534 setExclusiveActiveItem(ind.value());
535 }
536}
537
539{
540 return m_draggingFlag;
541}
542
544{
545 m_draggingFlag = flag;
546}
547
549{
550 d->delegate.slotUpdateIcon();
551}
552
553void NodeView::slotScrollerStateChanged(QScroller::State state){
555}
556
558{
559 setIndentation(KisNodeViewColorScheme::instance()->indentation());
561 d->delegate.slotConfigChanged();
562}
563
565{
566 KisConfig cfg(false);
567 if (cfg.useLayerSelectionCheckbox() == !header()->isSectionHidden(SELECTED_COL)) {
568 return;
569 }
570 header()->setSectionHidden(SELECTED_COL, !cfg.useLayerSelectionCheckbox());
571 // add/subtract width based on SELECTED_COL section's visibility
572 header()->resizeSection(DEFAULT_COL,
573 size().width()
574 + (cfg.useLayerSelectionCheckbox() ? header()->sectionSize(SELECTED_COL)
575 : -header()->sectionSize(SELECTED_COL)));
576}
577
578std::optional<QModelIndex> NodeView::getActiveItem()
579{
580 QAbstractItemModel* mdl = model();
581
582 int rows = mdl->rowCount();
583 int clmns = mdl->columnCount();
584
585 for (int i = 0; i < rows; i++) {
586 for (int j = 0; j < clmns; j++) {
587 auto index = mdl->index(i, j);
588 if (mdl->data(index, KisNodeModel::ActiveRole).toBool()){
589 return index;
590 }
591 }
592 }
593
594 return {};
595}
596
597void NodeView::setExclusiveActiveItem(QModelIndex index)
598{
599 QAbstractItemModel* mdl = model();
600
601 int rows = mdl->rowCount();
602 int clmns = mdl->columnCount();
603
604 for (int i = 0; i < rows; i++) {
605 for (int j = 0; j < clmns; j++) {
606 QModelIndex ind = mdl->index(i, j);
607
608 //Suppress the data changed event, so that it doesn't mess with the selection
609 d->ignoreDataChanged = true;
610 mdl->setData(ind, false, KisNodeModel::ActiveRole);
611 }
612 }
613
614 d->ignoreDataChanged = true;
615 mdl->setData(index, true, KisNodeModel::ActiveRole);
616
617 selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
618}
#define QT6_SHIFT_SELECTION_WORKAROUND
Definition NodeView.cpp:38
#define DRAG_WHILE_DRAG_WORKAROUND_START()
Definition NodeView.cpp:49
#define DRAG_WHILE_DRAG_WORKAROUND_STOP()
Definition NodeView.cpp:50
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:162
void setDraggingFlag(bool flag=true)
Definition NodeView.cpp:543
virtual void showContextMenu(const QPoint &globalPos, const QModelIndex &index)
Definition NodeView.cpp:306
void contextMenuRequested(const QPoint &globalPos, const QModelIndex &index)
void setExclusiveActiveItem(QModelIndex index)
Definition NodeView.cpp:597
void paintEvent(QPaintEvent *event) override
Definition NodeView.cpp:453
Private *const d
Definition NodeView.h:157
void mousePressEvent(QMouseEvent *e) override
Definition NodeView.cpp:524
void selectionChanged(const QModelIndexList &)
void dragMoveEvent(QDragMoveEvent *ev) override
Definition NodeView.cpp:512
Private(NodeView *_q)
Definition NodeView.cpp:57
void updateNode(const QModelIndex &index)
Definition NodeView.cpp:153
void resizeEvent(QResizeEvent *event) override
Definition NodeView.cpp:434
bool viewportEvent(QEvent *event) override
Definition NodeView.cpp:210
int cursorPageIndex() const
Definition NodeView.cpp:479
void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const override
Definition NodeView.cpp:459
NodeView(QWidget *parent=0)
Definition NodeView.cpp:81
void slotScrollerStateChanged(QScroller::State state)
Definition NodeView.cpp:553
NodeDelegate delegate
Definition NodeView.cpp:67
void toggleSolo(const QModelIndex &index)
Definition NodeView.cpp:158
void slotConfigurationChanged()
Definition NodeView.cpp:557
void slotUpdateIcons()
called with a theme change to refresh icon colors
Definition NodeView.cpp:548
void dropEvent(QDropEvent *ev) override
Definition NodeView.cpp:473
bool m_draggingFlag
Definition NodeView.h:151
@ SELECTED_COL
Definition NodeView.h:45
@ VISIBILITY_COL
Definition NodeView.h:44
@ DEFAULT_COL
Definition NodeView.h:43
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector< int > &roles=QVector< int >()) override
Definition NodeView.cpp:321
QStyleOptionViewItem optionForIndex(const QModelIndex &index) const
Definition NodeView.cpp:358
~NodeView() override
Definition NodeView.cpp:110
void slotActionToggled(bool on, const QPersistentModelIndex &index, int property)
Definition NodeView.cpp:351
QPixmap createDragPixmap() const
Definition NodeView.cpp:378
QPoint lastPos
Definition NodeView.cpp:69
QPersistentModelIndex hovered
Definition NodeView.cpp:68
void dragEnterEvent(QDragEnterEvent *e) override
Definition NodeView.cpp:501
void addPropertyActions(QMenu *menu, const QModelIndex &index)
Definition NodeView.cpp:140
std::optional< QModelIndex > getActiveItem()
Definition NodeView.cpp:578
void startDrag(Qt::DropActions supportedActions) override
Definition NodeView.cpp:372
bool isDragging() const
Definition NodeView.cpp:538
bool shiftClickFix
Definition NodeView.cpp:75
void setModel(QAbstractItemModel *model) override
Definition NodeView.cpp:115
void dragLeaveEvent(QDragLeaveEvent *e) override
Definition NodeView.cpp:518
void updateSelectedCheckboxColumn()
Definition NodeView.cpp:564
void contextMenuEvent(QContextMenuEvent *event) override
Definition NodeView.cpp:297
void currentChanged(const QModelIndex &current, const QModelIndex &previous) override
Definition NodeView.cpp:311
QModelIndex indexAt(const QPoint &point) const override
Definition NodeView.cpp:196
QString buttons(const T &ev)
KRITAWIDGETUTILS_EXPORT void updateCursor(QWidget *source, QScroller::State state)
KRITAWIDGETUTILS_EXPORT QScroller * createPreconfiguredScroller(QAbstractScrollArea *target)