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