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