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