1 /*
2 * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com>
3 * SPDX-FileCopyrightText: 2012 Frank Reininghaus <frank78ac@googlemail.com>
4 *
5 * Based on the Itemviews NG project from Trolltech Labs
6 *
7 * SPDX-License-Identifier: GPL-2.0-or-later
8 */
9
10 #include "kitemlistcontroller.h"
11
12 #include "kitemlistselectionmanager.h"
13 #include "kitemlistview.h"
14 #include "private/kitemlistkeyboardsearchmanager.h"
15 #include "private/kitemlistrubberband.h"
16 #include "private/ktwofingerswipe.h"
17 #include "private/ktwofingertap.h"
18 #include "views/draganddrophelper.h"
19
20 #include <QAccessible>
21 #include <QApplication>
22 #include <QDrag>
23 #include <QGesture>
24 #include <QGraphicsScene>
25 #include <QGraphicsSceneEvent>
26 #include <QGraphicsView>
27 #include <QMimeData>
28 #include <QTimer>
29 #include <QTouchEvent>
30
KItemListController(KItemModelBase * model,KItemListView * view,QObject * parent)31 KItemListController::KItemListController(KItemModelBase* model, KItemListView* view, QObject* parent) :
32 QObject(parent),
33 m_singleClickActivationEnforced(false),
34 m_selectionTogglePressed(false),
35 m_clearSelectionIfItemsAreNotDragged(false),
36 m_isSwipeGesture(false),
37 m_dragActionOrRightClick(false),
38 m_scrollerIsScrolling(false),
39 m_pinchGestureInProgress(false),
40 m_mousePress(false),
41 m_isTouchEvent(false),
42 m_selectionBehavior(NoSelection),
43 m_autoActivationBehavior(ActivationAndExpansion),
44 m_mouseDoubleClickAction(ActivateItemOnly),
45 m_model(nullptr),
46 m_view(nullptr),
47 m_selectionManager(new KItemListSelectionManager(this)),
48 m_keyboardManager(new KItemListKeyboardSearchManager(this)),
49 m_pressedIndex(-1),
50 m_pressedMousePos(),
51 m_autoActivationTimer(nullptr),
52 m_swipeGesture(Qt::CustomGesture),
53 m_twoFingerTapGesture(Qt::CustomGesture),
54 m_oldSelection(),
55 m_keyboardAnchorIndex(-1),
56 m_keyboardAnchorPos(0)
57 {
58 connect(m_keyboardManager, &KItemListKeyboardSearchManager::changeCurrentItem,
59 this, &KItemListController::slotChangeCurrentItem);
60 connect(m_selectionManager, &KItemListSelectionManager::currentChanged,
61 m_keyboardManager, &KItemListKeyboardSearchManager::slotCurrentChanged);
62 connect(m_selectionManager, &KItemListSelectionManager::selectionChanged,
63 m_keyboardManager, &KItemListKeyboardSearchManager::slotSelectionChanged);
64
65 m_autoActivationTimer = new QTimer(this);
66 m_autoActivationTimer->setSingleShot(true);
67 m_autoActivationTimer->setInterval(-1);
68 connect(m_autoActivationTimer, &QTimer::timeout, this, &KItemListController::slotAutoActivationTimeout);
69
70 setModel(model);
71 setView(view);
72
73 m_swipeGesture = QGestureRecognizer::registerRecognizer(new KTwoFingerSwipeRecognizer());
74 m_twoFingerTapGesture = QGestureRecognizer::registerRecognizer(new KTwoFingerTapRecognizer());
75 view->grabGesture(m_swipeGesture);
76 view->grabGesture(m_twoFingerTapGesture);
77 view->grabGesture(Qt::TapGesture);
78 view->grabGesture(Qt::TapAndHoldGesture);
79 view->grabGesture(Qt::PinchGesture);
80 }
81
~KItemListController()82 KItemListController::~KItemListController()
83 {
84 setView(nullptr);
85 Q_ASSERT(!m_view);
86
87 setModel(nullptr);
88 Q_ASSERT(!m_model);
89 }
90
setModel(KItemModelBase * model)91 void KItemListController::setModel(KItemModelBase* model)
92 {
93 if (m_model == model) {
94 return;
95 }
96
97 KItemModelBase* oldModel = m_model;
98 if (oldModel) {
99 oldModel->deleteLater();
100 }
101
102 m_model = model;
103 if (m_model) {
104 m_model->setParent(this);
105 }
106
107 if (m_view) {
108 m_view->setModel(m_model);
109 }
110
111 m_selectionManager->setModel(m_model);
112
113 Q_EMIT modelChanged(m_model, oldModel);
114 }
115
model() const116 KItemModelBase* KItemListController::model() const
117 {
118 return m_model;
119 }
120
selectionManager() const121 KItemListSelectionManager* KItemListController::selectionManager() const
122 {
123 return m_selectionManager;
124 }
125
setView(KItemListView * view)126 void KItemListController::setView(KItemListView* view)
127 {
128 if (m_view == view) {
129 return;
130 }
131
132 KItemListView* oldView = m_view;
133 if (oldView) {
134 disconnect(oldView, &KItemListView::scrollOffsetChanged, this, &KItemListController::slotViewScrollOffsetChanged);
135 oldView->deleteLater();
136 }
137
138 m_view = view;
139
140 if (m_view) {
141 m_view->setParent(this);
142 m_view->setController(this);
143 m_view->setModel(m_model);
144 connect(m_view, &KItemListView::scrollOffsetChanged, this, &KItemListController::slotViewScrollOffsetChanged);
145 updateExtendedSelectionRegion();
146 }
147
148 Q_EMIT viewChanged(m_view, oldView);
149 }
150
view() const151 KItemListView* KItemListController::view() const
152 {
153 return m_view;
154 }
155
setSelectionBehavior(SelectionBehavior behavior)156 void KItemListController::setSelectionBehavior(SelectionBehavior behavior)
157 {
158 m_selectionBehavior = behavior;
159 updateExtendedSelectionRegion();
160 }
161
selectionBehavior() const162 KItemListController::SelectionBehavior KItemListController::selectionBehavior() const
163 {
164 return m_selectionBehavior;
165 }
166
setAutoActivationBehavior(AutoActivationBehavior behavior)167 void KItemListController::setAutoActivationBehavior(AutoActivationBehavior behavior)
168 {
169 m_autoActivationBehavior = behavior;
170 }
171
autoActivationBehavior() const172 KItemListController::AutoActivationBehavior KItemListController::autoActivationBehavior() const
173 {
174 return m_autoActivationBehavior;
175 }
176
setMouseDoubleClickAction(MouseDoubleClickAction action)177 void KItemListController::setMouseDoubleClickAction(MouseDoubleClickAction action)
178 {
179 m_mouseDoubleClickAction = action;
180 }
181
mouseDoubleClickAction() const182 KItemListController::MouseDoubleClickAction KItemListController::mouseDoubleClickAction() const
183 {
184 return m_mouseDoubleClickAction;
185 }
186
indexCloseToMousePressedPosition() const187 int KItemListController::indexCloseToMousePressedPosition() const
188 {
189 QHashIterator<KItemListWidget*, KItemListGroupHeader*> it(m_view->m_visibleGroups);
190 while (it.hasNext()) {
191 it.next();
192 KItemListGroupHeader *groupHeader = it.value();
193 const QPointF mappedToGroup = groupHeader->mapFromItem(nullptr, m_pressedMousePos);
194 if (groupHeader->contains(mappedToGroup)) {
195 return it.key()->index();
196 }
197 }
198 return -1;
199 }
200
setAutoActivationDelay(int delay)201 void KItemListController::setAutoActivationDelay(int delay)
202 {
203 m_autoActivationTimer->setInterval(delay);
204 }
205
autoActivationDelay() const206 int KItemListController::autoActivationDelay() const
207 {
208 return m_autoActivationTimer->interval();
209 }
210
setSingleClickActivationEnforced(bool singleClick)211 void KItemListController::setSingleClickActivationEnforced(bool singleClick)
212 {
213 m_singleClickActivationEnforced = singleClick;
214 }
215
singleClickActivationEnforced() const216 bool KItemListController::singleClickActivationEnforced() const
217 {
218 return m_singleClickActivationEnforced;
219 }
220
keyPressEvent(QKeyEvent * event)221 bool KItemListController::keyPressEvent(QKeyEvent* event)
222 {
223 int index = m_selectionManager->currentItem();
224 int key = event->key();
225
226 // Handle the expanding/collapsing of items
227 if (m_view->supportsItemExpanding() && m_model->isExpandable(index)) {
228 if (key == Qt::Key_Right) {
229 if (m_model->setExpanded(index, true)) {
230 return true;
231 }
232 } else if (key == Qt::Key_Left) {
233 if (m_model->setExpanded(index, false)) {
234 return true;
235 }
236 }
237 }
238
239 const bool shiftPressed = event->modifiers() & Qt::ShiftModifier;
240 const bool controlPressed = event->modifiers() & Qt::ControlModifier;
241 const bool shiftOrControlPressed = shiftPressed || controlPressed;
242 const bool navigationPressed = key == Qt::Key_Home || key == Qt::Key_End ||
243 key == Qt::Key_PageUp || key == Qt::Key_PageDown ||
244 key == Qt::Key_Up || key == Qt::Key_Down ||
245 key == Qt::Key_Left || key == Qt::Key_Right;
246
247 const int itemCount = m_model->count();
248
249 // For horizontal scroll orientation, transform
250 // the arrow keys to simplify the event handling.
251 if (m_view->scrollOrientation() == Qt::Horizontal) {
252 switch (key) {
253 case Qt::Key_Up: key = Qt::Key_Left; break;
254 case Qt::Key_Down: key = Qt::Key_Right; break;
255 case Qt::Key_Left: key = Qt::Key_Up; break;
256 case Qt::Key_Right: key = Qt::Key_Down; break;
257 default: break;
258 }
259 }
260
261 const bool selectSingleItem = m_selectionBehavior != NoSelection && itemCount == 1 && navigationPressed;
262
263 if (selectSingleItem) {
264 const int current = m_selectionManager->currentItem();
265 m_selectionManager->setSelected(current);
266 return true;
267 }
268
269 switch (key) {
270 case Qt::Key_Home:
271 index = 0;
272 m_keyboardAnchorIndex = index;
273 m_keyboardAnchorPos = keyboardAnchorPos(index);
274 break;
275
276 case Qt::Key_End:
277 index = itemCount - 1;
278 m_keyboardAnchorIndex = index;
279 m_keyboardAnchorPos = keyboardAnchorPos(index);
280 break;
281
282 case Qt::Key_Left:
283 if (index > 0) {
284 const int expandedParentsCount = m_model->expandedParentsCount(index);
285 if (expandedParentsCount == 0) {
286 --index;
287 } else {
288 // Go to the parent of the current item.
289 do {
290 --index;
291 } while (index > 0 && m_model->expandedParentsCount(index) == expandedParentsCount);
292 }
293 m_keyboardAnchorIndex = index;
294 m_keyboardAnchorPos = keyboardAnchorPos(index);
295 }
296 break;
297
298 case Qt::Key_Right:
299 if (index < itemCount - 1) {
300 ++index;
301 m_keyboardAnchorIndex = index;
302 m_keyboardAnchorPos = keyboardAnchorPos(index);
303 }
304 break;
305
306 case Qt::Key_Up:
307 updateKeyboardAnchor();
308 index = previousRowIndex(index);
309 break;
310
311 case Qt::Key_Down:
312 updateKeyboardAnchor();
313 index = nextRowIndex(index);
314 break;
315
316 case Qt::Key_PageUp:
317 if (m_view->scrollOrientation() == Qt::Horizontal) {
318 // The new current index should correspond to the first item in the current column.
319 int newIndex = qMax(index - 1, 0);
320 while (newIndex != index && m_view->itemRect(newIndex).topLeft().y() < m_view->itemRect(index).topLeft().y()) {
321 index = newIndex;
322 newIndex = qMax(index - 1, 0);
323 }
324 m_keyboardAnchorIndex = index;
325 m_keyboardAnchorPos = keyboardAnchorPos(index);
326 } else {
327 const qreal currentItemBottom = m_view->itemRect(index).bottomLeft().y();
328 const qreal height = m_view->geometry().height();
329
330 // The new current item should be the first item in the current
331 // column whose itemRect's top coordinate is larger than targetY.
332 const qreal targetY = currentItemBottom - height;
333
334 updateKeyboardAnchor();
335 int newIndex = previousRowIndex(index);
336 do {
337 index = newIndex;
338 updateKeyboardAnchor();
339 newIndex = previousRowIndex(index);
340 } while (m_view->itemRect(newIndex).topLeft().y() > targetY && newIndex != index);
341 }
342 break;
343
344 case Qt::Key_PageDown:
345 if (m_view->scrollOrientation() == Qt::Horizontal) {
346 // The new current index should correspond to the last item in the current column.
347 int newIndex = qMin(index + 1, m_model->count() - 1);
348 while (newIndex != index && m_view->itemRect(newIndex).topLeft().y() > m_view->itemRect(index).topLeft().y()) {
349 index = newIndex;
350 newIndex = qMin(index + 1, m_model->count() - 1);
351 }
352 m_keyboardAnchorIndex = index;
353 m_keyboardAnchorPos = keyboardAnchorPos(index);
354 } else {
355 const qreal currentItemTop = m_view->itemRect(index).topLeft().y();
356 const qreal height = m_view->geometry().height();
357
358 // The new current item should be the last item in the current
359 // column whose itemRect's bottom coordinate is smaller than targetY.
360 const qreal targetY = currentItemTop + height;
361
362 updateKeyboardAnchor();
363 int newIndex = nextRowIndex(index);
364 do {
365 index = newIndex;
366 updateKeyboardAnchor();
367 newIndex = nextRowIndex(index);
368 } while (m_view->itemRect(newIndex).bottomLeft().y() < targetY && newIndex != index);
369 }
370 break;
371
372 case Qt::Key_Enter:
373 case Qt::Key_Return: {
374 const KItemSet selectedItems = m_selectionManager->selectedItems();
375 if (selectedItems.count() >= 2) {
376 Q_EMIT itemsActivated(selectedItems);
377 } else if (selectedItems.count() == 1) {
378 Q_EMIT itemActivated(selectedItems.first());
379 } else {
380 Q_EMIT itemActivated(index);
381 }
382 break;
383 }
384
385 case Qt::Key_Menu: {
386 // Emit the signal itemContextMenuRequested() in case if at least one
387 // item is selected. Otherwise the signal viewContextMenuRequested() will be emitted.
388 const KItemSet selectedItems = m_selectionManager->selectedItems();
389 int index = -1;
390 if (selectedItems.count() >= 2) {
391 const int currentItemIndex = m_selectionManager->currentItem();
392 index = selectedItems.contains(currentItemIndex)
393 ? currentItemIndex : selectedItems.first();
394 } else if (selectedItems.count() == 1) {
395 index = selectedItems.first();
396 }
397
398 if (index >= 0) {
399 const QRectF contextRect = m_view->itemContextRect(index);
400 const QPointF pos(m_view->scene()->views().first()->mapToGlobal(contextRect.bottomRight().toPoint()));
401 Q_EMIT itemContextMenuRequested(index, pos);
402 } else {
403 Q_EMIT viewContextMenuRequested(QCursor::pos());
404 }
405 break;
406 }
407
408 case Qt::Key_Escape:
409 if (m_selectionBehavior != SingleSelection) {
410 m_selectionManager->clearSelection();
411 }
412 m_keyboardManager->cancelSearch();
413 Q_EMIT escapePressed();
414 break;
415
416 case Qt::Key_Space:
417 if (m_selectionBehavior == MultiSelection) {
418 if (controlPressed) {
419 // Toggle the selection state of the current item.
420 m_selectionManager->endAnchoredSelection();
421 m_selectionManager->setSelected(index, 1, KItemListSelectionManager::Toggle);
422 m_selectionManager->beginAnchoredSelection(index);
423 break;
424 } else {
425 // Select the current item if it is not selected yet.
426 const int current = m_selectionManager->currentItem();
427 if (!m_selectionManager->isSelected(current)) {
428 m_selectionManager->setSelected(current);
429 break;
430 }
431 }
432 }
433 Q_FALLTHROUGH(); // fall through to the default case and add the Space to the current search string.
434 default:
435 m_keyboardManager->addKeys(event->text());
436 // Make sure unconsumed events get propagated up the chain. #302329
437 event->ignore();
438 return false;
439 }
440
441 if (m_selectionManager->currentItem() != index) {
442 switch (m_selectionBehavior) {
443 case NoSelection:
444 m_selectionManager->setCurrentItem(index);
445 break;
446
447 case SingleSelection:
448 m_selectionManager->setCurrentItem(index);
449 m_selectionManager->clearSelection();
450 m_selectionManager->setSelected(index, 1);
451 break;
452
453 case MultiSelection:
454 if (controlPressed) {
455 m_selectionManager->endAnchoredSelection();
456 }
457
458 m_selectionManager->setCurrentItem(index);
459
460 if (!shiftOrControlPressed) {
461 m_selectionManager->clearSelection();
462 m_selectionManager->setSelected(index, 1);
463 }
464
465 if (!shiftPressed) {
466 m_selectionManager->beginAnchoredSelection(index);
467 }
468 break;
469 }
470 }
471
472 if (navigationPressed) {
473 m_view->scrollToItem(index);
474 }
475 return true;
476 }
477
slotChangeCurrentItem(const QString & text,bool searchFromNextItem)478 void KItemListController::slotChangeCurrentItem(const QString& text, bool searchFromNextItem)
479 {
480 if (!m_model || m_model->count() == 0) {
481 return;
482 }
483 int index;
484 if (searchFromNextItem) {
485 const int currentIndex = m_selectionManager->currentItem();
486 index = m_model->indexForKeyboardSearch(text, (currentIndex + 1) % m_model->count());
487 } else {
488 index = m_model->indexForKeyboardSearch(text, 0);
489 }
490 if (index >= 0) {
491 m_selectionManager->setCurrentItem(index);
492
493 if (m_selectionBehavior != NoSelection) {
494 m_selectionManager->replaceSelection(index);
495 m_selectionManager->beginAnchoredSelection(index);
496 }
497
498 m_view->scrollToItem(index);
499 }
500 }
501
slotAutoActivationTimeout()502 void KItemListController::slotAutoActivationTimeout()
503 {
504 if (!m_model || !m_view) {
505 return;
506 }
507
508 const int index = m_autoActivationTimer->property("index").toInt();
509 if (index < 0 || index >= m_model->count()) {
510 return;
511 }
512
513 /* m_view->isUnderMouse() fixes a bug in the Folder-View-Panel and in the
514 * Places-Panel.
515 *
516 * Bug: When you drag a file onto a Folder-View-Item or a Places-Item and
517 * then move away before the auto-activation timeout triggers, than the
518 * item still becomes activated/expanded.
519 *
520 * See Bug 293200 and 305783
521 */
522 if (m_model->supportsDropping(index) && m_view->isUnderMouse()) {
523 if (m_view->supportsItemExpanding() && m_model->isExpandable(index)) {
524 const bool expanded = m_model->isExpanded(index);
525 m_model->setExpanded(index, !expanded);
526 } else if (m_autoActivationBehavior != ExpansionOnly) {
527 Q_EMIT itemActivated(index);
528 }
529 }
530 }
531
inputMethodEvent(QInputMethodEvent * event)532 bool KItemListController::inputMethodEvent(QInputMethodEvent* event)
533 {
534 Q_UNUSED(event)
535 return false;
536 }
537
mousePressEvent(QGraphicsSceneMouseEvent * event,const QTransform & transform)538 bool KItemListController::mousePressEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
539 {
540 m_mousePress = true;
541
542 if (event->source() == Qt::MouseEventSynthesizedByQt && m_isTouchEvent) {
543 return false;
544 }
545
546 if (!m_view) {
547 return false;
548 }
549
550 m_pressedMousePos = transform.map(event->pos());
551 m_pressedIndex = m_view->itemAt(m_pressedMousePos);
552
553 const Qt::MouseButtons buttons = event->buttons();
554
555 if (!onPress(event->screenPos(), event->pos(), event->modifiers(), buttons)) {
556 startRubberBand();
557 return false;
558 }
559
560 return true;
561 }
562
mouseMoveEvent(QGraphicsSceneMouseEvent * event,const QTransform & transform)563 bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
564 {
565 if (!m_view) {
566 return false;
567 }
568
569 if (m_view->m_tapAndHoldIndicator->isActive()) {
570 m_view->m_tapAndHoldIndicator->setActive(false);
571 }
572
573 if (event->source() == Qt::MouseEventSynthesizedByQt && !m_dragActionOrRightClick && m_isTouchEvent) {
574 return false;
575 }
576
577 if (m_pressedIndex >= 0) {
578 // Check whether a dragging should be started
579 if (event->buttons() & Qt::LeftButton) {
580 const QPointF pos = transform.map(event->pos());
581 if ((pos - m_pressedMousePos).manhattanLength() >= QApplication::startDragDistance()) {
582 if (!m_selectionManager->isSelected(m_pressedIndex)) {
583 // Always assure that the dragged item gets selected. Usually this is already
584 // done on the mouse-press event, but when using the selection-toggle on a
585 // selected item the dragged item is not selected yet.
586 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
587 } else {
588 // A selected item has been clicked to drag all selected items
589 // -> the selection should not be cleared when the mouse button is released.
590 m_clearSelectionIfItemsAreNotDragged = false;
591 }
592 startDragging();
593 m_mousePress = false;
594 }
595 }
596 } else {
597 KItemListRubberBand* rubberBand = m_view->rubberBand();
598 if (rubberBand->isActive()) {
599 QPointF endPos = transform.map(event->pos());
600
601 // Update the current item.
602 const int newCurrent = m_view->itemAt(endPos);
603 if (newCurrent >= 0) {
604 // It's expected that the new current index is also the new anchor (bug 163451).
605 m_selectionManager->endAnchoredSelection();
606 m_selectionManager->setCurrentItem(newCurrent);
607 m_selectionManager->beginAnchoredSelection(newCurrent);
608 }
609
610 if (m_view->scrollOrientation() == Qt::Vertical) {
611 endPos.ry() += m_view->scrollOffset();
612 if (m_view->itemSize().width() < 0) {
613 // Use a special rubberband for views that have only one column and
614 // expand the rubberband to use the whole width of the view.
615 endPos.setX(m_view->size().width());
616 }
617 } else {
618 endPos.rx() += m_view->scrollOffset();
619 }
620 rubberBand->setEndPosition(endPos);
621 }
622 }
623
624 return false;
625 }
626
mouseReleaseEvent(QGraphicsSceneMouseEvent * event,const QTransform & transform)627 bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
628 {
629 m_mousePress = false;
630 m_isTouchEvent = false;
631
632 if (!m_view) {
633 return false;
634 }
635
636 if (m_view->m_tapAndHoldIndicator->isActive()) {
637 m_view->m_tapAndHoldIndicator->setActive(false);
638 }
639
640 KItemListRubberBand* rubberBand = m_view->rubberBand();
641 if (event->source() == Qt::MouseEventSynthesizedByQt && !rubberBand->isActive() && m_isTouchEvent) {
642 return false;
643 }
644
645 Q_EMIT mouseButtonReleased(m_pressedIndex, event->buttons());
646
647 return onRelease(transform.map(event->pos()), event->modifiers(), event->button(), false);
648 }
649
mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event,const QTransform & transform)650 bool KItemListController::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
651 {
652 const QPointF pos = transform.map(event->pos());
653 const int index = m_view->itemAt(pos);
654
655 // Expand item if desired - See Bug 295573
656 if (m_mouseDoubleClickAction != ActivateItemOnly) {
657 if (m_view && m_model && m_view->supportsItemExpanding() && m_model->isExpandable(index)) {
658 const bool expanded = m_model->isExpanded(index);
659 m_model->setExpanded(index, !expanded);
660 }
661 }
662
663 if (event->button() & Qt::RightButton) {
664 m_selectionManager->clearSelection();
665 if (index >= 0) {
666 m_selectionManager->setSelected(index);
667 Q_EMIT itemContextMenuRequested(index, event->screenPos());
668 } else {
669 const QRectF headerBounds = m_view->headerBoundaries();
670 if (headerBounds.contains(event->pos())) {
671 Q_EMIT headerContextMenuRequested(event->screenPos());
672 } else {
673 Q_EMIT viewContextMenuRequested(event->screenPos());
674 }
675 }
676 return true;
677 }
678
679 bool emitItemActivated = !(m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced) &&
680 (event->button() & Qt::LeftButton) &&
681 index >= 0 && index < m_model->count();
682 if (emitItemActivated) {
683 Q_EMIT itemActivated(index);
684 }
685 return false;
686 }
687
dragEnterEvent(QGraphicsSceneDragDropEvent * event,const QTransform & transform)688 bool KItemListController::dragEnterEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
689 {
690 Q_UNUSED(event)
691 Q_UNUSED(transform)
692
693 DragAndDropHelper::clearUrlListMatchesUrlCache();
694
695 return false;
696 }
697
dragLeaveEvent(QGraphicsSceneDragDropEvent * event,const QTransform & transform)698 bool KItemListController::dragLeaveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
699 {
700 Q_UNUSED(event)
701 Q_UNUSED(transform)
702
703 m_autoActivationTimer->stop();
704 m_view->setAutoScroll(false);
705 m_view->hideDropIndicator();
706
707 KItemListWidget* widget = hoveredWidget();
708 if (widget) {
709 widget->setHovered(false);
710 Q_EMIT itemUnhovered(widget->index());
711 }
712 return false;
713 }
714
dragMoveEvent(QGraphicsSceneDragDropEvent * event,const QTransform & transform)715 bool KItemListController::dragMoveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
716 {
717 if (!m_model || !m_view) {
718 return false;
719 }
720
721
722 QUrl hoveredDir = m_model->directory();
723 KItemListWidget* oldHoveredWidget = hoveredWidget();
724
725 const QPointF pos = transform.map(event->pos());
726 KItemListWidget* newHoveredWidget = widgetForPos(pos);
727
728 if (oldHoveredWidget != newHoveredWidget) {
729 m_autoActivationTimer->stop();
730
731 if (oldHoveredWidget) {
732 oldHoveredWidget->setHovered(false);
733 Q_EMIT itemUnhovered(oldHoveredWidget->index());
734 }
735 }
736
737 if (newHoveredWidget) {
738 bool droppingBetweenItems = false;
739 if (m_model->sortRole().isEmpty()) {
740 // The model supports inserting items between other items.
741 droppingBetweenItems = (m_view->showDropIndicator(pos) >= 0);
742 }
743
744 const int index = newHoveredWidget->index();
745
746 if (m_model->isDir(index)) {
747 hoveredDir = m_model->url(index);
748 }
749
750 if (!droppingBetweenItems) {
751 if (m_model->supportsDropping(index)) {
752 // Something has been dragged on an item.
753 m_view->hideDropIndicator();
754 if (!newHoveredWidget->isHovered()) {
755 newHoveredWidget->setHovered(true);
756 Q_EMIT itemHovered(index);
757 }
758
759 if (!m_autoActivationTimer->isActive() && m_autoActivationTimer->interval() >= 0) {
760 m_autoActivationTimer->setProperty("index", index);
761 m_autoActivationTimer->start();
762 }
763 }
764 } else {
765 m_autoActivationTimer->stop();
766 if (newHoveredWidget && newHoveredWidget->isHovered()) {
767 newHoveredWidget->setHovered(false);
768 Q_EMIT itemUnhovered(index);
769 }
770 }
771 } else {
772 m_view->hideDropIndicator();
773 }
774
775 if (DragAndDropHelper::urlListMatchesUrl(event->mimeData()->urls(), hoveredDir)) {
776 event->setDropAction(Qt::IgnoreAction);
777 event->ignore();
778 } else {
779 event->setDropAction(event->proposedAction());
780 event->accept();
781 }
782 return false;
783 }
784
dropEvent(QGraphicsSceneDragDropEvent * event,const QTransform & transform)785 bool KItemListController::dropEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
786 {
787 if (!m_view) {
788 return false;
789 }
790
791 m_autoActivationTimer->stop();
792 m_view->setAutoScroll(false);
793
794 const QPointF pos = transform.map(event->pos());
795
796 int dropAboveIndex = -1;
797 if (m_model->sortRole().isEmpty()) {
798 // The model supports inserting of items between other items.
799 dropAboveIndex = m_view->showDropIndicator(pos);
800 }
801
802 if (dropAboveIndex >= 0) {
803 // Something has been dropped between two items.
804 m_view->hideDropIndicator();
805 Q_EMIT aboveItemDropEvent(dropAboveIndex, event);
806 } else if (!event->mimeData()->hasFormat(m_model->blacklistItemDropEventMimeType())) {
807 // Something has been dropped on an item or on an empty part of the view.
808 Q_EMIT itemDropEvent(m_view->itemAt(pos), event);
809 }
810
811 QAccessibleEvent accessibilityEvent(view(), QAccessible::DragDropEnd);
812 QAccessible::updateAccessibility(&accessibilityEvent);
813
814 return true;
815 }
816
hoverEnterEvent(QGraphicsSceneHoverEvent * event,const QTransform & transform)817 bool KItemListController::hoverEnterEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
818 {
819 Q_UNUSED(event)
820 Q_UNUSED(transform)
821 return false;
822 }
823
hoverMoveEvent(QGraphicsSceneHoverEvent * event,const QTransform & transform)824 bool KItemListController::hoverMoveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
825 {
826 Q_UNUSED(transform)
827 if (!m_model || !m_view) {
828 return false;
829 }
830
831 KItemListWidget* oldHoveredWidget = hoveredWidget();
832 const QPointF pos = transform.map(event->pos());
833 KItemListWidget* newHoveredWidget = widgetForPos(pos);
834
835 if (oldHoveredWidget != newHoveredWidget) {
836 if (oldHoveredWidget) {
837 oldHoveredWidget->setHovered(false);
838 Q_EMIT itemUnhovered(oldHoveredWidget->index());
839 }
840
841 if (newHoveredWidget) {
842 newHoveredWidget->setHovered(true);
843 const QPointF mappedPos = newHoveredWidget->mapFromItem(m_view, pos);
844 newHoveredWidget->setHoverPosition(mappedPos);
845 Q_EMIT itemHovered(newHoveredWidget->index());
846 }
847 } else if (oldHoveredWidget) {
848 const QPointF mappedPos = oldHoveredWidget->mapFromItem(m_view, pos);
849 oldHoveredWidget->setHoverPosition(mappedPos);
850 }
851
852 return false;
853 }
854
hoverLeaveEvent(QGraphicsSceneHoverEvent * event,const QTransform & transform)855 bool KItemListController::hoverLeaveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
856 {
857 Q_UNUSED(event)
858 Q_UNUSED(transform)
859
860 m_mousePress = false;
861 m_isTouchEvent = false;
862
863 if (!m_model || !m_view) {
864 return false;
865 }
866
867 const auto widgets = m_view->visibleItemListWidgets();
868 for (KItemListWidget* widget : widgets) {
869 if (widget->isHovered()) {
870 widget->setHovered(false);
871 Q_EMIT itemUnhovered(widget->index());
872 }
873 }
874 return false;
875 }
876
wheelEvent(QGraphicsSceneWheelEvent * event,const QTransform & transform)877 bool KItemListController::wheelEvent(QGraphicsSceneWheelEvent* event, const QTransform& transform)
878 {
879 Q_UNUSED(event)
880 Q_UNUSED(transform)
881 return false;
882 }
883
resizeEvent(QGraphicsSceneResizeEvent * event,const QTransform & transform)884 bool KItemListController::resizeEvent(QGraphicsSceneResizeEvent* event, const QTransform& transform)
885 {
886 Q_UNUSED(event)
887 Q_UNUSED(transform)
888 return false;
889 }
890
gestureEvent(QGestureEvent * event,const QTransform & transform)891 bool KItemListController::gestureEvent(QGestureEvent* event, const QTransform& transform)
892 {
893 if (!m_view) {
894 return false;
895 }
896
897 //you can touch on different views at the same time, but only one QWidget gets a mousePressEvent
898 //we use this to get the right QWidget
899 //the only exception is a tap gesture with state GestureStarted, we need to reset some variable
900 if (!m_mousePress) {
901 if (QGesture* tap = event->gesture(Qt::TapGesture)) {
902 QTapGesture* tapGesture = static_cast<QTapGesture*>(tap);
903 if (tapGesture->state() == Qt::GestureStarted) {
904 tapTriggered(tapGesture, transform);
905 }
906 }
907 return false;
908 }
909
910 bool accepted = false;
911
912 if (QGesture* tap = event->gesture(Qt::TapGesture)) {
913 tapTriggered(static_cast<QTapGesture*>(tap), transform);
914 accepted = true;
915 }
916 if (event->gesture(Qt::TapAndHoldGesture)) {
917 tapAndHoldTriggered(event, transform);
918 accepted = true;
919 }
920 if (event->gesture(Qt::PinchGesture)) {
921 pinchTriggered(event, transform);
922 accepted = true;
923 }
924 if (event->gesture(m_swipeGesture)) {
925 swipeTriggered(event, transform);
926 accepted = true;
927 }
928 if (event->gesture(m_twoFingerTapGesture)) {
929 twoFingerTapTriggered(event, transform);
930 accepted = true;
931 }
932 return accepted;
933 }
934
touchBeginEvent(QTouchEvent * event,const QTransform & transform)935 bool KItemListController::touchBeginEvent(QTouchEvent* event, const QTransform& transform)
936 {
937 Q_UNUSED(event)
938 Q_UNUSED(transform)
939
940 m_isTouchEvent = true;
941 return false;
942 }
943
tapTriggered(QTapGesture * tap,const QTransform & transform)944 void KItemListController::tapTriggered(QTapGesture* tap, const QTransform& transform)
945 {
946 static bool scrollerWasActive = false;
947
948 if (tap->state() == Qt::GestureStarted) {
949 m_dragActionOrRightClick = false;
950 m_isSwipeGesture = false;
951 m_pinchGestureInProgress = false;
952 scrollerWasActive = m_scrollerIsScrolling;
953 }
954
955 if (tap->state() == Qt::GestureFinished) {
956 m_mousePress = false;
957
958 //if at the moment of the gesture start the QScroller was active, the user made the tap
959 //to stop the QScroller and not to tap on an item
960 if (scrollerWasActive) {
961 return;
962 }
963
964 if (m_view->m_tapAndHoldIndicator->isActive()) {
965 m_view->m_tapAndHoldIndicator->setActive(false);
966 }
967
968 m_pressedMousePos = transform.map(tap->position());
969 m_pressedIndex = m_view->itemAt(m_pressedMousePos);
970
971 if (m_dragActionOrRightClick) {
972 onPress(tap->hotSpot().toPoint(), tap->position().toPoint(), Qt::NoModifier, Qt::RightButton);
973 onRelease(transform.map(tap->position()), Qt::NoModifier, Qt::RightButton, false);
974 m_dragActionOrRightClick = false;
975 }
976 else {
977 onPress(tap->hotSpot().toPoint(), tap->position().toPoint(), Qt::NoModifier, Qt::LeftButton);
978 onRelease(transform.map(tap->position()), Qt::NoModifier, Qt::LeftButton, true);
979 }
980 m_isTouchEvent = false;
981 }
982 }
983
tapAndHoldTriggered(QGestureEvent * event,const QTransform & transform)984 void KItemListController::tapAndHoldTriggered(QGestureEvent* event, const QTransform& transform)
985 {
986
987 //the Qt TabAndHold gesture is triggerable with a mouse click, we don't want this
988 if (!m_isTouchEvent) {
989 return;
990 }
991
992 const QTapAndHoldGesture* tap = static_cast<QTapAndHoldGesture*>(event->gesture(Qt::TapAndHoldGesture));
993 if (tap->state() == Qt::GestureFinished) {
994 //if a pinch gesture is in progress we don't want a TabAndHold gesture
995 if (m_pinchGestureInProgress) {
996 return;
997 }
998 m_pressedMousePos = transform.map(event->mapToGraphicsScene(tap->position()));
999 m_pressedIndex = m_view->itemAt(m_pressedMousePos);
1000
1001 if (m_pressedIndex >= 0 && !m_selectionManager->isSelected(m_pressedIndex)) {
1002 m_selectionManager->clearSelection();
1003 m_selectionManager->setSelected(m_pressedIndex);
1004 } else if (m_pressedIndex == -1) {
1005 m_selectionManager->clearSelection();
1006 startRubberBand();
1007 }
1008
1009 Q_EMIT scrollerStop();
1010
1011 m_view->m_tapAndHoldIndicator->setStartPosition(m_pressedMousePos);
1012 m_view->m_tapAndHoldIndicator->setActive(true);
1013
1014 m_dragActionOrRightClick = true;
1015 }
1016 }
1017
pinchTriggered(QGestureEvent * event,const QTransform & transform)1018 void KItemListController::pinchTriggered(QGestureEvent* event, const QTransform& transform)
1019 {
1020 Q_UNUSED(transform)
1021
1022 const QPinchGesture* pinch = static_cast<QPinchGesture*>(event->gesture(Qt::PinchGesture));
1023 const qreal sensitivityModifier = 0.2;
1024 static qreal counter = 0;
1025
1026 if (pinch->state() == Qt::GestureStarted) {
1027 m_pinchGestureInProgress = true;
1028 counter = 0;
1029 }
1030 if (pinch->state() == Qt::GestureUpdated) {
1031 //if a swipe gesture was recognized or in progress, we don't want a pinch gesture to change the zoom
1032 if (m_isSwipeGesture) {
1033 return;
1034 }
1035 counter = counter + (pinch->scaleFactor() - 1);
1036 if (counter >= sensitivityModifier) {
1037 Q_EMIT increaseZoom();
1038 counter = 0;
1039 } else if (counter <= -sensitivityModifier) {
1040 Q_EMIT decreaseZoom();
1041 counter = 0;
1042 }
1043 }
1044 }
1045
swipeTriggered(QGestureEvent * event,const QTransform & transform)1046 void KItemListController::swipeTriggered(QGestureEvent* event, const QTransform& transform)
1047 {
1048 Q_UNUSED(transform)
1049
1050 const KTwoFingerSwipe* swipe = static_cast<KTwoFingerSwipe*>(event->gesture(m_swipeGesture));
1051
1052 if (!swipe) {
1053 return;
1054 }
1055 if (swipe->state() == Qt::GestureStarted) {
1056 m_isSwipeGesture = true;
1057 }
1058
1059 if (swipe->state() == Qt::GestureCanceled) {
1060 m_isSwipeGesture = false;
1061 }
1062
1063 if (swipe->state() == Qt::GestureFinished) {
1064 Q_EMIT scrollerStop();
1065
1066 if (swipe->swipeAngle() <= 20 || swipe->swipeAngle() >= 340) {
1067 Q_EMIT mouseButtonPressed(m_pressedIndex, Qt::BackButton);
1068 } else if (swipe->swipeAngle() <= 200 && swipe->swipeAngle() >= 160) {
1069 Q_EMIT mouseButtonPressed(m_pressedIndex, Qt::ForwardButton);
1070 } else if (swipe->swipeAngle() <= 110 && swipe->swipeAngle() >= 60) {
1071 Q_EMIT swipeUp();
1072 }
1073 m_isSwipeGesture = true;
1074 }
1075 }
1076
twoFingerTapTriggered(QGestureEvent * event,const QTransform & transform)1077 void KItemListController::twoFingerTapTriggered(QGestureEvent* event, const QTransform& transform)
1078 {
1079 const KTwoFingerTap* twoTap = static_cast<KTwoFingerTap*>(event->gesture(m_twoFingerTapGesture));
1080
1081 if (!twoTap) {
1082 return;
1083 }
1084
1085 if (twoTap->state() == Qt::GestureStarted) {
1086 m_pressedMousePos = transform.map(twoTap->pos());
1087 m_pressedIndex = m_view->itemAt(m_pressedMousePos);
1088 if (m_pressedIndex >= 0) {
1089 onPress(twoTap->screenPos().toPoint(), twoTap->pos().toPoint(), Qt::ControlModifier, Qt::LeftButton);
1090 onRelease(transform.map(twoTap->pos()), Qt::ControlModifier, Qt::LeftButton, false);
1091 }
1092
1093 }
1094 }
1095
processEvent(QEvent * event,const QTransform & transform)1096 bool KItemListController::processEvent(QEvent* event, const QTransform& transform)
1097 {
1098 if (!event) {
1099 return false;
1100 }
1101
1102 switch (event->type()) {
1103 case QEvent::KeyPress:
1104 return keyPressEvent(static_cast<QKeyEvent*>(event));
1105 case QEvent::InputMethod:
1106 return inputMethodEvent(static_cast<QInputMethodEvent*>(event));
1107 case QEvent::GraphicsSceneMousePress:
1108 return mousePressEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
1109 case QEvent::GraphicsSceneMouseMove:
1110 return mouseMoveEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
1111 case QEvent::GraphicsSceneMouseRelease:
1112 return mouseReleaseEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
1113 case QEvent::GraphicsSceneMouseDoubleClick:
1114 return mouseDoubleClickEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
1115 case QEvent::GraphicsSceneWheel:
1116 return wheelEvent(static_cast<QGraphicsSceneWheelEvent*>(event), QTransform());
1117 case QEvent::GraphicsSceneDragEnter:
1118 return dragEnterEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
1119 case QEvent::GraphicsSceneDragLeave:
1120 return dragLeaveEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
1121 case QEvent::GraphicsSceneDragMove:
1122 return dragMoveEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
1123 case QEvent::GraphicsSceneDrop:
1124 return dropEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
1125 case QEvent::GraphicsSceneHoverEnter:
1126 return hoverEnterEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
1127 case QEvent::GraphicsSceneHoverMove:
1128 return hoverMoveEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
1129 case QEvent::GraphicsSceneHoverLeave:
1130 return hoverLeaveEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
1131 case QEvent::GraphicsSceneResize:
1132 return resizeEvent(static_cast<QGraphicsSceneResizeEvent*>(event), transform);
1133 case QEvent::Gesture:
1134 return gestureEvent(static_cast<QGestureEvent*>(event), transform);
1135 case QEvent::TouchBegin:
1136 return touchBeginEvent(static_cast<QTouchEvent*>(event), transform);
1137 default:
1138 break;
1139 }
1140
1141 return false;
1142 }
1143
slotViewScrollOffsetChanged(qreal current,qreal previous)1144 void KItemListController::slotViewScrollOffsetChanged(qreal current, qreal previous)
1145 {
1146 if (!m_view) {
1147 return;
1148 }
1149
1150 KItemListRubberBand* rubberBand = m_view->rubberBand();
1151 if (rubberBand->isActive()) {
1152 const qreal diff = current - previous;
1153 // TODO: Ideally just QCursor::pos() should be used as
1154 // new end-position but it seems there is no easy way
1155 // to have something like QWidget::mapFromGlobal() for QGraphicsWidget
1156 // (... or I just missed an easy way to do the mapping)
1157 QPointF endPos = rubberBand->endPosition();
1158 if (m_view->scrollOrientation() == Qt::Vertical) {
1159 endPos.ry() += diff;
1160 } else {
1161 endPos.rx() += diff;
1162 }
1163
1164 rubberBand->setEndPosition(endPos);
1165 }
1166 }
1167
slotRubberBandChanged()1168 void KItemListController::slotRubberBandChanged()
1169 {
1170 if (!m_view || !m_model || m_model->count() <= 0) {
1171 return;
1172 }
1173
1174 const KItemListRubberBand* rubberBand = m_view->rubberBand();
1175 const QPointF startPos = rubberBand->startPosition();
1176 const QPointF endPos = rubberBand->endPosition();
1177 QRectF rubberBandRect = QRectF(startPos, endPos).normalized();
1178
1179 const bool scrollVertical = (m_view->scrollOrientation() == Qt::Vertical);
1180 if (scrollVertical) {
1181 rubberBandRect.translate(0, -m_view->scrollOffset());
1182 } else {
1183 rubberBandRect.translate(-m_view->scrollOffset(), 0);
1184 }
1185
1186 if (!m_oldSelection.isEmpty()) {
1187 // Clear the old selection that was available before the rubberband has
1188 // been activated in case if no Shift- or Control-key are pressed
1189 const bool shiftOrControlPressed = QApplication::keyboardModifiers() & Qt::ShiftModifier ||
1190 QApplication::keyboardModifiers() & Qt::ControlModifier;
1191 if (!shiftOrControlPressed) {
1192 m_oldSelection.clear();
1193 }
1194 }
1195
1196 KItemSet selectedItems;
1197
1198 // Select all visible items that intersect with the rubberband
1199 const auto widgets = m_view->visibleItemListWidgets();
1200 for (const KItemListWidget* widget : widgets) {
1201 const int index = widget->index();
1202
1203 const QRectF widgetRect = m_view->itemRect(index);
1204 if (widgetRect.intersects(rubberBandRect)) {
1205 const QRectF iconRect = widget->iconRect().translated(widgetRect.topLeft());
1206 const QRectF textRect = widget->textRect().translated(widgetRect.topLeft());
1207 if (iconRect.intersects(rubberBandRect) || textRect.intersects(rubberBandRect)) {
1208 selectedItems.insert(index);
1209 }
1210 }
1211 }
1212
1213 // Select all invisible items that intersect with the rubberband. Instead of
1214 // iterating all items only the area which might be touched by the rubberband
1215 // will be checked.
1216 const bool increaseIndex = scrollVertical ?
1217 startPos.y() > endPos.y(): startPos.x() > endPos.x();
1218
1219 int index = increaseIndex ? m_view->lastVisibleIndex() + 1 : m_view->firstVisibleIndex() - 1;
1220 bool selectionFinished = false;
1221 do {
1222 const QRectF widgetRect = m_view->itemRect(index);
1223 if (widgetRect.intersects(rubberBandRect)) {
1224 selectedItems.insert(index);
1225 }
1226
1227 if (increaseIndex) {
1228 ++index;
1229 selectionFinished = (index >= m_model->count()) ||
1230 ( scrollVertical && widgetRect.top() > rubberBandRect.bottom()) ||
1231 (!scrollVertical && widgetRect.left() > rubberBandRect.right());
1232 } else {
1233 --index;
1234 selectionFinished = (index < 0) ||
1235 ( scrollVertical && widgetRect.bottom() < rubberBandRect.top()) ||
1236 (!scrollVertical && widgetRect.right() < rubberBandRect.left());
1237 }
1238 } while (!selectionFinished);
1239
1240 if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
1241 // If Control is pressed, the selection state of all items in the rubberband is toggled.
1242 // Therefore, the new selection contains:
1243 // 1. All previously selected items which are not inside the rubberband, and
1244 // 2. all items inside the rubberband which have not been selected previously.
1245 m_selectionManager->setSelectedItems(m_oldSelection ^ selectedItems);
1246 }
1247 else {
1248 m_selectionManager->setSelectedItems(selectedItems + m_oldSelection);
1249 }
1250 }
1251
startDragging()1252 void KItemListController::startDragging()
1253 {
1254 if (!m_view || !m_model) {
1255 return;
1256 }
1257
1258 const KItemSet selectedItems = m_selectionManager->selectedItems();
1259 if (selectedItems.isEmpty()) {
1260 return;
1261 }
1262
1263 QMimeData* data = m_model->createMimeData(selectedItems);
1264 if (!data) {
1265 return;
1266 }
1267
1268 // The created drag object will be owned and deleted
1269 // by QApplication::activeWindow().
1270 QDrag* drag = new QDrag(QApplication::activeWindow());
1271 drag->setMimeData(data);
1272
1273 const QPixmap pixmap = m_view->createDragPixmap(selectedItems);
1274 drag->setPixmap(pixmap);
1275
1276 const QPoint hotSpot((pixmap.width() / pixmap.devicePixelRatio()) / 2, 0);
1277 drag->setHotSpot(hotSpot);
1278
1279 drag->exec(Qt::MoveAction | Qt::CopyAction | Qt::LinkAction, Qt::CopyAction);
1280
1281 QAccessibleEvent accessibilityEvent(view(), QAccessible::DragDropStart);
1282 QAccessible::updateAccessibility(&accessibilityEvent);
1283 }
1284
hoveredWidget() const1285 KItemListWidget* KItemListController::hoveredWidget() const
1286 {
1287 Q_ASSERT(m_view);
1288
1289 const auto widgets = m_view->visibleItemListWidgets();
1290 for (KItemListWidget* widget : widgets) {
1291 if (widget->isHovered()) {
1292 return widget;
1293 }
1294 }
1295
1296 return nullptr;
1297 }
1298
widgetForPos(const QPointF & pos) const1299 KItemListWidget* KItemListController::widgetForPos(const QPointF& pos) const
1300 {
1301 Q_ASSERT(m_view);
1302
1303 const auto widgets = m_view->visibleItemListWidgets();
1304 for (KItemListWidget* widget : widgets) {
1305 const QPointF mappedPos = widget->mapFromItem(m_view, pos);
1306
1307 const bool hovered = widget->contains(mappedPos) &&
1308 !widget->expansionToggleRect().contains(mappedPos);
1309 if (hovered) {
1310 return widget;
1311 }
1312 }
1313
1314 return nullptr;
1315 }
1316
updateKeyboardAnchor()1317 void KItemListController::updateKeyboardAnchor()
1318 {
1319 const bool validAnchor = m_keyboardAnchorIndex >= 0 &&
1320 m_keyboardAnchorIndex < m_model->count() &&
1321 keyboardAnchorPos(m_keyboardAnchorIndex) == m_keyboardAnchorPos;
1322 if (!validAnchor) {
1323 const int index = m_selectionManager->currentItem();
1324 m_keyboardAnchorIndex = index;
1325 m_keyboardAnchorPos = keyboardAnchorPos(index);
1326 }
1327 }
1328
nextRowIndex(int index) const1329 int KItemListController::nextRowIndex(int index) const
1330 {
1331 if (m_keyboardAnchorIndex < 0) {
1332 return index;
1333 }
1334
1335 const int maxIndex = m_model->count() - 1;
1336 if (index == maxIndex) {
1337 return index;
1338 }
1339
1340 // Calculate the index of the last column inside the row of the current index
1341 int lastColumnIndex = index;
1342 while (keyboardAnchorPos(lastColumnIndex + 1) > keyboardAnchorPos(lastColumnIndex)) {
1343 ++lastColumnIndex;
1344 if (lastColumnIndex >= maxIndex) {
1345 return index;
1346 }
1347 }
1348
1349 // Based on the last column index go to the next row and calculate the nearest index
1350 // that is below the current index
1351 int nextRowIndex = lastColumnIndex + 1;
1352 int searchIndex = nextRowIndex;
1353 qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(nextRowIndex));
1354 while (searchIndex < maxIndex && keyboardAnchorPos(searchIndex + 1) > keyboardAnchorPos(searchIndex)) {
1355 ++searchIndex;
1356 const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex));
1357 if (searchDiff < minDiff) {
1358 minDiff = searchDiff;
1359 nextRowIndex = searchIndex;
1360 }
1361 }
1362
1363 return nextRowIndex;
1364 }
1365
previousRowIndex(int index) const1366 int KItemListController::previousRowIndex(int index) const
1367 {
1368 if (m_keyboardAnchorIndex < 0 || index == 0) {
1369 return index;
1370 }
1371
1372 // Calculate the index of the first column inside the row of the current index
1373 int firstColumnIndex = index;
1374 while (keyboardAnchorPos(firstColumnIndex - 1) < keyboardAnchorPos(firstColumnIndex)) {
1375 --firstColumnIndex;
1376 if (firstColumnIndex <= 0) {
1377 return index;
1378 }
1379 }
1380
1381 // Based on the first column index go to the previous row and calculate the nearest index
1382 // that is above the current index
1383 int previousRowIndex = firstColumnIndex - 1;
1384 int searchIndex = previousRowIndex;
1385 qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(previousRowIndex));
1386 while (searchIndex > 0 && keyboardAnchorPos(searchIndex - 1) < keyboardAnchorPos(searchIndex)) {
1387 --searchIndex;
1388 const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex));
1389 if (searchDiff < minDiff) {
1390 minDiff = searchDiff;
1391 previousRowIndex = searchIndex;
1392 }
1393 }
1394
1395 return previousRowIndex;
1396 }
1397
keyboardAnchorPos(int index) const1398 qreal KItemListController::keyboardAnchorPos(int index) const
1399 {
1400 const QRectF itemRect = m_view->itemRect(index);
1401 if (!itemRect.isEmpty()) {
1402 return (m_view->scrollOrientation() == Qt::Vertical) ? itemRect.x() : itemRect.y();
1403 }
1404
1405 return 0;
1406 }
1407
updateExtendedSelectionRegion()1408 void KItemListController::updateExtendedSelectionRegion()
1409 {
1410 if (m_view) {
1411 const bool extend = (m_selectionBehavior != MultiSelection);
1412 KItemListStyleOption option = m_view->styleOption();
1413 if (option.extendedSelectionRegion != extend) {
1414 option.extendedSelectionRegion = extend;
1415 m_view->setStyleOption(option);
1416 }
1417 }
1418 }
1419
onPress(const QPoint & screenPos,const QPointF & pos,const Qt::KeyboardModifiers modifiers,const Qt::MouseButtons buttons)1420 bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons)
1421 {
1422 Q_EMIT mouseButtonPressed(m_pressedIndex, buttons);
1423
1424 if (buttons & (Qt::BackButton | Qt::ForwardButton)) {
1425 // Do not select items when clicking the back/forward buttons, see
1426 // https://bugs.kde.org/show_bug.cgi?id=327412.
1427 return true;
1428 }
1429
1430 if (m_view->isAboveExpansionToggle(m_pressedIndex, m_pressedMousePos)) {
1431 m_selectionManager->endAnchoredSelection();
1432 m_selectionManager->setCurrentItem(m_pressedIndex);
1433 m_selectionManager->beginAnchoredSelection(m_pressedIndex);
1434 return true;
1435 }
1436
1437 m_selectionTogglePressed = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos);
1438 if (m_selectionTogglePressed) {
1439 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
1440 // The previous anchored selection has been finished already in
1441 // KItemListSelectionManager::setSelected(). We can safely change
1442 // the current item and start a new anchored selection now.
1443 m_selectionManager->setCurrentItem(m_pressedIndex);
1444 m_selectionManager->beginAnchoredSelection(m_pressedIndex);
1445 return true;
1446 }
1447
1448 const bool shiftPressed = modifiers & Qt::ShiftModifier;
1449 const bool controlPressed = modifiers & Qt::ControlModifier;
1450
1451 // The previous selection is cleared if either
1452 // 1. The selection mode is SingleSelection, or
1453 // 2. the selection mode is MultiSelection, and *none* of the following conditions are met:
1454 // a) Shift or Control are pressed.
1455 // b) The clicked item is selected already. In that case, the user might want to:
1456 // - start dragging multiple items, or
1457 // - open the context menu and perform an action for all selected items.
1458 const bool shiftOrControlPressed = shiftPressed || controlPressed;
1459 const bool pressedItemAlreadySelected = m_pressedIndex >= 0 && m_selectionManager->isSelected(m_pressedIndex);
1460 const bool clearSelection = m_selectionBehavior == SingleSelection ||
1461 (!shiftOrControlPressed && !pressedItemAlreadySelected);
1462 if (clearSelection) {
1463 m_selectionManager->clearSelection();
1464 } else if (pressedItemAlreadySelected && !shiftOrControlPressed && (buttons & Qt::LeftButton)) {
1465 // The user might want to start dragging multiple items, but if he clicks the item
1466 // in order to trigger it instead, the other selected items must be deselected.
1467 // However, we do not know yet what the user is going to do.
1468 // -> remember that the user pressed an item which had been selected already and
1469 // clear the selection in mouseReleaseEvent(), unless the items are dragged.
1470 m_clearSelectionIfItemsAreNotDragged = true;
1471
1472 if (m_selectionManager->selectedItems().count() == 1 && m_view->isAboveText(m_pressedIndex, m_pressedMousePos)) {
1473 Q_EMIT selectedItemTextPressed(m_pressedIndex);
1474 }
1475 }
1476
1477 if (!shiftPressed) {
1478 // Finish the anchored selection before the current index is changed
1479 m_selectionManager->endAnchoredSelection();
1480 }
1481
1482 if (buttons & Qt::RightButton) {
1483 // Stop rubber band from persisting after right-clicks
1484 KItemListRubberBand* rubberBand = m_view->rubberBand();
1485 if (rubberBand->isActive()) {
1486 disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
1487 rubberBand->setActive(false);
1488 m_view->setAutoScroll(false);
1489 }
1490 }
1491
1492 if (m_pressedIndex >= 0) {
1493 m_selectionManager->setCurrentItem(m_pressedIndex);
1494
1495 switch (m_selectionBehavior) {
1496 case NoSelection:
1497 break;
1498
1499 case SingleSelection:
1500 m_selectionManager->setSelected(m_pressedIndex);
1501 break;
1502
1503 case MultiSelection:
1504 if (controlPressed && !shiftPressed) {
1505 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
1506 m_selectionManager->beginAnchoredSelection(m_pressedIndex);
1507 } else if (!shiftPressed || !m_selectionManager->isAnchoredSelectionActive()) {
1508 // Select the pressed item and start a new anchored selection
1509 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select);
1510 m_selectionManager->beginAnchoredSelection(m_pressedIndex);
1511 }
1512 break;
1513
1514 default:
1515 Q_ASSERT(false);
1516 break;
1517 }
1518
1519 if (buttons & Qt::RightButton) {
1520 Q_EMIT itemContextMenuRequested(m_pressedIndex, screenPos);
1521 }
1522
1523 return true;
1524 }
1525
1526 if (buttons & Qt::RightButton) {
1527 const QRectF headerBounds = m_view->headerBoundaries();
1528 if (headerBounds.contains(pos)) {
1529 Q_EMIT headerContextMenuRequested(screenPos);
1530 } else {
1531 Q_EMIT viewContextMenuRequested(screenPos);
1532 }
1533 return true;
1534 }
1535
1536 return false;
1537 }
1538
onRelease(const QPointF & pos,const Qt::KeyboardModifiers modifiers,const Qt::MouseButtons buttons,bool touch)1539 bool KItemListController::onRelease(const QPointF& pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons, bool touch)
1540 {
1541 const bool isAboveSelectionToggle = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos);
1542 if (isAboveSelectionToggle) {
1543 m_selectionTogglePressed = false;
1544 return true;
1545 }
1546
1547 if (!isAboveSelectionToggle && m_selectionTogglePressed) {
1548 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
1549 m_selectionTogglePressed = false;
1550 return true;
1551 }
1552
1553 const bool controlPressed = modifiers & Qt::ControlModifier;
1554 const bool shiftOrControlPressed = modifiers & Qt::ShiftModifier ||
1555 controlPressed;
1556
1557 KItemListRubberBand* rubberBand = m_view->rubberBand();
1558 if (rubberBand->isActive()) {
1559 disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
1560 rubberBand->setActive(false);
1561 m_oldSelection.clear();
1562 m_view->setAutoScroll(false);
1563 }
1564
1565 const int index = m_view->itemAt(pos);
1566
1567 if (index >= 0 && index == m_pressedIndex) {
1568 // The release event is done above the same item as the press event
1569
1570 if (m_clearSelectionIfItemsAreNotDragged) {
1571 // A selected item has been clicked, but no drag operation has been started
1572 // -> clear the rest of the selection.
1573 m_selectionManager->clearSelection();
1574 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select);
1575 m_selectionManager->beginAnchoredSelection(m_pressedIndex);
1576 }
1577
1578 if (buttons & Qt::LeftButton) {
1579 bool emitItemActivated = true;
1580 if (m_view->isAboveExpansionToggle(index, pos)) {
1581 const bool expanded = m_model->isExpanded(index);
1582 m_model->setExpanded(index, !expanded);
1583
1584 Q_EMIT itemExpansionToggleClicked(index);
1585 emitItemActivated = false;
1586 } else if (shiftOrControlPressed) {
1587 // The mouse click should only update the selection, not trigger the item
1588 emitItemActivated = false;
1589 // When Ctrl-clicking an item when in single selection mode
1590 // i.e. where Ctrl won't change the selection, pretend it was middle clicked
1591 if (controlPressed && m_selectionBehavior == SingleSelection) {
1592 Q_EMIT itemMiddleClicked(index);
1593 }
1594 } else if (!(m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced)) {
1595 if (touch) {
1596 emitItemActivated = true;
1597 } else {
1598 emitItemActivated = false;
1599 }
1600 }
1601 if (emitItemActivated) {
1602 Q_EMIT itemActivated(index);
1603 }
1604 } else if (buttons & Qt::MiddleButton) {
1605 Q_EMIT itemMiddleClicked(index);
1606 }
1607 }
1608
1609 m_pressedMousePos = QPointF();
1610 m_pressedIndex = -1;
1611 m_clearSelectionIfItemsAreNotDragged = false;
1612 return false;
1613 }
1614
startRubberBand()1615 void KItemListController::startRubberBand()
1616 {
1617 if (m_selectionBehavior == MultiSelection) {
1618 QPointF startPos = m_pressedMousePos;
1619 if (m_view->scrollOrientation() == Qt::Vertical) {
1620 startPos.ry() += m_view->scrollOffset();
1621 if (m_view->itemSize().width() < 0) {
1622 // Use a special rubberband for views that have only one column and
1623 // expand the rubberband to use the whole width of the view.
1624 startPos.setX(0);
1625 }
1626 } else {
1627 startPos.rx() += m_view->scrollOffset();
1628 }
1629
1630 m_oldSelection = m_selectionManager->selectedItems();
1631 KItemListRubberBand* rubberBand = m_view->rubberBand();
1632 rubberBand->setStartPosition(startPos);
1633 rubberBand->setEndPosition(startPos);
1634 rubberBand->setActive(true);
1635 connect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
1636 m_view->setAutoScroll(true);
1637 }
1638 }
1639
slotStateChanged(QScroller::State newState)1640 void KItemListController::slotStateChanged(QScroller::State newState)
1641 {
1642 if (newState == QScroller::Scrolling) {
1643 m_scrollerIsScrolling = true;
1644 } else if (newState == QScroller::Inactive) {
1645 m_scrollerIsScrolling = false;
1646 }
1647 }
1648