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