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