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