Krita Source Code Documentation
Loading...
Searching...
No Matches
KisLongPressEventFilter.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-3.0-or-later
3#include <QAbstractButton>
4#include <QAbstractScrollArea>
5#include <QAbstractSlider>
6#include <QAbstractSpinBox>
7#include <QApplication>
8#include <QComboBox>
9#include <QContextMenuEvent>
10#include <QLineEdit>
11#include <QMenu>
12#include <QMenuBar>
13#include <QMouseEvent>
14#include <QStyleHints>
15#include <QTimer>
16#ifdef Q_OS_ANDROID
17#include <QtAndroid>
18#endif
19
21 : QObject(parent)
22{
23 m_timer = new QTimer(this);
24 m_timer->setTimerType(Qt::CoarseTimer);
25 m_timer->setSingleShot(true);
27}
28
29bool KisLongPressEventFilter::eventFilter(QObject *watched, QEvent *event)
30{
31 switch (event->type()) {
32 case QEvent::MouseButtonPress:
33 case QEvent::MouseButtonDblClick:
34 handleMousePress(qobject_cast<QWidget *>(watched), static_cast<QMouseEvent *>(event));
35 break;
36 case QEvent::MouseMove:
37 handleMouseMove(static_cast<QMouseEvent *>(event));
38 break;
39 case QEvent::MouseButtonRelease:
40 cancel();
41 break;
42 default:
43 break;
44 }
45 return QObject::eventFilter(watched, event);
46}
47
48void KisLongPressEventFilter::handleMousePress(QWidget *target, const QMouseEvent *me)
49{
51 const QStyleHints *sh = qApp->styleHints();
52 long long distance = qMax(MINIMUM_DISTANCE, sh->startDragDistance());
54#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
55 m_pressLocalPos = me->pos();
56 m_pressGlobalPos = me->globalPos();
57#else
58 m_pressLocalPos = me->position().toPoint();
59 m_pressGlobalPos = me->globalPosition().toPoint();
60#endif
62#ifdef Q_OS_ANDROID
63 int longPressInterval =
64 QAndroidJniObject::callStaticMethod<jint>("org/krita/android/MainActivity", "getLongPressTimeout", "()I");
65#else
66 int longPressInterval = sh->mousePressAndHoldInterval();
67#endif
68 m_timer->start(qMax(MINIMUM_DELAY, longPressInterval));
69 } else {
70 cancel();
71 }
72}
73
75{
76#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
77 QPoint globalPos = me->globalPos();
78#else
79 QPoint globalPos = me->globalPosition().toPoint();
80#endif
81 if (m_timer->isActive() && !isWithinDistance(globalPos)) {
82 cancel();
83 }
84}
85
87{
88 m_timer->stop();
89 m_target.clear();
90}
91
92bool KisLongPressEventFilter::isWithinDistance(const QPoint &globalPos) const
93{
94 long long x = globalPos.x() - m_pressGlobalPos.x();
95 long long y = globalPos.y() - m_pressGlobalPos.y();
96 return (x * x) + (y * y) <= m_distanceSquared;
97}
98
100{
101 QWidget *target = m_target.data();
103 qApp->postEvent(target, new QContextMenuEvent(QContextMenuEvent::Mouse, m_pressLocalPos, m_pressGlobalPos));
104 }
105}
106
108{
109 while (target && target->isVisible() && isLongPressableWidget(target)) {
110 switch (target->contextMenuPolicy()) {
111 case Qt::NoContextMenu:
112 target = target->parentWidget();
113 break;
114 case Qt::PreventContextMenu:
115 return false;
116 default:
117 return true;
118 }
119 }
120 return false;
121}
122
124{
125 QVariant prop = target->property(ENABLED_PROPERTY);
126 if (prop.isValid()) {
127 return prop.toBool();
128 }
129
130 // Several widget types don't have desirable long-press behavior. If they're
131 // our own widgets, we should just fix that in the widget, but we can"t
132 // really sensibly do that to Qt's widgets. Luckily however, those widgets
133 // really shouldn't have context menus in the first place, so we can just
134 // disregard them as long-press targets and be done with it.
135
136 // Buttons get depressed when you click and hold on them and won't pop back
137 // up if a context menu is opened during that. They may also have a menu
138 // attached to them that the user operates like a context menu.
139 if (qobject_cast<QAbstractButton *>(target)) {
140 return false;
141 }
142
143 // Slightly non-obvious, but this thing has many child classes, like text
144 // areas or the canvas, none of which have sensible context menus.
145 if (qobject_cast<QAbstractScrollArea *>(target)) {
146 return false;
147 }
148
149 // Sliders (including scrollbars) may get dragged slowly.
150 if (qobject_cast<QAbstractSlider *>(target)) {
151 return false;
152 }
153
154 // Spinners may get held down to keep a number spinning.
155 if (qobject_cast<QAbstractSpinBox *>(target)) {
156 return false;
157 }
158
159 // Combo boxes get held down to pick an item.
160 if (qobject_cast<QComboBox *>(target)) {
161 return false;
162 }
163
164 // Text editing will have its own long-press handling.
165 if (qobject_cast<QLineEdit *>(target)) {
166 return false;
167 }
168
169 // Menus don't have context menus.
170 if (qobject_cast<QMenu *>(target)) {
171 return false;
172 }
173
174 // Menu bars don't have context menus either.
175 if (qobject_cast<QMenuBar *>(target)) {
176 return false;
177 }
178
179 return true;
180}
KisMagneticGraph::vertex_descriptor target(typename KisMagneticGraph::edge_descriptor e, KisMagneticGraph g)
qreal distance(const QPointF &p1, const QPointF &p2)
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
static constexpr char ENABLED_PROPERTY[]
void handleMousePress(QWidget *target, const QMouseEvent *me)
static constexpr int MINIMUM_DISTANCE
KisLongPressEventFilter(QObject *parent=nullptr)
bool isWithinDistance(const QPoint &globalPos) const
void handleMouseMove(const QMouseEvent *me)
static bool isLongPressableWidget(QWidget *target)
bool eventFilter(QObject *watched, QEvent *event) override
static constexpr int MINIMUM_DELAY
static bool isContextMenuTarget(QWidget *target)