]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistcontroller.cpp
Ported KIcon to QIcon
[dolphin.git] / src / kitemviews / kitemlistcontroller.cpp
1 /***************************************************************************
2 * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
3 * Copyright (C) 2012 by Frank Reininghaus <frank78ac@googlemail.com> *
4 * *
5 * Based on the Itemviews NG project from Trolltech Labs: *
6 * http://qt.gitorious.org/qt-labs/itemviews-ng *
7 * *
8 * This program is free software; you can redistribute it and/or modify *
9 * it under the terms of the GNU General Public License as published by *
10 * the Free Software Foundation; either version 2 of the License, or *
11 * (at your option) any later version. *
12 * *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License *
19 * along with this program; if not, write to the *
20 * Free Software Foundation, Inc., *
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
22 ***************************************************************************/
23
24 #include "kitemlistcontroller.h"
25
26 #include <KGlobalSettings>
27 #include <KDebug>
28
29 #include "kitemlistview.h"
30 #include "kitemlistselectionmanager.h"
31
32 #include "private/kitemlistrubberband.h"
33 #include "private/kitemlistkeyboardsearchmanager.h"
34
35 #include <QApplication>
36 #include <QDrag>
37 #include <QEvent>
38 #include <QGraphicsScene>
39 #include <QGraphicsSceneEvent>
40 #include <QGraphicsView>
41 #include <QMimeData>
42 #include <QTimer>
43 #include <QAccessible>
44
45 KItemListController::KItemListController(KItemModelBase* model, KItemListView* view, QObject* parent) :
46 QObject(parent),
47 m_singleClickActivationEnforced(false),
48 m_selectionTogglePressed(false),
49 m_clearSelectionIfItemsAreNotDragged(false),
50 m_selectionBehavior(NoSelection),
51 m_autoActivationBehavior(ActivationAndExpansion),
52 m_mouseDoubleClickAction(ActivateItemOnly),
53 m_model(0),
54 m_view(0),
55 m_selectionManager(new KItemListSelectionManager(this)),
56 m_keyboardManager(new KItemListKeyboardSearchManager(this)),
57 m_pressedIndex(-1),
58 m_pressedMousePos(),
59 m_autoActivationTimer(0),
60 m_oldSelection(),
61 m_keyboardAnchorIndex(-1),
62 m_keyboardAnchorPos(0)
63 {
64 connect(m_keyboardManager, &KItemListKeyboardSearchManager::changeCurrentItem,
65 this, &KItemListController::slotChangeCurrentItem);
66 connect(m_selectionManager, &KItemListSelectionManager::currentChanged,
67 m_keyboardManager, &KItemListKeyboardSearchManager::slotCurrentChanged);
68
69 m_autoActivationTimer = new QTimer(this);
70 m_autoActivationTimer->setSingleShot(true);
71 m_autoActivationTimer->setInterval(-1);
72 connect(m_autoActivationTimer, &QTimer::timeout, this, &KItemListController::slotAutoActivationTimeout);
73
74 setModel(model);
75 setView(view);
76 }
77
78 KItemListController::~KItemListController()
79 {
80 setView(0);
81 Q_ASSERT(!m_view);
82
83 setModel(0);
84 Q_ASSERT(!m_model);
85 }
86
87 void KItemListController::setModel(KItemModelBase* model)
88 {
89 if (m_model == model) {
90 return;
91 }
92
93 KItemModelBase* oldModel = m_model;
94 if (oldModel) {
95 oldModel->deleteLater();
96 }
97
98 m_model = model;
99 if (m_model) {
100 m_model->setParent(this);
101 }
102
103 if (m_view) {
104 m_view->setModel(m_model);
105 }
106
107 m_selectionManager->setModel(m_model);
108
109 emit modelChanged(m_model, oldModel);
110 }
111
112 KItemModelBase* KItemListController::model() const
113 {
114 return m_model;
115 }
116
117 KItemListSelectionManager* KItemListController::selectionManager() const
118 {
119 return m_selectionManager;
120 }
121
122 void KItemListController::setView(KItemListView* view)
123 {
124 if (m_view == view) {
125 return;
126 }
127
128 KItemListView* oldView = m_view;
129 if (oldView) {
130 disconnect(oldView, &KItemListView::scrollOffsetChanged, this, &KItemListController::slotViewScrollOffsetChanged);
131 oldView->deleteLater();
132 }
133
134 m_view = view;
135
136 if (m_view) {
137 m_view->setParent(this);
138 m_view->setController(this);
139 m_view->setModel(m_model);
140 connect(m_view, &KItemListView::scrollOffsetChanged, this, &KItemListController::slotViewScrollOffsetChanged);
141 updateExtendedSelectionRegion();
142 }
143
144 emit viewChanged(m_view, oldView);
145 }
146
147 KItemListView* KItemListController::view() const
148 {
149 return m_view;
150 }
151
152 void KItemListController::setSelectionBehavior(SelectionBehavior behavior)
153 {
154 m_selectionBehavior = behavior;
155 updateExtendedSelectionRegion();
156 }
157
158 KItemListController::SelectionBehavior KItemListController::selectionBehavior() const
159 {
160 return m_selectionBehavior;
161 }
162
163 void KItemListController::setAutoActivationBehavior(AutoActivationBehavior behavior)
164 {
165 m_autoActivationBehavior = behavior;
166 }
167
168 KItemListController::AutoActivationBehavior KItemListController::autoActivationBehavior() const
169 {
170 return m_autoActivationBehavior;
171 }
172
173 void KItemListController::setMouseDoubleClickAction(MouseDoubleClickAction action)
174 {
175 m_mouseDoubleClickAction = action;
176 }
177
178 KItemListController::MouseDoubleClickAction KItemListController::mouseDoubleClickAction() const
179 {
180 return m_mouseDoubleClickAction;
181 }
182
183 void KItemListController::setAutoActivationDelay(int delay)
184 {
185 m_autoActivationTimer->setInterval(delay);
186 }
187
188 int KItemListController::autoActivationDelay() const
189 {
190 return m_autoActivationTimer->interval();
191 }
192
193 void KItemListController::setSingleClickActivationEnforced(bool singleClick)
194 {
195 m_singleClickActivationEnforced = singleClick;
196 }
197
198 bool KItemListController::singleClickActivationEnforced() const
199 {
200 return m_singleClickActivationEnforced;
201 }
202
203 bool KItemListController::showEvent(QShowEvent* event)
204 {
205 Q_UNUSED(event);
206 return false;
207 }
208
209 bool KItemListController::hideEvent(QHideEvent* event)
210 {
211 Q_UNUSED(event);
212 return false;
213 }
214
215 bool KItemListController::keyPressEvent(QKeyEvent* event)
216 {
217 int index = m_selectionManager->currentItem();
218 int key = event->key();
219
220 // Handle the expanding/collapsing of items
221 if (m_view->supportsItemExpanding() && m_model->isExpandable(index)) {
222 if (key == Qt::Key_Right) {
223 if (m_model->setExpanded(index, true)) {
224 return true;
225 }
226 } else if (key == Qt::Key_Left) {
227 if (m_model->setExpanded(index, false)) {
228 return true;
229 }
230 }
231 }
232
233 const bool shiftPressed = event->modifiers() & Qt::ShiftModifier;
234 const bool controlPressed = event->modifiers() & Qt::ControlModifier;
235 const bool shiftOrControlPressed = shiftPressed || controlPressed;
236
237 const int itemCount = m_model->count();
238
239 // For horizontal scroll orientation, transform
240 // the arrow keys to simplify the event handling.
241 if (m_view->scrollOrientation() == Qt::Horizontal) {
242 switch (key) {
243 case Qt::Key_Up: key = Qt::Key_Left; break;
244 case Qt::Key_Down: key = Qt::Key_Right; break;
245 case Qt::Key_Left: key = Qt::Key_Up; break;
246 case Qt::Key_Right: key = Qt::Key_Down; break;
247 default: break;
248 }
249 }
250
251 const bool selectSingleItem = m_selectionBehavior != NoSelection &&
252 itemCount == 1 &&
253 (key == Qt::Key_Home || key == Qt::Key_End ||
254 key == Qt::Key_Up || key == Qt::Key_Down ||
255 key == Qt::Key_Left || key == Qt::Key_Right);
256 if (selectSingleItem) {
257 const int current = m_selectionManager->currentItem();
258 m_selectionManager->setSelected(current);
259 return true;
260 }
261
262 switch (key) {
263 case Qt::Key_Home:
264 index = 0;
265 m_keyboardAnchorIndex = index;
266 m_keyboardAnchorPos = keyboardAnchorPos(index);
267 break;
268
269 case Qt::Key_End:
270 index = itemCount - 1;
271 m_keyboardAnchorIndex = index;
272 m_keyboardAnchorPos = keyboardAnchorPos(index);
273 break;
274
275 case Qt::Key_Left:
276 if (index > 0) {
277 const int expandedParentsCount = m_model->expandedParentsCount(index);
278 if (expandedParentsCount == 0) {
279 --index;
280 } else {
281 // Go to the parent of the current item.
282 do {
283 --index;
284 } while (index > 0 && m_model->expandedParentsCount(index) == expandedParentsCount);
285 }
286 m_keyboardAnchorIndex = index;
287 m_keyboardAnchorPos = keyboardAnchorPos(index);
288 }
289 break;
290
291 case Qt::Key_Right:
292 if (index < itemCount - 1) {
293 ++index;
294 m_keyboardAnchorIndex = index;
295 m_keyboardAnchorPos = keyboardAnchorPos(index);
296 }
297 break;
298
299 case Qt::Key_Up:
300 updateKeyboardAnchor();
301 index = previousRowIndex(index);
302 break;
303
304 case Qt::Key_Down:
305 updateKeyboardAnchor();
306 index = nextRowIndex(index);
307 break;
308
309 case Qt::Key_PageUp:
310 if (m_view->scrollOrientation() == Qt::Horizontal) {
311 // The new current index should correspond to the first item in the current column.
312 int newIndex = qMax(index - 1, 0);
313 while (newIndex != index && m_view->itemRect(newIndex).topLeft().y() < m_view->itemRect(index).topLeft().y()) {
314 index = newIndex;
315 newIndex = qMax(index - 1, 0);
316 }
317 m_keyboardAnchorIndex = index;
318 m_keyboardAnchorPos = keyboardAnchorPos(index);
319 } else {
320 const qreal currentItemBottom = m_view->itemRect(index).bottomLeft().y();
321 const qreal height = m_view->geometry().height();
322
323 // The new current item should be the first item in the current
324 // column whose itemRect's top coordinate is larger than targetY.
325 const qreal targetY = currentItemBottom - height;
326
327 updateKeyboardAnchor();
328 int newIndex = previousRowIndex(index);
329 do {
330 index = newIndex;
331 updateKeyboardAnchor();
332 newIndex = previousRowIndex(index);
333 } while (m_view->itemRect(newIndex).topLeft().y() > targetY && newIndex != index);
334 }
335 break;
336
337 case Qt::Key_PageDown:
338 if (m_view->scrollOrientation() == Qt::Horizontal) {
339 // The new current index should correspond to the last item in the current column.
340 int newIndex = qMin(index + 1, m_model->count() - 1);
341 while (newIndex != index && m_view->itemRect(newIndex).topLeft().y() > m_view->itemRect(index).topLeft().y()) {
342 index = newIndex;
343 newIndex = qMin(index + 1, m_model->count() - 1);
344 }
345 m_keyboardAnchorIndex = index;
346 m_keyboardAnchorPos = keyboardAnchorPos(index);
347 } else {
348 const qreal currentItemTop = m_view->itemRect(index).topLeft().y();
349 const qreal height = m_view->geometry().height();
350
351 // The new current item should be the last item in the current
352 // column whose itemRect's bottom coordinate is smaller than targetY.
353 const qreal targetY = currentItemTop + height;
354
355 updateKeyboardAnchor();
356 int newIndex = nextRowIndex(index);
357 do {
358 index = newIndex;
359 updateKeyboardAnchor();
360 newIndex = nextRowIndex(index);
361 } while (m_view->itemRect(newIndex).bottomLeft().y() < targetY && newIndex != index);
362 }
363 break;
364
365 case Qt::Key_Enter:
366 case Qt::Key_Return: {
367 const KItemSet selectedItems = m_selectionManager->selectedItems();
368 if (selectedItems.count() >= 2) {
369 emit itemsActivated(selectedItems);
370 } else if (selectedItems.count() == 1) {
371 emit itemActivated(selectedItems.first());
372 } else {
373 emit itemActivated(index);
374 }
375 break;
376 }
377
378 case Qt::Key_Menu: {
379 // Emit the signal itemContextMenuRequested() in case if at least one
380 // item is selected. Otherwise the signal viewContextMenuRequested() will be emitted.
381 const KItemSet selectedItems = m_selectionManager->selectedItems();
382 int index = -1;
383 if (selectedItems.count() >= 2) {
384 const int currentItemIndex = m_selectionManager->currentItem();
385 index = selectedItems.contains(currentItemIndex)
386 ? currentItemIndex : selectedItems.first();
387 } else if (selectedItems.count() == 1) {
388 index = selectedItems.first();
389 }
390
391 if (index >= 0) {
392 const QRectF contextRect = m_view->itemContextRect(index);
393 const QPointF pos(m_view->scene()->views().first()->mapToGlobal(contextRect.bottomRight().toPoint()));
394 emit itemContextMenuRequested(index, pos);
395 } else {
396 emit viewContextMenuRequested(QCursor::pos());
397 }
398 break;
399 }
400
401 case Qt::Key_Escape:
402 if (m_selectionBehavior != SingleSelection) {
403 m_selectionManager->clearSelection();
404 }
405 m_keyboardManager->cancelSearch();
406 emit escapePressed();
407 break;
408
409 case Qt::Key_Space:
410 if (m_selectionBehavior == MultiSelection) {
411 if (controlPressed) {
412 // Toggle the selection state of the current item.
413 m_selectionManager->endAnchoredSelection();
414 m_selectionManager->setSelected(index, 1, KItemListSelectionManager::Toggle);
415 m_selectionManager->beginAnchoredSelection(index);
416 break;
417 } else {
418 // Select the current item if it is not selected yet.
419 const int current = m_selectionManager->currentItem();
420 if (!m_selectionManager->isSelected(current)) {
421 m_selectionManager->setSelected(current);
422 break;
423 }
424 }
425 }
426 // Fall through to the default case and add the Space to the current search string.
427
428 default:
429 m_keyboardManager->addKeys(event->text());
430 // Make sure unconsumed events get propagated up the chain. #302329
431 event->ignore();
432 return false;
433 }
434
435 if (m_selectionManager->currentItem() != index) {
436 switch (m_selectionBehavior) {
437 case NoSelection:
438 m_selectionManager->setCurrentItem(index);
439 break;
440
441 case SingleSelection:
442 m_selectionManager->setCurrentItem(index);
443 m_selectionManager->clearSelection();
444 m_selectionManager->setSelected(index, 1);
445 break;
446
447 case MultiSelection:
448 if (controlPressed) {
449 m_selectionManager->endAnchoredSelection();
450 }
451
452 m_selectionManager->setCurrentItem(index);
453
454 if (!shiftOrControlPressed) {
455 m_selectionManager->clearSelection();
456 m_selectionManager->setSelected(index, 1);
457 }
458
459 if (!shiftPressed) {
460 m_selectionManager->beginAnchoredSelection(index);
461 }
462 break;
463 }
464
465 m_view->scrollToItem(index);
466 }
467 return true;
468 }
469
470 void KItemListController::slotChangeCurrentItem(const QString& text, bool searchFromNextItem)
471 {
472 if (!m_model || m_model->count() == 0) {
473 return;
474 }
475 const int currentIndex = m_selectionManager->currentItem();
476 int index;
477 if (searchFromNextItem) {
478 index = m_model->indexForKeyboardSearch(text, (currentIndex + 1) % m_model->count());
479 } else {
480 index = m_model->indexForKeyboardSearch(text, currentIndex);
481 }
482 if (index >= 0) {
483 m_selectionManager->setCurrentItem(index);
484
485 if (m_selectionBehavior != NoSelection) {
486 m_selectionManager->clearSelection();
487 m_selectionManager->setSelected(index, 1);
488 m_selectionManager->beginAnchoredSelection(index);
489 }
490
491 m_view->scrollToItem(index);
492 }
493 }
494
495 void KItemListController::slotAutoActivationTimeout()
496 {
497 if (!m_model || !m_view) {
498 return;
499 }
500
501 const int index = m_autoActivationTimer->property("index").toInt();
502 if (index < 0 || index >= m_model->count()) {
503 return;
504 }
505
506 /* m_view->isUnderMouse() fixes a bug in the Folder-View-Panel and in the
507 * Places-Panel.
508 *
509 * Bug: When you drag a file onto a Folder-View-Item or a Places-Item and
510 * then move away before the auto-activation timeout triggers, than the
511 * item still becomes activated/expanded.
512 *
513 * See Bug 293200 and 305783
514 */
515 if (m_model->supportsDropping(index) && m_view->isUnderMouse()) {
516 if (m_view->supportsItemExpanding() && m_model->isExpandable(index)) {
517 const bool expanded = m_model->isExpanded(index);
518 m_model->setExpanded(index, !expanded);
519 } else if (m_autoActivationBehavior != ExpansionOnly) {
520 emit itemActivated(index);
521 }
522 }
523 }
524
525 bool KItemListController::inputMethodEvent(QInputMethodEvent* event)
526 {
527 Q_UNUSED(event);
528 return false;
529 }
530
531 bool KItemListController::mousePressEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
532 {
533 if (!m_view) {
534 return false;
535 }
536
537 m_pressedMousePos = transform.map(event->pos());
538 m_pressedIndex = m_view->itemAt(m_pressedMousePos);
539 emit mouseButtonPressed(m_pressedIndex, event->buttons());
540
541 if (event->buttons() & (Qt::BackButton | Qt::ForwardButton)) {
542 // Do not select items when clicking the back/forward buttons, see
543 // https://bugs.kde.org/show_bug.cgi?id=327412.
544 return true;
545 }
546
547 if (m_view->isAboveExpansionToggle(m_pressedIndex, m_pressedMousePos)) {
548 m_selectionManager->endAnchoredSelection();
549 m_selectionManager->setCurrentItem(m_pressedIndex);
550 m_selectionManager->beginAnchoredSelection(m_pressedIndex);
551 return true;
552 }
553
554 m_selectionTogglePressed = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos);
555 if (m_selectionTogglePressed) {
556 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
557 // The previous anchored selection has been finished already in
558 // KItemListSelectionManager::setSelected(). We can safely change
559 // the current item and start a new anchored selection now.
560 m_selectionManager->setCurrentItem(m_pressedIndex);
561 m_selectionManager->beginAnchoredSelection(m_pressedIndex);
562 return true;
563 }
564
565 const bool shiftPressed = event->modifiers() & Qt::ShiftModifier;
566 const bool controlPressed = event->modifiers() & Qt::ControlModifier;
567
568 // The previous selection is cleared if either
569 // 1. The selection mode is SingleSelection, or
570 // 2. the selection mode is MultiSelection, and *none* of the following conditions are met:
571 // a) Shift or Control are pressed.
572 // b) The clicked item is selected already. In that case, the user might want to:
573 // - start dragging multiple items, or
574 // - open the context menu and perform an action for all selected items.
575 const bool shiftOrControlPressed = shiftPressed || controlPressed;
576 const bool pressedItemAlreadySelected = m_pressedIndex >= 0 && m_selectionManager->isSelected(m_pressedIndex);
577 const bool clearSelection = m_selectionBehavior == SingleSelection ||
578 (!shiftOrControlPressed && !pressedItemAlreadySelected);
579 if (clearSelection) {
580 m_selectionManager->clearSelection();
581 } else if (pressedItemAlreadySelected && !shiftOrControlPressed && (event->buttons() & Qt::LeftButton)) {
582 // The user might want to start dragging multiple items, but if he clicks the item
583 // in order to trigger it instead, the other selected items must be deselected.
584 // However, we do not know yet what the user is going to do.
585 // -> remember that the user pressed an item which had been selected already and
586 // clear the selection in mouseReleaseEvent(), unless the items are dragged.
587 m_clearSelectionIfItemsAreNotDragged = true;
588 }
589
590 if (!shiftPressed) {
591 // Finish the anchored selection before the current index is changed
592 m_selectionManager->endAnchoredSelection();
593 }
594
595 if (m_pressedIndex >= 0) {
596 m_selectionManager->setCurrentItem(m_pressedIndex);
597
598 switch (m_selectionBehavior) {
599 case NoSelection:
600 break;
601
602 case SingleSelection:
603 m_selectionManager->setSelected(m_pressedIndex);
604 break;
605
606 case MultiSelection:
607 if (controlPressed && !shiftPressed) {
608 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
609 m_selectionManager->beginAnchoredSelection(m_pressedIndex);
610 } else if (!shiftPressed || !m_selectionManager->isAnchoredSelectionActive()) {
611 // Select the pressed item and start a new anchored selection
612 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select);
613 m_selectionManager->beginAnchoredSelection(m_pressedIndex);
614 }
615 break;
616
617 default:
618 Q_ASSERT(false);
619 break;
620 }
621
622 if (event->buttons() & Qt::RightButton) {
623 emit itemContextMenuRequested(m_pressedIndex, event->screenPos());
624 }
625
626 return true;
627 }
628
629 if (event->buttons() & Qt::RightButton) {
630 const QRectF headerBounds = m_view->headerBoundaries();
631 if (headerBounds.contains(event->pos())) {
632 emit headerContextMenuRequested(event->screenPos());
633 } else {
634 emit viewContextMenuRequested(event->screenPos());
635 }
636 return true;
637 }
638
639 if (m_selectionBehavior == MultiSelection) {
640 QPointF startPos = m_pressedMousePos;
641 if (m_view->scrollOrientation() == Qt::Vertical) {
642 startPos.ry() += m_view->scrollOffset();
643 if (m_view->itemSize().width() < 0) {
644 // Use a special rubberband for views that have only one column and
645 // expand the rubberband to use the whole width of the view.
646 startPos.setX(0);
647 }
648 } else {
649 startPos.rx() += m_view->scrollOffset();
650 }
651
652 m_oldSelection = m_selectionManager->selectedItems();
653 KItemListRubberBand* rubberBand = m_view->rubberBand();
654 rubberBand->setStartPosition(startPos);
655 rubberBand->setEndPosition(startPos);
656 rubberBand->setActive(true);
657 connect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
658 m_view->setAutoScroll(true);
659 }
660
661 return false;
662 }
663
664 bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
665 {
666 if (!m_view) {
667 return false;
668 }
669
670 if (m_pressedIndex >= 0) {
671 // Check whether a dragging should be started
672 if (event->buttons() & Qt::LeftButton) {
673 const QPointF pos = transform.map(event->pos());
674 if ((pos - m_pressedMousePos).manhattanLength() >= QApplication::startDragDistance()) {
675 if (!m_selectionManager->isSelected(m_pressedIndex)) {
676 // Always assure that the dragged item gets selected. Usually this is already
677 // done on the mouse-press event, but when using the selection-toggle on a
678 // selected item the dragged item is not selected yet.
679 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
680 } else {
681 // A selected item has been clicked to drag all selected items
682 // -> the selection should not be cleared when the mouse button is released.
683 m_clearSelectionIfItemsAreNotDragged = false;
684 }
685
686 startDragging();
687 }
688 }
689 } else {
690 KItemListRubberBand* rubberBand = m_view->rubberBand();
691 if (rubberBand->isActive()) {
692 QPointF endPos = transform.map(event->pos());
693
694 // Update the current item.
695 const int newCurrent = m_view->itemAt(endPos);
696 if (newCurrent >= 0) {
697 // It's expected that the new current index is also the new anchor (bug 163451).
698 m_selectionManager->endAnchoredSelection();
699 m_selectionManager->setCurrentItem(newCurrent);
700 m_selectionManager->beginAnchoredSelection(newCurrent);
701 }
702
703 if (m_view->scrollOrientation() == Qt::Vertical) {
704 endPos.ry() += m_view->scrollOffset();
705 if (m_view->itemSize().width() < 0) {
706 // Use a special rubberband for views that have only one column and
707 // expand the rubberband to use the whole width of the view.
708 endPos.setX(m_view->size().width());
709 }
710 } else {
711 endPos.rx() += m_view->scrollOffset();
712 }
713 rubberBand->setEndPosition(endPos);
714 }
715 }
716
717 return false;
718 }
719
720 bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
721 {
722 if (!m_view) {
723 return false;
724 }
725
726 emit mouseButtonReleased(m_pressedIndex, event->buttons());
727
728 const bool isAboveSelectionToggle = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos);
729 if (isAboveSelectionToggle) {
730 m_selectionTogglePressed = false;
731 return true;
732 }
733
734 if (!isAboveSelectionToggle && m_selectionTogglePressed) {
735 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
736 m_selectionTogglePressed = false;
737 return true;
738 }
739
740 const bool shiftOrControlPressed = event->modifiers() & Qt::ShiftModifier ||
741 event->modifiers() & Qt::ControlModifier;
742
743 KItemListRubberBand* rubberBand = m_view->rubberBand();
744 if (rubberBand->isActive()) {
745 disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged);
746 rubberBand->setActive(false);
747 m_oldSelection.clear();
748 m_view->setAutoScroll(false);
749 }
750
751 const QPointF pos = transform.map(event->pos());
752 const int index = m_view->itemAt(pos);
753
754 if (index >= 0 && index == m_pressedIndex) {
755 // The release event is done above the same item as the press event
756
757 if (m_clearSelectionIfItemsAreNotDragged) {
758 // A selected item has been clicked, but no drag operation has been started
759 // -> clear the rest of the selection.
760 m_selectionManager->clearSelection();
761 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select);
762 m_selectionManager->beginAnchoredSelection(m_pressedIndex);
763 }
764
765 if (event->button() & Qt::LeftButton) {
766 bool emitItemActivated = true;
767 if (m_view->isAboveExpansionToggle(index, pos)) {
768 const bool expanded = m_model->isExpanded(index);
769 m_model->setExpanded(index, !expanded);
770
771 emit itemExpansionToggleClicked(index);
772 emitItemActivated = false;
773 } else if (shiftOrControlPressed) {
774 // The mouse click should only update the selection, not trigger the item
775 emitItemActivated = false;
776 } else if (!(KGlobalSettings::singleClick() || m_singleClickActivationEnforced)) {
777 emitItemActivated = false;
778 }
779 if (emitItemActivated) {
780 emit itemActivated(index);
781 }
782 } else if (event->button() & Qt::MidButton) {
783 emit itemMiddleClicked(index);
784 }
785 }
786
787 m_pressedMousePos = QPointF();
788 m_pressedIndex = -1;
789 m_clearSelectionIfItemsAreNotDragged = false;
790 return false;
791 }
792
793 bool KItemListController::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
794 {
795 const QPointF pos = transform.map(event->pos());
796 const int index = m_view->itemAt(pos);
797
798 // Expand item if desired - See Bug 295573
799 if (m_mouseDoubleClickAction != ActivateItemOnly) {
800 if (m_view && m_model && m_view->supportsItemExpanding() && m_model->isExpandable(index)) {
801 const bool expanded = m_model->isExpanded(index);
802 m_model->setExpanded(index, !expanded);
803 }
804 }
805
806 bool emitItemActivated = !(KGlobalSettings::singleClick() || m_singleClickActivationEnforced) &&
807 (event->button() & Qt::LeftButton) &&
808 index >= 0 && index < m_model->count();
809 if (emitItemActivated) {
810 emit itemActivated(index);
811 }
812 return false;
813 }
814
815 bool KItemListController::dragEnterEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
816 {
817 Q_UNUSED(event);
818 Q_UNUSED(transform);
819 return false;
820 }
821
822 bool KItemListController::dragLeaveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
823 {
824 Q_UNUSED(event);
825 Q_UNUSED(transform);
826
827 m_view->setAutoScroll(false);
828 m_view->hideDropIndicator();
829
830 KItemListWidget* widget = hoveredWidget();
831 if (widget) {
832 widget->setHovered(false);
833 emit itemUnhovered(widget->index());
834 }
835 return false;
836 }
837
838 bool KItemListController::dragMoveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
839 {
840 if (!m_model || !m_view) {
841 return false;
842 }
843
844 event->acceptProposedAction();
845
846 KItemListWidget* oldHoveredWidget = hoveredWidget();
847
848 const QPointF pos = transform.map(event->pos());
849 KItemListWidget* newHoveredWidget = widgetForPos(pos);
850
851 if (oldHoveredWidget != newHoveredWidget) {
852 m_autoActivationTimer->stop();
853
854 if (oldHoveredWidget) {
855 oldHoveredWidget->setHovered(false);
856 emit itemUnhovered(oldHoveredWidget->index());
857 }
858 }
859
860 if (newHoveredWidget) {
861 bool droppingBetweenItems = false;
862 if (m_model->sortRole().isEmpty()) {
863 // The model supports inserting items between other items.
864 droppingBetweenItems = (m_view->showDropIndicator(pos) >= 0);
865 }
866
867 const int index = newHoveredWidget->index();
868 if (!droppingBetweenItems) {
869 if (m_model->supportsDropping(index)) {
870 // Something has been dragged on an item.
871 m_view->hideDropIndicator();
872 if (!newHoveredWidget->isHovered()) {
873 newHoveredWidget->setHovered(true);
874 emit itemHovered(index);
875 }
876
877 if (!m_autoActivationTimer->isActive() && m_autoActivationTimer->interval() >= 0) {
878 m_autoActivationTimer->setProperty("index", index);
879 m_autoActivationTimer->start();
880 }
881 }
882 } else {
883 m_autoActivationTimer->stop();
884 if (newHoveredWidget && newHoveredWidget->isHovered()) {
885 newHoveredWidget->setHovered(false);
886 emit itemUnhovered(index);
887 }
888 }
889 } else {
890 m_view->hideDropIndicator();
891 }
892
893 return false;
894 }
895
896 bool KItemListController::dropEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
897 {
898 if (!m_view) {
899 return false;
900 }
901
902 m_autoActivationTimer->stop();
903 m_view->setAutoScroll(false);
904
905 const QPointF pos = transform.map(event->pos());
906
907 int dropAboveIndex = -1;
908 if (m_model->sortRole().isEmpty()) {
909 // The model supports inserting of items between other items.
910 dropAboveIndex = m_view->showDropIndicator(pos);
911 }
912
913 if (dropAboveIndex >= 0) {
914 // Something has been dropped between two items.
915 m_view->hideDropIndicator();
916 emit aboveItemDropEvent(dropAboveIndex, event);
917 } else {
918 // Something has been dropped on an item or on an empty part of the view.
919 emit itemDropEvent(m_view->itemAt(pos), event);
920 }
921
922 QAccessible::updateAccessibility(view(), 0, QAccessible::DragDropEnd);
923
924 return true;
925 }
926
927 bool KItemListController::hoverEnterEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
928 {
929 Q_UNUSED(event);
930 Q_UNUSED(transform);
931 return false;
932 }
933
934 bool KItemListController::hoverMoveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
935 {
936 Q_UNUSED(transform);
937 if (!m_model || !m_view) {
938 return false;
939 }
940
941 KItemListWidget* oldHoveredWidget = hoveredWidget();
942 const QPointF pos = transform.map(event->pos());
943 KItemListWidget* newHoveredWidget = widgetForPos(pos);
944
945 if (oldHoveredWidget != newHoveredWidget) {
946 if (oldHoveredWidget) {
947 oldHoveredWidget->setHovered(false);
948 emit itemUnhovered(oldHoveredWidget->index());
949 }
950
951 if (newHoveredWidget) {
952 newHoveredWidget->setHovered(true);
953 const QPointF mappedPos = newHoveredWidget->mapFromItem(m_view, pos);
954 newHoveredWidget->setHoverPosition(mappedPos);
955 emit itemHovered(newHoveredWidget->index());
956 }
957 } else if (oldHoveredWidget) {
958 const QPointF mappedPos = oldHoveredWidget->mapFromItem(m_view, pos);
959 oldHoveredWidget->setHoverPosition(mappedPos);
960 }
961
962 return false;
963 }
964
965 bool KItemListController::hoverLeaveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
966 {
967 Q_UNUSED(event);
968 Q_UNUSED(transform);
969
970 if (!m_model || !m_view) {
971 return false;
972 }
973
974 foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) {
975 if (widget->isHovered()) {
976 widget->setHovered(false);
977 emit itemUnhovered(widget->index());
978 }
979 }
980 return false;
981 }
982
983 bool KItemListController::wheelEvent(QGraphicsSceneWheelEvent* event, const QTransform& transform)
984 {
985 Q_UNUSED(event);
986 Q_UNUSED(transform);
987 return false;
988 }
989
990 bool KItemListController::resizeEvent(QGraphicsSceneResizeEvent* event, const QTransform& transform)
991 {
992 Q_UNUSED(event);
993 Q_UNUSED(transform);
994 return false;
995 }
996
997 bool KItemListController::processEvent(QEvent* event, const QTransform& transform)
998 {
999 if (!event) {
1000 return false;
1001 }
1002
1003 switch (event->type()) {
1004 case QEvent::KeyPress:
1005 return keyPressEvent(static_cast<QKeyEvent*>(event));
1006 case QEvent::InputMethod:
1007 return inputMethodEvent(static_cast<QInputMethodEvent*>(event));
1008 case QEvent::GraphicsSceneMousePress:
1009 return mousePressEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
1010 case QEvent::GraphicsSceneMouseMove:
1011 return mouseMoveEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
1012 case QEvent::GraphicsSceneMouseRelease:
1013 return mouseReleaseEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
1014 case QEvent::GraphicsSceneMouseDoubleClick:
1015 return mouseDoubleClickEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
1016 case QEvent::GraphicsSceneWheel:
1017 return wheelEvent(static_cast<QGraphicsSceneWheelEvent*>(event), QTransform());
1018 case QEvent::GraphicsSceneDragEnter:
1019 return dragEnterEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
1020 case QEvent::GraphicsSceneDragLeave:
1021 return dragLeaveEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
1022 case QEvent::GraphicsSceneDragMove:
1023 return dragMoveEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
1024 case QEvent::GraphicsSceneDrop:
1025 return dropEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
1026 case QEvent::GraphicsSceneHoverEnter:
1027 return hoverEnterEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
1028 case QEvent::GraphicsSceneHoverMove:
1029 return hoverMoveEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
1030 case QEvent::GraphicsSceneHoverLeave:
1031 return hoverLeaveEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
1032 case QEvent::GraphicsSceneResize:
1033 return resizeEvent(static_cast<QGraphicsSceneResizeEvent*>(event), transform);
1034 default:
1035 break;
1036 }
1037
1038 return false;
1039 }
1040
1041 void KItemListController::slotViewScrollOffsetChanged(qreal current, qreal previous)
1042 {
1043 if (!m_view) {
1044 return;
1045 }
1046
1047 KItemListRubberBand* rubberBand = m_view->rubberBand();
1048 if (rubberBand->isActive()) {
1049 const qreal diff = current - previous;
1050 // TODO: Ideally just QCursor::pos() should be used as
1051 // new end-position but it seems there is no easy way
1052 // to have something like QWidget::mapFromGlobal() for QGraphicsWidget
1053 // (... or I just missed an easy way to do the mapping)
1054 QPointF endPos = rubberBand->endPosition();
1055 if (m_view->scrollOrientation() == Qt::Vertical) {
1056 endPos.ry() += diff;
1057 } else {
1058 endPos.rx() += diff;
1059 }
1060
1061 rubberBand->setEndPosition(endPos);
1062 }
1063 }
1064
1065 void KItemListController::slotRubberBandChanged()
1066 {
1067 if (!m_view || !m_model || m_model->count() <= 0) {
1068 return;
1069 }
1070
1071 const KItemListRubberBand* rubberBand = m_view->rubberBand();
1072 const QPointF startPos = rubberBand->startPosition();
1073 const QPointF endPos = rubberBand->endPosition();
1074 QRectF rubberBandRect = QRectF(startPos, endPos).normalized();
1075
1076 const bool scrollVertical = (m_view->scrollOrientation() == Qt::Vertical);
1077 if (scrollVertical) {
1078 rubberBandRect.translate(0, -m_view->scrollOffset());
1079 } else {
1080 rubberBandRect.translate(-m_view->scrollOffset(), 0);
1081 }
1082
1083 if (!m_oldSelection.isEmpty()) {
1084 // Clear the old selection that was available before the rubberband has
1085 // been activated in case if no Shift- or Control-key are pressed
1086 const bool shiftOrControlPressed = QApplication::keyboardModifiers() & Qt::ShiftModifier ||
1087 QApplication::keyboardModifiers() & Qt::ControlModifier;
1088 if (!shiftOrControlPressed) {
1089 m_oldSelection.clear();
1090 }
1091 }
1092
1093 KItemSet selectedItems;
1094
1095 // Select all visible items that intersect with the rubberband
1096 foreach (const KItemListWidget* widget, m_view->visibleItemListWidgets()) {
1097 const int index = widget->index();
1098
1099 const QRectF widgetRect = m_view->itemRect(index);
1100 if (widgetRect.intersects(rubberBandRect)) {
1101 const QRectF iconRect = widget->iconRect().translated(widgetRect.topLeft());
1102 const QRectF textRect = widget->textRect().translated(widgetRect.topLeft());
1103 if (iconRect.intersects(rubberBandRect) || textRect.intersects(rubberBandRect)) {
1104 selectedItems.insert(index);
1105 }
1106 }
1107 }
1108
1109 // Select all invisible items that intersect with the rubberband. Instead of
1110 // iterating all items only the area which might be touched by the rubberband
1111 // will be checked.
1112 const bool increaseIndex = scrollVertical ?
1113 startPos.y() > endPos.y(): startPos.x() > endPos.x();
1114
1115 int index = increaseIndex ? m_view->lastVisibleIndex() + 1 : m_view->firstVisibleIndex() - 1;
1116 bool selectionFinished = false;
1117 do {
1118 const QRectF widgetRect = m_view->itemRect(index);
1119 if (widgetRect.intersects(rubberBandRect)) {
1120 selectedItems.insert(index);
1121 }
1122
1123 if (increaseIndex) {
1124 ++index;
1125 selectionFinished = (index >= m_model->count()) ||
1126 ( scrollVertical && widgetRect.top() > rubberBandRect.bottom()) ||
1127 (!scrollVertical && widgetRect.left() > rubberBandRect.right());
1128 } else {
1129 --index;
1130 selectionFinished = (index < 0) ||
1131 ( scrollVertical && widgetRect.bottom() < rubberBandRect.top()) ||
1132 (!scrollVertical && widgetRect.right() < rubberBandRect.left());
1133 }
1134 } while (!selectionFinished);
1135
1136 if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
1137 // If Control is pressed, the selection state of all items in the rubberband is toggled.
1138 // Therefore, the new selection contains:
1139 // 1. All previously selected items which are not inside the rubberband, and
1140 // 2. all items inside the rubberband which have not been selected previously.
1141 m_selectionManager->setSelectedItems(m_oldSelection ^ selectedItems);
1142 }
1143 else {
1144 m_selectionManager->setSelectedItems(selectedItems + m_oldSelection);
1145 }
1146 }
1147
1148 void KItemListController::startDragging()
1149 {
1150 if (!m_view || !m_model) {
1151 return;
1152 }
1153
1154 const KItemSet selectedItems = m_selectionManager->selectedItems();
1155 if (selectedItems.isEmpty()) {
1156 return;
1157 }
1158
1159 QMimeData* data = m_model->createMimeData(selectedItems);
1160 if (!data) {
1161 return;
1162 }
1163
1164 // The created drag object will be owned and deleted
1165 // by QApplication::activeWindow().
1166 QDrag* drag = new QDrag(QApplication::activeWindow());
1167 drag->setMimeData(data);
1168
1169 const QPixmap pixmap = m_view->createDragPixmap(selectedItems);
1170 drag->setPixmap(pixmap);
1171
1172 const QPoint hotSpot(pixmap.width() / 2, 0);
1173 drag->setHotSpot(hotSpot);
1174
1175 drag->exec(Qt::MoveAction | Qt::CopyAction | Qt::LinkAction, Qt::CopyAction);
1176 QAccessible::updateAccessibility(view(), 0, QAccessible::DragDropStart);
1177 }
1178
1179 KItemListWidget* KItemListController::hoveredWidget() const
1180 {
1181 Q_ASSERT(m_view);
1182
1183 foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) {
1184 if (widget->isHovered()) {
1185 return widget;
1186 }
1187 }
1188
1189 return 0;
1190 }
1191
1192 KItemListWidget* KItemListController::widgetForPos(const QPointF& pos) const
1193 {
1194 Q_ASSERT(m_view);
1195
1196 foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) {
1197 const QPointF mappedPos = widget->mapFromItem(m_view, pos);
1198
1199 const bool hovered = widget->contains(mappedPos) &&
1200 !widget->expansionToggleRect().contains(mappedPos);
1201 if (hovered) {
1202 return widget;
1203 }
1204 }
1205
1206 return 0;
1207 }
1208
1209 void KItemListController::updateKeyboardAnchor()
1210 {
1211 const bool validAnchor = m_keyboardAnchorIndex >= 0 &&
1212 m_keyboardAnchorIndex < m_model->count() &&
1213 keyboardAnchorPos(m_keyboardAnchorIndex) == m_keyboardAnchorPos;
1214 if (!validAnchor) {
1215 const int index = m_selectionManager->currentItem();
1216 m_keyboardAnchorIndex = index;
1217 m_keyboardAnchorPos = keyboardAnchorPos(index);
1218 }
1219 }
1220
1221 int KItemListController::nextRowIndex(int index) const
1222 {
1223 if (m_keyboardAnchorIndex < 0) {
1224 return index;
1225 }
1226
1227 const int maxIndex = m_model->count() - 1;
1228 if (index == maxIndex) {
1229 return index;
1230 }
1231
1232 // Calculate the index of the last column inside the row of the current index
1233 int lastColumnIndex = index;
1234 while (keyboardAnchorPos(lastColumnIndex + 1) > keyboardAnchorPos(lastColumnIndex)) {
1235 ++lastColumnIndex;
1236 if (lastColumnIndex >= maxIndex) {
1237 return index;
1238 }
1239 }
1240
1241 // Based on the last column index go to the next row and calculate the nearest index
1242 // that is below the current index
1243 int nextRowIndex = lastColumnIndex + 1;
1244 int searchIndex = nextRowIndex;
1245 qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(nextRowIndex));
1246 while (searchIndex < maxIndex && keyboardAnchorPos(searchIndex + 1) > keyboardAnchorPos(searchIndex)) {
1247 ++searchIndex;
1248 const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex));
1249 if (searchDiff < minDiff) {
1250 minDiff = searchDiff;
1251 nextRowIndex = searchIndex;
1252 }
1253 }
1254
1255 return nextRowIndex;
1256 }
1257
1258 int KItemListController::previousRowIndex(int index) const
1259 {
1260 if (m_keyboardAnchorIndex < 0 || index == 0) {
1261 return index;
1262 }
1263
1264 // Calculate the index of the first column inside the row of the current index
1265 int firstColumnIndex = index;
1266 while (keyboardAnchorPos(firstColumnIndex - 1) < keyboardAnchorPos(firstColumnIndex)) {
1267 --firstColumnIndex;
1268 if (firstColumnIndex <= 0) {
1269 return index;
1270 }
1271 }
1272
1273 // Based on the first column index go to the previous row and calculate the nearest index
1274 // that is above the current index
1275 int previousRowIndex = firstColumnIndex - 1;
1276 int searchIndex = previousRowIndex;
1277 qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(previousRowIndex));
1278 while (searchIndex > 0 && keyboardAnchorPos(searchIndex - 1) < keyboardAnchorPos(searchIndex)) {
1279 --searchIndex;
1280 const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex));
1281 if (searchDiff < minDiff) {
1282 minDiff = searchDiff;
1283 previousRowIndex = searchIndex;
1284 }
1285 }
1286
1287 return previousRowIndex;
1288 }
1289
1290 qreal KItemListController::keyboardAnchorPos(int index) const
1291 {
1292 const QRectF itemRect = m_view->itemRect(index);
1293 if (!itemRect.isEmpty()) {
1294 return (m_view->scrollOrientation() == Qt::Vertical) ? itemRect.x() : itemRect.y();
1295 }
1296
1297 return 0;
1298 }
1299
1300 void KItemListController::updateExtendedSelectionRegion()
1301 {
1302 if (m_view) {
1303 const bool extend = (m_selectionBehavior != MultiSelection);
1304 KItemListStyleOption option = m_view->styleOption();
1305 if (option.extendedSelectionRegion != extend) {
1306 option.extendedSelectionRegion = extend;
1307 m_view->setStyleOption(option);
1308 }
1309 }
1310 }
1311