]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistcontroller.cpp
Fixes Bug 305783 - dragging a file over a directory #c4
[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 return false;
423 }
424
425 if (m_selectionManager->currentItem() != index) {
426 switch (m_selectionBehavior) {
427 case NoSelection:
428 m_selectionManager->setCurrentItem(index);
429 break;
430
431 case SingleSelection:
432 m_selectionManager->setCurrentItem(index);
433 m_selectionManager->clearSelection();
434 m_selectionManager->setSelected(index, 1);
435 break;
436
437 case MultiSelection:
438 if (controlPressed) {
439 m_selectionManager->endAnchoredSelection();
440 }
441
442 m_selectionManager->setCurrentItem(index);
443
444 if (!shiftOrControlPressed) {
445 m_selectionManager->clearSelection();
446 m_selectionManager->setSelected(index, 1);
447 }
448
449 if (!shiftPressed) {
450 m_selectionManager->beginAnchoredSelection(index);
451 }
452 break;
453 }
454
455 m_view->scrollToItem(index);
456 }
457 return true;
458 }
459
460 void KItemListController::slotChangeCurrentItem(const QString& text, bool searchFromNextItem)
461 {
462 if (!m_model || m_model->count() == 0) {
463 return;
464 }
465 const int currentIndex = m_selectionManager->currentItem();
466 int index;
467 if (searchFromNextItem) {
468 index = m_model->indexForKeyboardSearch(text, (currentIndex + 1) % m_model->count());
469 } else {
470 index = m_model->indexForKeyboardSearch(text, currentIndex);
471 }
472 if (index >= 0) {
473 m_selectionManager->setCurrentItem(index);
474 m_selectionManager->clearSelection();
475 m_selectionManager->setSelected(index, 1);
476 m_selectionManager->beginAnchoredSelection(index);
477 m_view->scrollToItem(index);
478 }
479 }
480
481 void KItemListController::slotAutoActivationTimeout()
482 {
483 if (!m_model || !m_view) {
484 return;
485 }
486
487 const int index = m_autoActivationTimer->property("index").toInt();
488 if (index < 0 || index >= m_model->count()) {
489 return;
490 }
491
492 /* m_view->isUnderMouse() fixes a bug in the Folder-View-Panel and in the
493 * Places-Panel.
494 *
495 * Bug: When you drag a file onto a Folder-View-Item or a Places-Item and
496 * then move away before the auto-activation timeout triggers, than the
497 * item still becomes activated/expanded.
498 *
499 * See Bug 293200 and 305783
500 */
501 if (m_model->supportsDropping(index) && m_view->isUnderMouse()) {
502 if (m_view->supportsItemExpanding() && m_model->isExpandable(index)) {
503 const bool expanded = m_model->isExpanded(index);
504 m_model->setExpanded(index, !expanded);
505 } else if (m_autoActivationBehavior != ExpansionOnly) {
506 emit itemActivated(index);
507 }
508 }
509 }
510
511 bool KItemListController::inputMethodEvent(QInputMethodEvent* event)
512 {
513 Q_UNUSED(event);
514 return false;
515 }
516
517 bool KItemListController::mousePressEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
518 {
519 if (!m_view) {
520 return false;
521 }
522
523 m_pressedMousePos = transform.map(event->pos());
524 m_pressedIndex = m_view->itemAt(m_pressedMousePos);
525 emit mouseButtonPressed(m_pressedIndex, event->buttons());
526
527 if (m_view->isAboveExpansionToggle(m_pressedIndex, m_pressedMousePos)) {
528 m_selectionManager->endAnchoredSelection();
529 m_selectionManager->setCurrentItem(m_pressedIndex);
530 m_selectionManager->beginAnchoredSelection(m_pressedIndex);
531 return true;
532 }
533
534 m_selectionTogglePressed = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos);
535 if (m_selectionTogglePressed) {
536 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
537 // The previous anchored selection has been finished already in
538 // KItemListSelectionManager::setSelected(). We can safely change
539 // the current item and start a new anchored selection now.
540 m_selectionManager->setCurrentItem(m_pressedIndex);
541 m_selectionManager->beginAnchoredSelection(m_pressedIndex);
542 return true;
543 }
544
545 const bool shiftPressed = event->modifiers() & Qt::ShiftModifier;
546 const bool controlPressed = event->modifiers() & Qt::ControlModifier;
547
548 // The previous selection is cleared if either
549 // 1. The selection mode is SingleSelection, or
550 // 2. the selection mode is MultiSelection, and *none* of the following conditions are met:
551 // a) Shift or Control are pressed.
552 // b) The clicked item is selected already. In that case, the user might want to:
553 // - start dragging multiple items, or
554 // - open the context menu and perform an action for all selected items.
555 const bool shiftOrControlPressed = shiftPressed || controlPressed;
556 const bool pressedItemAlreadySelected = m_pressedIndex >= 0 && m_selectionManager->isSelected(m_pressedIndex);
557 const bool clearSelection = m_selectionBehavior == SingleSelection ||
558 (!shiftOrControlPressed && !pressedItemAlreadySelected);
559 if (clearSelection) {
560 m_selectionManager->clearSelection();
561 } else if (pressedItemAlreadySelected && !shiftOrControlPressed && (event->buttons() & Qt::LeftButton)) {
562 // The user might want to start dragging multiple items, but if he clicks the item
563 // in order to trigger it instead, the other selected items must be deselected.
564 // However, we do not know yet what the user is going to do.
565 // -> remember that the user pressed an item which had been selected already and
566 // clear the selection in mouseReleaseEvent(), unless the items are dragged.
567 m_clearSelectionIfItemsAreNotDragged = true;
568 }
569
570 if (!shiftPressed) {
571 // Finish the anchored selection before the current index is changed
572 m_selectionManager->endAnchoredSelection();
573 }
574
575 if (m_pressedIndex >= 0) {
576 m_selectionManager->setCurrentItem(m_pressedIndex);
577
578 switch (m_selectionBehavior) {
579 case NoSelection:
580 break;
581
582 case SingleSelection:
583 m_selectionManager->setSelected(m_pressedIndex);
584 break;
585
586 case MultiSelection:
587 if (controlPressed && !shiftPressed) {
588 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
589 m_selectionManager->beginAnchoredSelection(m_pressedIndex);
590 } else if (!shiftPressed || !m_selectionManager->isAnchoredSelectionActive()) {
591 // Select the pressed item and start a new anchored selection
592 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select);
593 m_selectionManager->beginAnchoredSelection(m_pressedIndex);
594 }
595 break;
596
597 default:
598 Q_ASSERT(false);
599 break;
600 }
601
602 if (event->buttons() & Qt::RightButton) {
603 emit itemContextMenuRequested(m_pressedIndex, event->screenPos());
604 }
605
606 return true;
607 }
608
609 if (event->buttons() & Qt::RightButton) {
610 const QRectF headerBounds = m_view->headerBoundaries();
611 if (headerBounds.contains(event->pos())) {
612 emit headerContextMenuRequested(event->screenPos());
613 } else {
614 emit viewContextMenuRequested(event->screenPos());
615 }
616 return true;
617 }
618
619 if (m_selectionBehavior == MultiSelection) {
620 QPointF startPos = m_pressedMousePos;
621 if (m_view->scrollOrientation() == Qt::Vertical) {
622 startPos.ry() += m_view->scrollOffset();
623 if (m_view->itemSize().width() < 0) {
624 // Use a special rubberband for views that have only one column and
625 // expand the rubberband to use the whole width of the view.
626 startPos.setX(0);
627 }
628 } else {
629 startPos.rx() += m_view->scrollOffset();
630 }
631
632 m_oldSelection = m_selectionManager->selectedItems();
633 KItemListRubberBand* rubberBand = m_view->rubberBand();
634 rubberBand->setStartPosition(startPos);
635 rubberBand->setEndPosition(startPos);
636 rubberBand->setActive(true);
637 connect(rubberBand, SIGNAL(endPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandChanged()));
638 m_view->setAutoScroll(true);
639 }
640
641 return false;
642 }
643
644 bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
645 {
646 if (!m_view) {
647 return false;
648 }
649
650 if (m_pressedIndex >= 0) {
651 // Check whether a dragging should be started
652 if (event->buttons() & Qt::LeftButton) {
653 const QPointF pos = transform.map(event->pos());
654 if ((pos - m_pressedMousePos).manhattanLength() >= QApplication::startDragDistance()) {
655 if (!m_selectionManager->isSelected(m_pressedIndex)) {
656 // Always assure that the dragged item gets selected. Usually this is already
657 // done on the mouse-press event, but when using the selection-toggle on a
658 // selected item the dragged item is not selected yet.
659 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
660 } else {
661 // A selected item has been clicked to drag all selected items
662 // -> the selection should not be cleared when the mouse button is released.
663 m_clearSelectionIfItemsAreNotDragged = false;
664 }
665
666 startDragging();
667 }
668 }
669 } else {
670 KItemListRubberBand* rubberBand = m_view->rubberBand();
671 if (rubberBand->isActive()) {
672 QPointF endPos = transform.map(event->pos());
673
674 // Update the current item.
675 const int newCurrent = m_view->itemAt(endPos);
676 if (newCurrent >= 0) {
677 // It's expected that the new current index is also the new anchor (bug 163451).
678 m_selectionManager->endAnchoredSelection();
679 m_selectionManager->setCurrentItem(newCurrent);
680 m_selectionManager->beginAnchoredSelection(newCurrent);
681 }
682
683 if (m_view->scrollOrientation() == Qt::Vertical) {
684 endPos.ry() += m_view->scrollOffset();
685 if (m_view->itemSize().width() < 0) {
686 // Use a special rubberband for views that have only one column and
687 // expand the rubberband to use the whole width of the view.
688 endPos.setX(m_view->size().width());
689 }
690 } else {
691 endPos.rx() += m_view->scrollOffset();
692 }
693 rubberBand->setEndPosition(endPos);
694 }
695 }
696
697 return false;
698 }
699
700 bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
701 {
702 if (!m_view) {
703 return false;
704 }
705
706 emit mouseButtonReleased(m_pressedIndex, event->buttons());
707
708 const bool isAboveSelectionToggle = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos);
709 if (isAboveSelectionToggle) {
710 m_selectionTogglePressed = false;
711 return true;
712 }
713
714 if (!isAboveSelectionToggle && m_selectionTogglePressed) {
715 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
716 m_selectionTogglePressed = false;
717 return true;
718 }
719
720 const bool shiftOrControlPressed = event->modifiers() & Qt::ShiftModifier ||
721 event->modifiers() & Qt::ControlModifier;
722
723 KItemListRubberBand* rubberBand = m_view->rubberBand();
724 if (rubberBand->isActive()) {
725 disconnect(rubberBand, SIGNAL(endPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandChanged()));
726 rubberBand->setActive(false);
727 m_oldSelection.clear();
728 m_view->setAutoScroll(false);
729 }
730
731 const QPointF pos = transform.map(event->pos());
732 const int index = m_view->itemAt(pos);
733
734 if (index >= 0 && index == m_pressedIndex) {
735 // The release event is done above the same item as the press event
736
737 if (m_clearSelectionIfItemsAreNotDragged) {
738 // A selected item has been clicked, but no drag operation has been started
739 // -> clear the rest of the selection.
740 m_selectionManager->clearSelection();
741 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select);
742 m_selectionManager->beginAnchoredSelection(m_pressedIndex);
743 }
744
745 if (event->button() & Qt::LeftButton) {
746 bool emitItemActivated = true;
747 if (m_view->isAboveExpansionToggle(index, pos)) {
748 const bool expanded = m_model->isExpanded(index);
749 m_model->setExpanded(index, !expanded);
750
751 emit itemExpansionToggleClicked(index);
752 emitItemActivated = false;
753 } else if (shiftOrControlPressed) {
754 // The mouse click should only update the selection, not trigger the item
755 emitItemActivated = false;
756 } else if (!m_singleClickActivation) {
757 emitItemActivated = false;
758 }
759 if (emitItemActivated) {
760 emit itemActivated(index);
761 }
762 } else if (event->button() & Qt::MidButton) {
763 emit itemMiddleClicked(index);
764 }
765 }
766
767 m_pressedMousePos = QPointF();
768 m_pressedIndex = -1;
769 m_clearSelectionIfItemsAreNotDragged = false;
770 return false;
771 }
772
773 bool KItemListController::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
774 {
775 const QPointF pos = transform.map(event->pos());
776 const int index = m_view->itemAt(pos);
777
778 // Expand item if desired - See Bug 295573
779 if (m_mouseDoubleClickAction != ActivateItemOnly) {
780 if (m_view && m_model && m_view->supportsItemExpanding() && m_model->isExpandable(index)) {
781 const bool expanded = m_model->isExpanded(index);
782 m_model->setExpanded(index, !expanded);
783 }
784 }
785
786 bool emitItemActivated = !m_singleClickActivation &&
787 (event->button() & Qt::LeftButton) &&
788 index >= 0 && index < m_model->count();
789 if (emitItemActivated) {
790 emit itemActivated(index);
791 }
792 return false;
793 }
794
795 bool KItemListController::dragEnterEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
796 {
797 Q_UNUSED(event);
798 Q_UNUSED(transform);
799 return false;
800 }
801
802 bool KItemListController::dragLeaveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
803 {
804 Q_UNUSED(event);
805 Q_UNUSED(transform);
806
807 m_view->setAutoScroll(false);
808 m_view->hideDropIndicator();
809
810 KItemListWidget* widget = hoveredWidget();
811 if (widget) {
812 widget->setHovered(false);
813 emit itemUnhovered(widget->index());
814 }
815 return false;
816 }
817
818 bool KItemListController::dragMoveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
819 {
820 if (!m_model || !m_view) {
821 return false;
822 }
823
824 event->acceptProposedAction();
825
826 KItemListWidget* oldHoveredWidget = hoveredWidget();
827
828 const QPointF pos = transform.map(event->pos());
829 KItemListWidget* newHoveredWidget = widgetForPos(pos);
830
831 if (oldHoveredWidget != newHoveredWidget) {
832 m_autoActivationTimer->stop();
833
834 if (oldHoveredWidget) {
835 oldHoveredWidget->setHovered(false);
836 emit itemUnhovered(oldHoveredWidget->index());
837 }
838
839 if (newHoveredWidget) {
840 bool droppingBetweenItems = false;
841 if (m_model->sortRole().isEmpty()) {
842 // The model supports inserting items between other items.
843 droppingBetweenItems = (m_view->showDropIndicator(pos) >= 0);
844 }
845
846 const int index = newHoveredWidget->index();
847 if (!droppingBetweenItems && m_model->supportsDropping(index)) {
848 // Something has been dragged on an item.
849 m_view->hideDropIndicator();
850 newHoveredWidget->setHovered(true);
851 emit itemHovered(index);
852
853 if (m_autoActivationTimer->interval() >= 0) {
854 m_autoActivationTimer->setProperty("index", index);
855 m_autoActivationTimer->start();
856 }
857 }
858 }
859 }
860
861 return false;
862 }
863
864 bool KItemListController::dropEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
865 {
866 if (!m_view) {
867 return false;
868 }
869
870 m_autoActivationTimer->stop();
871 m_view->setAutoScroll(false);
872
873 const QPointF pos = transform.map(event->pos());
874
875 int dropAboveIndex = -1;
876 if (m_model->sortRole().isEmpty()) {
877 // The model supports inserting of items between other items.
878 dropAboveIndex = m_view->showDropIndicator(pos);
879 }
880
881 if (dropAboveIndex >= 0) {
882 // Something has been dropped between two items.
883 m_view->hideDropIndicator();
884 emit aboveItemDropEvent(dropAboveIndex, event);
885 } else {
886 // Something has been dropped on an item or on an empty part of the view.
887 emit itemDropEvent(m_view->itemAt(pos), event);
888 }
889
890 return true;
891 }
892
893 bool KItemListController::hoverEnterEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
894 {
895 Q_UNUSED(event);
896 Q_UNUSED(transform);
897 return false;
898 }
899
900 bool KItemListController::hoverMoveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
901 {
902 Q_UNUSED(transform);
903 if (!m_model || !m_view) {
904 return false;
905 }
906
907 KItemListWidget* oldHoveredWidget = hoveredWidget();
908 const QPointF pos = transform.map(event->pos());
909 KItemListWidget* newHoveredWidget = widgetForPos(pos);
910
911 if (oldHoveredWidget != newHoveredWidget) {
912 if (oldHoveredWidget) {
913 oldHoveredWidget->setHovered(false);
914 emit itemUnhovered(oldHoveredWidget->index());
915 }
916
917 if (newHoveredWidget) {
918 newHoveredWidget->setHovered(true);
919 emit itemHovered(newHoveredWidget->index());
920 }
921 }
922
923 return false;
924 }
925
926 bool KItemListController::hoverLeaveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
927 {
928 Q_UNUSED(event);
929 Q_UNUSED(transform);
930
931 if (!m_model || !m_view) {
932 return false;
933 }
934
935 foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) {
936 if (widget->isHovered()) {
937 widget->setHovered(false);
938 emit itemUnhovered(widget->index());
939 }
940 }
941 return false;
942 }
943
944 bool KItemListController::wheelEvent(QGraphicsSceneWheelEvent* event, const QTransform& transform)
945 {
946 Q_UNUSED(event);
947 Q_UNUSED(transform);
948 return false;
949 }
950
951 bool KItemListController::resizeEvent(QGraphicsSceneResizeEvent* event, const QTransform& transform)
952 {
953 Q_UNUSED(event);
954 Q_UNUSED(transform);
955 return false;
956 }
957
958 bool KItemListController::processEvent(QEvent* event, const QTransform& transform)
959 {
960 if (!event) {
961 return false;
962 }
963
964 switch (event->type()) {
965 case QEvent::KeyPress:
966 return keyPressEvent(static_cast<QKeyEvent*>(event));
967 case QEvent::InputMethod:
968 return inputMethodEvent(static_cast<QInputMethodEvent*>(event));
969 case QEvent::GraphicsSceneMousePress:
970 return mousePressEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
971 case QEvent::GraphicsSceneMouseMove:
972 return mouseMoveEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
973 case QEvent::GraphicsSceneMouseRelease:
974 return mouseReleaseEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
975 case QEvent::GraphicsSceneMouseDoubleClick:
976 return mouseDoubleClickEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
977 case QEvent::GraphicsSceneWheel:
978 return wheelEvent(static_cast<QGraphicsSceneWheelEvent*>(event), QTransform());
979 case QEvent::GraphicsSceneDragEnter:
980 return dragEnterEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
981 case QEvent::GraphicsSceneDragLeave:
982 return dragLeaveEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
983 case QEvent::GraphicsSceneDragMove:
984 return dragMoveEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
985 case QEvent::GraphicsSceneDrop:
986 return dropEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
987 case QEvent::GraphicsSceneHoverEnter:
988 return hoverEnterEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
989 case QEvent::GraphicsSceneHoverMove:
990 return hoverMoveEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
991 case QEvent::GraphicsSceneHoverLeave:
992 return hoverLeaveEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
993 case QEvent::GraphicsSceneResize:
994 return resizeEvent(static_cast<QGraphicsSceneResizeEvent*>(event), transform);
995 default:
996 break;
997 }
998
999 return false;
1000 }
1001
1002 void KItemListController::slotViewScrollOffsetChanged(qreal current, qreal previous)
1003 {
1004 if (!m_view) {
1005 return;
1006 }
1007
1008 KItemListRubberBand* rubberBand = m_view->rubberBand();
1009 if (rubberBand->isActive()) {
1010 const qreal diff = current - previous;
1011 // TODO: Ideally just QCursor::pos() should be used as
1012 // new end-position but it seems there is no easy way
1013 // to have something like QWidget::mapFromGlobal() for QGraphicsWidget
1014 // (... or I just missed an easy way to do the mapping)
1015 QPointF endPos = rubberBand->endPosition();
1016 if (m_view->scrollOrientation() == Qt::Vertical) {
1017 endPos.ry() += diff;
1018 } else {
1019 endPos.rx() += diff;
1020 }
1021
1022 rubberBand->setEndPosition(endPos);
1023 }
1024 }
1025
1026 void KItemListController::slotRubberBandChanged()
1027 {
1028 if (!m_view || !m_model || m_model->count() <= 0) {
1029 return;
1030 }
1031
1032 const KItemListRubberBand* rubberBand = m_view->rubberBand();
1033 const QPointF startPos = rubberBand->startPosition();
1034 const QPointF endPos = rubberBand->endPosition();
1035 QRectF rubberBandRect = QRectF(startPos, endPos).normalized();
1036
1037 const bool scrollVertical = (m_view->scrollOrientation() == Qt::Vertical);
1038 if (scrollVertical) {
1039 rubberBandRect.translate(0, -m_view->scrollOffset());
1040 } else {
1041 rubberBandRect.translate(-m_view->scrollOffset(), 0);
1042 }
1043
1044 if (!m_oldSelection.isEmpty()) {
1045 // Clear the old selection that was available before the rubberband has
1046 // been activated in case if no Shift- or Control-key are pressed
1047 const bool shiftOrControlPressed = QApplication::keyboardModifiers() & Qt::ShiftModifier ||
1048 QApplication::keyboardModifiers() & Qt::ControlModifier;
1049 if (!shiftOrControlPressed) {
1050 m_oldSelection.clear();
1051 }
1052 }
1053
1054 QSet<int> selectedItems;
1055
1056 // Select all visible items that intersect with the rubberband
1057 foreach (const KItemListWidget* widget, m_view->visibleItemListWidgets()) {
1058 const int index = widget->index();
1059
1060 const QRectF widgetRect = m_view->itemRect(index);
1061 if (widgetRect.intersects(rubberBandRect)) {
1062 const QRectF iconRect = widget->iconRect().translated(widgetRect.topLeft());
1063 const QRectF textRect = widget->textRect().translated(widgetRect.topLeft());
1064 if (iconRect.intersects(rubberBandRect) || textRect.intersects(rubberBandRect)) {
1065 selectedItems.insert(index);
1066 }
1067 }
1068 }
1069
1070 // Select all invisible items that intersect with the rubberband. Instead of
1071 // iterating all items only the area which might be touched by the rubberband
1072 // will be checked.
1073 const bool increaseIndex = scrollVertical ?
1074 startPos.y() > endPos.y(): startPos.x() > endPos.x();
1075
1076 int index = increaseIndex ? m_view->lastVisibleIndex() + 1 : m_view->firstVisibleIndex() - 1;
1077 bool selectionFinished = false;
1078 do {
1079 const QRectF widgetRect = m_view->itemRect(index);
1080 if (widgetRect.intersects(rubberBandRect)) {
1081 selectedItems.insert(index);
1082 }
1083
1084 if (increaseIndex) {
1085 ++index;
1086 selectionFinished = (index >= m_model->count()) ||
1087 ( scrollVertical && widgetRect.top() > rubberBandRect.bottom()) ||
1088 (!scrollVertical && widgetRect.left() > rubberBandRect.right());
1089 } else {
1090 --index;
1091 selectionFinished = (index < 0) ||
1092 ( scrollVertical && widgetRect.bottom() < rubberBandRect.top()) ||
1093 (!scrollVertical && widgetRect.right() < rubberBandRect.left());
1094 }
1095 } while (!selectionFinished);
1096
1097 if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
1098 // If Control is pressed, the selection state of all items in the rubberband is toggled.
1099 // Therefore, the new selection contains:
1100 // 1. All previously selected items which are not inside the rubberband, and
1101 // 2. all items inside the rubberband which have not been selected previously.
1102 m_selectionManager->setSelectedItems((m_oldSelection - selectedItems) + (selectedItems - m_oldSelection));
1103 }
1104 else {
1105 m_selectionManager->setSelectedItems(selectedItems + m_oldSelection);
1106 }
1107 }
1108
1109 void KItemListController::startDragging()
1110 {
1111 if (!m_view || !m_model) {
1112 return;
1113 }
1114
1115 const QSet<int> selectedItems = m_selectionManager->selectedItems();
1116 if (selectedItems.isEmpty()) {
1117 return;
1118 }
1119
1120 QMimeData* data = m_model->createMimeData(selectedItems);
1121 if (!data) {
1122 return;
1123 }
1124
1125 // The created drag object will be owned and deleted
1126 // by QApplication::activeWindow().
1127 QDrag* drag = new QDrag(QApplication::activeWindow());
1128 drag->setMimeData(data);
1129
1130 const QPixmap pixmap = m_view->createDragPixmap(selectedItems);
1131 drag->setPixmap(pixmap);
1132
1133 const QPoint hotSpot(pixmap.width() / 2, 0);
1134 drag->setHotSpot(hotSpot);
1135
1136 drag->exec(Qt::MoveAction | Qt::CopyAction | Qt::LinkAction, Qt::CopyAction);
1137 }
1138
1139 KItemListWidget* KItemListController::hoveredWidget() const
1140 {
1141 Q_ASSERT(m_view);
1142
1143 foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) {
1144 if (widget->isHovered()) {
1145 return widget;
1146 }
1147 }
1148
1149 return 0;
1150 }
1151
1152 KItemListWidget* KItemListController::widgetForPos(const QPointF& pos) const
1153 {
1154 Q_ASSERT(m_view);
1155
1156 foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) {
1157 const QPointF mappedPos = widget->mapFromItem(m_view, pos);
1158
1159 const bool hovered = widget->contains(mappedPos) &&
1160 !widget->expansionToggleRect().contains(mappedPos);
1161 if (hovered) {
1162 return widget;
1163 }
1164 }
1165
1166 return 0;
1167 }
1168
1169 void KItemListController::updateKeyboardAnchor()
1170 {
1171 const bool validAnchor = m_keyboardAnchorIndex >= 0 &&
1172 m_keyboardAnchorIndex < m_model->count() &&
1173 keyboardAnchorPos(m_keyboardAnchorIndex) == m_keyboardAnchorPos;
1174 if (!validAnchor) {
1175 const int index = m_selectionManager->currentItem();
1176 m_keyboardAnchorIndex = index;
1177 m_keyboardAnchorPos = keyboardAnchorPos(index);
1178 }
1179 }
1180
1181 int KItemListController::nextRowIndex(int index) const
1182 {
1183 if (m_keyboardAnchorIndex < 0) {
1184 return index;
1185 }
1186
1187 const int maxIndex = m_model->count() - 1;
1188 if (index == maxIndex) {
1189 return index;
1190 }
1191
1192 // Calculate the index of the last column inside the row of the current index
1193 int lastColumnIndex = index;
1194 while (keyboardAnchorPos(lastColumnIndex + 1) > keyboardAnchorPos(lastColumnIndex)) {
1195 ++lastColumnIndex;
1196 if (lastColumnIndex >= maxIndex) {
1197 return index;
1198 }
1199 }
1200
1201 // Based on the last column index go to the next row and calculate the nearest index
1202 // that is below the current index
1203 int nextRowIndex = lastColumnIndex + 1;
1204 int searchIndex = nextRowIndex;
1205 qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(nextRowIndex));
1206 while (searchIndex < maxIndex && keyboardAnchorPos(searchIndex + 1) > keyboardAnchorPos(searchIndex)) {
1207 ++searchIndex;
1208 const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex));
1209 if (searchDiff < minDiff) {
1210 minDiff = searchDiff;
1211 nextRowIndex = searchIndex;
1212 }
1213 }
1214
1215 return nextRowIndex;
1216 }
1217
1218 int KItemListController::previousRowIndex(int index) const
1219 {
1220 if (m_keyboardAnchorIndex < 0 || index == 0) {
1221 return index;
1222 }
1223
1224 // Calculate the index of the first column inside the row of the current index
1225 int firstColumnIndex = index;
1226 while (keyboardAnchorPos(firstColumnIndex - 1) < keyboardAnchorPos(firstColumnIndex)) {
1227 --firstColumnIndex;
1228 if (firstColumnIndex <= 0) {
1229 return index;
1230 }
1231 }
1232
1233 // Based on the first column index go to the previous row and calculate the nearest index
1234 // that is above the current index
1235 int previousRowIndex = firstColumnIndex - 1;
1236 int searchIndex = previousRowIndex;
1237 qreal minDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(previousRowIndex));
1238 while (searchIndex > 0 && keyboardAnchorPos(searchIndex - 1) < keyboardAnchorPos(searchIndex)) {
1239 --searchIndex;
1240 const qreal searchDiff = qAbs(m_keyboardAnchorPos - keyboardAnchorPos(searchIndex));
1241 if (searchDiff < minDiff) {
1242 minDiff = searchDiff;
1243 previousRowIndex = searchIndex;
1244 }
1245 }
1246
1247 return previousRowIndex;
1248 }
1249
1250 qreal KItemListController::keyboardAnchorPos(int index) const
1251 {
1252 const QRectF itemRect = m_view->itemRect(index);
1253 if (!itemRect.isEmpty()) {
1254 return (m_view->scrollOrientation() == Qt::Vertical) ? itemRect.x() : itemRect.y();
1255 }
1256
1257 return 0;
1258 }
1259
1260 void KItemListController::updateExtendedSelectionRegion()
1261 {
1262 if (m_view) {
1263 const bool extend = (m_selectionBehavior != MultiSelection);
1264 KItemListStyleOption option = m_view->styleOption();
1265 if (option.extendedSelectionRegion != extend) {
1266 option.extendedSelectionRegion = extend;
1267 m_view->setStyleOption(option);
1268 }
1269 }
1270 }
1271
1272 #include "kitemlistcontroller.moc"