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