-/***************************************************************************
- * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
- * Copyright (C) 2012 by Frank Reininghaus <frank78ac@googlemail.com> *
- * *
- * Based on the Itemviews NG project from Trolltech Labs: *
- * http://qt.gitorious.org/qt-labs/itemviews-ng *
- * *
- * This program is free software; you can redistribute it and/or modify *
- * it under the terms of the GNU General Public License as published by *
- * the Free Software Foundation; either version 2 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 General Public License for more details. *
- * *
- * You should have received a copy of the GNU 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 *
- ***************************************************************************/
+/*
+ * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com>
+ * SPDX-FileCopyrightText: 2012 Frank Reininghaus <frank78ac@googlemail.com>
+ *
+ * Based on the Itemviews NG project from Trolltech Labs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
#include "kitemlistcontroller.h"
#include "kitemlistview.h"
#include "private/kitemlistkeyboardsearchmanager.h"
#include "private/kitemlistrubberband.h"
+#include "private/ktwofingerswipe.h"
+#include "private/ktwofingertap.h"
#include "views/draganddrophelper.h"
#include <QAccessible>
#include <QApplication>
#include <QDrag>
+#include <QGesture>
#include <QGraphicsScene>
#include <QGraphicsSceneEvent>
#include <QGraphicsView>
#include <QMimeData>
#include <QTimer>
+#include <QTouchEvent>
KItemListController::KItemListController(KItemModelBase* model, KItemListView* view, QObject* parent) :
QObject(parent),
m_singleClickActivationEnforced(false),
m_selectionTogglePressed(false),
m_clearSelectionIfItemsAreNotDragged(false),
+ m_isSwipeGesture(false),
+ m_dragActionOrRightClick(false),
+ m_scrollerIsScrolling(false),
+ m_pinchGestureInProgress(false),
+ m_mousePress(false),
+ m_isTouchEvent(false),
m_selectionBehavior(NoSelection),
m_autoActivationBehavior(ActivationAndExpansion),
m_mouseDoubleClickAction(ActivateItemOnly),
m_pressedIndex(-1),
m_pressedMousePos(),
m_autoActivationTimer(nullptr),
+ m_swipeGesture(Qt::CustomGesture),
+ m_twoFingerTapGesture(Qt::CustomGesture),
m_oldSelection(),
m_keyboardAnchorIndex(-1),
m_keyboardAnchorPos(0)
this, &KItemListController::slotChangeCurrentItem);
connect(m_selectionManager, &KItemListSelectionManager::currentChanged,
m_keyboardManager, &KItemListKeyboardSearchManager::slotCurrentChanged);
+ connect(m_selectionManager, &KItemListSelectionManager::selectionChanged,
+ m_keyboardManager, &KItemListKeyboardSearchManager::slotSelectionChanged);
m_autoActivationTimer = new QTimer(this);
m_autoActivationTimer->setSingleShot(true);
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()
m_selectionManager->setModel(m_model);
- emit modelChanged(m_model, oldModel);
+ Q_EMIT modelChanged(m_model, oldModel);
}
KItemModelBase* KItemListController::model() const
updateExtendedSelectionRegion();
}
- emit viewChanged(m_view, oldView);
+ Q_EMIT viewChanged(m_view, oldView);
}
KItemListView* KItemListController::view() const
case Qt::Key_Return: {
const KItemSet selectedItems = m_selectionManager->selectedItems();
if (selectedItems.count() >= 2) {
- emit itemsActivated(selectedItems);
+ Q_EMIT itemsActivated(selectedItems);
} else if (selectedItems.count() == 1) {
- emit itemActivated(selectedItems.first());
+ Q_EMIT itemActivated(selectedItems.first());
} else {
- emit itemActivated(index);
+ Q_EMIT itemActivated(index);
}
break;
}
if (index >= 0) {
const QRectF contextRect = m_view->itemContextRect(index);
const QPointF pos(m_view->scene()->views().first()->mapToGlobal(contextRect.bottomRight().toPoint()));
- emit itemContextMenuRequested(index, pos);
+ Q_EMIT itemContextMenuRequested(index, pos);
} else {
- emit viewContextMenuRequested(QCursor::pos());
+ Q_EMIT viewContextMenuRequested(QCursor::pos());
}
break;
}
m_selectionManager->clearSelection();
}
m_keyboardManager->cancelSearch();
- emit escapePressed();
+ Q_EMIT escapePressed();
break;
case Qt::Key_Space:
if (!m_model || m_model->count() == 0) {
return;
}
- const int currentIndex = m_selectionManager->currentItem();
int index;
if (searchFromNextItem) {
+ const int currentIndex = m_selectionManager->currentItem();
index = m_model->indexForKeyboardSearch(text, (currentIndex + 1) % m_model->count());
} else {
- index = m_model->indexForKeyboardSearch(text, currentIndex);
+ index = m_model->indexForKeyboardSearch(text, 0);
}
if (index >= 0) {
m_selectionManager->setCurrentItem(index);
if (m_selectionBehavior != NoSelection) {
- m_selectionManager->clearSelection();
- m_selectionManager->setSelected(index, 1);
+ m_selectionManager->replaceSelection(index);
m_selectionManager->beginAnchoredSelection(index);
}
const bool expanded = m_model->isExpanded(index);
m_model->setExpanded(index, !expanded);
} else if (m_autoActivationBehavior != ExpansionOnly) {
- emit itemActivated(index);
+ Q_EMIT itemActivated(index);
}
}
}
bool KItemListController::inputMethodEvent(QInputMethodEvent* event)
{
- Q_UNUSED(event);
+ Q_UNUSED(event)
return false;
}
bool KItemListController::mousePressEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
{
+ m_mousePress = true;
+
+ if (event->source() == Qt::MouseEventSynthesizedByQt && m_isTouchEvent) {
+ 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
- // https://bugs.kde.org/show_bug.cgi?id=327412.
- 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 (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;
- }
+ const Qt::MouseButtons buttons = event->buttons();
- 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);
+ if (!onPress(event->screenPos(), event->pos(), event->modifiers(), buttons)) {
+ startRubberBand();
+ return false;
}
- return false;
+ return true;
}
bool KItemListController::mouseMoveEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
return false;
}
+ if (m_view->m_tapAndHoldIndicator->isActive()) {
+ m_view->m_tapAndHoldIndicator->setActive(false);
+ }
+
+ if (event->source() == Qt::MouseEventSynthesizedByQt && !m_dragActionOrRightClick && m_isTouchEvent) {
+ return false;
+ }
+
if (m_pressedIndex >= 0) {
// Check whether a dragging should be started
if (event->buttons() & Qt::LeftButton) {
// -> the selection should not be cleared when the mouse button is released.
m_clearSelectionIfItemsAreNotDragged = false;
}
-
startDragging();
+ m_mousePress = false;
}
}
} else {
bool KItemListController::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, const QTransform& transform)
{
+ m_mousePress = false;
+ m_isTouchEvent = false;
+
if (!m_view) {
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 (!isAboveSelectionToggle && m_selectionTogglePressed) {
- m_selectionManager->setSelected(m_pressedIndex, 1, KItemListSelectionManager::Toggle);
- m_selectionTogglePressed = false;
- return true;
+ if (m_view->m_tapAndHoldIndicator->isActive()) {
+ m_view->m_tapAndHoldIndicator->setActive(false);
}
- 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() && m_isTouchEvent) {
+ 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);
- }
- }
+ Q_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)
m_selectionManager->clearSelection();
if (index >= 0) {
m_selectionManager->setSelected(index);
- emit itemContextMenuRequested(index, event->screenPos());
+ Q_EMIT itemContextMenuRequested(index, event->screenPos());
} else {
const QRectF headerBounds = m_view->headerBoundaries();
if (headerBounds.contains(event->pos())) {
- emit headerContextMenuRequested(event->screenPos());
+ Q_EMIT headerContextMenuRequested(event->screenPos());
} else {
- emit viewContextMenuRequested(event->screenPos());
+ Q_EMIT viewContextMenuRequested(event->screenPos());
}
}
return true;
(event->button() & Qt::LeftButton) &&
index >= 0 && index < m_model->count();
if (emitItemActivated) {
- emit itemActivated(index);
+ Q_EMIT itemActivated(index);
}
return false;
}
bool KItemListController::dragEnterEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
{
- Q_UNUSED(event);
- Q_UNUSED(transform);
+ Q_UNUSED(event)
+ Q_UNUSED(transform)
DragAndDropHelper::clearUrlListMatchesUrlCache();
bool KItemListController::dragLeaveEvent(QGraphicsSceneDragDropEvent* event, const QTransform& transform)
{
- Q_UNUSED(event);
- Q_UNUSED(transform);
+ Q_UNUSED(event)
+ Q_UNUSED(transform)
m_autoActivationTimer->stop();
m_view->setAutoScroll(false);
KItemListWidget* widget = hoveredWidget();
if (widget) {
widget->setHovered(false);
- emit itemUnhovered(widget->index());
+ Q_EMIT itemUnhovered(widget->index());
}
return false;
}
if (oldHoveredWidget) {
oldHoveredWidget->setHovered(false);
- emit itemUnhovered(oldHoveredWidget->index());
+ Q_EMIT itemUnhovered(oldHoveredWidget->index());
}
}
m_view->hideDropIndicator();
if (!newHoveredWidget->isHovered()) {
newHoveredWidget->setHovered(true);
- emit itemHovered(index);
+ Q_EMIT itemHovered(index);
}
if (!m_autoActivationTimer->isActive() && m_autoActivationTimer->interval() >= 0) {
m_autoActivationTimer->stop();
if (newHoveredWidget && newHoveredWidget->isHovered()) {
newHoveredWidget->setHovered(false);
- emit itemUnhovered(index);
+ Q_EMIT itemUnhovered(index);
}
}
} else {
m_view->hideDropIndicator();
}
- event->setAccepted(!DragAndDropHelper::urlListMatchesUrl(event->mimeData()->urls(), hoveredDir));
-
+ if (DragAndDropHelper::urlListMatchesUrl(event->mimeData()->urls(), hoveredDir)) {
+ event->setDropAction(Qt::IgnoreAction);
+ event->ignore();
+ } else {
+ event->setDropAction(event->proposedAction());
+ event->accept();
+ }
return false;
}
if (dropAboveIndex >= 0) {
// Something has been dropped between two items.
m_view->hideDropIndicator();
- emit aboveItemDropEvent(dropAboveIndex, event);
+ Q_EMIT aboveItemDropEvent(dropAboveIndex, event);
} else if (!event->mimeData()->hasFormat(m_model->blacklistItemDropEventMimeType())) {
// Something has been dropped on an item or on an empty part of the view.
- emit itemDropEvent(m_view->itemAt(pos), event);
+ Q_EMIT itemDropEvent(m_view->itemAt(pos), event);
}
QAccessibleEvent accessibilityEvent(view(), QAccessible::DragDropEnd);
bool KItemListController::hoverEnterEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
{
- Q_UNUSED(event);
- Q_UNUSED(transform);
+ Q_UNUSED(event)
+ Q_UNUSED(transform)
return false;
}
bool KItemListController::hoverMoveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
{
- Q_UNUSED(transform);
+ Q_UNUSED(transform)
if (!m_model || !m_view) {
return false;
}
if (oldHoveredWidget != newHoveredWidget) {
if (oldHoveredWidget) {
oldHoveredWidget->setHovered(false);
- emit itemUnhovered(oldHoveredWidget->index());
+ Q_EMIT itemUnhovered(oldHoveredWidget->index());
}
if (newHoveredWidget) {
newHoveredWidget->setHovered(true);
const QPointF mappedPos = newHoveredWidget->mapFromItem(m_view, pos);
newHoveredWidget->setHoverPosition(mappedPos);
- emit itemHovered(newHoveredWidget->index());
+ Q_EMIT itemHovered(newHoveredWidget->index());
}
} else if (oldHoveredWidget) {
const QPointF mappedPos = oldHoveredWidget->mapFromItem(m_view, pos);
bool KItemListController::hoverLeaveEvent(QGraphicsSceneHoverEvent* event, const QTransform& transform)
{
- Q_UNUSED(event);
- Q_UNUSED(transform);
+ Q_UNUSED(event)
+ Q_UNUSED(transform)
+
+ m_mousePress = false;
+ m_isTouchEvent = false;
if (!m_model || !m_view) {
return false;
}
- foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) {
+ const auto widgets = m_view->visibleItemListWidgets();
+ for (KItemListWidget* widget : widgets) {
if (widget->isHovered()) {
widget->setHovered(false);
- emit itemUnhovered(widget->index());
+ Q_EMIT itemUnhovered(widget->index());
}
}
return false;
bool KItemListController::wheelEvent(QGraphicsSceneWheelEvent* event, const QTransform& transform)
{
- Q_UNUSED(event);
- Q_UNUSED(transform);
+ Q_UNUSED(event)
+ Q_UNUSED(transform)
return false;
}
bool KItemListController::resizeEvent(QGraphicsSceneResizeEvent* event, const QTransform& transform)
{
- Q_UNUSED(event);
- Q_UNUSED(transform);
+ Q_UNUSED(event)
+ Q_UNUSED(transform)
+ 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, but only one QWidget gets a mousePressEvent
+ //we use this to get the right QWidget
+ //the only exception is a tap gesture with state GestureStarted, we need to reset some variable
+ if (!m_mousePress) {
+ if (QGesture* tap = event->gesture(Qt::TapGesture)) {
+ QTapGesture* tapGesture = static_cast<QTapGesture*>(tap);
+ if (tapGesture->state() == Qt::GestureStarted) {
+ tapTriggered(tapGesture, transform);
+ }
+ }
+ return false;
+ }
+
+ bool accepted = false;
+
+ if (QGesture* tap = event->gesture(Qt::TapGesture)) {
+ tapTriggered(static_cast<QTapGesture*>(tap), transform);
+ accepted = true;
+ }
+ if (event->gesture(Qt::TapAndHoldGesture)) {
+ tapAndHoldTriggered(event, transform);
+ accepted = true;
+ }
+ if (event->gesture(Qt::PinchGesture)) {
+ pinchTriggered(event, transform);
+ accepted = true;
+ }
+ if (event->gesture(m_swipeGesture)) {
+ swipeTriggered(event, transform);
+ accepted = true;
+ }
+ if (event->gesture(m_twoFingerTapGesture)) {
+ twoFingerTapTriggered(event, transform);
+ accepted = true;
+ }
+ return accepted;
+}
+
+bool KItemListController::touchBeginEvent(QTouchEvent* event, const QTransform& transform)
+{
+ Q_UNUSED(event)
+ Q_UNUSED(transform)
+
+ m_isTouchEvent = true;
return false;
}
+void KItemListController::tapTriggered(QTapGesture* tap, const QTransform& transform)
+{
+ static bool scrollerWasActive = false;
+
+ if (tap->state() == Qt::GestureStarted) {
+ m_dragActionOrRightClick = false;
+ m_isSwipeGesture = false;
+ m_pinchGestureInProgress = false;
+ scrollerWasActive = m_scrollerIsScrolling;
+ }
+
+ if (tap->state() == Qt::GestureFinished) {
+ m_mousePress = false;
+
+ //if at the moment of the gesture start the QScroller was active, the user made the tap
+ //to stop the QScroller and not to tap on an item
+ if (scrollerWasActive) {
+ 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);
+ }
+ m_isTouchEvent = false;
+ }
+}
+
+void KItemListController::tapAndHoldTriggered(QGestureEvent* event, const QTransform& transform)
+{
+
+ //the Qt TabAndHold gesture is triggerable with a mouse click, we don't want this
+ if (!m_isTouchEvent) {
+ return;
+ }
+
+ const QTapAndHoldGesture* tap = static_cast<QTapAndHoldGesture*>(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();
+ }
+
+ Q_EMIT scrollerStop();
+
+ 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<QPinchGesture*>(event->gesture(Qt::PinchGesture));
+ const qreal sensitivityModifier = 0.2;
+ static qreal counter = 0;
+
+ 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) {
+ Q_EMIT increaseZoom();
+ counter = 0;
+ } else if (counter <= -sensitivityModifier) {
+ Q_EMIT decreaseZoom();
+ counter = 0;
+ }
+ }
+}
+
+void KItemListController::swipeTriggered(QGestureEvent* event, const QTransform& transform)
+{
+ Q_UNUSED(transform)
+
+ const KTwoFingerSwipe* swipe = static_cast<KTwoFingerSwipe*>(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) {
+ Q_EMIT scrollerStop();
+
+ if (swipe->swipeAngle() <= 20 || swipe->swipeAngle() >= 340) {
+ Q_EMIT mouseButtonPressed(m_pressedIndex, Qt::BackButton);
+ } else if (swipe->swipeAngle() <= 200 && swipe->swipeAngle() >= 160) {
+ Q_EMIT mouseButtonPressed(m_pressedIndex, Qt::ForwardButton);
+ } else if (swipe->swipeAngle() <= 110 && swipe->swipeAngle() >= 60) {
+ Q_EMIT swipeUp();
+ }
+ m_isSwipeGesture = true;
+ }
+}
+
+void KItemListController::twoFingerTapTriggered(QGestureEvent* event, const QTransform& transform)
+{
+ const KTwoFingerTap* twoTap = static_cast<KTwoFingerTap*>(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) {
return hoverLeaveEvent(static_cast<QGraphicsSceneHoverEvent*>(event), QTransform());
case QEvent::GraphicsSceneResize:
return resizeEvent(static_cast<QGraphicsSceneResizeEvent*>(event), transform);
+ case QEvent::Gesture:
+ return gestureEvent(static_cast<QGestureEvent*>(event), transform);
+ case QEvent::TouchBegin:
+ return touchBeginEvent(static_cast<QTouchEvent*>(event), transform);
default:
break;
}
KItemSet selectedItems;
// Select all visible items that intersect with the rubberband
- foreach (const KItemListWidget* widget, m_view->visibleItemListWidgets()) {
+ const auto widgets = m_view->visibleItemListWidgets();
+ for (const KItemListWidget* widget : widgets) {
const int index = widget->index();
const QRectF widgetRect = m_view->itemRect(index);
{
Q_ASSERT(m_view);
- foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) {
+ const auto widgets = m_view->visibleItemListWidgets();
+ for (KItemListWidget* widget : widgets) {
if (widget->isHovered()) {
return widget;
}
{
Q_ASSERT(m_view);
- foreach (KItemListWidget* widget, m_view->visibleItemListWidgets()) {
+ const auto widgets = m_view->visibleItemListWidgets();
+ for (KItemListWidget* widget : widgets) {
const QPointF mappedPos = widget->mapFromItem(m_view, pos);
const bool hovered = widget->contains(mappedPos) &&
}
}
+bool KItemListController::onPress(const QPoint& screenPos, const QPointF& pos, const Qt::KeyboardModifiers modifiers, const Qt::MouseButtons buttons)
+{
+ Q_EMIT mouseButtonPressed(m_pressedIndex, buttons);
+
+ if (buttons & (Qt::BackButton | Qt::ForwardButton)) {
+ // Do not select items when clicking the back/forward buttons, see
+ // https://bugs.kde.org/show_bug.cgi?id=327412.
+ 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 = 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)) {
+ Q_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) {
+ Q_EMIT itemContextMenuRequested(m_pressedIndex, screenPos);
+ }
+
+ return true;
+ }
+
+ if (buttons & Qt::RightButton) {
+ const QRectF headerBounds = m_view->headerBoundaries();
+ if (headerBounds.contains(pos)) {
+ Q_EMIT headerContextMenuRequested(screenPos);
+ } else {
+ Q_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 controlPressed = modifiers & Qt::ControlModifier;
+ const bool shiftOrControlPressed = modifiers & Qt::ShiftModifier ||
+ controlPressed;
+
+ 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);
+
+ Q_EMIT itemExpansionToggleClicked(index);
+ emitItemActivated = false;
+ } else if (shiftOrControlPressed) {
+ // The mouse click should only update the selection, not trigger the item
+ emitItemActivated = false;
+ // When Ctrl-clicking an item when in single selection mode
+ // i.e. where Ctrl won't change the selection, pretend it was middle clicked
+ if (controlPressed && m_selectionBehavior == SingleSelection) {
+ Q_EMIT itemMiddleClicked(index);
+ }
+ } else if (!(m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick) || m_singleClickActivationEnforced)) {
+ if (touch) {
+ emitItemActivated = true;
+ } else {
+ emitItemActivated = false;
+ }
+ }
+ if (emitItemActivated) {
+ Q_EMIT itemActivated(index);
+ }
+ } else if (buttons & Qt::MiddleButton) {
+ Q_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_scrollerIsScrolling = true;
+ } else if (newState == QScroller::Inactive) {
+ m_scrollerIsScrolling = false;
+ }
+}