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