diff options
author | Mitch Curtis <mitch.curtis@qt.io> | 2016-07-05 13:50:59 +0200 |
---|---|---|
committer | Mitch Curtis <mitch.curtis@qt.io> | 2016-07-06 14:21:18 +0000 |
commit | 99c170e3fcd214a15fda00e3419666c1a456b76e (patch) | |
tree | 8ffb2b968c78f356aed3e050f100d7a0b1a9c01b | |
parent | 39f80448dac5cd46b8dd26b035a17ea5a99c5c40 (diff) |
Add pressed() and released() signals to TextField and TextArea
Users need these to e.g. open context menus when editors are
right-clicked.
[ChangeLog][TextArea][TextField] Added pressed() and released()
signals.
Change-Id: I32b79a8de0120a4f9115fa1c3cb832aff0134a15
Task-number: QTBUG-51009
Reviewed-by: J-P Nurmi <jpnurmi@qt.io>
-rw-r--r-- | src/quicktemplates2/qquickpresshandler.cpp | 37 | ||||
-rw-r--r-- | src/quicktemplates2/qquickpresshandler_p_p.h | 2 | ||||
-rw-r--r-- | src/quicktemplates2/qquicktextarea.cpp | 30 | ||||
-rw-r--r-- | src/quicktemplates2/qquicktextarea_p.h | 2 | ||||
-rw-r--r-- | src/quicktemplates2/qquicktextfield.cpp | 25 | ||||
-rw-r--r-- | src/quicktemplates2/qquicktextfield_p.h | 2 | ||||
-rw-r--r-- | tests/auto/controls/data/tst_textarea.qml | 170 | ||||
-rw-r--r-- | tests/auto/controls/data/tst_textfield.qml | 170 |
8 files changed, 434 insertions, 4 deletions
diff --git a/src/quicktemplates2/qquickpresshandler.cpp b/src/quicktemplates2/qquickpresshandler.cpp index 13468a58..5c868ee2 100644 --- a/src/quicktemplates2/qquickpresshandler.cpp +++ b/src/quicktemplates2/qquickpresshandler.cpp @@ -48,6 +48,8 @@ QQuickPressHandler::QQuickPressHandler() : control(nullptr) , longPress(false) , pressAndHoldSignalIndex(-1) + , pressedSignalIndex(-1) + , releasedSignalIndex(-1) , delayedMousePressEvent(nullptr) { } @@ -61,6 +63,21 @@ void QQuickPressHandler::mousePressEvent(QMouseEvent *event) } else { timer.stop(); } + + if (pressedSignalIndex == -1) + pressedSignalIndex = control->metaObject()->indexOfSignal("pressed(QQuickMouseEvent*)"); + Q_ASSERT(pressedSignalIndex != -1); + + if (QObjectPrivate::get(control)->isSignalConnected(pressedSignalIndex)) { + QQuickMouseEvent mev; + mev.reset(pressPos.x(), pressPos.y(), event->button(), event->buttons(), + QGuiApplication::keyboardModifiers(), false/*isClick*/, false/*wasHeld*/); + mev.setAccepted(true); + QQuickMouseEvent *mevPtr = &mev; + void *args[] = { nullptr, &mevPtr }; + QMetaObject::metacall(control, QMetaObject::InvokeMetaMethod, pressedSignalIndex, args); + event->setAccepted(mev.isAccepted()); + } } void QQuickPressHandler::mouseMoveEvent(QMouseEvent *event) @@ -69,10 +86,26 @@ void QQuickPressHandler::mouseMoveEvent(QMouseEvent *event) timer.stop(); } -void QQuickPressHandler::mouseReleaseEvent(QMouseEvent *) +void QQuickPressHandler::mouseReleaseEvent(QMouseEvent *event) { - if (!longPress) + if (!longPress) { timer.stop(); + + if (releasedSignalIndex == -1) + releasedSignalIndex = control->metaObject()->indexOfSignal("released(QQuickMouseEvent*)"); + Q_ASSERT(releasedSignalIndex != -1); + + if (QObjectPrivate::get(control)->isSignalConnected(releasedSignalIndex)) { + QQuickMouseEvent mev; + mev.reset(pressPos.x(), pressPos.y(), event->button(), event->buttons(), + QGuiApplication::keyboardModifiers(), false/*isClick*/, false/*wasHeld*/); + mev.setAccepted(true); + QQuickMouseEvent *mevPtr = &mev; + void *args[] = { nullptr, &mevPtr }; + QMetaObject::metacall(control, QMetaObject::InvokeMetaMethod, releasedSignalIndex, args); + event->setAccepted(mev.isAccepted()); + } + } } void QQuickPressHandler::timerEvent(QTimerEvent *) diff --git a/src/quicktemplates2/qquickpresshandler_p_p.h b/src/quicktemplates2/qquickpresshandler_p_p.h index dec6f202..e432fe10 100644 --- a/src/quicktemplates2/qquickpresshandler_p_p.h +++ b/src/quicktemplates2/qquickpresshandler_p_p.h @@ -74,6 +74,8 @@ struct QQuickPressHandler QPointF pressPos; bool longPress; int pressAndHoldSignalIndex; + int pressedSignalIndex; + int releasedSignalIndex; QMouseEvent *delayedMousePressEvent; }; diff --git a/src/quicktemplates2/qquicktextarea.cpp b/src/quicktemplates2/qquicktextarea.cpp index 7bda34f5..96b690fa 100644 --- a/src/quicktemplates2/qquicktextarea.cpp +++ b/src/quicktemplates2/qquicktextarea.cpp @@ -104,6 +104,30 @@ QT_BEGIN_NAMESPACE This signal is emitted when there is a long press (the delay depends on the platform plugin). The \l {MouseEvent}{mouse} parameter provides information about the press, including the x and y position of the press, and which button is pressed. + + \sa pressed, released +*/ + +/*! + \qmlsignal QtQuick.Controls::TextArea::pressed(MouseEvent event) + \since QtQuick.Controls 2.1 + + This signal is emitted when the text area is pressed by the user. + The \l {MouseEvent}{event} parameter provides information about the press, + including the x and y position of the press, and which button is pressed. + + \sa released, pressAndHold +*/ + +/*! + \qmlsignal QtQuick.Controls::TextArea::released(MouseEvent event) + \since QtQuick.Controls 2.1 + + This signal is emitted when the text area is released by the user. + The \l {MouseEvent}{event} parameter provides information about the release, + including the x and y position of the press, and which button is pressed. + + \sa pressed, pressAndHold */ QQuickTextAreaPrivate::QQuickTextAreaPrivate() @@ -283,6 +307,7 @@ QQuickTextArea::QQuickTextArea(QQuickItem *parent) : { Q_D(QQuickTextArea); setActiveFocusOnTab(true); + setAcceptedMouseButtons(Qt::AllButtons); d->setImplicitResizeEnabled(false); d->pressHandler.control = this; QObjectPrivate::connect(this, &QQuickTextEdit::readOnlyChanged, @@ -617,7 +642,12 @@ void QQuickTextArea::mousePressEvent(QMouseEvent *event) QQuickTextEdit::mousePressEvent(d->pressHandler.delayedMousePressEvent); d->pressHandler.clearDelayedMouseEvent(); } + // Calling the base class implementation will result in QQuickTextControl's + // press handler being called, which ignores events that aren't Qt::LeftButton. + const bool wasAccepted = event->isAccepted(); QQuickTextEdit::mousePressEvent(event); + if (wasAccepted) + event->accept(); } } diff --git a/src/quicktemplates2/qquicktextarea_p.h b/src/quicktemplates2/qquicktextarea_p.h index f52bb01c..92c24bfc 100644 --- a/src/quicktemplates2/qquicktextarea_p.h +++ b/src/quicktemplates2/qquicktextarea_p.h @@ -104,6 +104,8 @@ Q_SIGNALS: Q_REVISION(1) void hoveredChanged(); Q_REVISION(1) void hoverEnabledChanged(); void pressAndHold(QQuickMouseEvent *event); + Q_REVISION(1) void pressed(QQuickMouseEvent *event); + Q_REVISION(1) void released(QQuickMouseEvent *event); protected: void classBegin() override; diff --git a/src/quicktemplates2/qquicktextfield.cpp b/src/quicktemplates2/qquicktextfield.cpp index 44da850a..28e26cd5 100644 --- a/src/quicktemplates2/qquicktextfield.cpp +++ b/src/quicktemplates2/qquicktextfield.cpp @@ -87,6 +87,30 @@ QT_BEGIN_NAMESPACE This signal is emitted when there is a long press (the delay depends on the platform plugin). The \l {MouseEvent}{mouse} parameter provides information about the press, including the x and y position of the press, and which button is pressed. + + \sa pressed, released +*/ + +/*! + \qmlsignal QtQuick.Controls::TextField::pressed(MouseEvent event) + \since QtQuick.Controls 2.1 + + This signal is emitted when the text field is pressed by the user. + The \l {MouseEvent}{event} parameter provides information about the press, + including the x and y position of the press, and which button is pressed. + + \sa released, pressAndHold +*/ + +/*! + \qmlsignal QtQuick.Controls::TextField::released(MouseEvent event) + \since QtQuick.Controls 2.1 + + This signal is emitted when the text field is released by the user. + The \l {MouseEvent}{event} parameter provides information about the release, + including the x and y position of the press, and which button is pressed. + + \sa pressed, pressAndHold */ QQuickTextFieldPrivate::QQuickTextFieldPrivate() @@ -153,6 +177,7 @@ QQuickTextField::QQuickTextField(QQuickItem *parent) : Q_D(QQuickTextField); d->pressHandler.control = this; d->setImplicitResizeEnabled(false); + setAcceptedMouseButtons(Qt::AllButtons); setActiveFocusOnTab(true); QObjectPrivate::connect(this, &QQuickTextInput::readOnlyChanged, d, &QQuickTextFieldPrivate::_q_readOnlyChanged); diff --git a/src/quicktemplates2/qquicktextfield_p.h b/src/quicktemplates2/qquicktextfield_p.h index 851435a7..79b4c39d 100644 --- a/src/quicktemplates2/qquicktextfield_p.h +++ b/src/quicktemplates2/qquicktextfield_p.h @@ -101,6 +101,8 @@ Q_SIGNALS: Q_REVISION(1) void hoveredChanged(); Q_REVISION(1) void hoverEnabledChanged(); void pressAndHold(QQuickMouseEvent *mouse); + Q_REVISION(1) void pressed(QQuickMouseEvent *event); + Q_REVISION(1) void released(QQuickMouseEvent *event); protected: void classBegin() override; diff --git a/tests/auto/controls/data/tst_textarea.qml b/tests/auto/controls/data/tst_textarea.qml index 9bbbf726..e3699759 100644 --- a/tests/auto/controls/data/tst_textarea.qml +++ b/tests/auto/controls/data/tst_textarea.qml @@ -40,7 +40,7 @@ import QtQuick 2.2 import QtTest 1.0 -import QtQuick.Controls 2.0 +import QtQuick.Controls 2.1 TestCase { id: testCase @@ -198,4 +198,172 @@ TestCase { control.destroy() } + + function test_pressedReleased_data() { + return [ + { + tag: "pressed outside", x: -1, y: -1, button: Qt.LeftButton, + controlPressEvent: null, + controlReleaseEvent: null, + parentPressEvent: { + x: 0, y: 0, button: Qt.LeftButton, buttons: Qt.LeftButton, modifiers: Qt.NoModifier, wasHeld: false, isClick: false + }, + parentReleaseEvent: { + x: 0, y: 0, button: Qt.LeftButton, buttons: Qt.NoButton, modifiers: Qt.NoModifier, wasHeld: false, isClick: false + }, + }, + { + tag: "left click", x: 0, y: 0, button: Qt.LeftButton, + controlPressEvent: { + x: 0, y: 0, button: Qt.LeftButton, buttons: Qt.LeftButton, modifiers: Qt.NoModifier, wasHeld: false, isClick: false + }, + controlReleaseEvent: { + x: 0, y: 0, button: Qt.LeftButton, buttons: Qt.NoButton, modifiers: Qt.NoModifier, wasHeld: false, isClick: false + }, + parentPressEvent: null, + parentReleaseEvent: null, + }, + { + tag: "right click", x: 0, y: 0, button: Qt.RightButton, + controlPressEvent: { + x: 0, y: 0, button: Qt.RightButton, buttons: Qt.RightButton, modifiers: Qt.NoModifier, wasHeld: false, isClick: false + }, + controlReleaseEvent: { + x: 0, y: 0, button: Qt.RightButton, buttons: Qt.NoButton, modifiers: Qt.NoModifier, wasHeld: false, isClick: false + }, + parentPressEvent: null, + parentReleaseEvent: null, + }, + ]; + } + + Component { + id: mouseAreaComponent + MouseArea { + anchors.fill: parent + } + } + + function checkMouseEvent(event, expectedEvent) { + compare(event.x, expectedEvent.x) + compare(event.y, expectedEvent.y) + compare(event.button, expectedEvent.button) + compare(event.buttons, expectedEvent.buttons) + } + + function test_pressedReleased(data) { + var mouseArea = mouseAreaComponent.createObject(testCase) + verify(mouseArea) + var control = textArea.createObject(mouseArea) + verify(control) + + // Give enough room to check presses outside of the control and on the parent. + control.x = 1; + control.y = 1; + + function checkControlPressEvent(event) { + checkMouseEvent(event, data.controlPressEvent) + } + function checkControlReleaseEvent(event) { + checkMouseEvent(event, data.controlReleaseEvent) + } + function checkParentPressEvent(event) { + checkMouseEvent(event, data.parentPressEvent) + } + function checkParentReleaseEvent(event) { + checkMouseEvent(event, data.parentReleaseEvent) + } + + // Can't use signalArguments, because the event won't live that long. + if (data.controlPressEvent) + control.onPressed.connect(checkControlPressEvent) + if (data.controlReleaseEvent) + control.onReleased.connect(checkControlReleaseEvent) + if (data.parentPressEvent) + control.onPressed.connect(checkParentPressEvent) + if (data.parentReleaseEvent) + control.onReleased.connect(checkParentReleaseEvent) + + var controlPressedSpy = signalSpy.createObject(control, { target: control, signalName: "pressed" }) + verify(controlPressedSpy.valid) + var controlReleasedSpy = signalSpy.createObject(control, { target: control, signalName: "released" }) + verify(controlReleasedSpy.valid) + var parentPressedSpy = signalSpy.createObject(mouseArea, { target: mouseArea, signalName: "pressed" }) + verify(parentPressedSpy.valid) + var parentReleasedSpy = signalSpy.createObject(mouseArea, { target: mouseArea, signalName: "released" }) + verify(parentReleasedSpy.valid) + + mousePress(control, data.x, data.y, data.button) + compare(controlPressedSpy.count, data.controlPressEvent ? 1 : 0) + compare(parentPressedSpy.count, data.parentPressEvent ? 1 : 0) + mouseRelease(control, data.x, data.y, data.button) + compare(controlReleasedSpy.count, data.controlReleaseEvent ? 1 : 0) + compare(parentReleasedSpy.count, data.parentReleaseEvent ? 1 : 0) + + mouseArea.destroy() + } + + Component { + id: ignoreTextArea + + TextArea { + property bool ignorePress: false + property bool ignoreRelease: false + + onPressed: if (ignorePress) event.accepted = false + onReleased: if (ignoreRelease) event.accepted = false + } + } + + function checkEventAccepted(event) { + compare(event.accepted, true) + } + + function checkEventIgnored(event) { + compare(event.accepted, false) + } + + function test_ignorePressRelease() { + var mouseArea = mouseAreaComponent.createObject(testCase) + verify(mouseArea) + var control = ignoreTextArea.createObject(mouseArea) + verify(control) + + var controlPressedSpy = signalSpy.createObject(control, { target: control, signalName: "pressed" }) + verify(controlPressedSpy.valid) + var controlReleasedSpy = signalSpy.createObject(control, { target: control, signalName: "released" }) + verify(controlReleasedSpy.valid) + var parentPressedSpy = signalSpy.createObject(mouseArea, { target: mouseArea, signalName: "pressed" }) + verify(parentPressedSpy.valid) + var parentReleasedSpy = signalSpy.createObject(mouseArea, { target: mouseArea, signalName: "released" }) + verify(parentReleasedSpy.valid) + + // Ignore only press events. + control.onPressed.connect(checkEventIgnored) + control.ignorePress = true + mousePress(control, 0, 0, data.button) + // The control will still get the signal, it just won't accept the event. + compare(controlPressedSpy.count, 1) + compare(parentPressedSpy.count, 1) + mouseRelease(control, 0, 0, data.button) + compare(controlReleasedSpy.count, 0) + compare(parentReleasedSpy.count, 1) + control.onPressed.disconnect(checkEventIgnored) + + // Ignore only release events. + control.onPressed.connect(checkEventAccepted) + control.onReleased.connect(checkEventIgnored) + control.ignorePress = false + control.ignoreRelease = true + mousePress(control, 0, 0, data.button) + compare(controlPressedSpy.count, 2) + compare(parentPressedSpy.count, 1) + mouseRelease(control, 0, 0, data.button) + compare(controlReleasedSpy.count, 1) + compare(parentReleasedSpy.count, 1) + control.onPressed.disconnect(checkEventAccepted) + control.onReleased.disconnect(checkEventIgnored) + + mouseArea.destroy() + } } diff --git a/tests/auto/controls/data/tst_textfield.qml b/tests/auto/controls/data/tst_textfield.qml index 18bcd63f..eb6cbb36 100644 --- a/tests/auto/controls/data/tst_textfield.qml +++ b/tests/auto/controls/data/tst_textfield.qml @@ -40,7 +40,7 @@ import QtQuick 2.2 import QtTest 1.0 -import QtQuick.Controls 2.0 +import QtQuick.Controls 2.1 TestCase { id: testCase @@ -166,4 +166,172 @@ TestCase { control.destroy() } + + function test_pressedReleased_data() { + return [ + { + tag: "pressed outside", x: -1, y: -1, button: Qt.LeftButton, + controlPressEvent: null, + controlReleaseEvent: null, + parentPressEvent: { + x: 0, y: 0, button: Qt.LeftButton, buttons: Qt.LeftButton, modifiers: Qt.NoModifier, wasHeld: false, isClick: false + }, + parentReleaseEvent: { + x: 0, y: 0, button: Qt.LeftButton, buttons: Qt.NoButton, modifiers: Qt.NoModifier, wasHeld: false, isClick: false + }, + }, + { + tag: "left click", x: 0, y: 0, button: Qt.LeftButton, + controlPressEvent: { + x: 0, y: 0, button: Qt.LeftButton, buttons: Qt.LeftButton, modifiers: Qt.NoModifier, wasHeld: false, isClick: false + }, + controlReleaseEvent: { + x: 0, y: 0, button: Qt.LeftButton, buttons: Qt.NoButton, modifiers: Qt.NoModifier, wasHeld: false, isClick: false + }, + parentPressEvent: null, + parentReleaseEvent: null, + }, + { + tag: "right click", x: 0, y: 0, button: Qt.RightButton, + controlPressEvent: { + x: 0, y: 0, button: Qt.RightButton, buttons: Qt.RightButton, modifiers: Qt.NoModifier, wasHeld: false, isClick: false + }, + controlReleaseEvent: { + x: 0, y: 0, button: Qt.RightButton, buttons: Qt.NoButton, modifiers: Qt.NoModifier, wasHeld: false, isClick: false + }, + parentPressEvent: null, + parentReleaseEvent: null, + }, + ]; + } + + Component { + id: mouseAreaComponent + MouseArea { + anchors.fill: parent + } + } + + function checkMouseEvent(event, expectedEvent) { + compare(event.x, expectedEvent.x) + compare(event.y, expectedEvent.y) + compare(event.button, expectedEvent.button) + compare(event.buttons, expectedEvent.buttons) + } + + function test_pressedReleased(data) { + var mouseArea = mouseAreaComponent.createObject(testCase) + verify(mouseArea) + var control = textField.createObject(mouseArea) + verify(control) + + // Give enough room to check presses outside of the control and on the parent. + control.x = 1; + control.y = 1; + + function checkControlPressEvent(event) { + checkMouseEvent(event, data.controlPressEvent) + } + function checkControlReleaseEvent(event) { + checkMouseEvent(event, data.controlReleaseEvent) + } + function checkParentPressEvent(event) { + checkMouseEvent(event, data.parentPressEvent) + } + function checkParentReleaseEvent(event) { + checkMouseEvent(event, data.parentReleaseEvent) + } + + // Can't use signalArguments, because the event won't live that long. + if (data.controlPressEvent) + control.onPressed.connect(checkControlPressEvent) + if (data.controlReleaseEvent) + control.onReleased.connect(checkControlReleaseEvent) + if (data.parentPressEvent) + control.onPressed.connect(checkParentPressEvent) + if (data.parentReleaseEvent) + control.onReleased.connect(checkParentReleaseEvent) + + var controlPressedSpy = signalSpy.createObject(control, { target: control, signalName: "pressed" }) + verify(controlPressedSpy.valid) + var controlReleasedSpy = signalSpy.createObject(control, { target: control, signalName: "released" }) + verify(controlReleasedSpy.valid) + var parentPressedSpy = signalSpy.createObject(mouseArea, { target: mouseArea, signalName: "pressed" }) + verify(parentPressedSpy.valid) + var parentReleasedSpy = signalSpy.createObject(mouseArea, { target: mouseArea, signalName: "released" }) + verify(parentReleasedSpy.valid) + + mousePress(control, data.x, data.y, data.button) + compare(controlPressedSpy.count, data.controlPressEvent ? 1 : 0) + compare(parentPressedSpy.count, data.parentPressEvent ? 1 : 0) + mouseRelease(control, data.x, data.y, data.button) + compare(controlReleasedSpy.count, data.controlReleaseEvent ? 1 : 0) + compare(parentReleasedSpy.count, data.parentReleaseEvent ? 1 : 0) + + mouseArea.destroy() + } + + Component { + id: ignoreTextField + + TextField { + property bool ignorePress: false + property bool ignoreRelease: false + + onPressed: if (ignorePress) event.accepted = false + onReleased: if (ignoreRelease) event.accepted = false + } + } + + function checkEventAccepted(event) { + compare(event.accepted, true) + } + + function checkEventIgnored(event) { + compare(event.accepted, false) + } + + function test_ignorePressRelease() { + var mouseArea = mouseAreaComponent.createObject(testCase) + verify(mouseArea) + var control = ignoreTextField.createObject(mouseArea) + verify(control) + + var controlPressedSpy = signalSpy.createObject(control, { target: control, signalName: "pressed" }) + verify(controlPressedSpy.valid) + var controlReleasedSpy = signalSpy.createObject(control, { target: control, signalName: "released" }) + verify(controlReleasedSpy.valid) + var parentPressedSpy = signalSpy.createObject(mouseArea, { target: mouseArea, signalName: "pressed" }) + verify(parentPressedSpy.valid) + var parentReleasedSpy = signalSpy.createObject(mouseArea, { target: mouseArea, signalName: "released" }) + verify(parentReleasedSpy.valid) + + // Ignore only press events. + control.onPressed.connect(checkEventIgnored) + control.ignorePress = true + mousePress(control, 0, 0, data.button) + // The control will still get the signal, it just won't accept the event. + compare(controlPressedSpy.count, 1) + compare(parentPressedSpy.count, 1) + mouseRelease(control, 0, 0, data.button) + compare(controlReleasedSpy.count, 0) + compare(parentReleasedSpy.count, 1) + control.onPressed.disconnect(checkEventIgnored) + + // Ignore only release events. + control.onPressed.connect(checkEventAccepted) + control.onReleased.connect(checkEventIgnored) + control.ignorePress = false + control.ignoreRelease = true + mousePress(control, 0, 0, data.button) + compare(controlPressedSpy.count, 2) + compare(parentPressedSpy.count, 1) + mouseRelease(control, 0, 0, data.button) + compare(controlReleasedSpy.count, 1) + compare(parentReleasedSpy.count, 1) + control.onPressed.disconnect(checkEventAccepted) + control.onReleased.disconnect(checkEventIgnored) + + mouseArea.destroy() + } } |