Index: src/CMakeLists.txt =================================================================== --- src/CMakeLists.txt +++ src/CMakeLists.txt @@ -90,6 +90,8 @@ kitemviews/private/kitemlistviewanimation.cpp kitemviews/private/kitemlistviewlayouter.cpp kitemviews/private/kpixmapmodifier.cpp + kitemviews/private/ktwofingerswipe.cpp + kitemviews/private/ktwofingertap.cpp settings/applyviewpropsjob.cpp settings/viewmodes/viewmodesettings.cpp settings/viewpropertiesdialog.cpp Index: src/dolphinmainwindow.cpp =================================================================== --- src/dolphinmainwindow.cpp +++ src/dolphinmainwindow.cpp @@ -2128,6 +2128,8 @@ this, &DolphinMainWindow::goForward); connect(view, &DolphinView::urlActivated, this, &DolphinMainWindow::handleUrl); + connect(view, &DolphinView::goUpRequested, + this, &DolphinMainWindow::goUp); const KUrlNavigator* navigator = container->urlNavigator(); connect(navigator, &KUrlNavigator::urlChanged, Index: src/kitemviews/kitemlistcontainer.h =================================================================== --- src/kitemviews/kitemlistcontainer.h +++ src/kitemviews/kitemlistcontainer.h @@ -30,6 +30,7 @@ class KItemListSmoothScroller; class KItemListView; class KItemModelBase; +class QScroller; /** * @brief Provides a QWidget based scrolling view for a KItemListController. @@ -70,6 +71,7 @@ void scrollTo(qreal offset); void updateScrollOffsetScrollBar(); void updateItemOffsetScrollBar(); + void stopQScroller(); private: void updateGeometries(); @@ -87,6 +89,7 @@ KItemListSmoothScroller* m_horizontalSmoothScroller; KItemListSmoothScroller* m_verticalSmoothScroller; + QScroller* m_qScroller; }; #endif Index: src/kitemviews/kitemlistcontainer.cpp =================================================================== --- src/kitemviews/kitemlistcontainer.cpp +++ src/kitemviews/kitemlistcontainer.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include /** @@ -67,7 +68,8 @@ QAbstractScrollArea(parent), m_controller(controller), m_horizontalSmoothScroller(nullptr), - m_verticalSmoothScroller(nullptr) + m_verticalSmoothScroller(nullptr), + m_qScroller(nullptr) { Q_ASSERT(controller); controller->setParent(this); @@ -89,6 +91,13 @@ this, &KItemListContainer::slotModelChanged); connect(controller, &KItemListController::viewChanged, this, &KItemListContainer::slotViewChanged); + + m_qScroller = QScroller::scroller(viewport()); + m_qScroller->grabGesture(viewport()); + connect(controller, &KItemListController::qScrollerStop, + this, &KItemListContainer::stopQScroller); + connect(m_qScroller, &QScroller::stateChanged, + controller, &KItemListController::slotStateChanged); } KItemListContainer::~KItemListContainer() @@ -338,6 +347,11 @@ } } +void KItemListContainer::stopQScroller() +{ + m_qScroller->stop(); +} + void KItemListContainer::updateGeometries() { QRect rect = geometry(); Index: src/kitemviews/kitemlistcontroller.h =================================================================== --- src/kitemviews/kitemlistcontroller.h +++ src/kitemviews/kitemlistcontroller.h @@ -27,6 +27,7 @@ #include #include +#include class QTimer; class KItemModelBase; @@ -34,6 +35,7 @@ class KItemListSelectionManager; class KItemListView; class KItemListWidget; +class QGestureEvent; class QGraphicsSceneHoverEvent; class QGraphicsSceneDragDropEvent; class QGraphicsSceneMouseEvent; @@ -41,6 +43,7 @@ class QGraphicsSceneWheelEvent; class QInputMethodEvent; class QKeyEvent; +class QTapGesture; class QTransform; /** @@ -221,6 +224,14 @@ void selectedItemTextPressed(int index); + void qScrollerStop(); + void increaseZoom(); + void decreaseZoom(); + void swipeUp(); + +public slots: + void slotStateChanged(QScroller::State newState); + private slots: void slotViewScrollOffsetChanged(qreal current, qreal previous); @@ -302,11 +313,24 @@ bool hoverLeaveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform); bool wheelEvent(QGraphicsSceneWheelEvent* event, const QTransform& transform); bool resizeEvent(QGraphicsSceneResizeEvent* event, const QTransform& transform); + bool gestureEvent(QGestureEvent* event, const QTransform& transform); + void tapTriggered(QTapGesture* tap, const QTransform& transform); + void tapAndHoldTriggered(QGestureEvent* event, const QTransform& transform); + void pinchTriggered(QGestureEvent* event, const QTransform& transform); + void swipeTriggered(QGestureEvent* event, const QTransform& transform); + void twoFingerTapTriggered(QGestureEvent* event, const QTransform& transform); + bool onPress(const QPoint& screenPos, const QPointF& pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons); + bool onRelease(const QPointF& pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons, bool touch); + void startRubberBand(); private: bool m_singleClickActivationEnforced; bool m_selectionTogglePressed; bool m_clearSelectionIfItemsAreNotDragged; + bool m_isSwipeGesture; + bool m_dragActionOrRightClick; + bool m_qScrollerIsScrolling; + bool m_pinchGestureInProgress; SelectionBehavior m_selectionBehavior; AutoActivationBehavior m_autoActivationBehavior; MouseDoubleClickAction m_mouseDoubleClickAction; @@ -319,6 +343,9 @@ QTimer* m_autoActivationTimer; + Qt::GestureType m_swipeGesture; + Qt::GestureType m_twoFingerTapGesture; + /** * When starting a rubberband selection during a Shift- or Control-key has been * pressed the current selection should never be deleted. To be able to restore Index: src/kitemviews/kitemlistcontroller.cpp =================================================================== --- src/kitemviews/kitemlistcontroller.cpp +++ src/kitemviews/kitemlistcontroller.cpp @@ -26,11 +26,14 @@ #include "kitemlistview.h" #include "private/kitemlistkeyboardsearchmanager.h" #include "private/kitemlistrubberband.h" +#include "private/ktwofingerswipe.h" +#include "private/ktwofingertap.h" #include "views/draganddrophelper.h" #include #include #include +#include #include #include #include @@ -42,6 +45,10 @@ m_singleClickActivationEnforced(false), m_selectionTogglePressed(false), m_clearSelectionIfItemsAreNotDragged(false), + m_isSwipeGesture(false), + m_dragActionOrRightClick(false), + m_qScrollerIsScrolling(false), + m_pinchGestureInProgress(false), m_selectionBehavior(NoSelection), m_autoActivationBehavior(ActivationAndExpansion), m_mouseDoubleClickAction(ActivateItemOnly), @@ -52,6 +59,8 @@ m_pressedIndex(-1), m_pressedMousePos(), m_autoActivationTimer(nullptr), + m_swipeGesture(Qt::CustomGesture), + m_twoFingerTapGesture(Qt::CustomGesture), m_oldSelection(), m_keyboardAnchorIndex(-1), m_keyboardAnchorPos(0) @@ -70,6 +79,14 @@ setModel(model); setView(view); + + m_swipeGesture = QGestureRecognizer::registerRecognizer(new KTwoFingerSwipeRecognizer()); + m_twoFingerTapGesture = QGestureRecognizer::registerRecognizer(new KTwoFingerTapRecognizer()); + view->grabGesture(m_swipeGesture); + view->grabGesture(m_twoFingerTapGesture); + view->grabGesture(Qt::TapGesture); + view->grabGesture(Qt::TapAndHoldGesture); + view->grabGesture(Qt::PinchGesture); } KItemListController::~KItemListController() @@ -530,13 +547,16 @@ bool KItemListController::mousePressEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform) { + if (event->source() == Qt::MouseEventSynthesizedByQt) { + return false; + } + if (!m_view) { return false; } m_pressedMousePos = transform.map(event->pos()); m_pressedIndex = m_view->itemAt(m_pressedMousePos); - emit mouseButtonPressed(m_pressedIndex, event->buttons()); if (event->buttons() & (Qt::BackButton | Qt::ForwardButton)) { // Do not select items when clicking the back/forward buttons, see @@ -544,135 +564,12 @@ return true; } - if (m_view->isAboveExpansionToggle(m_pressedIndex, m_pressedMousePos)) { - m_selectionManager->endAnchoredSelection(); - m_selectionManager->setCurrentItem(m_pressedIndex); - m_selectionManager->beginAnchoredSelection(m_pressedIndex); - return true; - } - - m_selectionTogglePressed = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos); - if (m_selectionTogglePressed) { - m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle); - // The previous anchored selection has been finished already in - // KItemListSelectionManager::setSelected(). We can safely change - // the current item and start a new anchored selection now. - m_selectionManager->setCurrentItem(m_pressedIndex); - m_selectionManager->beginAnchoredSelection(m_pressedIndex); - return true; - } - - const bool shiftPressed = event->modifiers() & Qt::ShiftModifier; - const bool controlPressed = event->modifiers() & Qt::ControlModifier; - - // The previous selection is cleared if either - // 1. The selection mode is SingleSelection, or - // 2. the selection mode is MultiSelection, and *none* of the following conditions are met: - // a) Shift or Control are pressed. - // b) The clicked item is selected already. In that case, the user might want to: - // - start dragging multiple items, or - // - open the context menu and perform an action for all selected items. - const bool shiftOrControlPressed = shiftPressed || controlPressed; - const bool pressedItemAlreadySelected = m_pressedIndex >= 0 && m_selectionManager->isSelected(m_pressedIndex); - const bool clearSelection = m_selectionBehavior == SingleSelection || - (!shiftOrControlPressed && !pressedItemAlreadySelected); - if (clearSelection) { - m_selectionManager->clearSelection(); - } else if (pressedItemAlreadySelected && !shiftOrControlPressed && (event->buttons() & Qt::LeftButton)) { - // The user might want to start dragging multiple items, but if he clicks the item - // in order to trigger it instead, the other selected items must be deselected. - // However, we do not know yet what the user is going to do. - // -> remember that the user pressed an item which had been selected already and - // clear the selection in mouseReleaseEvent(), unless the items are dragged. - m_clearSelectionIfItemsAreNotDragged = true; - - if (m_selectionManager->selectedItems().count() == 1 && m_view->isAboveText(m_pressedIndex, m_pressedMousePos)) { - emit selectedItemTextPressed(m_pressedIndex); - } - } - - if (!shiftPressed) { - // Finish the anchored selection before the current index is changed - m_selectionManager->endAnchoredSelection(); - } - - if (event->buttons() & Qt::RightButton) { - // Stop rubber band from persisting after right-clicks - KItemListRubberBand* rubberBand = m_view->rubberBand(); - if (rubberBand->isActive()) { - disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged); - rubberBand->setActive(false); - m_view->setAutoScroll(false); - } - } - - if (m_pressedIndex >= 0) { - m_selectionManager->setCurrentItem(m_pressedIndex); - - switch (m_selectionBehavior) { - case NoSelection: - break; - - case SingleSelection: - m_selectionManager->setSelected(m_pressedIndex); - break; - - case MultiSelection: - if (controlPressed && !shiftPressed) { - m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle); - m_selectionManager->beginAnchoredSelection(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; - } - - if (event->buttons() & Qt::RightButton) { - emit itemContextMenuRequested(m_pressedIndex, event->screenPos()); - } - - return true; - } - - if (event->buttons() & Qt::RightButton) { - const QRectF headerBounds = m_view->headerBoundaries(); - if (headerBounds.contains(event->pos())) { - emit headerContextMenuRequested(event->screenPos()); - } else { - emit viewContextMenuRequested(event->screenPos()); - } - return true; - } - - if (m_selectionBehavior == MultiSelection) { - QPointF startPos = m_pressedMousePos; - if (m_view->scrollOrientation() == Qt::Vertical) { - startPos.ry() += m_view->scrollOffset(); - 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->scrollOffset(); - } - - m_oldSelection = m_selectionManager->selectedItems(); - KItemListRubberBand* rubberBand = m_view->rubberBand(); - rubberBand->setStartPosition(startPos); - rubberBand->setEndPosition(startPos); - rubberBand->setActive(true); - connect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged); - m_view->setAutoScroll(true); + const Qt::MouseButtons buttons = event->buttons(); + if (!onPress(event->screenPos(), event->pos(), event->modifiers(), buttons)) { + startRubberBand(); + return false; } - - return false; + return true; } bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform) @@ -681,6 +578,14 @@ return false; } + if (m_view->m_tapAndHoldIndicator->isActive()) { + m_view->m_tapAndHoldIndicator->setActive(false); + } + + if (event->source() == Qt::MouseEventSynthesizedByQt && !m_dragActionOrRightClick) { + return false; + } + if (m_pressedIndex >= 0) { // Check whether a dragging should be started if (event->buttons() & Qt::LeftButton) { @@ -696,7 +601,6 @@ // -> the selection should not be cleared when the mouse button is released. m_clearSelectionIfItemsAreNotDragged = false; } - startDragging(); } } @@ -737,71 +641,18 @@ return false; } - emit mouseButtonReleased(m_pressedIndex, event->buttons()); - - const bool isAboveSelectionToggle = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos); - if (isAboveSelectionToggle) { - m_selectionTogglePressed = false; - return true; + if (m_view->m_tapAndHoldIndicator->isActive()) { + m_view->m_tapAndHoldIndicator->setActive(false); } - if (!isAboveSelectionToggle && m_selectionTogglePressed) { - m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle); - m_selectionTogglePressed = false; - return true; - } - - const bool shiftOrControlPressed = event->modifiers() & Qt::ShiftModifier || - event->modifiers() & Qt::ControlModifier; - KItemListRubberBand* rubberBand = m_view->rubberBand(); - if (rubberBand->isActive()) { - disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged); - rubberBand->setActive(false); - m_oldSelection.clear(); - m_view->setAutoScroll(false); + if (event->source() == Qt::MouseEventSynthesizedByQt && !rubberBand->isActive()) { + return 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 (m_clearSelectionIfItemsAreNotDragged) { - // A selected item has been clicked, but no drag operation has been started - // -> clear the rest of the selection. - m_selectionManager->clearSelection(); - m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select); - m_selectionManager->beginAnchoredSelection(m_pressedIndex); - } - - if (event->button() & Qt::LeftButton) { - bool emitItemActivated = true; - if (m_view->isAboveExpansionToggle(index, pos)) { - const bool expanded = m_model->isExpanded(index); - m_model->setExpanded(index, !expanded); - - emit itemExpansionToggleClicked(index); - emitItemActivated = false; - } else if (shiftOrControlPressed) { - // The mouse click should only update the selection, not trigger the item - emitItemActivated = false; - } else if (!(m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced)) { - emitItemActivated = false; - } - if (emitItemActivated) { - emit itemActivated(index); - } - } else if (event->button() & Qt::MidButton) { - emit itemMiddleClicked(index); - } - } + emit mouseButtonReleased(m_pressedIndex, event->buttons()); - m_pressedMousePos = QPointF(); - m_pressedIndex = -1; - m_clearSelectionIfItemsAreNotDragged = false; - return false; + return onRelease(transform.map(event->pos()), event->modifiers(), event->button(), false); } bool KItemListController::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform) @@ -1041,6 +892,188 @@ return false; } +bool KItemListController::gestureEvent(QGestureEvent* event, const QTransform& transform) +{ + if (!m_view) { + return false; + } + + //you can touch on different views at the same time, Qt sets the isUnderMouse flag to + //the view whit contains the first touch point. We don't want process gesture events from + //other views + if (!m_view->isUnderMouse()) { + return false; + } + bool ret = false; + + if (QGesture* tap = event->gesture(Qt::TapGesture)) { + tapTriggered(static_cast(tap), transform); + ret = true; + } + if (event->gesture(Qt::TapAndHoldGesture)) { + tapAndHoldTriggered(event, transform); + ret = true; + } + if (event->gesture(Qt::PinchGesture)) { + pinchTriggered(event, transform); + ret = true; + } + if (event->gesture(m_swipeGesture)) { + swipeTriggered(event, transform); + ret = true; + } + if (event->gesture(m_twoFingerTapGesture)) { + twoFingerTapTriggered(event, transform); + ret = true; + } + return ret; +} + +void KItemListController::tapTriggered(QTapGesture* tap, const QTransform& transform) +{ + static bool qScrollerWasActive = false; + + if (tap->state() == Qt::GestureStarted) { + m_dragActionOrRightClick = false; + m_isSwipeGesture = false; + m_pinchGestureInProgress = false; + qScrollerWasActive = m_qScrollerIsScrolling; + } + if (tap->state() == Qt::GestureFinished) { + //if at the moment of the gesture start the QScroller was active, the user was make the tap + //to stop the QScroller and not to tap on an item + if (qScrollerWasActive) { + return; + } + + if (m_view->m_tapAndHoldIndicator->isActive()) { + m_view->m_tapAndHoldIndicator->setActive(false); + } + + m_pressedMousePos = transform.map(tap->position()); + m_pressedIndex = m_view->itemAt(m_pressedMousePos); + if (m_dragActionOrRightClick) { + onPress(tap->hotSpot().toPoint(), tap->position().toPoint(), Qt::NoModifier, Qt::RightButton); + onRelease(transform.map(tap->position()), Qt::NoModifier, Qt::RightButton, false); + m_dragActionOrRightClick = false; + } + else { + onPress(tap->hotSpot().toPoint(), tap->position().toPoint(), Qt::NoModifier, Qt::LeftButton); + onRelease(transform.map(tap->position()), Qt::NoModifier, Qt::LeftButton, true); + } + } +} + +void KItemListController::tapAndHoldTriggered(QGestureEvent* event, const QTransform& transform) +{ + if (!m_view->isUnderMouse()) { + return; + } + + const QTapAndHoldGesture* tap = static_cast(event->gesture(Qt::TapAndHoldGesture)); + if (tap->state() == Qt::GestureFinished) { + //if a pinch gesture is in progress we don't want a TabAndHold gesture + if (m_pinchGestureInProgress) { + return; + } + m_pressedMousePos = transform.map(event->mapToGraphicsScene(tap->position())); + m_pressedIndex = m_view->itemAt(m_pressedMousePos); + + if (m_pressedIndex >= 0 && !m_selectionManager->isSelected(m_pressedIndex)) { + m_selectionManager->clearSelection(); + m_selectionManager->setSelected(m_pressedIndex); + } else if (m_pressedIndex == -1) { + m_selectionManager->clearSelection(); + startRubberBand(); + } + + emit qScrollerStop(); + + m_view->m_tapAndHoldIndicator->setStartPosition(m_pressedMousePos); + m_view->m_tapAndHoldIndicator->setActive(true); + + m_dragActionOrRightClick = true; + } +} + +void KItemListController::pinchTriggered(QGestureEvent* event, const QTransform& transform) +{ + Q_UNUSED(transform) + + const QPinchGesture* pinch = static_cast(event->gesture(Qt::PinchGesture)); + const qreal sensitivityModifier = 0.2; + static qreal counter; + + if (pinch->state() == Qt::GestureStarted) { + m_pinchGestureInProgress = true; + counter = 0; + } + if (pinch->state() == Qt::GestureUpdated) { + //if a swipe gesture was recognized or in progress, we don't want a pinch gesture to change the zoom + if (m_isSwipeGesture) { + return; + } + counter = counter + (pinch->scaleFactor() - 1); + if (counter >= sensitivityModifier) { + emit increaseZoom(); + counter = 0; + } else if (counter <= -sensitivityModifier) { + emit decreaseZoom(); + counter = 0; + } + } +} + +void KItemListController::swipeTriggered(QGestureEvent* event, const QTransform& transform) +{ + Q_UNUSED(transform) + + const KTwoFingerSwipe* swipe = static_cast(event->gesture(m_swipeGesture)); + + if (!swipe) { + return; + } + if (swipe->state() == Qt::GestureStarted) { + m_isSwipeGesture = true; + } + + if (swipe->state() == Qt::GestureCanceled) { + m_isSwipeGesture = false; + } + + if (swipe->state() == Qt::GestureFinished) { + emit qScrollerStop(); + + if (swipe->swipeAngle() <= 20 || swipe->swipeAngle() >= 340) { + emit mouseButtonPressed(m_pressedIndex, Qt::BackButton); + } else if (swipe->swipeAngle() <= 200 && swipe->swipeAngle() >= 160) { + emit mouseButtonPressed(m_pressedIndex, Qt::ForwardButton); + } else if (swipe->swipeAngle() <= 110 && swipe->swipeAngle() >= 60) { + emit swipeUp(); + } + m_isSwipeGesture = true; + } +} + +void KItemListController::twoFingerTapTriggered(QGestureEvent* event, const QTransform& transform) +{ + const KTwoFingerTap* twoTap = static_cast(event->gesture(m_twoFingerTapGesture)); + + if (!twoTap) { + return; + } + + if (twoTap->state() == Qt::GestureStarted) { + m_pressedMousePos = transform.map(twoTap->pos()); + m_pressedIndex = m_view->itemAt(m_pressedMousePos); + if (m_pressedIndex >= 0) { + onPress(twoTap->screenPos().toPoint(), twoTap->pos().toPoint(), Qt::ControlModifier, Qt::LeftButton); + onRelease(transform.map(twoTap->pos()), Qt::ControlModifier, Qt::LeftButton, false); + } + + } +} + bool KItemListController::processEvent(QEvent* event, const QTransform& transform) { if (!event) { @@ -1078,6 +1111,8 @@ return hoverLeaveEvent(static_cast(event), QTransform()); case QEvent::GraphicsSceneResize: return resizeEvent(static_cast(event), transform); + case QEvent::Gesture: + return gestureEvent(static_cast(event), transform); default: break; } @@ -1358,3 +1393,219 @@ } } +bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons) +{ + emit mouseButtonPressed(m_pressedIndex, buttons); + + if (m_view->isAboveExpansionToggle(m_pressedIndex, m_pressedMousePos)) { + m_selectionManager->endAnchoredSelection(); + m_selectionManager->setCurrentItem(m_pressedIndex); + m_selectionManager->beginAnchoredSelection(m_pressedIndex); + return true; + } + + m_selectionTogglePressed = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos); + if (m_selectionTogglePressed) { + m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle); + // The previous anchored selection has been finished already in + // KItemListSelectionManager::setSelected(). We can safely change + // the current item and start a new anchored selection now. + m_selectionManager->setCurrentItem(m_pressedIndex); + m_selectionManager->beginAnchoredSelection(m_pressedIndex); + return true; + } + + const bool shiftPressed = modifiers & Qt::ShiftModifier; + const bool controlPressed = modifiers & Qt::ControlModifier; + + // The previous selection is cleared if either + // 1. The selection mode is SingleSelection, or + // 2. the selection mode is MultiSelection, and *none* of the following conditions are met: + // a) Shift or Control are pressed. + // b) The clicked item is selected already. In that case, the user might want to: + // - start dragging multiple items, or + // - open the context menu and perform an action for all selected items. + const bool shiftOrControlPressed = shiftPressed || controlPressed; + const bool pressedItemAlreadySelected = m_pressedIndex >= 0 && m_selectionManager->isSelected(m_pressedIndex); + const bool clearSelection = m_selectionBehavior == SingleSelection || + (!shiftOrControlPressed && !pressedItemAlreadySelected); + if (clearSelection) { + m_selectionManager->clearSelection(); + } else if (pressedItemAlreadySelected && !shiftOrControlPressed && (buttons & Qt::LeftButton)) { + // The user might want to start dragging multiple items, but if he clicks the item + // in order to trigger it instead, the other selected items must be deselected. + // However, we do not know yet what the user is going to do. + // -> remember that the user pressed an item which had been selected already and + // clear the selection in mouseReleaseEvent(), unless the items are dragged. + m_clearSelectionIfItemsAreNotDragged = true; + + if (m_selectionManager->selectedItems().count() == 1 && m_view->isAboveText(m_pressedIndex, m_pressedMousePos)) { + emit selectedItemTextPressed(m_pressedIndex); + } + } + + if (!shiftPressed) { + // Finish the anchored selection before the current index is changed + m_selectionManager->endAnchoredSelection(); + } + + if (buttons & Qt::RightButton) { + // Stop rubber band from persisting after right-clicks + KItemListRubberBand* rubberBand = m_view->rubberBand(); + if (rubberBand->isActive()) { + disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged); + rubberBand->setActive(false); + m_view->setAutoScroll(false); + } + } + + if (m_pressedIndex >= 0) { + m_selectionManager->setCurrentItem(m_pressedIndex); + + switch (m_selectionBehavior) { + case NoSelection: + break; + + case SingleSelection: + m_selectionManager->setSelected(m_pressedIndex); + break; + + case MultiSelection: + if (controlPressed && !shiftPressed) { + m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle); + m_selectionManager->beginAnchoredSelection(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; + } + + if (buttons & Qt::RightButton) { + emit itemContextMenuRequested(m_pressedIndex, screenPos); + } + + return true; + } + + if (buttons & Qt::RightButton) { + const QRectF headerBounds = m_view->headerBoundaries(); + if (headerBounds.contains(pos)) { + emit headerContextMenuRequested(screenPos); + } else { + emit viewContextMenuRequested(screenPos); + } + return true; + } + + return false; +} + +bool KItemListController::onRelease(const QPointF& pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons, bool touch) +{ + const bool isAboveSelectionToggle = m_view->isAboveSelectionToggle(m_pressedIndex, m_pressedMousePos); + if (isAboveSelectionToggle) { + m_selectionTogglePressed = false; + return true; + } + + if (!isAboveSelectionToggle && m_selectionTogglePressed) { + m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle); + m_selectionTogglePressed = false; + return true; + } + + const bool shiftOrControlPressed = modifiers & Qt::ShiftModifier || + modifiers & Qt::ControlModifier; + + KItemListRubberBand* rubberBand = m_view->rubberBand(); + if (rubberBand->isActive()) { + disconnect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged); + rubberBand->setActive(false); + m_oldSelection.clear(); + m_view->setAutoScroll(false); + } + + 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 (m_clearSelectionIfItemsAreNotDragged) { + // A selected item has been clicked, but no drag operation has been started + // -> clear the rest of the selection. + m_selectionManager->clearSelection(); + m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Select); + m_selectionManager->beginAnchoredSelection(m_pressedIndex); + } + + if (buttons & Qt::LeftButton) { + bool emitItemActivated = true; + if (m_view->isAboveExpansionToggle(index, pos)) { + const bool expanded = m_model->isExpanded(index); + m_model->setExpanded(index, !expanded); + + emit itemExpansionToggleClicked(index); + emitItemActivated = false; + } else if (shiftOrControlPressed) { + // The mouse click should only update the selection, not trigger the item + emitItemActivated = false; + } else if (!(m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced)) { + if (touch) { + emitItemActivated = true; + } else { + emitItemActivated = false; + } + } + if (emitItemActivated) { + emit itemActivated(index); + } + } else if (buttons & Qt::MidButton) { + emit itemMiddleClicked(index); + } + } + + m_pressedMousePos = QPointF(); + m_pressedIndex = -1; + m_clearSelectionIfItemsAreNotDragged = false; + return false; +} + +void KItemListController::startRubberBand() +{ + if (m_selectionBehavior == MultiSelection) { + QPointF startPos = m_pressedMousePos; + if (m_view->scrollOrientation() == Qt::Vertical) { + startPos.ry() += m_view->scrollOffset(); + 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->scrollOffset(); + } + + m_oldSelection = m_selectionManager->selectedItems(); + KItemListRubberBand* rubberBand = m_view->rubberBand(); + rubberBand->setStartPosition(startPos); + rubberBand->setEndPosition(startPos); + rubberBand->setActive(true); + connect(rubberBand, &KItemListRubberBand::endPositionChanged, this, &KItemListController::slotRubberBandChanged); + m_view->setAutoScroll(true); + } +} + +void KItemListController::slotStateChanged(QScroller::State newState) +{ + if (newState == QScroller::Scrolling) { + m_qScrollerIsScrolling = true; + } else if (newState == QScroller::Inactive) { + m_qScrollerIsScrolling = false; + } +} Index: src/kitemviews/kitemlistview.h =================================================================== --- src/kitemviews/kitemlistview.h +++ src/kitemviews/kitemlistview.h @@ -44,6 +44,7 @@ class KItemListWidgetInformant; class KItemListWidgetCreatorBase; class QTimer; +class QPropertyAnimation; /** * @brief Represents the view of an item-list. @@ -740,6 +741,7 @@ bool m_skipAutoScrollForRubberBand; KItemListRubberBand* m_rubberBand; + KItemListRubberBand* m_tapAndHoldIndicator; QPointF m_mousePos; int m_autoScrollIncrement; @@ -748,6 +750,8 @@ KItemListHeader* m_header; KItemListHeaderWidget* m_headerWidget; + QPropertyAnimation* m_indicatorAnimation; + // When dragging items into the view where the sort-role of the model // is empty, a visual indicator should be shown during dragging where // the dropping will happen. This indicator is specified by an index Index: src/kitemviews/kitemlistview.cpp =================================================================== --- src/kitemviews/kitemlistview.cpp +++ src/kitemviews/kitemlistview.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include @@ -93,11 +94,13 @@ m_oldMaximumItemOffset(0), m_skipAutoScrollForRubberBand(false), m_rubberBand(nullptr), + m_tapAndHoldIndicator(nullptr), m_mousePos(), m_autoScrollIncrement(0), m_autoScrollTimer(nullptr), m_header(nullptr), m_headerWidget(nullptr), + m_indicatorAnimation(nullptr), m_dropIndicator() { setAcceptHoverEvents(true); @@ -118,6 +121,23 @@ m_rubberBand = new KItemListRubberBand(this); connect(m_rubberBand, &KItemListRubberBand::activationChanged, this, &KItemListView::slotRubberBandActivationChanged); + m_tapAndHoldIndicator = new KItemListRubberBand(this); + m_indicatorAnimation = new QPropertyAnimation(m_tapAndHoldIndicator, "endPosition"); + connect(m_tapAndHoldIndicator, &KItemListRubberBand::activationChanged, this, [this](bool active) { + if (active) { + m_indicatorAnimation->setDuration(150); + m_indicatorAnimation->setStartValue(QPointF(1, 1)); + m_indicatorAnimation->setEndValue(QPointF(40, 40)); + m_indicatorAnimation->start(); + } + update(); + }); + connect(m_tapAndHoldIndicator, &KItemListRubberBand::endPositionChanged, this, [this]() { + if (m_tapAndHoldIndicator->isActive()) { + update(); + } + }); + m_headerWidget = new KItemListHeaderWidget(this); m_headerWidget->setVisible(false); @@ -142,6 +162,9 @@ delete m_sizeHintResolver; m_sizeHintResolver = nullptr; + + delete m_indicatorAnimation; + m_indicatorAnimation = nullptr; } void KItemListView::setScrollOffset(qreal offset) @@ -671,6 +694,18 @@ style()->drawControl(QStyle::CE_RubberBand, &opt, painter); } + if (m_tapAndHoldIndicator->isActive()) { + const QPointF indicatorSize = m_tapAndHoldIndicator->endPosition(); + const QRectF rubberBandRect = QRectF(m_tapAndHoldIndicator->startPosition() - indicatorSize, + (m_tapAndHoldIndicator->startPosition()) + indicatorSize).normalized(); + QStyleOptionRubberBand opt; + initStyleOption(&opt); + opt.shape = QRubberBand::Rectangle; + opt.opaque = false; + opt.rect = rubberBandRect.toRect(); + style()->drawControl(QStyle::CE_RubberBand, &opt, painter); + } + if (!m_dropIndicator.isEmpty()) { const QRectF r = m_dropIndicator.toRect(); Index: src/kitemviews/private/kitemlistrubberband.h =================================================================== --- src/kitemviews/private/kitemlistrubberband.h +++ src/kitemviews/private/kitemlistrubberband.h @@ -31,6 +31,7 @@ class DOLPHIN_EXPORT KItemListRubberBand : public QObject { Q_OBJECT + Q_PROPERTY(QPointF endPosition MEMBER m_endPos READ endPosition WRITE setEndPosition) public: explicit KItemListRubberBand(QObject* parent = nullptr); Index: src/kitemviews/private/ktwofingerswipe.h =================================================================== --- /dev/null +++ src/kitemviews/private/ktwofingerswipe.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2020 Steffen Hartleib + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef KTWOFINGERSWIPE_H +#define KTWOFINGERSWIPE_H + +#include "dolphin_export.h" +// Qt +#include +#include + +class DOLPHIN_EXPORT KTwoFingerSwipe : public QGesture +{ + Q_OBJECT + Q_PROPERTY(QPointF pos READ pos WRITE setPos) + Q_PROPERTY(QPointF screenPos READ screenPos WRITE setScreenPos) + Q_PROPERTY(QPointF scenePos READ scenePos WRITE setScenePos) + Q_PROPERTY(qreal swipeAngle READ swipeAngle WRITE setSwipeAngle) +public: + explicit KTwoFingerSwipe(QObject* parent = nullptr); + ~KTwoFingerSwipe(); + QPointF pos() const; + void setPos(QPointF pos); + QPointF screenPos() const; + void setScreenPos(QPointF screenPos); + QPointF scenePos() const; + void setScenePos(QPointF scenePos); + qreal swipeAngle() const; + void setSwipeAngle(qreal swipeAngle); +private: + class KTwoFingerSwipePrivate* const d; +}; + +class DOLPHIN_EXPORT KTwoFingerSwipeRecognizer : public QGestureRecognizer +{ +public: + explicit KTwoFingerSwipeRecognizer(); + ~KTwoFingerSwipeRecognizer(); + QGesture* create(QObject*) override; + Result recognize(QGesture*, QObject*, QEvent*) override; +private: + Q_DISABLE_COPY( KTwoFingerSwipeRecognizer ) + class KTwoFingerSwipeRecognizerPrivate* const d; +}; + +#endif /* KTWOFINGERSWIPE_H */ + Index: src/kitemviews/private/ktwofingerswipe.cpp =================================================================== --- /dev/null +++ src/kitemviews/private/ktwofingerswipe.cpp @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2020 Steffen Hartleib + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +// Self +#include "ktwofingerswipe.h" + +// Qt +#include +#include + +class KTwoFingerSwipePrivate +{ +public: + KTwoFingerSwipePrivate() + { + } + ~KTwoFingerSwipePrivate() + { + } + KTwoFingerSwipe* q; + QPointF m_pos; + QPointF m_screenPos; + QPointF m_scenePos; + qreal m_swipeAngle; +}; + +class KTwoFingerSwipeRecognizerPrivate +{ +public: + KTwoFingerSwipeRecognizerPrivate() + { + } + ~KTwoFingerSwipeRecognizerPrivate() + { + } + void setGesturePositions(KTwoFingerSwipe* const gesture, QTouchEvent::TouchPoint tp) + { + gesture->setHotSpot(tp.startScreenPos()); + gesture->setPos(tp.startPos()); + gesture->setScreenPos(tp.startScreenPos()); + gesture->setScenePos(tp.startScenePos()); + } + void setAngle(KTwoFingerSwipe* const gesture, QTouchEvent::TouchPoint tp) + { + const QLineF ql = QLineF(tp.startPos(), tp.pos()); + gesture->setSwipeAngle(ql.angle()); + } + KTwoFingerSwipeRecognizer* q; + bool m_TargetIsGrapicsWidget = false; + qint64 m_TouchBeginnTimestamp; + bool m_GestureAlreadyTriggered; +}; + +KTwoFingerSwipeRecognizer::KTwoFingerSwipeRecognizer() + : QGestureRecognizer() + , d (new KTwoFingerSwipeRecognizerPrivate) +{ + d->q = this; +} + +KTwoFingerSwipeRecognizer::~KTwoFingerSwipeRecognizer() +{ + delete d; +} + +QGesture* KTwoFingerSwipeRecognizer::create(QObject*) +{ + return static_cast(new KTwoFingerSwipe()); +} + +QGestureRecognizer::Result KTwoFingerSwipeRecognizer::recognize(QGesture* gesture, QObject* watched, QEvent* event) +{ + Q_UNUSED(watched) + + KTwoFingerSwipe* const kTwoFingerSwipe = static_cast(gesture); + const QTouchEvent* touchEvent = static_cast(event); + + const int maxTimeFrameForSwipe = 90; + const int minDistanceForSwipe = 30; + + switch (event->type()) { + case QEvent::TouchBegin: { + d->m_TouchBeginnTimestamp = touchEvent->timestamp(); + d->m_GestureAlreadyTriggered = false; + d->setGesturePositions(kTwoFingerSwipe, touchEvent->touchPoints().first()); + return MayBeGesture; + } + + case QEvent::TouchUpdate: { + const qint64 now = touchEvent->timestamp(); + const qint64 elapsedTime = now - d->m_TouchBeginnTimestamp; + const QPointF distance = touchEvent->touchPoints().first().startPos() - touchEvent->touchPoints().first().pos(); + d->setGesturePositions(kTwoFingerSwipe, touchEvent->touchPoints().first()); + d->setAngle(kTwoFingerSwipe, touchEvent->touchPoints().first()); + + if (touchEvent->touchPoints().size() > 2) { + return CancelGesture; + } + + if (touchEvent->touchPoints().size() == 2) { + if ((elapsedTime) > maxTimeFrameForSwipe) { + return CancelGesture; + } + + if (distance.manhattanLength() >= minDistanceForSwipe && + (elapsedTime) <= maxTimeFrameForSwipe && !d->m_GestureAlreadyTriggered) { + d->m_GestureAlreadyTriggered = true; + return FinishGesture; + } else if ((elapsedTime) <= maxTimeFrameForSwipe && !d->m_GestureAlreadyTriggered) { + return TriggerGesture; + } + } + break; + } + + default: + return Ignore; + } + return Ignore; +} + +KTwoFingerSwipe::KTwoFingerSwipe(QObject* parent) + : QGesture(parent) + , d (new KTwoFingerSwipePrivate) +{ + d->q = this; +} + +KTwoFingerSwipe::~KTwoFingerSwipe() +{ + delete d; +} + + +QPointF KTwoFingerSwipe::pos() const +{ + return d->m_pos; +} + +void KTwoFingerSwipe::setPos(QPointF _pos) +{ + d->m_pos = _pos; +} + +QPointF KTwoFingerSwipe::screenPos() const +{ + return d->m_screenPos; +} + +void KTwoFingerSwipe::setScreenPos(QPointF _screenPos) +{ + d->m_screenPos = _screenPos; +} + +QPointF KTwoFingerSwipe::scenePos() const +{ + return d->m_scenePos; +} + +void KTwoFingerSwipe::setScenePos(QPointF _scenePos) +{ + d->m_scenePos = _scenePos; +} + +qreal KTwoFingerSwipe::swipeAngle() const +{ + return d->m_swipeAngle; +} + void KTwoFingerSwipe::setSwipeAngle(qreal _swipeAngle) +{ + d->m_swipeAngle = _swipeAngle; +} + Index: src/kitemviews/private/ktwofingertap.h =================================================================== --- /dev/null +++ src/kitemviews/private/ktwofingertap.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 Steffen Hartleib + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef KTWOFINGERTAP_H +#define KTWOFINGERTAP_H + +#include +// Qt +#include +#include + +class DOLPHIN_EXPORT KTwoFingerTap : public QGesture +{ + Q_OBJECT + Q_PROPERTY(QPointF pos READ pos WRITE setPos) + Q_PROPERTY(QPointF screenPos READ screenPos WRITE setScreenPos) + Q_PROPERTY(QPointF scenePos READ scenePos WRITE setScenePos) +public: + explicit KTwoFingerTap(QObject* parent = nullptr); + ~KTwoFingerTap(); + QPointF pos() const; + void setPos(QPointF pos); + QPointF screenPos() const; + void setScreenPos(QPointF screenPos); + QPointF scenePos() const; + void setScenePos(QPointF scenePos); +private: + class KTwoFingerTapPrivate* const d; +}; + +class DOLPHIN_EXPORT KTwoFingerTapRecognizer : public QGestureRecognizer +{ +public: + explicit KTwoFingerTapRecognizer(); + ~KTwoFingerTapRecognizer(); + QGesture* create(QObject*) override; + Result recognize(QGesture*, QObject*, QEvent*) override; +private: + Q_DISABLE_COPY(KTwoFingerTapRecognizer) + class KTwoFingerTapRecognizerPrivate* const d; +}; + +#endif /* KTWOFINGERTAP_H */ Index: src/kitemviews/private/ktwofingertap.cpp =================================================================== --- /dev/null +++ src/kitemviews/private/ktwofingertap.cpp @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2019 Steffen Hartleib + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +// Self +#include "ktwofingertap.h" + +// Qt +#include +#include + +class KTwoFingerTapPrivate +{ +public: + KTwoFingerTapPrivate() + { + } + ~KTwoFingerTapPrivate() + { + } + KTwoFingerTap* q; + QPointF m_pos; + QPointF m_screenPos; + QPointF m_scenePos; +}; + +class KTwoFingerTapRecognizerPrivate +{ +public: + KTwoFingerTapRecognizerPrivate() + { + } + ~KTwoFingerTapRecognizerPrivate() + { + } + void setGesturePositions(KTwoFingerTap* const gesture, QTouchEvent::TouchPoint tp) + { + gesture->setHotSpot(tp.startScreenPos()); + gesture->setPos(tp.startPos()); + gesture->setScreenPos(tp.startScreenPos()); + gesture->setScenePos(tp.startScenePos()); + } + KTwoFingerTapRecognizer* q; + bool m_TargetIsGrapicsWidget = false; + qint64 m_TouchBeginnTimestamp; + bool m_GestureTriggered; +}; + +KTwoFingerTapRecognizer::KTwoFingerTapRecognizer() + : QGestureRecognizer() + , d (new KTwoFingerTapRecognizerPrivate) +{ + d->q = this; +} + +KTwoFingerTapRecognizer::~KTwoFingerTapRecognizer() +{ + delete d; +} + +QGesture* KTwoFingerTapRecognizer::create(QObject*) +{ + return static_cast(new KTwoFingerTap()); +} + +QGestureRecognizer::Result KTwoFingerTapRecognizer::recognize(QGesture* gesture, QObject* watched, QEvent* event) +{ + Q_UNUSED(watched) + + KTwoFingerTap* const kTwoFingerTap = static_cast(gesture); + const QTouchEvent* touchEvent = static_cast(event); + + switch (event->type()) { + case QEvent::TouchBegin: { + d->m_TouchBeginnTimestamp = touchEvent->timestamp(); + d->setGesturePositions(kTwoFingerTap, touchEvent->touchPoints().first()); + d->m_GestureTriggered = false; + return MayBeGesture; + } + + case QEvent::TouchUpdate: { + + if (touchEvent->touchPoints().size() > 2) { + d->m_GestureTriggered = false; + return CancelGesture; + } + + if (touchEvent->touchPoints().size() == 2) { + if ((touchEvent->touchPoints().first().startPos() - touchEvent->touchPoints().first().pos()).manhattanLength() >= QApplication::startDragDistance()) { + d->m_GestureTriggered = false; + return CancelGesture; + } + if ((touchEvent->touchPoints().at(1).startPos() - touchEvent->touchPoints().at(1).pos()).manhattanLength() >= QApplication::startDragDistance()) { + d->m_GestureTriggered = false; + return CancelGesture; + } + if (touchEvent->touchPointStates() & Qt::TouchPointPressed) { + d->m_GestureTriggered = true; + } + if (touchEvent->touchPointStates() & Qt::TouchPointReleased && d->m_GestureTriggered) { + d->m_GestureTriggered = false; + return FinishGesture; + } + } + break; + } + + default: + return Ignore; + } + return Ignore; +} + +KTwoFingerTap::KTwoFingerTap(QObject* parent) + : QGesture(parent) + , d (new KTwoFingerTapPrivate) +{ + d->q = this; +} + +KTwoFingerTap::~KTwoFingerTap() +{ + delete d; +} + +QPointF KTwoFingerTap::pos() const +{ + return d->m_pos; +} + +void KTwoFingerTap::setPos(QPointF _pos) +{ + d->m_pos = _pos; +} + +QPointF KTwoFingerTap::screenPos() const +{ + return d->m_screenPos; +} + +void KTwoFingerTap::setScreenPos(QPointF _screenPos) +{ + d->m_screenPos = _screenPos; +} + +QPointF KTwoFingerTap::scenePos() const +{ + return d->m_scenePos; +} + +void KTwoFingerTap::setScenePos(QPointF _scenePos) +{ + d->m_scenePos = _scenePos; +} Index: src/panels/information/informationpanel.cpp =================================================================== --- src/panels/information/informationpanel.cpp +++ src/panels/information/informationpanel.cpp @@ -418,6 +418,7 @@ m_content = new InformationPanelContent(this); connect(m_content, &InformationPanelContent::urlActivated, this, &InformationPanel::urlActivated); connect(m_content, &InformationPanelContent::configurationFinished, this, [this]() { m_inConfigurationMode = false; }); + connect(m_content, &InformationPanelContent::contextMenuRequested, this, &InformationPanel::showContextMenu); QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); Index: src/panels/information/informationpanelcontent.h =================================================================== --- src/panels/information/informationpanelcontent.h +++ src/panels/information/informationpanelcontent.h @@ -36,6 +36,7 @@ class QString; class QLabel; class QScrollArea; +class QGestureEvent; namespace KIO { class PreviewJob; @@ -91,6 +92,7 @@ signals: void urlActivated( const QUrl& url ); void configurationFinished(); + void contextMenuRequested(const QPoint& pos); public slots: /** @@ -103,6 +105,8 @@ /** @see QObject::eventFilter() */ bool eventFilter(QObject* obj, QEvent* event) override; + bool event(QEvent * event) override; + private slots: /** * Is invoked if no preview is available for the item. In this @@ -144,6 +148,8 @@ */ void refreshPixmapView(); + bool gestureEvent(QGestureEvent* event); + private: KFileItem m_item; Index: src/panels/information/informationpanelcontent.cpp =================================================================== --- src/panels/information/informationpanelcontent.cpp +++ src/panels/information/informationpanelcontent.cpp @@ -46,11 +46,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include "dolphin_informationpanelsettings.h" #include "phononwidget.h" @@ -147,6 +149,7 @@ m_metaDataArea->setFrameShape(QFrame::NoFrame); QWidget* viewport = m_metaDataArea->viewport(); + QScroller::grabGesture(viewport, QScroller::TouchGesture); viewport->installEventFilter(this); layout->addWidget(m_preview); @@ -157,6 +160,8 @@ layout->addWidget(m_metaDataArea); layout->addWidget(m_configureButtons); + grabGesture(Qt::TapAndHoldGesture); + m_placesItemModel = new PlacesItemModel(this); } @@ -351,6 +356,36 @@ return QWidget::eventFilter(obj, event); } +bool InformationPanelContent::event(QEvent* event) +{ + if (event->type() == QEvent::Gesture) { + gestureEvent(static_cast(event)); + return true; + } + return QWidget::event(event); +} + +bool InformationPanelContent::gestureEvent(QGestureEvent* event) +{ + //you can touch on different views in the same time, Qt set the isUnderMouse flag to + //the view whit contains the first touch point. We don't want process gesture events from + //other views + if (!underMouse()) { + return false; + } + + QTapAndHoldGesture* tap = static_cast(event->gesture(Qt::TapAndHoldGesture)); + + if (tap) { + if (tap->state() == Qt::GestureFinished) { + emit contextMenuRequested(tap->position().toPoint()); + } + event->accept(); + return true; + } + return false; +} + void InformationPanelContent::showIcon(const KFileItem& item) { m_outdatedPreviewTimer->stop(); Index: src/settings/general/previewssettingspage.cpp =================================================================== --- src/settings/general/previewssettingspage.cpp +++ src/settings/general/previewssettingspage.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -53,6 +54,7 @@ QLabel* showPreviewsLabel = new QLabel(i18nc("@title:group", "Show previews in the view for:"), this); m_listView = new QListView(this); + QScroller::grabGesture(m_listView->viewport(), QScroller::TouchGesture); ServiceItemDelegate* delegate = new ServiceItemDelegate(m_listView, m_listView); connect(delegate, &ServiceItemDelegate::requestServiceConfiguration, @@ -67,6 +69,7 @@ m_listView->setModel(proxyModel); m_listView->setItemDelegate(delegate); m_listView->setVerticalScrollMode(QListView::ScrollPerPixel); + m_listView->setUniformItemSizes(true); QLabel* remoteFileSizeLabel = new QLabel(i18nc("@label", "Skip previews for remote files above:"), this); Index: src/settings/services/servicessettingspage.cpp =================================================================== --- src/settings/services/servicessettingspage.cpp +++ src/settings/services/servicessettingspage.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -69,6 +70,8 @@ }); m_listView = new QListView(this); + QScroller::grabGesture(m_listView->viewport(), QScroller::TouchGesture); + auto *delegate = new ServiceItemDelegate(m_listView, m_listView); m_serviceModel = new ServiceModel(this); m_sortModel = new QSortFilterProxyModel(this); Index: src/views/dolphinview.h =================================================================== --- src/views/dolphinview.h +++ src/views/dolphinview.h @@ -590,6 +590,8 @@ */ void urlActivated(const QUrl& url); + void goUpRequested(); + protected: /** Changes the zoom level if Control is pressed during a wheel event. */ void wheelEvent(QWheelEvent* event) override; @@ -619,6 +621,9 @@ void slotRenameDialogRenamingFinished(const QList& urls); void slotSelectedItemTextPressed(int index); void slotCopyingDone(KIO::Job *, const QUrl &, const QUrl &to); + void slotIncreaseZoom(); + void slotDecreaseZoom(); + void slotSwipeUp(); /* * Is called when new items get pasted or dropped. Index: src/views/dolphinview.cpp =================================================================== --- src/views/dolphinview.cpp +++ src/views/dolphinview.cpp @@ -147,6 +147,9 @@ connect(controller, &KItemListController::escapePressed, this, &DolphinView::stopLoading); connect(controller, &KItemListController::modelChanged, this, &DolphinView::slotModelChanged); connect(controller, &KItemListController::selectedItemTextPressed, this, &DolphinView::slotSelectedItemTextPressed); + connect(controller, &KItemListController::increaseZoom, this, &DolphinView::slotIncreaseZoom); + connect(controller, &KItemListController::decreaseZoom, this, &DolphinView::slotDecreaseZoom); + connect(controller, &KItemListController::swipeUp, this, &DolphinView::slotSwipeUp); connect(m_model, &KFileItemModel::directoryLoadingStarted, this, &DolphinView::slotDirectoryLoadingStarted); connect(m_model, &KFileItemModel::directoryLoadingCompleted, this, &DolphinView::slotDirectoryLoadingCompleted); @@ -1945,3 +1948,18 @@ markUrlAsCurrent(current); markUrlsAsSelected(selected); } + +void DolphinView::slotIncreaseZoom() +{ + setZoomLevel(zoomLevel() + 1); +} + +void DolphinView::slotDecreaseZoom() +{ + setZoomLevel(zoomLevel() - 1); +} + +void DolphinView::slotSwipeUp() +{ + emit goUpRequested(); +}