]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistcontroller.cpp
Fix build failure after last commit
[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 if (buttons & (Qt::BackButton | Qt::ForwardButton)) {
561 // Do not select items when clicking the back/forward buttons, see
562 // https://bugs.kde.org/show_bug.cgi?id=327412.
563 return true;
564 }
565
566 return true;
567 }
568
569 bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
570 {
571 if (!m_view) {
572 return false;
573 }
574
575 if (m_view->m_tapAndHoldIndicator->isActive()) {
576 m_view->m_tapAndHoldIndicator->setActive(false);
577 }
578
579 if (event->source() == Qt::MouseEventSynthesizedByQt && !m_dragActionOrRightClick && m_isTouchEvent) {
580 return false;
581 }
582
583 if (m_pressedIndex >= 0) {
584 // Check whether a dragging should be started
585 if (event->buttons() & Qt::LeftButton) {
586 const QPointF pos = transform.map(event->pos());
587 if ((pos - m_pressedMousePos).manhattanLength() >= QApplication::startDragDistance()) {
588 if (!m_selectionManager->isSelected(m_pressedIndex)) {
589 // Always assure that the dragged item gets selected. Usually this is already
590 // done on the mouse-press event, but when using the selection-toggle on a
591 // selected item the dragged item is not selected yet.
592 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
593 } else {
594 // A selected item has been clicked to drag all selected items
595 // -> the selection should not be cleared when the mouse button is released.
596 m_clearSelectionIfItemsAreNotDragged = false;
597 }
598 startDragging();
599 m_mousePress = false;
600 }
601 }
602 } else {
603 KItemListRubberBand* rubberBand = m_view->rubberBand();
604 if (rubberBand->isActive()) {
605 QPointF endPos = transform.map(event->pos());
606
607 // Update the current item.
608 const int newCurrent = m_view->itemAt(endPos);
609 if (newCurrent >= 0) {
610 // It's expected that the new current index is also the new anchor (bug 163451).
611 m_selectionManager->endAnchoredSelection();
612 m_selectionManager->setCurrentItem(newCurrent);
613 m_selectionManager->beginAnchoredSelection(newCurrent);
614 }
615
616 if (m_view->scrollOrientation() == Qt::Vertical) {
617 endPos.ry() += m_view->scrollOffset();
618 if (m_view->itemSize().width() < 0) {
619 // Use a special rubberband for views that have only one column and
620 // expand the rubberband to use the whole width of the view.
621 endPos.setX(m_view->size().width());
622 }
623 } else {
624 endPos.rx() += m_view->scrollOffset();
625 }
626 rubberBand->setEndPosition(endPos);
627 }
628 }
629
630 return false;
631 }
632
633 bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
634 {
635 m_mousePress = false;
636 m_isTouchEvent = false;
637
638 if (!m_view) {
639 return false;
640 }
641
642 if (m_view->m_tapAndHoldIndicator->isActive()) {
643 m_view->m_tapAndHoldIndicator->setActive(false);
644 }
645
646 KItemListRubberBand* rubberBand = m_view->rubberBand();
647 if (event->source() == Qt::MouseEventSynthesizedByQt && !rubberBand->isActive() && m_isTouchEvent) {
648 return false;
649 }
650
651 emit mouseButtonReleased(m_pressedIndex, event->buttons());
652
653 return onRelease(transform.map(event->pos()), event->modifiers(), event->button(), false);
654 }
655
656 bool KItemListController::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
657 {
658 const QPointF pos = transform.map(event->pos());
659 const int index = m_view->itemAt(pos);
660
661 // Expand item if desired - See Bug 295573
662 if (m_mouseDoubleClickAction != ActivateItemOnly) {
663 if (m_view && m_model && m_view->supportsItemExpanding() && m_model->isExpandable(index)) {
664 const bool expanded = m_model->isExpanded(index);
665 m_model->setExpanded(index, !expanded);
666 }
667 }
668
669 if (event->button() & Qt::RightButton) {
670 m_selectionManager->clearSelection();
671 if (index >= 0) {
672 m_selectionManager->setSelected(index);
673 emit itemContextMenuRequested(index, event->screenPos());
674 } else {
675 const QRectF headerBounds = m_view->headerBoundaries();
676 if (headerBounds.contains(event->pos())) {
677 emit headerContextMenuRequested(event->screenPos());
678 } else {
679 emit viewContextMenuRequested(event->screenPos());
680 }
681 }
682 return true;
683 }
684
685 bool emitItemActivated = !(m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced) &&
686 (event->button() & Qt::LeftButton) &&
687 index >= 0 && index < m_model->count();
688 if (emitItemActivated) {
689 emit itemActivated(index);
690 }
691 return false;
692 }
693
694 bool KItemListController::dragEnterEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
695 {
696 Q_UNUSED(event)
697 Q_UNUSED(transform)
698
699 DragAndDropHelper::clearUrlListMatchesUrlCache();
700
701 return false;
702 }
703
704 bool KItemListController::dragLeaveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
705 {
706 Q_UNUSED(event)
707 Q_UNUSED(transform)
708
709 m_autoActivationTimer->stop();
710 m_view->setAutoScroll(false);
711 m_view->hideDropIndicator();
712
713 KItemListWidget* widget = hoveredWidget();
714 if (widget) {
715 widget->setHovered(false);
716 emit itemUnhovered(widget->index());
717 }
718 return false;
719 }
720
721 bool KItemListController::dragMoveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
722 {
723 if (!m_model || !m_view) {
724 return false;
725 }
726
727
728 QUrl hoveredDir = m_model->directory();
729 KItemListWidget* oldHoveredWidget = hoveredWidget();
730
731 const QPointF pos = transform.map(event->pos());
732 KItemListWidget* newHoveredWidget = widgetForPos(pos);
733
734 if (oldHoveredWidget != newHoveredWidget) {
735 m_autoActivationTimer->stop();
736
737 if (oldHoveredWidget) {
738 oldHoveredWidget->setHovered(false);
739 emit itemUnhovered(oldHoveredWidget->index());
740 }
741 }
742
743 if (newHoveredWidget) {
744 bool droppingBetweenItems = false;
745 if (m_model->sortRole().isEmpty()) {
746 // The model supports inserting items between other items.
747 droppingBetweenItems = (m_view->showDropIndicator(pos) >= 0);
748 }
749
750 const int index = newHoveredWidget->index();
751
752 if (m_model->isDir(index)) {
753 hoveredDir = m_model->url(index);
754 }
755
756 if (!droppingBetweenItems) {
757 if (m_model->supportsDropping(index)) {
758 // Something has been dragged on an item.
759 m_view->hideDropIndicator();
760 if (!newHoveredWidget->isHovered()) {
761 newHoveredWidget->setHovered(true);
762 emit itemHovered(index);
763 }
764
765 if (!m_autoActivationTimer->isActive() && m_autoActivationTimer->interval() >= 0) {
766 m_autoActivationTimer->setProperty("index", index);
767 m_autoActivationTimer->start();
768 }
769 }
770 } else {
771 m_autoActivationTimer->stop();
772 if (newHoveredWidget && newHoveredWidget->isHovered()) {
773 newHoveredWidget->setHovered(false);
774 emit itemUnhovered(index);
775 }
776 }
777 } else {
778 m_view->hideDropIndicator();
779 }
780
781 if (DragAndDropHelper::urlListMatchesUrl(event->mimeData()->urls(), hoveredDir)) {
782 event->setDropAction(Qt::IgnoreAction);
783 event->ignore();
784 } else {
785 event->setDropAction(event->proposedAction());
786 event->accept();
787 }
788 return false;
789 }
790
791 bool KItemListController::dropEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
792 {
793 if (!m_view) {
794 return false;
795 }
796
797 m_autoActivationTimer->stop();
798 m_view->setAutoScroll(false);
799
800 const QPointF pos = transform.map(event->pos());
801
802 int dropAboveIndex = -1;
803 if (m_model->sortRole().isEmpty()) {
804 // The model supports inserting of items between other items.
805 dropAboveIndex = m_view->showDropIndicator(pos);
806 }
807
808 if (dropAboveIndex >= 0) {
809 // Something has been dropped between two items.
810 m_view->hideDropIndicator();
811 emit aboveItemDropEvent(dropAboveIndex, event);
812 } else if (!event->mimeData()->hasFormat(m_model->blacklistItemDropEventMimeType())) {
813 // Something has been dropped on an item or on an empty part of the view.
814 emit itemDropEvent(m_view->itemAt(pos), event);
815 }
816
817 QAccessibleEvent accessibilityEvent(view(), QAccessible::DragDropEnd);
818 QAccessible::updateAccessibility(&accessibilityEvent);
819
820 return true;
821 }
822
823 bool KItemListController::hoverEnterEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
824 {
825 Q_UNUSED(event)
826 Q_UNUSED(transform)
827 return false;
828 }
829
830 bool KItemListController::hoverMoveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
831 {
832 Q_UNUSED(transform)
833 if (!m_model || !m_view) {
834 return false;
835 }
836
837 KItemListWidget* oldHoveredWidget = hoveredWidget();
838 const QPointF pos = transform.map(event->pos());
839 KItemListWidget* newHoveredWidget = widgetForPos(pos);
840
841 if (oldHoveredWidget != newHoveredWidget) {
842 if (oldHoveredWidget) {
843 oldHoveredWidget->setHovered(false);
844 emit itemUnhovered(oldHoveredWidget->index());
845 }
846
847 if (newHoveredWidget) {
848 newHoveredWidget->setHovered(true);
849 const QPointF mappedPos = newHoveredWidget->mapFromItem(m_view, pos);
850 newHoveredWidget->setHoverPosition(mappedPos);
851 emit itemHovered(newHoveredWidget->index());
852 }
853 } else if (oldHoveredWidget) {
854 const QPointF mappedPos = oldHoveredWidget->mapFromItem(m_view, pos);
855 oldHoveredWidget->setHoverPosition(mappedPos);
856 }
857
858 return false;
859 }
860
861 bool KItemListController::hoverLeaveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
862 {
863 Q_UNUSED(event)
864 Q_UNUSED(transform)
865
866 m_mousePress = false;
867 m_isTouchEvent = false;
868
869 if (!m_model || !m_view) {
870 return false;
871 }
872
873 foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) {
874 if (widget->isHovered()) {
875 widget->setHovered(false);
876 emit itemUnhovered(widget->index());
877 }
878 }
879 return false;
880 }
881
882 bool KItemListController::wheelEvent(QGraphicsSceneWheelEvent* event, const QTransform& transform)
883 {
884 Q_UNUSED(event)
885 Q_UNUSED(transform)
886 return false;
887 }
888
889 bool KItemListController::resizeEvent(QGraphicsSceneResizeEvent* event, const QTransform& transform)
890 {
891 Q_UNUSED(event)
892 Q_UNUSED(transform)
893 return false;
894 }
895
896 bool KItemListController::gestureEvent(QGestureEvent* event, const QTransform& transform)
897 {
898 if (!m_view) {
899 return false;
900 }
901
902 //you can touch on different views at the same time, but only one QWidget gets a mousePressEvent
903 //we use this to get the right QWidget
904 //the only exception is a tap gesture with state GestureStarted, we need to reset some variable
905 if (!m_mousePress) {
906 if (QGesture* tap = event->gesture(Qt::TapGesture)) {
907 QTapGesture* tapGesture = static_cast<QTapGesture*>(tap);
908 if (tapGesture->state() == Qt::GestureStarted) {
909 tapTriggered(tapGesture, transform);
910 }
911 }
912 return false;
913 }
914
915 bool accepted = false;
916
917 if (QGesture* tap = event->gesture(Qt::TapGesture)) {
918 tapTriggered(static_cast<QTapGesture*>(tap), transform);
919 accepted = true;
920 }
921 if (event->gesture(Qt::TapAndHoldGesture)) {
922 tapAndHoldTriggered(event, transform);
923 accepted = true;
924 }
925 if (event->gesture(Qt::PinchGesture)) {
926 pinchTriggered(event, transform);
927 accepted = true;
928 }
929 if (event->gesture(m_swipeGesture)) {
930 swipeTriggered(event, transform);
931 accepted = true;
932 }
933 if (event->gesture(m_twoFingerTapGesture)) {
934 twoFingerTapTriggered(event, transform);
935 accepted = true;
936 }
937 return accepted;
938 }
939
940 bool KItemListController::touchBeginEvent(QTouchEvent* event, const QTransform& transform)
941 {
942 Q_UNUSED(event)
943 Q_UNUSED(transform)
944
945 m_isTouchEvent = true;
946 return false;
947 }
948
949 void KItemListController::tapTriggered(QTapGesture* tap, const QTransform& transform)
950 {
951 static bool scrollerWasActive = false;
952
953 if (tap->state() == Qt::GestureStarted) {
954 m_dragActionOrRightClick = false;
955 m_isSwipeGesture = false;
956 m_pinchGestureInProgress = false;
957 scrollerWasActive = m_scrollerIsScrolling;
958 }
959
960 if (tap->state() == Qt::GestureFinished) {
961 m_mousePress = false;
962
963 //if at the moment of the gesture start the QScroller was active, the user made the tap
964 //to stop the QScroller and not to tap on an item
965 if (scrollerWasActive) {
966 return;
967 }
968
969 if (m_view->m_tapAndHoldIndicator->isActive()) {
970 m_view->m_tapAndHoldIndicator->setActive(false);
971 }
972
973 m_pressedMousePos = transform.map(tap->position());
974 m_pressedIndex = m_view->itemAt(m_pressedMousePos);
975
976 if (m_dragActionOrRightClick) {
977 onPress(tap->hotSpot().toPoint(), tap->position().toPoint(), Qt::NoModifier, Qt::RightButton);
978 onRelease(transform.map(tap->position()), Qt::NoModifier, Qt::RightButton, false);
979 m_dragActionOrRightClick = false;
980 }
981 else {
982 onPress(tap->hotSpot().toPoint(), tap->position().toPoint(), Qt::NoModifier, Qt::LeftButton);
983 onRelease(transform.map(tap->position()), Qt::NoModifier, Qt::LeftButton, true);
984 }
985 m_isTouchEvent = false;
986 }
987 }
988
989 void KItemListController::tapAndHoldTriggered(QGestureEvent* event, const QTransform& transform)
990 {
991
992 //the Qt TabAndHold gesture is triggerable with a mouse click, we don't want this
993 if (!m_isTouchEvent) {
994 return;
995 }
996
997 const QTapAndHoldGesture* tap = static_cast<QTapAndHoldGesture*>(event->gesture(Qt::TapAndHoldGesture));
998 if (tap->state() == Qt::GestureFinished) {
999 //if a pinch gesture is in progress we don't want a TabAndHold gesture
1000 if (m_pinchGestureInProgress) {
1001 return;
1002 }
1003 m_pressedMousePos = transform.map(event->mapToGraphicsScene(tap->position()));
1004 m_pressedIndex = m_view->itemAt(m_pressedMousePos);
1005
1006 if (m_pressedIndex >= 0 && !m_selectionManager->isSelected(m_pressedIndex)) {
1007 m_selectionManager->clearSelection();
1008 m_selectionManager->setSelected(m_pressedIndex);
1009 } else if (m_pressedIndex == -1) {
1010 m_selectionManager->clearSelection();
1011 startRubberBand();
1012 }
1013
1014 emit scrollerStop();
1015
1016 m_view->m_tapAndHoldIndicator->setStartPosition(m_pressedMousePos);
1017 m_view->m_tapAndHoldIndicator->setActive(true);
1018
1019 m_dragActionOrRightClick = true;
1020 }
1021 }
1022
1023 void KItemListController::pinchTriggered(QGestureEvent* event, const QTransform& transform)
1024 {
1025 Q_UNUSED(transform)
1026
1027 const QPinchGesture* pinch = static_cast<QPinchGesture*>(event->gesture(Qt::PinchGesture));
1028 const qreal sensitivityModifier = 0.2;
1029 static qreal counter = 0;
1030
1031 if (pinch->state() == Qt::GestureStarted) {
1032 m_pinchGestureInProgress = true;
1033 counter = 0;
1034 }
1035 if (pinch->state() == Qt::GestureUpdated) {
1036 //if a swipe gesture was recognized or in progress, we don't want a pinch gesture to change the zoom
1037 if (m_isSwipeGesture) {
1038 return;
1039 }
1040 counter = counter + (pinch->scaleFactor() - 1);
1041 if (counter >= sensitivityModifier) {
1042 emit increaseZoom();
1043 counter = 0;
1044 } else if (counter <= -sensitivityModifier) {
1045 emit decreaseZoom();
1046 counter = 0;
1047 }
1048 }
1049 }
1050
1051 void KItemListController::swipeTriggered(QGestureEvent* event, const QTransform& transform)
1052 {
1053 Q_UNUSED(transform)
1054
1055 const KTwoFingerSwipe* swipe = static_cast<KTwoFingerSwipe*>(event->gesture(m_swipeGesture));
1056
1057 if (!swipe) {
1058 return;
1059 }
1060 if (swipe->state() == Qt::GestureStarted) {
1061 m_isSwipeGesture = true;
1062 }
1063
1064 if (swipe->state() == Qt::GestureCanceled) {
1065 m_isSwipeGesture = false;
1066 }
1067
1068 if (swipe->state() == Qt::GestureFinished) {
1069 emit scrollerStop();
1070
1071 if (swipe->swipeAngle() <= 20 || swipe->swipeAngle() >= 340) {
1072 emit mouseButtonPressed(m_pressedIndex, Qt::BackButton);
1073 } else if (swipe->swipeAngle() <= 200 && swipe->swipeAngle() >= 160) {
1074 emit mouseButtonPressed(m_pressedIndex, Qt::ForwardButton);
1075 } else if (swipe->swipeAngle() <= 110 && swipe->swipeAngle() >= 60) {
1076 emit swipeUp();
1077 }
1078 m_isSwipeGesture = true;
1079 }
1080 }
1081
1082 void KItemListController::twoFingerTapTriggered(QGestureEvent* event, const QTransform& transform)
1083 {
1084 const KTwoFingerTap* twoTap = static_cast<KTwoFingerTap*>(event->gesture(m_twoFingerTapGesture));
1085
1086 if (!twoTap) {
1087 return;
1088 }
1089
1090 if (twoTap->state() == Qt::GestureStarted) {
1091 m_pressedMousePos = transform.map(twoTap->pos());
1092 m_pressedIndex = m_view->itemAt(m_pressedMousePos);
1093 if (m_pressedIndex >= 0) {
1094 onPress(twoTap->screenPos().toPoint(), twoTap->pos().toPoint(), Qt::ControlModifier, Qt::LeftButton);
1095 onRelease(transform.map(twoTap->pos()), Qt::ControlModifier, Qt::LeftButton, false);
1096 }
1097
1098 }
1099 }
1100
1101 bool KItemListController::processEvent(QEvent* event, const QTransform& transform)
1102 {
1103 if (!event) {
1104 return false;
1105 }
1106
1107 switch (event->type()) {
1108 case QEvent::KeyPress:
1109 return keyPressEvent(static_cast<QKeyEvent*>(event));
1110 case QEvent::InputMethod:
1111 return inputMethodEvent(static_cast<QInputMethodEvent*>(event));
1112 case QEvent::GraphicsSceneMousePress:
1113 return mousePressEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
1114 case QEvent::GraphicsSceneMouseMove:
1115 return mouseMoveEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
1116 case QEvent::GraphicsSceneMouseRelease:
1117 return mouseReleaseEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
1118 case QEvent::GraphicsSceneMouseDoubleClick:
1119 return mouseDoubleClickEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
1120 case QEvent::GraphicsSceneWheel:
1121 return wheelEvent(static_cast<QGraphicsSceneWheelEvent*>(event), QTransform());
1122 case QEvent::GraphicsSceneDragEnter:
1123 return dragEnterEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
1124 case QEvent::GraphicsSceneDragLeave:
1125 return dragLeaveEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
1126 case QEvent::GraphicsSceneDragMove:
1127 return dragMoveEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
1128 case QEvent::GraphicsSceneDrop:
1129 return dropEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
1130 case QEvent::GraphicsSceneHoverEnter:
1131 return hoverEnterEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
1132 case QEvent::GraphicsSceneHoverMove:
1133 return hoverMoveEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
1134 case QEvent::GraphicsSceneHoverLeave:
1135 return hoverLeaveEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
1136 case QEvent::GraphicsSceneResize:
1137 return resizeEvent(static_cast<QGraphicsSceneResizeEvent*>(event), transform);
1138 case QEvent::Gesture:
1139 return gestureEvent(static_cast<QGestureEvent*>(event), transform);
1140 case QEvent::TouchBegin:
1141 return touchBeginEvent(static_cast<QTouchEvent*>(event), transform);
1142 default:
1143 break;
1144 }
1145
1146 return false;
1147 }
1148
1149 void KItemListController::slotViewScrollOffsetChanged(qreal current, qreal previous)
1150 {
1151 if (!m_view) {
1152 return;
1153 }
1154
1155 KItemListRubberBand* rubberBand = m_view->rubberBand();
1156 if (rubberBand->isActive()) {
1157 const qreal diff = current - previous;
1158 // TODO: Ideally just QCursor::pos() should be used as
1159 // new end-position but it seems there is no easy way
1160 // to have something like QWidget::mapFromGlobal() for QGraphicsWidget
1161 // (... or I just missed an easy way to do the mapping)
1162 QPointF endPos = rubberBand->endPosition();
1163 if (m_view->scrollOrientation() == Qt::Vertical) {
1164 endPos.ry() += diff;
1165 } else {
1166 endPos.rx() += diff;
1167 }
1168
1169 rubberBand->setEndPosition(endPos);
1170 }
1171 }
1172
1173 void KItemListController::slotRubberBandChanged()
1174 {
1175 if (!m_view || !m_model || m_model->count() <= 0) {
1176 return;
1177 }
1178
1179 const KItemListRubberBand* rubberBand = m_view->rubberBand();
1180 const QPointF startPos = rubberBand->startPosition();
1181 const QPointF endPos = rubberBand->endPosition();
1182 QRectF rubberBandRect = QRectF(startPos, endPos).normalized();
1183
1184 const bool scrollVertical = (m_view->scrollOrientation() == Qt::Vertical);
1185 if (scrollVertical) {
1186 rubberBandRect.translate(0, -m_view->scrollOffset());
1187 } else {
1188 rubberBandRect.translate(-m_view->scrollOffset(), 0);
1189 }
1190
1191 if (!m_oldSelection.isEmpty()) {
1192 // Clear the old selection that was available before the rubberband has
1193 // been activated in case if no Shift- or Control-key are pressed
1194 const bool shiftOrControlPressed = QApplication::keyboardModifiers() & Qt::ShiftModifier ||
1195 QApplication::keyboardModifiers() & Qt::ControlModifier;
1196 if (!shiftOrControlPressed) {
1197 m_oldSelection.clear();
1198 }
1199 }
1200
1201 KItemSet selectedItems;
1202
1203 // Select all visible items that intersect with the rubberband
1204 foreach (const KItemListWidget* widget, m_view->visibleItemListWidgets()) {
1205 const int index = widget->index();
1206
1207 const QRectF widgetRect = m_view->itemRect(index);
1208 if (widgetRect.intersects(rubberBandRect)) {
1209 const QRectF iconRect = widget->iconRect().translated(widgetRect.topLeft());
1210 const QRectF textRect = widget->textRect().translated(widgetRect.topLeft());
1211 if (iconRect.intersects(rubberBandRect) || textRect.intersects(rubberBandRect)) {
1212 selectedItems.insert(index);
1213 }
1214 }
1215 }
1216
1217 // Select all invisible items that intersect with the rubberband. Instead of
1218 // iterating all items only the area which might be touched by the rubberband
1219 // will be checked.
1220 const bool increaseIndex = scrollVertical ?
1221 startPos.y() > endPos.y(): startPos.x() > endPos.x();
1222
1223 int index = increaseIndex ? m_view->lastVisibleIndex() + 1 : m_view->firstVisibleIndex() - 1;
1224 bool selectionFinished = false;
1225 do {
1226 const QRectF widgetRect = m_view->itemRect(index);
1227 if (widgetRect.intersects(rubberBandRect)) {
1228 selectedItems.insert(index);
1229 }
1230
1231 if (increaseIndex) {
1232 ++index;
1233 selectionFinished = (index >= m_model->count()) ||
1234 ( scrollVertical && widgetRect.top() > rubberBandRect.bottom()) ||
1235 (!scrollVertical && widgetRect.left() > rubberBandRect.right());
1236 } else {
1237 --index;
1238 selectionFinished = (index < 0) ||
1239 ( scrollVertical && widgetRect.bottom() < rubberBandRect.top()) ||
1240 (!scrollVertical && widgetRect.right() < rubberBandRect.left());
1241 }
1242 } while (!selectionFinished);
1243
1244 if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
1245 // If Control is pressed, the selection state of all items in the rubberband is toggled.
1246 // Therefore, the new selection contains:
1247 // 1. All previously selected items which are not inside the rubberband, and
1248 // 2. all items inside the rubberband which have not been selected previously.
1249 m_selectionManager->setSelectedItems(m_oldSelection ^ selectedItems);
1250 }
1251 else {
1252 m_selectionManager->setSelectedItems(selectedItems + m_oldSelection);
1253 }
1254 }
1255
1256 void KItemListController::startDragging()
1257 {
1258 if (!m_view || !m_model) {
1259 return;
1260 }
1261
1262 const KItemSet selectedItems = m_selectionManager->selectedItems();
1263 if (selectedItems.isEmpty()) {
1264 return;
1265 }
1266
1267 QMimeData* data = m_model->createMimeData(selectedItems);
1268 if (!data) {
1269 return;
1270 }
1271
1272 // The created drag object will be owned and deleted
1273 // by QApplication::activeWindow().
1274 QDrag* drag = new QDrag(QApplication::activeWindow());
1275 drag->setMimeData(data);
1276
1277 const QPixmap pixmap = m_view->createDragPixmap(selectedItems);
1278 drag->setPixmap(pixmap);
1279
1280 const QPoint hotSpot((pixmap.width() / pixmap.devicePixelRatio()) / 2, 0);
1281 drag->setHotSpot(hotSpot);
1282
1283 drag->exec(Qt::MoveAction | Qt::CopyAction | Qt::LinkAction, Qt::CopyAction);
1284
1285 QAccessibleEvent accessibilityEvent(view(), QAccessible::DragDropStart);
1286 QAccessible::updateAccessibility(&accessibilityEvent);
1287 }
1288
1289 KItemListWidget* KItemListController::hoveredWidget() const
1290 {
1291 Q_ASSERT(m_view);
1292
1293 foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) {
1294 if (widget->isHovered()) {
1295 return widget;
1296 }
1297 }
1298
1299 return nullptr;
1300 }
1301
1302 KItemListWidget* KItemListController::widgetForPos(const QPointF& pos) const
1303 {
1304 Q_ASSERT(m_view);
1305
1306 foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) {
1307 const QPointF mappedPos = widget->mapFromItem(m_view, pos);
1308
1309 const bool hovered = widget->contains(mappedPos) &&
1310 !widget->expansionToggleRect().contains(mappedPos);
1311 if (hovered) {
1312 return widget;
1313 }
1314 }
1315
1316 return nullptr;
1317 }
1318
1319 void KItemListController::updateKeyboardAnchor()
1320 {
1321 const bool validAnchor = m_keyboardAnchorIndex >= 0 &&
1322 m_keyboardAnchorIndex < m_model->count() &&
1323 keyboardAnchorPos(m_keyboardAnchorIndex) == m_keyboardAnchorPos;
1324 if (!validAnchor) {
1325 const int index = m_selectionManager->currentItem();
1326 m_keyboardAnchorIndex = index;
1327 m_keyboardAnchorPos = keyboardAnchorPos(index);
1328 }
1329 }
1330
1331 int KItemListController::nextRowIndex(int index) const
1332 {
1333 if (m_keyboardAnchorIndex < 0) {
1334 return index;
1335 }
1336
1337 const int maxIndex = m_model->count() - 1;
1338 if (index == maxIndex) {
1339 return index;
1340 }
1341
1342 // Calculate the index of the last column inside the row of the current index
1343 int lastColumnIndex = index;
1344 while (keyboardAnchorPos(lastColumnIndex + 1) > keyboardAnchorPos(lastColumnIndex)) {
1345 ++lastColumnIndex;
1346 if (lastColumnIndex >= maxIndex) {
1347 return index;
1348 }
1349 }
1350
1351 // Based on the last column index go to the next row and calculate the nearest index
1352 // that is below the current index
1353 int nextRowIndex = lastColumnIndex + 1;
1354 int searchIndex = nextRowIndex;
1355 qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(nextRowIndex));
1356 while (searchIndex < maxIndex && keyboardAnchorPos(searchIndex + 1) > keyboardAnchorPos(searchIndex)) {
1357 ++searchIndex;
1358 const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex));
1359 if (searchDiff < minDiff) {
1360 minDiff = searchDiff;
1361 nextRowIndex = searchIndex;
1362 }
1363 }
1364
1365 return nextRowIndex;
1366 }
1367
1368 int KItemListController::previousRowIndex(int index) const
1369 {
1370 if (m_keyboardAnchorIndex < 0 || index == 0) {
1371 return index;
1372 }
1373
1374 // Calculate the index of the first column inside the row of the current index
1375 int firstColumnIndex = index;
1376 while (keyboardAnchorPos(firstColumnIndex - 1) < keyboardAnchorPos(firstColumnIndex)) {
1377 --firstColumnIndex;
1378 if (firstColumnIndex <= 0) {
1379 return index;
1380 }
1381 }
1382
1383 // Based on the first column index go to the previous row and calculate the nearest index
1384 // that is above the current index
1385 int previousRowIndex = firstColumnIndex - 1;
1386 int searchIndex = previousRowIndex;
1387 qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(previousRowIndex));
1388 while (searchIndex > 0 && keyboardAnchorPos(searchIndex - 1) < keyboardAnchorPos(searchIndex)) {
1389 --searchIndex;
1390 const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex));
1391 if (searchDiff < minDiff) {
1392 minDiff = searchDiff;
1393 previousRowIndex = searchIndex;
1394 }
1395 }
1396
1397 return previousRowIndex;
1398 }
1399
1400 qreal KItemListController::keyboardAnchorPos(int index) const
1401 {
1402 const QRectF itemRect = m_view->itemRect(index);
1403 if (!itemRect.isEmpty()) {
1404 return (m_view->scrollOrientation() == Qt::Vertical) ? itemRect.x() : itemRect.y();
1405 }
1406
1407 return 0;
1408 }
1409
1410 void KItemListController::updateExtendedSelectionRegion()
1411 {
1412 if (m_view) {
1413 const bool extend = (m_selectionBehavior != MultiSelection);
1414 KItemListStyleOption option = m_view->styleOption();
1415 if (option.extendedSelectionRegion != extend) {
1416 option.extendedSelectionRegion = extend;
1417 m_view->setStyleOption(option);
1418 }
1419 }
1420 }
1421
1422 bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons)
1423 {
1424 emit mouseButtonPressed(m_pressedIndex, buttons);
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 }