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