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