X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/58ab93541c70de4fa7cbb90dc3423cfec556dc38..5070666ad2cd5fe3e559adca00a52aed5d137153:/src/kitemviews/kitemlistcontroller.cpp diff --git a/src/kitemviews/kitemlistcontroller.cpp b/src/kitemviews/kitemlistcontroller.cpp index afa70e60c..ed23dd1f7 100644 --- a/src/kitemviews/kitemlistcontroller.cpp +++ b/src/kitemviews/kitemlistcontroller.cpp @@ -23,12 +23,17 @@ #include "kitemlistcontroller.h" #include "kitemlistview.h" +#include "kitemlistrubberband_p.h" #include "kitemlistselectionmanager.h" +#include "kitemlistkeyboardsearchmanager_p.h" +#include +#include #include #include -#include +#include +#include #include KItemListController::KItemListController(QObject* parent) : @@ -37,8 +42,13 @@ KItemListController::KItemListController(QObject* parent) : m_model(0), m_view(0), m_selectionManager(new KItemListSelectionManager(this)), - m_pressedIndex(-1) + m_keyboardManager(new KItemListKeyboardSearchManager(this)), + m_pressedIndex(-1), + m_pressedMousePos(), + m_oldSelection() { + connect(m_keyboardManager, SIGNAL(changeCurrentItem(QString,bool)), + this, SLOT(slotChangeCurrentItem(QString,bool))); } KItemListController::~KItemListController() @@ -80,11 +90,16 @@ void KItemListController::setView(KItemListView* view) } KItemListView* oldView = m_view; + if (oldView) { + disconnect(oldView, SIGNAL(offsetChanged(qreal,qreal)), this, SLOT(slotViewOffsetChanged(qreal,qreal))); + } + m_view = view; if (m_view) { m_view->setController(this); m_view->setModel(m_model); + connect(m_view, SIGNAL(offsetChanged(qreal,qreal)), this, SLOT(slotViewOffsetChanged(qreal,qreal))); } emit viewChanged(m_view, oldView); @@ -119,15 +134,128 @@ bool KItemListController::hideEvent(QHideEvent* event) bool KItemListController::keyPressEvent(QKeyEvent* event) { - switch (event->key()) { + const bool shiftPressed = event->modifiers() & Qt::ShiftModifier; + const bool controlPressed = event->modifiers() & Qt::ControlModifier; + const bool shiftOrControlPressed = shiftPressed || controlPressed; + + int index = m_selectionManager->currentItem(); + const int itemCount = m_model->count(); + const int itemsPerRow = m_view->itemsPerOffset(); + + // For horizontal scroll orientation, transform + // the arrow keys to simplify the event handling. + int key = event->key(); + if (m_view->scrollOrientation() == Qt::Horizontal) { + switch (key) { + case Qt::Key_Up: key = Qt::Key_Left; break; + case Qt::Key_Down: key = Qt::Key_Right; break; + case Qt::Key_Left: key = Qt::Key_Up; break; + case Qt::Key_Right: key = Qt::Key_Down; break; + default: break; + } + } + + switch (key) { case Qt::Key_Home: - m_selectionManager->setCurrentItem(0); + index = 0; break; + case Qt::Key_End: - m_selectionManager->setCurrentItem(m_model->count() - 1); + index = itemCount - 1; break; + + case Qt::Key_Left: + if (index > 0) { + index--; + } + break; + + case Qt::Key_Right: + if (index < itemCount - 1) { + index++; + } + break; + + case Qt::Key_Up: + if (index >= itemsPerRow) { + index -= itemsPerRow; + } + break; + + case Qt::Key_Down: + if (index + itemsPerRow < itemCount) { + // We are not in the last row yet. + index += itemsPerRow; + } + else { + // We are either in the last row already, or we are in the second-last row, + // and there is no item below the current item. + // In the latter case, we jump to the very last item. + const int currentColumn = index % itemsPerRow; + const int lastItemColumn = (itemCount - 1) % itemsPerRow; + const bool inLastRow = currentColumn < lastItemColumn; + if (!inLastRow) { + index = itemCount - 1; + } + } + break; + + case Qt::Key_Enter: + case Qt::Key_Return: + emit itemActivated(index); + break; + + case Qt::Key_Space: + if (controlPressed) { + m_selectionManager->endAnchoredSelection(); + m_selectionManager->setSelected(index, 1, KItemListSelectionManager::Toggle); + m_selectionManager->beginAnchoredSelection(index); + break; + } + + default: + m_keyboardManager->addKeys(event->text()); + return false; + } + + if (m_selectionManager->currentItem() != index) { + if (controlPressed) { + m_selectionManager->endAnchoredSelection(); + } + + m_selectionManager->setCurrentItem(index); + + if (!shiftOrControlPressed || m_selectionBehavior == SingleSelection) { + m_selectionManager->clearSelection(); + m_selectionManager->setSelected(index, 1); + } + + if (!shiftPressed) { + m_selectionManager->beginAnchoredSelection(index); + } + } + return true; +} + +void KItemListController::slotChangeCurrentItem(const QString& text, bool searchFromNextItem) +{ + if (!m_model) { + return; + } + const int currentIndex = m_selectionManager->currentItem(); + int index; + if (searchFromNextItem) { + index = m_model->indexForKeyboardSearch(text, (currentIndex + 1) % m_model->count()); + } + else { + index = m_model->indexForKeyboardSearch(text, currentIndex); + } + if (index >= 0) { + m_selectionManager->setCurrentItem(index); + m_selectionManager->clearSelection(); + m_selectionManager->setSelected(index, 1); + m_selectionManager->beginAnchoredSelection(index); } - return false; } bool KItemListController::inputMethodEvent(QInputMethodEvent* event) @@ -138,18 +266,22 @@ bool KItemListController::inputMethodEvent(QInputMethodEvent* event) bool KItemListController::mousePressEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform) { - const QPointF pos = transform.map(event->pos()); - m_pressedIndex = m_view->itemAt(pos); + if (!m_view) { + return false; + } + + m_pressedMousePos = transform.map(event->pos()); + m_pressedIndex = m_view->itemAt(m_pressedMousePos); - if (m_view->isAboveExpansionToggle(m_pressedIndex, pos)) { + if (m_view->isAboveExpansionToggle(m_pressedIndex, m_pressedMousePos)) { + m_selectionManager->setCurrentItem(m_pressedIndex); return true; } const bool shiftPressed = event->modifiers() & Qt::ShiftModifier; const bool controlPressed = event->modifiers() & Qt::ControlModifier; - const bool shiftOrControlPressed = shiftPressed || controlPressed; - if (!shiftOrControlPressed || m_selectionBehavior == SingleSelection) { + if (m_selectionBehavior == SingleSelection) { m_selectionManager->clearSelection(); } @@ -163,29 +295,58 @@ bool KItemListController::mousePressEvent(QGraphicsSceneMouseEvent* event, const switch (m_selectionBehavior) { case NoSelection: - return true; + break; + case SingleSelection: m_selectionManager->setSelected(m_pressedIndex); - return true; + break; + case MultiSelection: if (controlPressed) { m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle); m_selectionManager->beginAnchoredSelection(m_pressedIndex); - } - else { - if (shiftPressed && m_selectionManager->isAnchoredSelectionActive()) { - // The anchored selection is continued automatically by calling - // m_selectionManager->setCurrentItem(m_pressedIndex), see above -> nothing more to do here - return true; + } else if (event->buttons() & Qt::RightButton) { + // Only clear the selection if a context menu is requested above a non-selected item + if (!m_selectionManager->selectedItems().contains(m_pressedIndex)) { + m_selectionManager->setSelectedItems(QSet() << m_pressedIndex); } - + } else if (!shiftPressed || !m_selectionManager->isAnchoredSelectionActive()) { // Select the pressed item and start a new anchored selection m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select); m_selectionManager->beginAnchoredSelection(m_pressedIndex); } + break; + + default: + Q_ASSERT(false); + break; } return true; + } else { + if (event->buttons() & Qt::RightButton) { + m_selectionManager->clearSelection(); + } + + KItemListRubberBand* rubberBand = m_view->rubberBand(); + QPointF startPos = m_pressedMousePos; + if (m_view->scrollOrientation() == Qt::Vertical) { + startPos.ry() += m_view->offset(); + if (m_view->itemSize().width() < 0) { + // Use a special rubberband for views that have only one column and + // expand the rubberband to use the whole width of the view. + startPos.setX(0); + } + } else { + startPos.rx() += m_view->offset(); + } + + m_oldSelection = m_selectionManager->selectedItems(); + rubberBand->setStartPosition(startPos); + rubberBand->setEndPosition(startPos); + rubberBand->setActive(true); + connect(rubberBand, SIGNAL(endPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandChanged())); + m_view->setAutoScroll(true); } return false; @@ -193,48 +354,115 @@ bool KItemListController::mousePressEvent(QGraphicsSceneMouseEvent* event, const bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform) { - Q_UNUSED(event); - Q_UNUSED(transform); + if (!m_view) { + return false; + } + + if (m_pressedIndex >= 0) { + // Check whether a dragging should be started + if (event->buttons() & Qt::LeftButton) { + const QPointF pos = transform.map(event->pos()); + const qreal minDragDiff = 4; + const bool hasMinDragDiff = qAbs(pos.x() - m_pressedMousePos.x()) >= minDragDiff || + qAbs(pos.y() - m_pressedMousePos.y()) >= minDragDiff; + if (hasMinDragDiff) { + startDragging(); + } + } + } else { + KItemListRubberBand* rubberBand = m_view->rubberBand(); + if (rubberBand->isActive()) { + QPointF endPos = transform.map(event->pos()); + if (m_view->scrollOrientation() == Qt::Vertical) { + endPos.ry() += m_view->offset(); + if (m_view->itemSize().width() < 0) { + // Use a special rubberband for views that have only one column and + // expand the rubberband to use the whole width of the view. + endPos.setX(m_view->size().width()); + } + } else { + endPos.rx() += m_view->offset(); + } + rubberBand->setEndPosition(endPos); + } + } + return false; } bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform) { - if (m_view) { - const QPointF pos = transform.map(event->pos()); - const int index = m_view->itemAt(pos); - const bool shiftOrControlPressed = event->modifiers() & Qt::ShiftModifier || event->modifiers() & Qt::ControlModifier; - - if (index >= 0 && index == m_pressedIndex) { - // The release event is done above the same item as the press event - bool emitItemClicked = true; - if (event->button() & Qt::LeftButton) { - if (m_view->isAboveExpansionToggle(index, pos)) { - emit itemExpansionToggleClicked(index); - emitItemClicked = false; - } - else if (shiftOrControlPressed) { - // The mouse click should only update the selection, not trigger the item - emitItemClicked = false; - } - } + if (!m_view) { + return false; + } + + const bool shiftOrControlPressed = event->modifiers() & Qt::ShiftModifier || + event->modifiers() & Qt::ControlModifier; + + bool clearSelection = !shiftOrControlPressed && event->button() != Qt::RightButton; - if (emitItemClicked) { - emit itemClicked(index, event->button()); + KItemListRubberBand* rubberBand = m_view->rubberBand(); + if (rubberBand->isActive()) { + disconnect(rubberBand, SIGNAL(endPositionChanged(QPointF,QPointF)), this, SLOT(slotRubberBandChanged())); + rubberBand->setActive(false); + m_oldSelection.clear(); + m_view->setAutoScroll(false); + + if (rubberBand->startPosition() != rubberBand->endPosition()) { + clearSelection = false; + } + } + + const QPointF pos = transform.map(event->pos()); + const int index = m_view->itemAt(pos); + + if (index >= 0 && index == m_pressedIndex) { + // The release event is done above the same item as the press event + + if (clearSelection) { + // Clear the previous selection but reselect the current index + m_selectionManager->setSelectedItems(QSet() << index); + } + + if (event->button() & Qt::LeftButton) { + bool emitItemActivated = true; + if (m_view->isAboveExpansionToggle(index, pos)) { + emit itemExpansionToggleClicked(index); + emitItemActivated = false; + } else if (shiftOrControlPressed) { + // The mouse click should only update the selection, not trigger the item + emitItemActivated = false; + } else if (!KGlobalSettings::singleClick()) { + emitItemActivated = false; } - } else if (!shiftOrControlPressed) { - m_selectionManager->clearSelection(); + if (emitItemActivated) { + emit itemActivated(index); + } + } else if (event->button() & Qt::MidButton) { + emit itemMiddleClicked(index); + } else if (event->button() & Qt::RightButton) { + emit contextMenuRequested(index, QPointF(event->pos())); } + } else if (clearSelection) { + m_selectionManager->clearSelection(); } + m_pressedMousePos = QPointF(); m_pressedIndex = -1; return false; } bool KItemListController::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform) { - Q_UNUSED(event); - Q_UNUSED(transform); + const QPointF pos = transform.map(event->pos()); + const int index = m_view->itemAt(pos); + + bool emitItemActivated = !KGlobalSettings::singleClick() && + (event->button() & Qt::LeftButton) && + index >= 0 && index < m_model->count(); + if (emitItemActivated) { + emit itemActivated(index); + } return false; } @@ -254,16 +482,43 @@ bool KItemListController::dragLeaveEvent(QGraphicsSceneDragDropEvent* event, con bool KItemListController::dragMoveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform) { - Q_UNUSED(event); Q_UNUSED(transform); + if (!m_model || !m_view) { + return false; + } + + KItemListWidget* oldHoveredWidget = hoveredWidget(); + KItemListWidget* newHoveredWidget = widgetForPos(event->pos()); + if (oldHoveredWidget != newHoveredWidget) { + if (oldHoveredWidget) { + oldHoveredWidget->setHovered(false); + emit itemUnhovered(oldHoveredWidget->index()); + } + + if (newHoveredWidget) { + const int index = newHoveredWidget->index(); + if (m_model->supportsDropping(index)) { + newHoveredWidget->setHovered(true); + } + emit itemHovered(index); + } + } + return false; } bool KItemListController::dropEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform) { - Q_UNUSED(event); - Q_UNUSED(transform); - return false; + Q_UNUSED(transform) + if (!m_view) { + return false; + } + + const QPointF pos = transform.map(event->pos()); + const int index = m_view->itemAt(pos); + emit itemDropEvent(index, event); + + return true; } bool KItemListController::hoverEnterEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform) @@ -275,46 +530,22 @@ bool KItemListController::hoverEnterEvent(QGraphicsSceneHoverEvent* event, const bool KItemListController::hoverMoveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform) { - // The implementation assumes that only one item can get hovered no matter - // whether they overlap or not. - Q_UNUSED(transform); if (!m_model || !m_view) { return false; } - // Search the previously hovered item that might get unhovered - KItemListWidget* unhoveredWidget = 0; - foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) { - if (widget->isHovered()) { - unhoveredWidget = widget; - break; - } - } - - // Search the currently hovered item - KItemListWidget* hoveredWidget = 0; - foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) { - const QPointF mappedPos = widget->mapFromItem(m_view, event->pos()); - - const bool hovered = widget->contains(mappedPos) && - !widget->expansionToggleRect().contains(mappedPos) && - !widget->selectionToggleRect().contains(mappedPos); - if (hovered) { - hoveredWidget = widget; - break; - } - } - - if (unhoveredWidget != hoveredWidget) { - if (unhoveredWidget) { - unhoveredWidget->setHovered(false); - emit itemUnhovered(unhoveredWidget->index()); + KItemListWidget* oldHoveredWidget = hoveredWidget(); + KItemListWidget* newHoveredWidget = widgetForPos(event->pos()); + if (oldHoveredWidget != newHoveredWidget) { + if (oldHoveredWidget) { + oldHoveredWidget->setHovered(false); + emit itemUnhovered(oldHoveredWidget->index()); } - if (hoveredWidget) { - hoveredWidget->setHovered(true); - emit itemHovered(hoveredWidget->index()); + if (newHoveredWidget) { + newHoveredWidget->setHovered(true); + emit itemHovered(newHoveredWidget->index()); } } @@ -370,6 +601,8 @@ bool KItemListController::processEvent(QEvent* event, const QTransform& transfor return mouseMoveEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneMouseRelease: return mouseReleaseEvent(static_cast(event), QTransform()); + case QEvent::GraphicsSceneMouseDoubleClick: + return mouseDoubleClickEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneWheel: return wheelEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneDragEnter: @@ -395,4 +628,165 @@ bool KItemListController::processEvent(QEvent* event, const QTransform& transfor return false; } +void KItemListController::slotViewOffsetChanged(qreal current, qreal previous) +{ + if (!m_view) { + return; + } + + KItemListRubberBand* rubberBand = m_view->rubberBand(); + if (rubberBand->isActive()) { + const qreal diff = current - previous; + // TODO: Ideally just QCursor::pos() should be used as + // new end-position but it seems there is no easy way + // to have something like QWidget::mapFromGlobal() for QGraphicsWidget + // (... or I just missed an easy way to do the mapping) + QPointF endPos = rubberBand->endPosition(); + if (m_view->scrollOrientation() == Qt::Vertical) { + endPos.ry() += diff; + } else { + endPos.rx() += diff; + } + + rubberBand->setEndPosition(endPos); + } +} + +void KItemListController::slotRubberBandChanged() +{ + if (!m_view || !m_model || m_model->count() <= 0) { + return; + } + + const KItemListRubberBand* rubberBand = m_view->rubberBand(); + const QPointF startPos = rubberBand->startPosition(); + const QPointF endPos = rubberBand->endPosition(); + QRectF rubberBandRect = QRectF(startPos, endPos).normalized(); + + const bool scrollVertical = (m_view->scrollOrientation() == Qt::Vertical); + if (scrollVertical) { + rubberBandRect.translate(0, -m_view->offset()); + } else { + rubberBandRect.translate(-m_view->offset(), 0); + } + + if (!m_oldSelection.isEmpty()) { + // Clear the old selection that was available before the rubberband has + // been activated in case if no Shift- or Control-key are pressed + const bool shiftOrControlPressed = QApplication::keyboardModifiers() & Qt::ShiftModifier || + QApplication::keyboardModifiers() & Qt::ControlModifier; + if (!shiftOrControlPressed) { + m_oldSelection.clear(); + } + } + + QSet selectedItems; + + // Select all visible items that intersect with the rubberband + foreach (const KItemListWidget* widget, m_view->visibleItemListWidgets()) { + const int index = widget->index(); + + const QRectF widgetRect = m_view->itemBoundingRect(index); + if (widgetRect.intersects(rubberBandRect)) { + const QRectF iconRect = widget->iconBoundingRect().translated(widgetRect.topLeft()); + const QRectF textRect = widget->textBoundingRect().translated(widgetRect.topLeft()); + if (iconRect.intersects(rubberBandRect) || textRect.intersects(rubberBandRect)) { + selectedItems.insert(index); + } + } + } + + // Select all invisible items that intersect with the rubberband. Instead of + // iterating all items only the area which might be touched by the rubberband + // will be checked. + const bool increaseIndex = scrollVertical ? + startPos.y() > endPos.y(): startPos.x() > endPos.x(); + + int index = increaseIndex ? m_view->lastVisibleIndex() + 1 : m_view->firstVisibleIndex() - 1; + bool selectionFinished = false; + do { + const QRectF widgetRect = m_view->itemBoundingRect(index); + if (widgetRect.intersects(rubberBandRect)) { + selectedItems.insert(index); + } + + if (increaseIndex) { + ++index; + selectionFinished = (index >= m_model->count()) || + ( scrollVertical && widgetRect.top() > rubberBandRect.bottom()) || + (!scrollVertical && widgetRect.left() > rubberBandRect.right()); + } else { + --index; + selectionFinished = (index < 0) || + ( scrollVertical && widgetRect.bottom() < rubberBandRect.top()) || + (!scrollVertical && widgetRect.right() < rubberBandRect.left()); + } + } while (!selectionFinished); + + if (QApplication::keyboardModifiers() & Qt::ControlModifier) { + // If Control is pressed, the selection state of all items in the rubberband is toggled. + // Therefore, the new selection contains: + // 1. All previously selected items which are not inside the rubberband, and + // 2. all items inside the rubberband which have not been selected previously. + m_selectionManager->setSelectedItems((m_oldSelection - selectedItems) + (selectedItems - m_oldSelection)); + } + else { + m_selectionManager->setSelectedItems(selectedItems + m_oldSelection); + } +} + +void KItemListController::startDragging() +{ + if (!m_view || !m_model) { + return; + } + + const QSet selectedItems = m_selectionManager->selectedItems(); + QMimeData* data = m_model->createMimeData(selectedItems); + if (!data) { + return; + } + + // The created drag object will be owned and deleted + // by QApplication::activeWindow(). + QDrag* drag = new QDrag(QApplication::activeWindow()); + drag->setMimeData(data); + + const QPixmap pixmap = m_view->createDragPixmap(selectedItems); + drag->setPixmap(pixmap); + + drag->exec(Qt::MoveAction | Qt::CopyAction | Qt::LinkAction, Qt::IgnoreAction); +} + +KItemListWidget* KItemListController::hoveredWidget() const +{ + Q_ASSERT(m_view); + + foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) { + if (widget->isHovered()) { + return widget; + } + } + + return 0; +} + +KItemListWidget* KItemListController::widgetForPos(const QPointF& pos) const +{ + Q_ASSERT(m_view); + + foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) { + const QPointF mappedPos = widget->mapFromItem(m_view, pos); + + const bool hovered = widget->contains(mappedPos) && + !widget->expansionToggleRect().contains(mappedPos) && + !widget->selectionToggleRect().contains(mappedPos); + if (hovered) { + return widget; + } + } + + return 0; +} + #include "kitemlistcontroller.moc"