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