]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistcontroller.cpp
Merge branch 'release/20.08' into master
[dolphin.git] / src / kitemviews / kitemlistcontroller.cpp
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
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
82 KItemListController::~KItemListController()
83 {
84 setView(nullptr);
85 Q_ASSERT(!m_view);
86
87 setModel(nullptr);
88 Q_ASSERT(!m_model);
89 }
90
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 emit modelChanged(m_model, oldModel);
114 }
115
116 KItemModelBase* KItemListController::model() const
117 {
118 return m_model;
119 }
120
121 KItemListSelectionManager* KItemListController::selectionManager() const
122 {
123 return m_selectionManager;
124 }
125
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 emit viewChanged(m_view, oldView);
149 }
150
151 KItemListView* KItemListController::view() const
152 {
153 return m_view;
154 }
155
156 void KItemListController::setSelectionBehavior(SelectionBehavior behavior)
157 {
158 m_selectionBehavior = behavior;
159 updateExtendedSelectionRegion();
160 }
161
162 KItemListController::SelectionBehavior KItemListController::selectionBehavior() const
163 {
164 return m_selectionBehavior;
165 }
166
167 void KItemListController::setAutoActivationBehavior(AutoActivationBehavior behavior)
168 {
169 m_autoActivationBehavior = behavior;
170 }
171
172 KItemListController::AutoActivationBehavior KItemListController::autoActivationBehavior() const
173 {
174 return m_autoActivationBehavior;
175 }
176
177 void KItemListController::setMouseDoubleClickAction(MouseDoubleClickAction action)
178 {
179 m_mouseDoubleClickAction = action;
180 }
181
182 KItemListController::MouseDoubleClickAction KItemListController::mouseDoubleClickAction() const
183 {
184 return m_mouseDoubleClickAction;
185 }
186
187 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
201 void KItemListController::setAutoActivationDelay(int delay)
202 {
203 m_autoActivationTimer->setInterval(delay);
204 }
205
206 int KItemListController::autoActivationDelay() const
207 {
208 return m_autoActivationTimer->interval();
209 }
210
211 void KItemListController::setSingleClickActivationEnforced(bool singleClick)
212 {
213 m_singleClickActivationEnforced = singleClick;
214 }
215
216 bool KItemListController::singleClickActivationEnforced() const
217 {
218 return m_singleClickActivationEnforced;
219 }
220
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 emit itemsActivated(selectedItems);
377 } else if (selectedItems.count() == 1) {
378 emit itemActivated(selectedItems.first());
379 } else {
380 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 emit itemContextMenuRequested(index, pos);
402 } else {
403 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 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
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
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 emit itemActivated(index);
528 }
529 }
530 }
531
532 bool KItemListController::inputMethodEvent(QInputMethodEvent* event)
533 {
534 Q_UNUSED(event)
535 return false;
536 }
537
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
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
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 emit mouseButtonReleased(m_pressedIndex, event->buttons());
646
647 return onRelease(transform.map(event->pos()), event->modifiers(), event->button(), false);
648 }
649
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 emit itemContextMenuRequested(index, event->screenPos());
668 } else {
669 const QRectF headerBounds = m_view->headerBoundaries();
670 if (headerBounds.contains(event->pos())) {
671 emit headerContextMenuRequested(event->screenPos());
672 } else {
673 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 emit itemActivated(index);
684 }
685 return false;
686 }
687
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
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 emit itemUnhovered(widget->index());
711 }
712 return false;
713 }
714
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 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 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 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
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 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 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
817 bool KItemListController::hoverEnterEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
818 {
819 Q_UNUSED(event)
820 Q_UNUSED(transform)
821 return false;
822 }
823
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 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 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
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 foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) {
868 if (widget->isHovered()) {
869 widget->setHovered(false);
870 emit itemUnhovered(widget->index());
871 }
872 }
873 return false;
874 }
875
876 bool KItemListController::wheelEvent(QGraphicsSceneWheelEvent* event, const QTransform& transform)
877 {
878 Q_UNUSED(event)
879 Q_UNUSED(transform)
880 return false;
881 }
882
883 bool KItemListController::resizeEvent(QGraphicsSceneResizeEvent* event, const QTransform& transform)
884 {
885 Q_UNUSED(event)
886 Q_UNUSED(transform)
887 return false;
888 }
889
890 bool KItemListController::gestureEvent(QGestureEvent* event, const QTransform& transform)
891 {
892 if (!m_view) {
893 return false;
894 }
895
896 //you can touch on different views at the same time, but only one QWidget gets a mousePressEvent
897 //we use this to get the right QWidget
898 //the only exception is a tap gesture with state GestureStarted, we need to reset some variable
899 if (!m_mousePress) {
900 if (QGesture* tap = event->gesture(Qt::TapGesture)) {
901 QTapGesture* tapGesture = static_cast<QTapGesture*>(tap);
902 if (tapGesture->state() == Qt::GestureStarted) {
903 tapTriggered(tapGesture, transform);
904 }
905 }
906 return false;
907 }
908
909 bool accepted = false;
910
911 if (QGesture* tap = event->gesture(Qt::TapGesture)) {
912 tapTriggered(static_cast<QTapGesture*>(tap), transform);
913 accepted = true;
914 }
915 if (event->gesture(Qt::TapAndHoldGesture)) {
916 tapAndHoldTriggered(event, transform);
917 accepted = true;
918 }
919 if (event->gesture(Qt::PinchGesture)) {
920 pinchTriggered(event, transform);
921 accepted = true;
922 }
923 if (event->gesture(m_swipeGesture)) {
924 swipeTriggered(event, transform);
925 accepted = true;
926 }
927 if (event->gesture(m_twoFingerTapGesture)) {
928 twoFingerTapTriggered(event, transform);
929 accepted = true;
930 }
931 return accepted;
932 }
933
934 bool KItemListController::touchBeginEvent(QTouchEvent* event, const QTransform& transform)
935 {
936 Q_UNUSED(event)
937 Q_UNUSED(transform)
938
939 m_isTouchEvent = true;
940 return false;
941 }
942
943 void KItemListController::tapTriggered(QTapGesture* tap, const QTransform& transform)
944 {
945 static bool scrollerWasActive = false;
946
947 if (tap->state() == Qt::GestureStarted) {
948 m_dragActionOrRightClick = false;
949 m_isSwipeGesture = false;
950 m_pinchGestureInProgress = false;
951 scrollerWasActive = m_scrollerIsScrolling;
952 }
953
954 if (tap->state() == Qt::GestureFinished) {
955 m_mousePress = false;
956
957 //if at the moment of the gesture start the QScroller was active, the user made the tap
958 //to stop the QScroller and not to tap on an item
959 if (scrollerWasActive) {
960 return;
961 }
962
963 if (m_view->m_tapAndHoldIndicator->isActive()) {
964 m_view->m_tapAndHoldIndicator->setActive(false);
965 }
966
967 m_pressedMousePos = transform.map(tap->position());
968 m_pressedIndex = m_view->itemAt(m_pressedMousePos);
969
970 if (m_dragActionOrRightClick) {
971 onPress(tap->hotSpot().toPoint(), tap->position().toPoint(), Qt::NoModifier, Qt::RightButton);
972 onRelease(transform.map(tap->position()), Qt::NoModifier, Qt::RightButton, false);
973 m_dragActionOrRightClick = false;
974 }
975 else {
976 onPress(tap->hotSpot().toPoint(), tap->position().toPoint(), Qt::NoModifier, Qt::LeftButton);
977 onRelease(transform.map(tap->position()), Qt::NoModifier, Qt::LeftButton, true);
978 }
979 m_isTouchEvent = false;
980 }
981 }
982
983 void KItemListController::tapAndHoldTriggered(QGestureEvent* event, const QTransform& transform)
984 {
985
986 //the Qt TabAndHold gesture is triggerable with a mouse click, we don't want this
987 if (!m_isTouchEvent) {
988 return;
989 }
990
991 const QTapAndHoldGesture* tap = static_cast<QTapAndHoldGesture*>(event->gesture(Qt::TapAndHoldGesture));
992 if (tap->state() == Qt::GestureFinished) {
993 //if a pinch gesture is in progress we don't want a TabAndHold gesture
994 if (m_pinchGestureInProgress) {
995 return;
996 }
997 m_pressedMousePos = transform.map(event->mapToGraphicsScene(tap->position()));
998 m_pressedIndex = m_view->itemAt(m_pressedMousePos);
999
1000 if (m_pressedIndex >= 0 && !m_selectionManager->isSelected(m_pressedIndex)) {
1001 m_selectionManager->clearSelection();
1002 m_selectionManager->setSelected(m_pressedIndex);
1003 } else if (m_pressedIndex == -1) {
1004 m_selectionManager->clearSelection();
1005 startRubberBand();
1006 }
1007
1008 emit scrollerStop();
1009
1010 m_view->m_tapAndHoldIndicator->setStartPosition(m_pressedMousePos);
1011 m_view->m_tapAndHoldIndicator->setActive(true);
1012
1013 m_dragActionOrRightClick = true;
1014 }
1015 }
1016
1017 void KItemListController::pinchTriggered(QGestureEvent* event, const QTransform& transform)
1018 {
1019 Q_UNUSED(transform)
1020
1021 const QPinchGesture* pinch = static_cast<QPinchGesture*>(event->gesture(Qt::PinchGesture));
1022 const qreal sensitivityModifier = 0.2;
1023 static qreal counter = 0;
1024
1025 if (pinch->state() == Qt::GestureStarted) {
1026 m_pinchGestureInProgress = true;
1027 counter = 0;
1028 }
1029 if (pinch->state() == Qt::GestureUpdated) {
1030 //if a swipe gesture was recognized or in progress, we don't want a pinch gesture to change the zoom
1031 if (m_isSwipeGesture) {
1032 return;
1033 }
1034 counter = counter + (pinch->scaleFactor() - 1);
1035 if (counter >= sensitivityModifier) {
1036 emit increaseZoom();
1037 counter = 0;
1038 } else if (counter <= -sensitivityModifier) {
1039 emit decreaseZoom();
1040 counter = 0;
1041 }
1042 }
1043 }
1044
1045 void KItemListController::swipeTriggered(QGestureEvent* event, const QTransform& transform)
1046 {
1047 Q_UNUSED(transform)
1048
1049 const KTwoFingerSwipe* swipe = static_cast<KTwoFingerSwipe*>(event->gesture(m_swipeGesture));
1050
1051 if (!swipe) {
1052 return;
1053 }
1054 if (swipe->state() == Qt::GestureStarted) {
1055 m_isSwipeGesture = true;
1056 }
1057
1058 if (swipe->state() == Qt::GestureCanceled) {
1059 m_isSwipeGesture = false;
1060 }
1061
1062 if (swipe->state() == Qt::GestureFinished) {
1063 emit scrollerStop();
1064
1065 if (swipe->swipeAngle() <= 20 || swipe->swipeAngle() >= 340) {
1066 emit mouseButtonPressed(m_pressedIndex, Qt::BackButton);
1067 } else if (swipe->swipeAngle() <= 200 && swipe->swipeAngle() >= 160) {
1068 emit mouseButtonPressed(m_pressedIndex, Qt::ForwardButton);
1069 } else if (swipe->swipeAngle() <= 110 && swipe->swipeAngle() >= 60) {
1070 emit swipeUp();
1071 }
1072 m_isSwipeGesture = true;
1073 }
1074 }
1075
1076 void KItemListController::twoFingerTapTriggered(QGestureEvent* event, const QTransform& transform)
1077 {
1078 const KTwoFingerTap* twoTap = static_cast<KTwoFingerTap*>(event->gesture(m_twoFingerTapGesture));
1079
1080 if (!twoTap) {
1081 return;
1082 }
1083
1084 if (twoTap->state() == Qt::GestureStarted) {
1085 m_pressedMousePos = transform.map(twoTap->pos());
1086 m_pressedIndex = m_view->itemAt(m_pressedMousePos);
1087 if (m_pressedIndex >= 0) {
1088 onPress(twoTap->screenPos().toPoint(), twoTap->pos().toPoint(), Qt::ControlModifier, Qt::LeftButton);
1089 onRelease(transform.map(twoTap->pos()), Qt::ControlModifier, Qt::LeftButton, false);
1090 }
1091
1092 }
1093 }
1094
1095 bool KItemListController::processEvent(QEvent* event, const QTransform& transform)
1096 {
1097 if (!event) {
1098 return false;
1099 }
1100
1101 switch (event->type()) {
1102 case QEvent::KeyPress:
1103 return keyPressEvent(static_cast<QKeyEvent*>(event));
1104 case QEvent::InputMethod:
1105 return inputMethodEvent(static_cast<QInputMethodEvent*>(event));
1106 case QEvent::GraphicsSceneMousePress:
1107 return mousePressEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
1108 case QEvent::GraphicsSceneMouseMove:
1109 return mouseMoveEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
1110 case QEvent::GraphicsSceneMouseRelease:
1111 return mouseReleaseEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
1112 case QEvent::GraphicsSceneMouseDoubleClick:
1113 return mouseDoubleClickEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
1114 case QEvent::GraphicsSceneWheel:
1115 return wheelEvent(static_cast<QGraphicsSceneWheelEvent*>(event), QTransform());
1116 case QEvent::GraphicsSceneDragEnter:
1117 return dragEnterEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
1118 case QEvent::GraphicsSceneDragLeave:
1119 return dragLeaveEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
1120 case QEvent::GraphicsSceneDragMove:
1121 return dragMoveEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
1122 case QEvent::GraphicsSceneDrop:
1123 return dropEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
1124 case QEvent::GraphicsSceneHoverEnter:
1125 return hoverEnterEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
1126 case QEvent::GraphicsSceneHoverMove:
1127 return hoverMoveEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
1128 case QEvent::GraphicsSceneHoverLeave:
1129 return hoverLeaveEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
1130 case QEvent::GraphicsSceneResize:
1131 return resizeEvent(static_cast<QGraphicsSceneResizeEvent*>(event), transform);
1132 case QEvent::Gesture:
1133 return gestureEvent(static_cast<QGestureEvent*>(event), transform);
1134 case QEvent::TouchBegin:
1135 return touchBeginEvent(static_cast<QTouchEvent*>(event), transform);
1136 default:
1137 break;
1138 }
1139
1140 return false;
1141 }
1142
1143 void KItemListController::slotViewScrollOffsetChanged(qreal current, qreal previous)
1144 {
1145 if (!m_view) {
1146 return;
1147 }
1148
1149 KItemListRubberBand* rubberBand = m_view->rubberBand();
1150 if (rubberBand->isActive()) {
1151 const qreal diff = current - previous;
1152 // TODO: Ideally just QCursor::pos() should be used as
1153 // new end-position but it seems there is no easy way
1154 // to have something like QWidget::mapFromGlobal() for QGraphicsWidget
1155 // (... or I just missed an easy way to do the mapping)
1156 QPointF endPos = rubberBand->endPosition();
1157 if (m_view->scrollOrientation() == Qt::Vertical) {
1158 endPos.ry() += diff;
1159 } else {
1160 endPos.rx() += diff;
1161 }
1162
1163 rubberBand->setEndPosition(endPos);
1164 }
1165 }
1166
1167 void KItemListController::slotRubberBandChanged()
1168 {
1169 if (!m_view || !m_model || m_model->count() <= 0) {
1170 return;
1171 }
1172
1173 const KItemListRubberBand* rubberBand = m_view->rubberBand();
1174 const QPointF startPos = rubberBand->startPosition();
1175 const QPointF endPos = rubberBand->endPosition();
1176 QRectF rubberBandRect = QRectF(startPos, endPos).normalized();
1177
1178 const bool scrollVertical = (m_view->scrollOrientation() == Qt::Vertical);
1179 if (scrollVertical) {
1180 rubberBandRect.translate(0, -m_view->scrollOffset());
1181 } else {
1182 rubberBandRect.translate(-m_view->scrollOffset(), 0);
1183 }
1184
1185 if (!m_oldSelection.isEmpty()) {
1186 // Clear the old selection that was available before the rubberband has
1187 // been activated in case if no Shift- or Control-key are pressed
1188 const bool shiftOrControlPressed = QApplication::keyboardModifiers() & Qt::ShiftModifier ||
1189 QApplication::keyboardModifiers() & Qt::ControlModifier;
1190 if (!shiftOrControlPressed) {
1191 m_oldSelection.clear();
1192 }
1193 }
1194
1195 KItemSet selectedItems;
1196
1197 // Select all visible items that intersect with the rubberband
1198 foreach (const KItemListWidget* widget, m_view->visibleItemListWidgets()) {
1199 const int index = widget->index();
1200
1201 const QRectF widgetRect = m_view->itemRect(index);
1202 if (widgetRect.intersects(rubberBandRect)) {
1203 const QRectF iconRect = widget->iconRect().translated(widgetRect.topLeft());
1204 const QRectF textRect = widget->textRect().translated(widgetRect.topLeft());
1205 if (iconRect.intersects(rubberBandRect) || textRect.intersects(rubberBandRect)) {
1206 selectedItems.insert(index);
1207 }
1208 }
1209 }
1210
1211 // Select all invisible items that intersect with the rubberband. Instead of
1212 // iterating all items only the area which might be touched by the rubberband
1213 // will be checked.
1214 const bool increaseIndex = scrollVertical ?
1215 startPos.y() > endPos.y(): startPos.x() > endPos.x();
1216
1217 int index = increaseIndex ? m_view->lastVisibleIndex() + 1 : m_view->firstVisibleIndex() - 1;
1218 bool selectionFinished = false;
1219 do {
1220 const QRectF widgetRect = m_view->itemRect(index);
1221 if (widgetRect.intersects(rubberBandRect)) {
1222 selectedItems.insert(index);
1223 }
1224
1225 if (increaseIndex) {
1226 ++index;
1227 selectionFinished = (index >= m_model->count()) ||
1228 ( scrollVertical && widgetRect.top() > rubberBandRect.bottom()) ||
1229 (!scrollVertical && widgetRect.left() > rubberBandRect.right());
1230 } else {
1231 --index;
1232 selectionFinished = (index < 0) ||
1233 ( scrollVertical && widgetRect.bottom() < rubberBandRect.top()) ||
1234 (!scrollVertical && widgetRect.right() < rubberBandRect.left());
1235 }
1236 } while (!selectionFinished);
1237
1238 if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
1239 // If Control is pressed, the selection state of all items in the rubberband is toggled.
1240 // Therefore, the new selection contains:
1241 // 1. All previously selected items which are not inside the rubberband, and
1242 // 2. all items inside the rubberband which have not been selected previously.
1243 m_selectionManager->setSelectedItems(m_oldSelection ^ selectedItems);
1244 }
1245 else {
1246 m_selectionManager->setSelectedItems(selectedItems + m_oldSelection);
1247 }
1248 }
1249
1250 void KItemListController::startDragging()
1251 {
1252 if (!m_view || !m_model) {
1253 return;
1254 }
1255
1256 const KItemSet selectedItems = m_selectionManager->selectedItems();
1257 if (selectedItems.isEmpty()) {
1258 return;
1259 }
1260
1261 QMimeData* data = m_model->createMimeData(selectedItems);
1262 if (!data) {
1263 return;
1264 }
1265
1266 // The created drag object will be owned and deleted
1267 // by QApplication::activeWindow().
1268 QDrag* drag = new QDrag(QApplication::activeWindow());
1269 drag->setMimeData(data);
1270
1271 const QPixmap pixmap = m_view->createDragPixmap(selectedItems);
1272 drag->setPixmap(pixmap);
1273
1274 const QPoint hotSpot((pixmap.width() / pixmap.devicePixelRatio()) / 2, 0);
1275 drag->setHotSpot(hotSpot);
1276
1277 drag->exec(Qt::MoveAction | Qt::CopyAction | Qt::LinkAction, Qt::CopyAction);
1278
1279 QAccessibleEvent accessibilityEvent(view(), QAccessible::DragDropStart);
1280 QAccessible::updateAccessibility(&accessibilityEvent);
1281 }
1282
1283 KItemListWidget* KItemListController::hoveredWidget() const
1284 {
1285 Q_ASSERT(m_view);
1286
1287 foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) {
1288 if (widget->isHovered()) {
1289 return widget;
1290 }
1291 }
1292
1293 return nullptr;
1294 }
1295
1296 KItemListWidget* KItemListController::widgetForPos(const QPointF& pos) const
1297 {
1298 Q_ASSERT(m_view);
1299
1300 foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) {
1301 const QPointF mappedPos = widget->mapFromItem(m_view, pos);
1302
1303 const bool hovered = widget->contains(mappedPos) &&
1304 !widget->expansionToggleRect().contains(mappedPos);
1305 if (hovered) {
1306 return widget;
1307 }
1308 }
1309
1310 return nullptr;
1311 }
1312
1313 void KItemListController::updateKeyboardAnchor()
1314 {
1315 const bool validAnchor = m_keyboardAnchorIndex >= 0 &&
1316 m_keyboardAnchorIndex < m_model->count() &&
1317 keyboardAnchorPos(m_keyboardAnchorIndex) == m_keyboardAnchorPos;
1318 if (!validAnchor) {
1319 const int index = m_selectionManager->currentItem();
1320 m_keyboardAnchorIndex = index;
1321 m_keyboardAnchorPos = keyboardAnchorPos(index);
1322 }
1323 }
1324
1325 int KItemListController::nextRowIndex(int index) const
1326 {
1327 if (m_keyboardAnchorIndex < 0) {
1328 return index;
1329 }
1330
1331 const int maxIndex = m_model->count() - 1;
1332 if (index == maxIndex) {
1333 return index;
1334 }
1335
1336 // Calculate the index of the last column inside the row of the current index
1337 int lastColumnIndex = index;
1338 while (keyboardAnchorPos(lastColumnIndex + 1) > keyboardAnchorPos(lastColumnIndex)) {
1339 ++lastColumnIndex;
1340 if (lastColumnIndex >= maxIndex) {
1341 return index;
1342 }
1343 }
1344
1345 // Based on the last column index go to the next row and calculate the nearest index
1346 // that is below the current index
1347 int nextRowIndex = lastColumnIndex + 1;
1348 int searchIndex = nextRowIndex;
1349 qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(nextRowIndex));
1350 while (searchIndex < maxIndex && keyboardAnchorPos(searchIndex + 1) > keyboardAnchorPos(searchIndex)) {
1351 ++searchIndex;
1352 const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex));
1353 if (searchDiff < minDiff) {
1354 minDiff = searchDiff;
1355 nextRowIndex = searchIndex;
1356 }
1357 }
1358
1359 return nextRowIndex;
1360 }
1361
1362 int KItemListController::previousRowIndex(int index) const
1363 {
1364 if (m_keyboardAnchorIndex < 0 || index == 0) {
1365 return index;
1366 }
1367
1368 // Calculate the index of the first column inside the row of the current index
1369 int firstColumnIndex = index;
1370 while (keyboardAnchorPos(firstColumnIndex - 1) < keyboardAnchorPos(firstColumnIndex)) {
1371 --firstColumnIndex;
1372 if (firstColumnIndex <= 0) {
1373 return index;
1374 }
1375 }
1376
1377 // Based on the first column index go to the previous row and calculate the nearest index
1378 // that is above the current index
1379 int previousRowIndex = firstColumnIndex - 1;
1380 int searchIndex = previousRowIndex;
1381 qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(previousRowIndex));
1382 while (searchIndex > 0 && keyboardAnchorPos(searchIndex - 1) < keyboardAnchorPos(searchIndex)) {
1383 --searchIndex;
1384 const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex));
1385 if (searchDiff < minDiff) {
1386 minDiff = searchDiff;
1387 previousRowIndex = searchIndex;
1388 }
1389 }
1390
1391 return previousRowIndex;
1392 }
1393
1394 qreal KItemListController::keyboardAnchorPos(int index) const
1395 {
1396 const QRectF itemRect = m_view->itemRect(index);
1397 if (!itemRect.isEmpty()) {
1398 return (m_view->scrollOrientation() == Qt::Vertical) ? itemRect.x() : itemRect.y();
1399 }
1400
1401 return 0;
1402 }
1403
1404 void KItemListController::updateExtendedSelectionRegion()
1405 {
1406 if (m_view) {
1407 const bool extend = (m_selectionBehavior != MultiSelection);
1408 KItemListStyleOption option = m_view->styleOption();
1409 if (option.extendedSelectionRegion != extend) {
1410 option.extendedSelectionRegion = extend;
1411 m_view->setStyleOption(option);
1412 }
1413 }
1414 }
1415
1416 bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons)
1417 {
1418 emit mouseButtonPressed(m_pressedIndex, buttons);
1419
1420 if (buttons & (Qt::BackButton | Qt::ForwardButton)) {
1421 // Do not select items when clicking the back/forward buttons, see
1422 // https://bugs.kde.org/show_bug.cgi?id=327412.
1423 return true;
1424 }
1425
1426 if (m_view->isAboveExpansionToggle(m_pressedIndex, m_pressedMousePos)) {
1427 m_selectionManager->endAnchoredSelection();
1428 m_selectionManager->setCurrentItem(m_pressedIndex);
1429 m_selectionManager->beginAnchoredSelection(m_pressedIndex);
1430 return true;
1431 }
1432
1433 m_selectionTogglePressed = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos);
1434 if (m_selectionTogglePressed) {
1435 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
1436 // The previous anchored selection has been finished already in
1437 // KItemListSelectionManager::setSelected(). We can safely change
1438 // the current item and start a new anchored selection now.
1439 m_selectionManager->setCurrentItem(m_pressedIndex);
1440 m_selectionManager->beginAnchoredSelection(m_pressedIndex);
1441 return true;
1442 }
1443
1444 const bool shiftPressed = modifiers & Qt::ShiftModifier;
1445 const bool controlPressed = modifiers & Qt::ControlModifier;
1446
1447 // The previous selection is cleared if either
1448 // 1. The selection mode is SingleSelection, or
1449 // 2. the selection mode is MultiSelection, and *none* of the following conditions are met:
1450 // a) Shift or Control are pressed.
1451 // b) The clicked item is selected already. In that case, the user might want to:
1452 // - start dragging multiple items, or
1453 // - open the context menu and perform an action for all selected items.
1454 const bool shiftOrControlPressed = shiftPressed || controlPressed;
1455 const bool pressedItemAlreadySelected = m_pressedIndex >= 0 && m_selectionManager->isSelected(m_pressedIndex);
1456 const bool clearSelection = m_selectionBehavior == SingleSelection ||
1457 (!shiftOrControlPressed && !pressedItemAlreadySelected);
1458 if (clearSelection) {
1459 m_selectionManager->clearSelection();
1460 } else if (pressedItemAlreadySelected && !shiftOrControlPressed && (buttons & Qt::LeftButton)) {
1461 // The user might want to start dragging multiple items, but if he clicks the item
1462 // in order to trigger it instead, the other selected items must be deselected.
1463 // However, we do not know yet what the user is going to do.
1464 // -> remember that the user pressed an item which had been selected already and
1465 // clear the selection in mouseReleaseEvent(), unless the items are dragged.
1466 m_clearSelectionIfItemsAreNotDragged = true;
1467
1468 if (m_selectionManager->selectedItems().count() == 1 && m_view->isAboveText(m_pressedIndex, m_pressedMousePos)) {
1469 emit selectedItemTextPressed(m_pressedIndex);
1470 }
1471 }
1472
1473 if (!shiftPressed) {
1474 // Finish the anchored selection before the current index is changed
1475 m_selectionManager->endAnchoredSelection();
1476 }
1477
1478 if (buttons & Qt::RightButton) {
1479 // Stop rubber band from persisting after right-clicks
1480 KItemListRubberBand* rubberBand = m_view->rubberBand();
1481 if (rubberBand->isActive()) {
1482 disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
1483 rubberBand->setActive(false);
1484 m_view->setAutoScroll(false);
1485 }
1486 }
1487
1488 if (m_pressedIndex >= 0) {
1489 m_selectionManager->setCurrentItem(m_pressedIndex);
1490
1491 switch (m_selectionBehavior) {
1492 case NoSelection:
1493 break;
1494
1495 case SingleSelection:
1496 m_selectionManager->setSelected(m_pressedIndex);
1497 break;
1498
1499 case MultiSelection:
1500 if (controlPressed && !shiftPressed) {
1501 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
1502 m_selectionManager->beginAnchoredSelection(m_pressedIndex);
1503 } else if (!shiftPressed || !m_selectionManager->isAnchoredSelectionActive()) {
1504 // Select the pressed item and start a new anchored selection
1505 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select);
1506 m_selectionManager->beginAnchoredSelection(m_pressedIndex);
1507 }
1508 break;
1509
1510 default:
1511 Q_ASSERT(false);
1512 break;
1513 }
1514
1515 if (buttons & Qt::RightButton) {
1516 emit itemContextMenuRequested(m_pressedIndex, screenPos);
1517 }
1518
1519 return true;
1520 }
1521
1522 if (buttons & Qt::RightButton) {
1523 const QRectF headerBounds = m_view->headerBoundaries();
1524 if (headerBounds.contains(pos)) {
1525 emit headerContextMenuRequested(screenPos);
1526 } else {
1527 emit viewContextMenuRequested(screenPos);
1528 }
1529 return true;
1530 }
1531
1532 return false;
1533 }
1534
1535 bool KItemListController::onRelease(const QPointF& pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons, bool touch)
1536 {
1537 const bool isAboveSelectionToggle = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos);
1538 if (isAboveSelectionToggle) {
1539 m_selectionTogglePressed = false;
1540 return true;
1541 }
1542
1543 if (!isAboveSelectionToggle && m_selectionTogglePressed) {
1544 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
1545 m_selectionTogglePressed = false;
1546 return true;
1547 }
1548
1549 const bool shiftOrControlPressed = modifiers & Qt::ShiftModifier ||
1550 modifiers & Qt::ControlModifier;
1551
1552 KItemListRubberBand* rubberBand = m_view->rubberBand();
1553 if (rubberBand->isActive()) {
1554 disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
1555 rubberBand->setActive(false);
1556 m_oldSelection.clear();
1557 m_view->setAutoScroll(false);
1558 }
1559
1560 const int index = m_view->itemAt(pos);
1561
1562 if (index >= 0 && index == m_pressedIndex) {
1563 // The release event is done above the same item as the press event
1564
1565 if (m_clearSelectionIfItemsAreNotDragged) {
1566 // A selected item has been clicked, but no drag operation has been started
1567 // -> clear the rest of the selection.
1568 m_selectionManager->clearSelection();
1569 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select);
1570 m_selectionManager->beginAnchoredSelection(m_pressedIndex);
1571 }
1572
1573 if (buttons & Qt::LeftButton) {
1574 bool emitItemActivated = true;
1575 if (m_view->isAboveExpansionToggle(index, pos)) {
1576 const bool expanded = m_model->isExpanded(index);
1577 m_model->setExpanded(index, !expanded);
1578
1579 emit itemExpansionToggleClicked(index);
1580 emitItemActivated = false;
1581 } else if (shiftOrControlPressed) {
1582 // The mouse click should only update the selection, not trigger the item
1583 emitItemActivated = false;
1584 } else if (!(m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced)) {
1585 if (touch) {
1586 emitItemActivated = true;
1587 } else {
1588 emitItemActivated = false;
1589 }
1590 }
1591 if (emitItemActivated) {
1592 emit itemActivated(index);
1593 }
1594 } else if (buttons & Qt::MiddleButton) {
1595 emit itemMiddleClicked(index);
1596 }
1597 }
1598
1599 m_pressedMousePos = QPointF();
1600 m_pressedIndex = -1;
1601 m_clearSelectionIfItemsAreNotDragged = false;
1602 return false;
1603 }
1604
1605 void KItemListController::startRubberBand()
1606 {
1607 if (m_selectionBehavior == MultiSelection) {
1608 QPointF startPos = m_pressedMousePos;
1609 if (m_view->scrollOrientation() == Qt::Vertical) {
1610 startPos.ry() += m_view->scrollOffset();
1611 if (m_view->itemSize().width() < 0) {
1612 // Use a special rubberband for views that have only one column and
1613 // expand the rubberband to use the whole width of the view.
1614 startPos.setX(0);
1615 }
1616 } else {
1617 startPos.rx() += m_view->scrollOffset();
1618 }
1619
1620 m_oldSelection = m_selectionManager->selectedItems();
1621 KItemListRubberBand* rubberBand = m_view->rubberBand();
1622 rubberBand->setStartPosition(startPos);
1623 rubberBand->setEndPosition(startPos);
1624 rubberBand->setActive(true);
1625 connect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
1626 m_view->setAutoScroll(true);
1627 }
1628 }
1629
1630 void KItemListController::slotStateChanged(QScroller::State newState)
1631 {
1632 if (newState == QScroller::Scrolling) {
1633 m_scrollerIsScrolling = true;
1634 } else if (newState == QScroller::Inactive) {
1635 m_scrollerIsScrolling = false;
1636 }
1637 }