]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kitemlistcontroller.cpp
Simplify rubberband selection for views that have only one column
[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 if (m_view->itemSize().width() < 0) {
289 // Use a special rubberband for views that have only one column and
290 // expand the rubberband to use the whole width of the view.
291 startPos.setX(0);
292 }
293 } else {
294 startPos.rx() += m_view->offset();
295 }
296 rubberBand->setStartPosition(startPos);
297 rubberBand->setEndPosition(startPos);
298 rubberBand->setActive(true);
299 connect(rubberBand, SIGNAL(endPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandChanged()));
300 }
301
302 return false;
303 }
304
305 bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
306 {
307 if (!m_view) {
308 return false;
309 }
310
311 KItemListRubberBand* rubberBand = m_view->rubberBand();
312 if (rubberBand->isActive()) {
313 QPointF endPos = transform.map(event->pos());
314 if (m_view->scrollOrientation() == Qt::Vertical) {
315 endPos.ry() += m_view->offset();
316 if (m_view->itemSize().width() < 0) {
317 // Use a special rubberband for views that have only one column and
318 // expand the rubberband to use the whole width of the view.
319 endPos.setX(m_view->size().width());
320 }
321 } else {
322 endPos.rx() += m_view->offset();
323 }
324 rubberBand->setEndPosition(endPos);
325 }
326
327 return false;
328 }
329
330 bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
331 {
332 if (!m_view) {
333 return false;
334 }
335
336 KItemListRubberBand* rubberBand = m_view->rubberBand();
337 if (rubberBand->isActive()) {
338 disconnect(rubberBand, SIGNAL(endPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandChanged()));
339 rubberBand->setActive(false);
340 m_pressedIndex = -1;
341 return false;
342 }
343
344 const QPointF pos = transform.map(event->pos());
345 const int index = m_view->itemAt(pos);
346 const bool shiftOrControlPressed = event->modifiers() & Qt::ShiftModifier || event->modifiers() & Qt::ControlModifier;
347
348 if (index >= 0 && index == m_pressedIndex) {
349 // The release event is done above the same item as the press event
350 bool emitItemClicked = true;
351 if (event->button() & Qt::LeftButton) {
352 if (m_view->isAboveExpansionToggle(index, pos)) {
353 emit itemExpansionToggleClicked(index);
354 emitItemClicked = false;
355 }
356 else if (shiftOrControlPressed) {
357 // The mouse click should only update the selection, not trigger the item
358 emitItemClicked = false;
359 }
360 }
361
362 if (emitItemClicked) {
363 emit itemClicked(index, event->button());
364 }
365 } else if (!shiftOrControlPressed) {
366 m_selectionManager->clearSelection();
367 }
368
369 m_pressedIndex = -1;
370 return false;
371 }
372
373 bool KItemListController::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
374 {
375 Q_UNUSED(event);
376 Q_UNUSED(transform);
377 return false;
378 }
379
380 bool KItemListController::dragEnterEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
381 {
382 Q_UNUSED(event);
383 Q_UNUSED(transform);
384 return false;
385 }
386
387 bool KItemListController::dragLeaveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
388 {
389 Q_UNUSED(event);
390 Q_UNUSED(transform);
391 return false;
392 }
393
394 bool KItemListController::dragMoveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
395 {
396 Q_UNUSED(event);
397 Q_UNUSED(transform);
398 return false;
399 }
400
401 bool KItemListController::dropEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
402 {
403 Q_UNUSED(event);
404 Q_UNUSED(transform);
405 return false;
406 }
407
408 bool KItemListController::hoverEnterEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
409 {
410 Q_UNUSED(event);
411 Q_UNUSED(transform);
412 return false;
413 }
414
415 bool KItemListController::hoverMoveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
416 {
417 // The implementation assumes that only one item can get hovered no matter
418 // whether they overlap or not.
419
420 Q_UNUSED(transform);
421 if (!m_model || !m_view) {
422 return false;
423 }
424
425 // Search the previously hovered item that might get unhovered
426 KItemListWidget* unhoveredWidget = 0;
427 foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) {
428 if (widget->isHovered()) {
429 unhoveredWidget = widget;
430 break;
431 }
432 }
433
434 // Search the currently hovered item
435 KItemListWidget* hoveredWidget = 0;
436 foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) {
437 const QPointF mappedPos = widget->mapFromItem(m_view, event->pos());
438
439 const bool hovered = widget->contains(mappedPos) &&
440 !widget->expansionToggleRect().contains(mappedPos) &&
441 !widget->selectionToggleRect().contains(mappedPos);
442 if (hovered) {
443 hoveredWidget = widget;
444 break;
445 }
446 }
447
448 if (unhoveredWidget != hoveredWidget) {
449 if (unhoveredWidget) {
450 unhoveredWidget->setHovered(false);
451 emit itemUnhovered(unhoveredWidget->index());
452 }
453
454 if (hoveredWidget) {
455 hoveredWidget->setHovered(true);
456 emit itemHovered(hoveredWidget->index());
457 }
458 }
459
460 return false;
461 }
462
463 bool KItemListController::hoverLeaveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
464 {
465 Q_UNUSED(event);
466 Q_UNUSED(transform);
467
468 if (!m_model || !m_view) {
469 return false;
470 }
471
472 foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) {
473 if (widget->isHovered()) {
474 widget->setHovered(false);
475 emit itemUnhovered(widget->index());
476 }
477 }
478 return false;
479 }
480
481 bool KItemListController::wheelEvent(QGraphicsSceneWheelEvent* event, const QTransform& transform)
482 {
483 Q_UNUSED(event);
484 Q_UNUSED(transform);
485 return false;
486 }
487
488 bool KItemListController::resizeEvent(QGraphicsSceneResizeEvent* event, const QTransform& transform)
489 {
490 Q_UNUSED(event);
491 Q_UNUSED(transform);
492 return false;
493 }
494
495 bool KItemListController::processEvent(QEvent* event, const QTransform& transform)
496 {
497 if (!event) {
498 return false;
499 }
500
501 switch (event->type()) {
502 case QEvent::KeyPress:
503 return keyPressEvent(static_cast<QKeyEvent*>(event));
504 case QEvent::InputMethod:
505 return inputMethodEvent(static_cast<QInputMethodEvent*>(event));
506 case QEvent::GraphicsSceneMousePress:
507 return mousePressEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
508 case QEvent::GraphicsSceneMouseMove:
509 return mouseMoveEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
510 case QEvent::GraphicsSceneMouseRelease:
511 return mouseReleaseEvent(static_cast<QGraphicsSceneMouseEvent*>(event), QTransform());
512 case QEvent::GraphicsSceneWheel:
513 return wheelEvent(static_cast<QGraphicsSceneWheelEvent*>(event), QTransform());
514 case QEvent::GraphicsSceneDragEnter:
515 return dragEnterEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
516 case QEvent::GraphicsSceneDragLeave:
517 return dragLeaveEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
518 case QEvent::GraphicsSceneDragMove:
519 return dragMoveEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
520 case QEvent::GraphicsSceneDrop:
521 return dropEvent(static_cast<QGraphicsSceneDragDropEvent*>(event), QTransform());
522 case QEvent::GraphicsSceneHoverEnter:
523 return hoverEnterEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
524 case QEvent::GraphicsSceneHoverMove:
525 return hoverMoveEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
526 case QEvent::GraphicsSceneHoverLeave:
527 return hoverLeaveEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
528 case QEvent::GraphicsSceneResize:
529 return resizeEvent(static_cast<QGraphicsSceneResizeEvent*>(event), transform);
530 default:
531 break;
532 }
533
534 return false;
535 }
536
537 void KItemListController::slotViewOffsetChanged(qreal current, qreal previous)
538 {
539 if (!m_view) {
540 return;
541 }
542
543 KItemListRubberBand* rubberBand = m_view->rubberBand();
544 if (rubberBand->isActive()) {
545 const qreal diff = current - previous;
546 // TODO: Ideally just QCursor::pos() should be used as
547 // new end-position but it seems there is no easy way
548 // to have something like QWidget::mapFromGlobal() for QGraphicsWidget
549 // (... or I just missed an easy way to do the mapping)
550 QPointF endPos = rubberBand->endPosition();
551 if (m_view->scrollOrientation() == Qt::Vertical) {
552 endPos.ry() += diff;
553 } else {
554 endPos.rx() += diff;
555 }
556
557 rubberBand->setEndPosition(endPos);
558 }
559 }
560
561 void KItemListController::slotRubberBandChanged()
562 {
563 if (!m_view || !m_model || m_model->count() <= 0) {
564 return;
565 }
566
567 const KItemListRubberBand* rubberBand = m_view->rubberBand();
568 const QPointF startPos = rubberBand->startPosition();
569 const QPointF endPos = rubberBand->endPosition();
570 QRectF rubberBandRect = QRectF(startPos, endPos).normalized();
571
572 const bool scrollVertical = (m_view->scrollOrientation() == Qt::Vertical);
573 if (scrollVertical) {
574 rubberBandRect.translate(0, -m_view->offset());
575 } else {
576 rubberBandRect.translate(-m_view->offset(), 0);
577 }
578
579 QSet<int> selectedItems;
580
581 // Select all visible items that intersect with the rubberband
582 foreach (const KItemListWidget* widget, m_view->visibleItemListWidgets()) {
583 const int index = widget->index();
584
585 const QRectF widgetRect = m_view->itemBoundingRect(index);
586 if (widgetRect.intersects(rubberBandRect)) {
587 const QRectF iconRect = widget->iconBoundingRect().translated(widgetRect.topLeft());
588 const QRectF textRect = widget->textBoundingRect().translated(widgetRect.topLeft());
589 if (iconRect.intersects(rubberBandRect) || textRect.intersects(rubberBandRect)) {
590 selectedItems.insert(index);
591 }
592 }
593 }
594
595 // Select all invisible items that intersect with the rubberband. Instead of
596 // iterating all items only the area which might be touched by the rubberband
597 // will be checked.
598 const bool increaseIndex = scrollVertical ?
599 startPos.y() > endPos.y(): startPos.x() > endPos.x();
600
601 int index = increaseIndex ? m_view->lastVisibleIndex() + 1 : m_view->firstVisibleIndex() - 1;
602 bool selectionFinished = false;
603 do {
604 const QRectF widgetRect = m_view->itemBoundingRect(index);
605 if (widgetRect.intersects(rubberBandRect)) {
606 selectedItems.insert(index);
607 }
608
609 if (increaseIndex) {
610 ++index;
611 selectionFinished = (index >= m_model->count()) ||
612 ( scrollVertical && widgetRect.top() > rubberBandRect.bottom()) ||
613 (!scrollVertical && widgetRect.left() > rubberBandRect.right());
614 } else {
615 --index;
616 selectionFinished = (index < 0) ||
617 ( scrollVertical && widgetRect.bottom() < rubberBandRect.top()) ||
618 (!scrollVertical && widgetRect.right() < rubberBandRect.left());
619 }
620 } while (!selectionFinished);
621
622 m_selectionManager->setSelectedItems(selectedItems);
623 }
624
625 #include "kitemlistcontroller.moc"