]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistcontroller.cpp
Fix rubberband-issue in combination with Shift- and Control-key
[dolphin.git] / src / kitemviews / kitemlistcontroller.cpp
1 /***************************************************************************
2 * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
3 * *
4 * Based on the Itemviews NG project from Trolltech Labs: *
5 * http://qt.gitorious.org/qt-labs/itemviews-ng *
6 * *
7 * This program is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation; either version 2 of the License, or *
10 * (at your option) any later version. *
11 * *
12 * This program is distributed in the hope that it will be useful, *
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15 * GNU General Public License for more details. *
16 * *
17 * You should have received a copy of the GNU General Public License *
18 * along with this program; if not, write to the *
19 * Free Software Foundation, Inc., *
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
21 ***************************************************************************/
22
23 #include "kitemlistcontroller.h"
24
25 #include "kitemlistview.h"
26 #include "kitemlistrubberband_p.h"
27 #include "kitemlistselectionmanager.h"
28
29 #include <QApplication>
30 #include <QEvent>
31 #include <QGraphicsSceneEvent>
32
33 #include <KDebug>
34
35 KItemListController::KItemListController(QObject* parent) :
36 QObject(parent),
37 m_selectionBehavior(NoSelection),
38 m_model(0),
39 m_view(0),
40 m_selectionManager(new KItemListSelectionManager(this)),
41 m_pressedIndex(-1),
42 m_oldSelection()
43 {
44 }
45
46 KItemListController::~KItemListController()
47 {
48 }
49
50 void KItemListController::setModel(KItemModelBase* model)
51 {
52 if (m_model == model) {
53 return;
54 }
55
56 KItemModelBase* oldModel = m_model;
57 m_model = model;
58
59 if (m_view) {
60 m_view->setModel(m_model);
61 }
62
63 m_selectionManager->setModel(m_model);
64
65 emit modelChanged(m_model, oldModel);
66 }
67
68 KItemModelBase* KItemListController::model() const
69 {
70 return m_model;
71 }
72
73 KItemListSelectionManager* KItemListController::selectionManager() const
74 {
75 return m_selectionManager;
76 }
77
78 void KItemListController::setView(KItemListView* view)
79 {
80 if (m_view == view) {
81 return;
82 }
83
84 KItemListView* oldView = m_view;
85 if (oldView) {
86 disconnect(oldView, SIGNAL(offsetChanged(qreal,qreal)), this, SLOT(slotViewOffsetChanged(qreal,qreal)));
87 }
88
89 m_view = view;
90
91 if (m_view) {
92 m_view->setController(this);
93 m_view->setModel(m_model);
94 connect(m_view, SIGNAL(offsetChanged(qreal,qreal)), this, SLOT(slotViewOffsetChanged(qreal,qreal)));
95 }
96
97 emit viewChanged(m_view, oldView);
98 }
99
100 KItemListView* KItemListController::view() const
101 {
102 return m_view;
103 }
104
105 void KItemListController::setSelectionBehavior(SelectionBehavior behavior)
106 {
107 m_selectionBehavior = behavior;
108 }
109
110 KItemListController::SelectionBehavior KItemListController::selectionBehavior() const
111 {
112 return m_selectionBehavior;
113 }
114
115 bool KItemListController::showEvent(QShowEvent* event)
116 {
117 Q_UNUSED(event);
118 return false;
119 }
120
121 bool KItemListController::hideEvent(QHideEvent* event)
122 {
123 Q_UNUSED(event);
124 return false;
125 }
126
127 bool KItemListController::keyPressEvent(QKeyEvent* event)
128 {
129 const bool shiftPressed = event->modifiers() & Qt::ShiftModifier;
130 const bool controlPressed = event->modifiers() & Qt::ControlModifier;
131 const bool shiftOrControlPressed = shiftPressed || controlPressed;
132
133 int index = m_selectionManager->currentItem();
134 const int itemCount = m_model->count();
135 const int itemsPerRow = m_view->itemsPerOffset();
136
137 // For horizontal scroll orientation, transform
138 // the arrow keys to simplify the event handling.
139 int key = event->key();
140 if (m_view->scrollOrientation() == Qt::Horizontal) {
141 switch (key) {
142 case Qt::Key_Up: key = Qt::Key_Left; break;
143 case Qt::Key_Down: key = Qt::Key_Right; break;
144 case Qt::Key_Left: key = Qt::Key_Up; break;
145 case Qt::Key_Right: key = Qt::Key_Down; break;
146 default: break;
147 }
148 }
149
150 switch (key) {
151 case Qt::Key_Home:
152 index = 0;
153 break;
154
155 case Qt::Key_End:
156 index = itemCount - 1;
157 break;
158
159 case Qt::Key_Left:
160 if (index > 0) {
161 index--;
162 }
163 break;
164
165 case Qt::Key_Right:
166 if (index < itemCount - 1) {
167 index++;
168 }
169 break;
170
171 case Qt::Key_Up:
172 if (index >= itemsPerRow) {
173 index -= itemsPerRow;
174 }
175 break;
176
177 case Qt::Key_Down:
178 if (index + itemsPerRow < itemCount) {
179 // We are not in the last row yet.
180 index += itemsPerRow;
181 }
182 else {
183 // We are either in the last row already, or we are in the second-last row,
184 // and there is no item below the current item.
185 // In the latter case, we jump to the very last item.
186 const int currentColumn = index % itemsPerRow;
187 const int lastItemColumn = (itemCount - 1) % itemsPerRow;
188 const bool inLastRow = currentColumn < lastItemColumn;
189 if (!inLastRow) {
190 index = itemCount - 1;
191 }
192 }
193 break;
194
195 case Qt::Key_Space:
196 if (controlPressed) {
197 m_selectionManager->endAnchoredSelection();
198 m_selectionManager->setSelected(index, 1, KItemListSelectionManager::Toggle);
199 m_selectionManager->beginAnchoredSelection(index);
200 }
201
202 default:
203 break;
204 }
205
206 if (m_selectionManager->currentItem() != index) {
207 if (controlPressed) {
208 m_selectionManager->endAnchoredSelection();
209 }
210
211 m_selectionManager->setCurrentItem(index);
212
213 if (!shiftOrControlPressed || m_selectionBehavior == SingleSelection) {
214 m_selectionManager->clearSelection();
215 m_selectionManager->setSelected(index, 1);
216 }
217
218 if (!shiftPressed) {
219 m_selectionManager->beginAnchoredSelection(index);
220 }
221 }
222 return true;
223 }
224
225 bool KItemListController::inputMethodEvent(QInputMethodEvent* event)
226 {
227 Q_UNUSED(event);
228 return false;
229 }
230
231 bool KItemListController::mousePressEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
232 {
233 if (!m_view) {
234 return false;
235 }
236
237 const QPointF pos = transform.map(event->pos());
238 m_pressedIndex = m_view->itemAt(pos);
239
240 if (m_view->isAboveExpansionToggle(m_pressedIndex, pos)) {
241 return true;
242 }
243
244 const bool shiftPressed = event->modifiers() & Qt::ShiftModifier;
245 const bool controlPressed = event->modifiers() & Qt::ControlModifier;
246 const bool shiftOrControlPressed = shiftPressed || controlPressed;
247
248 if (!shiftOrControlPressed || m_selectionBehavior == SingleSelection) {
249 m_selectionManager->clearSelection();
250 }
251
252 if (!shiftPressed) {
253 // Finish the anchored selection before the current index is changed
254 m_selectionManager->endAnchoredSelection();
255 }
256
257 if (m_pressedIndex >= 0) {
258 m_selectionManager->setCurrentItem(m_pressedIndex);
259
260 switch (m_selectionBehavior) {
261 case NoSelection:
262 break;
263
264 case SingleSelection:
265 m_selectionManager->setSelected(m_pressedIndex);
266 break;
267
268 case MultiSelection:
269 if (controlPressed) {
270 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
271 m_selectionManager->beginAnchoredSelection(m_pressedIndex);
272 } else if (!shiftPressed || !m_selectionManager->isAnchoredSelectionActive()) {
273 // Select the pressed item and start a new anchored selection
274 m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select);
275 m_selectionManager->beginAnchoredSelection(m_pressedIndex);
276 }
277 break;
278
279 default:
280 Q_ASSERT(false);
281 break;
282 }
283
284 return true;
285 } else {
286 KItemListRubberBand* rubberBand = m_view->rubberBand();
287 QPointF startPos = pos;
288 if (m_view->scrollOrientation() == Qt::Vertical) {
289 startPos.ry() += m_view->offset();
290 if (m_view->itemSize().width() < 0) {
291 // Use a special rubberband for views that have only one column and
292 // expand the rubberband to use the whole width of the view.
293 startPos.setX(0);
294 }
295 } else {
296 startPos.rx() += m_view->offset();
297 }
298
299 m_oldSelection = m_selectionManager->selectedItems();
300 rubberBand->setStartPosition(startPos);
301 rubberBand->setEndPosition(startPos);
302 rubberBand->setActive(true);
303 connect(rubberBand, SIGNAL(endPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandChanged()));
304 }
305
306 return false;
307 }
308
309 bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
310 {
311 if (!m_view) {
312 return false;
313 }
314
315 KItemListRubberBand* rubberBand = m_view->rubberBand();
316 if (rubberBand->isActive()) {
317 QPointF endPos = transform.map(event->pos());
318 if (m_view->scrollOrientation() == Qt::Vertical) {
319 endPos.ry() += m_view->offset();
320 if (m_view->itemSize().width() < 0) {
321 // Use a special rubberband for views that have only one column and
322 // expand the rubberband to use the whole width of the view.
323 endPos.setX(m_view->size().width());
324 }
325 } else {
326 endPos.rx() += m_view->offset();
327 }
328 rubberBand->setEndPosition(endPos);
329 }
330
331 return false;
332 }
333
334 bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
335 {
336 if (!m_view) {
337 return false;
338 }
339
340 KItemListRubberBand* rubberBand = m_view->rubberBand();
341 if (rubberBand->isActive()) {
342 disconnect(rubberBand, SIGNAL(endPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandChanged()));
343 rubberBand->setActive(false);
344 m_oldSelection.clear();
345 m_pressedIndex = -1;
346 return false;
347 }
348
349 const QPointF pos = transform.map(event->pos());
350 const int index = m_view->itemAt(pos);
351 const bool shiftOrControlPressed = event->modifiers() & Qt::ShiftModifier || event->modifiers() & Qt::ControlModifier;
352
353 if (index >= 0 && index == m_pressedIndex) {
354 // The release event is done above the same item as the press event
355 bool emitItemClicked = true;
356 if (event->button() & Qt::LeftButton) {
357 if (m_view->isAboveExpansionToggle(index, pos)) {
358 emit itemExpansionToggleClicked(index);
359 emitItemClicked = false;
360 }
361 else if (shiftOrControlPressed) {
362 // The mouse click should only update the selection, not trigger the item
363 emitItemClicked = false;
364 }
365 }
366
367 if (emitItemClicked) {
368 emit itemClicked(index, event->button());
369 }
370 } else if (!shiftOrControlPressed) {
371 m_selectionManager->clearSelection();
372 }
373
374 m_pressedIndex = -1;
375 return false;
376 }
377
378 bool KItemListController::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
379 {
380 Q_UNUSED(event);
381 Q_UNUSED(transform);
382 return false;
383 }
384
385 bool KItemListController::dragEnterEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
386 {
387 Q_UNUSED(event);
388 Q_UNUSED(transform);
389 return false;
390 }
391
392 bool KItemListController::dragLeaveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
393 {
394 Q_UNUSED(event);
395 Q_UNUSED(transform);
396 return false;
397 }
398
399 bool KItemListController::dragMoveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
400 {
401 Q_UNUSED(event);
402 Q_UNUSED(transform);
403 return false;
404 }
405
406 bool KItemListController::dropEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
407 {
408 Q_UNUSED(event);
409 Q_UNUSED(transform);
410 return false;
411 }
412
413 bool KItemListController::hoverEnterEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
414 {
415 Q_UNUSED(event);
416 Q_UNUSED(transform);
417 return false;
418 }
419
420 bool KItemListController::hoverMoveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
421 {
422 // The implementation assumes that only one item can get hovered no matter
423 // whether they overlap or not.
424
425 Q_UNUSED(transform);
426 if (!m_model || !m_view) {
427 return false;
428 }
429
430 // Search the previously hovered item that might get unhovered
431 KItemListWidget* unhoveredWidget = 0;
432 foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) {
433 if (widget->isHovered()) {
434 unhoveredWidget = widget;
435 break;
436 }
437 }
438
439 // Search the currently hovered item
440 KItemListWidget* hoveredWidget = 0;
441 foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) {
442 const QPointF mappedPos = widget->mapFromItem(m_view, event->pos());
443
444 const bool hovered = widget->contains(mappedPos) &&
445 !widget->expansionToggleRect().contains(mappedPos) &&
446 !widget->selectionToggleRect().contains(mappedPos);
447 if (hovered) {
448 hoveredWidget = widget;
449 break;
450 }
451 }
452
453 if (unhoveredWidget != hoveredWidget) {
454 if (unhoveredWidget) {
455 unhoveredWidget->setHovered(false);
456 emit itemUnhovered(unhoveredWidget->index());
457 }
458
459 if (hoveredWidget) {
460 hoveredWidget->setHovered(true);
461 emit itemHovered(hoveredWidget->index());
462 }
463 }
464
465 return false;
466 }
467
468 bool KItemListController::hoverLeaveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
469 {
470 Q_UNUSED(event);
471 Q_UNUSED(transform);
472
473 if (!m_model || !m_view) {
474 return false;
475 }
476
477 foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) {
478 if (widget->isHovered()) {
479 widget->setHovered(false);
480 emit itemUnhovered(widget->index());
481 }
482 }
483 return false;
484 }
485
486 bool KItemListController::wheelEvent(QGraphicsSceneWheelEvent* event, const QTransform& transform)
487 {
488 Q_UNUSED(event);
489 Q_UNUSED(transform);
490 return false;
491 }
492
493 bool KItemListController::resizeEvent(QGraphicsSceneResizeEvent* event, const QTransform& transform)
494 {
495 Q_UNUSED(event);
496 Q_UNUSED(transform);
497 return false;
498 }
499
500 bool KItemListController::processEvent(QEvent* event, const QTransform& transform)
501 {
502 if (!event) {
503 return false;
504 }
505
506 switch (event->type()) {
507 case QEvent::KeyPress:
508 return keyPressEvent(static_cast<QKeyEvent*>(event));
509 case QEvent::InputMethod:
510 return inputMethodEvent(static_cast<QInputMethodEvent*>(event));
511 case QEvent::GraphicsSceneMousePress:
512 return mousePressEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
513 case QEvent::GraphicsSceneMouseMove:
514 return mouseMoveEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
515 case QEvent::GraphicsSceneMouseRelease:
516 return mouseReleaseEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
517 case QEvent::GraphicsSceneWheel:
518 return wheelEvent(static_cast<QGraphicsSceneWheelEvent*>(event), QTransform());
519 case QEvent::GraphicsSceneDragEnter:
520 return dragEnterEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
521 case QEvent::GraphicsSceneDragLeave:
522 return dragLeaveEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
523 case QEvent::GraphicsSceneDragMove:
524 return dragMoveEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
525 case QEvent::GraphicsSceneDrop:
526 return dropEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
527 case QEvent::GraphicsSceneHoverEnter:
528 return hoverEnterEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
529 case QEvent::GraphicsSceneHoverMove:
530 return hoverMoveEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
531 case QEvent::GraphicsSceneHoverLeave:
532 return hoverLeaveEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
533 case QEvent::GraphicsSceneResize:
534 return resizeEvent(static_cast<QGraphicsSceneResizeEvent*>(event), transform);
535 default:
536 break;
537 }
538
539 return false;
540 }
541
542 void KItemListController::slotViewOffsetChanged(qreal current, qreal previous)
543 {
544 if (!m_view) {
545 return;
546 }
547
548 KItemListRubberBand* rubberBand = m_view->rubberBand();
549 if (rubberBand->isActive()) {
550 const qreal diff = current - previous;
551 // TODO: Ideally just QCursor::pos() should be used as
552 // new end-position but it seems there is no easy way
553 // to have something like QWidget::mapFromGlobal() for QGraphicsWidget
554 // (... or I just missed an easy way to do the mapping)
555 QPointF endPos = rubberBand->endPosition();
556 if (m_view->scrollOrientation() == Qt::Vertical) {
557 endPos.ry() += diff;
558 } else {
559 endPos.rx() += diff;
560 }
561
562 rubberBand->setEndPosition(endPos);
563 }
564 }
565
566 void KItemListController::slotRubberBandChanged()
567 {
568 if (!m_view || !m_model || m_model->count() <= 0) {
569 return;
570 }
571
572 const KItemListRubberBand* rubberBand = m_view->rubberBand();
573 const QPointF startPos = rubberBand->startPosition();
574 const QPointF endPos = rubberBand->endPosition();
575 QRectF rubberBandRect = QRectF(startPos, endPos).normalized();
576
577 const bool scrollVertical = (m_view->scrollOrientation() == Qt::Vertical);
578 if (scrollVertical) {
579 rubberBandRect.translate(0, -m_view->offset());
580 } else {
581 rubberBandRect.translate(-m_view->offset(), 0);
582 }
583
584 if (!m_oldSelection.isEmpty()) {
585 // Clear the old selection that was available before the rubberband has
586 // been activated in case if no Shift- or Control-key are pressed
587 const bool shiftOrControlPressed = QApplication::keyboardModifiers() & Qt::ShiftModifier ||
588 QApplication::keyboardModifiers() & Qt::ControlModifier;
589 if (!shiftOrControlPressed) {
590 m_oldSelection.clear();
591 }
592 }
593
594 QSet<int> selectedItems;
595
596 // Select all visible items that intersect with the rubberband
597 foreach (const KItemListWidget* widget, m_view->visibleItemListWidgets()) {
598 const int index = widget->index();
599
600 const QRectF widgetRect = m_view->itemBoundingRect(index);
601 if (widgetRect.intersects(rubberBandRect)) {
602 const QRectF iconRect = widget->iconBoundingRect().translated(widgetRect.topLeft());
603 const QRectF textRect = widget->textBoundingRect().translated(widgetRect.topLeft());
604 if (iconRect.intersects(rubberBandRect) || textRect.intersects(rubberBandRect)) {
605 selectedItems.insert(index);
606 }
607 }
608 }
609
610 // Select all invisible items that intersect with the rubberband. Instead of
611 // iterating all items only the area which might be touched by the rubberband
612 // will be checked.
613 const bool increaseIndex = scrollVertical ?
614 startPos.y() > endPos.y(): startPos.x() > endPos.x();
615
616 int index = increaseIndex ? m_view->lastVisibleIndex() + 1 : m_view->firstVisibleIndex() - 1;
617 bool selectionFinished = false;
618 do {
619 const QRectF widgetRect = m_view->itemBoundingRect(index);
620 if (widgetRect.intersects(rubberBandRect)) {
621 selectedItems.insert(index);
622 }
623
624 if (increaseIndex) {
625 ++index;
626 selectionFinished = (index >= m_model->count()) ||
627 ( scrollVertical && widgetRect.top() > rubberBandRect.bottom()) ||
628 (!scrollVertical && widgetRect.left() > rubberBandRect.right());
629 } else {
630 --index;
631 selectionFinished = (index < 0) ||
632 ( scrollVertical && widgetRect.bottom() < rubberBandRect.top()) ||
633 (!scrollVertical && widgetRect.right() < rubberBandRect.left());
634 }
635 } while (!selectionFinished);
636
637 m_selectionManager->setSelectedItems(selectedItems + m_oldSelection);
638 }
639
640 #include "kitemlistcontroller.moc"