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