diff options
author | Knud Dollereder <knud.dollereder@qt.io> | 2020-11-12 12:08:22 +0100 |
---|---|---|
committer | Knud Dollereder <knud.dollereder@qt.io> | 2020-11-17 11:53:11 +0000 |
commit | 83187ba54fd63b9d9985d5f7fcddc7d8d3463c28 (patch) | |
tree | dcad02cd1f3f07d703493ebfa6972bcc3ab564f4 /src/plugins/qmldesigner/components/componentcore | |
parent | ad2d635a099f7d0f7035442134cf519d690dda5b (diff) |
Enable pan and zoom gestures for the formeditor
Use continuous zoom for mouse-wheel zoom
Directly apply zoom and not by detouring through custom notification
Moved logic related to zooming from the zoom-action into the view
Change-Id: Ib56779f3ea686736cc419aec94d7b5566091fb96
Reviewed-by: Henning Gründl <henning.gruendl@qt.io>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
Diffstat (limited to 'src/plugins/qmldesigner/components/componentcore')
7 files changed, 375 insertions, 113 deletions
diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore.pri b/src/plugins/qmldesigner/components/componentcore/componentcore.pri index decf24a5a8..ef97a93343 100644 --- a/src/plugins/qmldesigner/components/componentcore/componentcore.pri +++ b/src/plugins/qmldesigner/components/componentcore/componentcore.pri @@ -5,6 +5,7 @@ SOURCES += addimagesdialog.cpp SOURCES += changestyleaction.cpp SOURCES += theme.cpp SOURCES += findimplementation.cpp +SOURCES += gestures.cpp SOURCES += addsignalhandlerdialog.cpp SOURCES += layoutingridlayout.cpp SOURCES += abstractactiongroup.cpp @@ -24,6 +25,7 @@ HEADERS += addimagesdialog.h HEADERS += changestyleaction.h HEADERS += theme.h HEADERS += findimplementation.h +HEADERS += gestures.h HEADERS += addsignalhandlerdialog.h HEADERS += layoutingridlayout.h HEADERS += abstractactiongroup.h diff --git a/src/plugins/qmldesigner/components/componentcore/gestures.cpp b/src/plugins/qmldesigner/components/componentcore/gestures.cpp new file mode 100644 index 0000000000..29ff23c243 --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/gestures.cpp @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Design Tooling +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "gestures.h" + +#include <QWidget> + +namespace QmlDesigner { + +Qt::GestureType TwoFingerSwipe::m_type = static_cast<Qt::GestureType>(0); + +TwoFingerSwipe::TwoFingerSwipe() {} + +Qt::GestureType TwoFingerSwipe::type() +{ + return m_type; +} + +void TwoFingerSwipe::registerRecognizer() +{ + m_type = QGestureRecognizer::registerRecognizer(new TwoFingerSwipeRecognizer()); +} + +QPointF TwoFingerSwipe::direction() const +{ + return m_current.center() - m_last.center(); +} + +void TwoFingerSwipe::reset() +{ + m_start = QLineF(); + m_current = QLineF(); + m_last = QLineF(); +} + +QGestureRecognizer::Result TwoFingerSwipe::begin(QTouchEvent *event) +{ + Q_UNUSED(event); + return QGestureRecognizer::MayBeGesture; +} + +QGestureRecognizer::Result TwoFingerSwipe::update(QTouchEvent *event) +{ + if (event->touchPoints().size() != 2) { + if (state() == Qt::NoGesture) + return QGestureRecognizer::Ignore; + else + return QGestureRecognizer::FinishGesture; + } + + QTouchEvent::TouchPoint p0 = event->touchPoints().at(0); + QTouchEvent::TouchPoint p1 = event->touchPoints().at(1); + + QLineF line(p0.scenePos(), p1.screenPos()); + + if (m_start.isNull()) { + m_start = line; + m_current = line; + m_last = line; + } else { + auto deltaLength = line.length() - m_current.length(); + auto deltaCenter = QLineF(line.center(), m_current.center()).length(); + if (deltaLength > deltaCenter) + return QGestureRecognizer::CancelGesture; + + m_last = m_current; + m_current = line; + } + + setHotSpot(m_current.center()); + + return QGestureRecognizer::TriggerGesture; +} + +QGestureRecognizer::Result TwoFingerSwipe::end(QTouchEvent *event) +{ + Q_UNUSED(event); + bool finish = state() != Qt::NoGesture; + + reset(); + + if (finish) + return QGestureRecognizer::FinishGesture; + else + return QGestureRecognizer::CancelGesture; +} + +TwoFingerSwipeRecognizer::TwoFingerSwipeRecognizer() + : QGestureRecognizer() +{} + +QGesture *TwoFingerSwipeRecognizer::create(QObject *target) +{ + if (target && target->isWidgetType()) + qobject_cast<QWidget *>(target)->setAttribute(Qt::WA_AcceptTouchEvents); + + return new TwoFingerSwipe; +} + +QGestureRecognizer::Result TwoFingerSwipeRecognizer::recognize(QGesture *gesture, + QObject *, + QEvent *event) +{ + if (gesture->gestureType() != TwoFingerSwipe::type()) + return QGestureRecognizer::Ignore; + + TwoFingerSwipe *swipe = static_cast<TwoFingerSwipe *>(gesture); + QTouchEvent *touch = static_cast<QTouchEvent *>(event); + + switch (event->type()) { + case QEvent::TouchBegin: + return swipe->begin(touch); + + case QEvent::TouchUpdate: + return swipe->update(touch); + + case QEvent::TouchEnd: + return swipe->end(touch); + + default: + return QGestureRecognizer::Ignore; + } +} + +void TwoFingerSwipeRecognizer::reset(QGesture *gesture) +{ + if (gesture->gestureType() == TwoFingerSwipe::type()) { + TwoFingerSwipe *swipe = static_cast<TwoFingerSwipe *>(gesture); + swipe->reset(); + } + QGestureRecognizer::reset(gesture); +} + +} // End namespace QmlDesigner. diff --git a/src/plugins/qmldesigner/components/componentcore/gestures.h b/src/plugins/qmldesigner/components/componentcore/gestures.h new file mode 100644 index 0000000000..3100560904 --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/gestures.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Design Tooling +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ +#pragma once + +#include <QGesture> +#include <QGestureRecognizer> +#include <QLineF> + +QT_FORWARD_DECLARE_CLASS(QTouchEvent) + +namespace QmlDesigner { + +class TwoFingerSwipe : public QGesture +{ + Q_OBJECT + +public: + TwoFingerSwipe(); + + static Qt::GestureType type(); + static void registerRecognizer(); + + QPointF direction() const; + + void reset(); + QGestureRecognizer::Result begin(QTouchEvent *event); + QGestureRecognizer::Result update(QTouchEvent *event); + QGestureRecognizer::Result end(QTouchEvent *event); + +private: + static Qt::GestureType m_type; + + QLineF m_start; + QLineF m_current; + QLineF m_last; +}; + +class TwoFingerSwipeRecognizer : public QGestureRecognizer +{ +public: + TwoFingerSwipeRecognizer(); + + QGesture *create(QObject *target) override; + + QGestureRecognizer::Result recognize(QGesture *gesture, QObject *watched, QEvent *event) override; + + void reset(QGesture *gesture) override; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp b/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp index 94d6b5ad38..eccd8a517e 100644 --- a/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp +++ b/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp @@ -23,10 +23,13 @@ ** ****************************************************************************/ #include "navigation2d.h" +#include "gestures.h" #include <QGestureEvent> #include <QWheelEvent> +#include <cmath> + namespace QmlDesigner { Navigation2dScrollBar::Navigation2dScrollBar(QWidget *parent) @@ -53,39 +56,60 @@ Navigation2dFilter::Navigation2dFilter(QWidget *parent, Navigation2dScrollBar *s : QObject(parent) , m_scrollbar(scrollbar) { - if (parent) + if (parent) { parent->grabGesture(Qt::PinchGesture); + if (!scrollbar) + parent->grabGesture(TwoFingerSwipe::type()); + } } -bool Navigation2dFilter::eventFilter(QObject *, QEvent *event) +bool Navigation2dFilter::eventFilter(QObject *object, QEvent *event) { if (event->type() == QEvent::Gesture) return gestureEvent(static_cast<QGestureEvent *>(event)); - else if (event->type() == QEvent::Wheel && m_scrollbar) + else if (event->type() == QEvent::Wheel) return wheelEvent(static_cast<QWheelEvent *>(event)); - return QObject::event(event); + return QObject::eventFilter(object, event); } bool Navigation2dFilter::gestureEvent(QGestureEvent *event) { if (QPinchGesture *pinch = static_cast<QPinchGesture *>(event->gesture(Qt::PinchGesture))) { QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags(); - if (changeFlags & QPinchGesture::ScaleFactorChanged) { + if (changeFlags.testFlag(QPinchGesture::ScaleFactorChanged)) { emit zoomChanged(-(1.0 - pinch->scaleFactor()), pinch->startCenterPoint()); event->accept(); return true; } + } else if (TwoFingerSwipe *swipe = static_cast<TwoFingerSwipe *>( + event->gesture(TwoFingerSwipe::type()))) { + emit panChanged(swipe->direction()); + event->accept(); + return true; } return false; } bool Navigation2dFilter::wheelEvent(QWheelEvent *event) { - if (m_scrollbar->postEvent(event)) - event->ignore(); - - return false; + if (m_scrollbar) { + if (m_scrollbar->postEvent(event)) + event->ignore(); + } else if (event->source() == Qt::MouseEventNotSynthesized) { + if (event->modifiers().testFlag(Qt::ControlModifier)) { + if (QPointF angle = event->angleDelta(); !angle.isNull()) { + double delta = std::abs(angle.x()) > std::abs(angle.y()) ? angle.x() : angle.y(); + if (delta > 0) + emit zoomIn(); + else + emit zoomOut(); + event->accept(); + return true; + } + } + } + return true; } } // End namespace QmlDesigner. diff --git a/src/plugins/qmldesigner/components/componentcore/navigation2d.h b/src/plugins/qmldesigner/components/componentcore/navigation2d.h index 434b59055c..fe432b4a6b 100644 --- a/src/plugins/qmldesigner/components/componentcore/navigation2d.h +++ b/src/plugins/qmldesigner/components/componentcore/navigation2d.h @@ -51,6 +51,10 @@ class Navigation2dFilter : public QObject signals: void zoomChanged(double scale, const QPointF &pos); + void panChanged(const QPointF &direction); + + void zoomIn(); + void zoomOut(); public: Navigation2dFilter(QWidget *parent = nullptr, Navigation2dScrollBar *scrollbar = nullptr); diff --git a/src/plugins/qmldesigner/components/componentcore/zoomaction.cpp b/src/plugins/qmldesigner/components/componentcore/zoomaction.cpp index c59c1ffe83..024dd60502 100644 --- a/src/plugins/qmldesigner/components/componentcore/zoomaction.cpp +++ b/src/plugins/qmldesigner/components/componentcore/zoomaction.cpp @@ -24,130 +24,135 @@ ****************************************************************************/ #include "zoomaction.h" +#include "formeditorwidget.h" +#include <algorithm> +#include <iterator> +#include <utility> -#include <QComboBox> #include <QAbstractItemView> +#include <QComboBox> +#include <QToolBar> + +#include <cmath> namespace QmlDesigner { -const int defaultZoomIndex = 13; +// Order matters! +std::array<double, 27> ZoomAction::m_zooms = { + 0.01, 0.02, 0.05, 0.0625, 0.1, 0.125, 0.2, 0.25, 0.33, 0.5, 0.66, 0.75, 0.9, + 1.0, 1.1, 1.25, 1.33, 1.5, 1.66, 1.75, 2.0, 3.0, 4.0, 6.0, 8.0, 10.0, 16.0 +}; -ZoomAction::ZoomAction(QObject *parent) - : QWidgetAction(parent), - m_zoomLevel(1.0), - m_currentComboBoxIndex(defaultZoomIndex) +bool isValidIndex(int index) { + if (index >= 0 && index < static_cast<int>(ZoomAction::zoomLevels().size())) + return true; + return false; } -float ZoomAction::zoomLevel() const -{ - return m_zoomLevel; -} +ZoomAction::ZoomAction(QObject *parent) + : QWidgetAction(parent) + , m_combo(nullptr) +{} -void ZoomAction::zoomIn() +std::array<double, 27> ZoomAction::zoomLevels() { - if (m_currentComboBoxIndex < (m_comboBoxModel->rowCount() - 1)) - emit indexChanged(m_currentComboBoxIndex + 1); + return m_zooms; } -void ZoomAction::zoomOut() +int ZoomAction::indexOf(double zoom) { - if (m_currentComboBoxIndex > 0) - emit indexChanged(m_currentComboBoxIndex - 1); -} + auto finder = [zoom](double val) { return qFuzzyCompare(val, zoom); }; + if (auto iter = std::find_if(m_zooms.begin(), m_zooms.end(), finder); iter != m_zooms.end()) + return static_cast<int>(std::distance(m_zooms.begin(), iter)); -void ZoomAction::resetZoomLevel() -{ - m_zoomLevel = 1.0; - m_currentComboBoxIndex = defaultZoomIndex; - emit reseted(); + return -1; } -void ZoomAction::setZoomLevel(float zoomLevel) +void ZoomAction::setZoomFactor(double zoom) { - if (qFuzzyCompare(m_zoomLevel, zoomLevel)) + if (int index = indexOf(zoom); index >= 0) { + m_combo->setCurrentIndex(index); + m_combo->setToolTip(m_combo->currentText()); return; - - forceZoomLevel(zoomLevel); + } + int rounded = static_cast<int>(std::round(zoom * 100)); + m_combo->setEditable(true); + m_combo->setEditText(QString::number(rounded) + " %"); + m_combo->setToolTip(m_combo->currentText()); } -void ZoomAction::forceZoomLevel(float zoomLevel) +double ZoomAction::setNextZoomFactor(double zoom) { - m_zoomLevel = qBound(0.01f, zoomLevel, 16.0f); - emit zoomLevelChanged(m_zoomLevel); + if (zoom >= m_zooms.back()) + return zoom; + + auto greater = [zoom](double val) { return val > zoom; }; + if (auto iter = std::find_if(m_zooms.begin(), m_zooms.end(), greater); iter != m_zooms.end()) { + auto index = std::distance(m_zooms.begin(), iter); + m_combo->setCurrentIndex(static_cast<int>(index)); + m_combo->setToolTip(m_combo->currentText()); + return *iter; + } + return zoom; } -//initial m_zoomLevel and m_currentComboBoxIndex -const QVector<float> s_zoomFactors = {0.01f, 0.02f, 0.05f, 0.0625f, 0.1f, 0.125f, 0.2f, 0.25f, - 0.33f, 0.5f, 0.66f, 0.75f, 0.9f, 1.0f, 1.1f, 1.25f, 1.33f, - 1.5f, 1.66f, 1.75f, 2.0f, 3.0f, 4.0f, 6.0f, 8.0f, 10.0f, 16.0f }; - -int getZoomIndex(float zoom) +double ZoomAction::setPreviousZoomFactor(double zoom) { - for (int i = 0; i < s_zoomFactors.length(); i++) { - if (qFuzzyCompare(s_zoomFactors.at(i), zoom)) - return i; + if (zoom <= m_zooms.front()) + return zoom; + + auto smaller = [zoom](double val) { return val < zoom; }; + if (auto iter = std::find_if(m_zooms.rbegin(), m_zooms.rend(), smaller); iter != m_zooms.rend()) { + auto index = std::distance(iter, m_zooms.rend() - 1); + m_combo->setCurrentIndex(static_cast<int>(index)); + m_combo->setToolTip(m_combo->currentText()); + return *iter; } - return -1; + return zoom; } -float ZoomAction::getClosestZoomLevel(float zoomLevel) +bool parentIsFormEditor(QWidget *parent) { - int i = 0; - while (i < s_zoomFactors.size() && s_zoomFactors[i] < zoomLevel) - ++i; + while (parent) { + if (qobject_cast<FormEditorWidget *>(parent)) + return true; + parent = qobject_cast<QWidget *>(parent->parent()); + } + return false; +} - return s_zoomFactors[qBound(0, i - 1, s_zoomFactors.size() - 1)]; +QComboBox *createZoomComboBox(QWidget *parent) +{ + auto *combo = new QComboBox(parent); + for (double z : ZoomAction::zoomLevels()) { + const QString name = QString::number(z * 100., 'g', 4) + " %"; + combo->addItem(name, z); + } + return combo; } QWidget *ZoomAction::createWidget(QWidget *parent) { - auto comboBox = new QComboBox(parent); - - /* - * When add zoom levels do not forget to update defaultZoomIndex - */ - if (m_comboBoxModel.isNull()) { - m_comboBoxModel = comboBox->model(); - for (float z : s_zoomFactors) { - const QString name = QString::number(z * 100, 'g', 4) + " %"; - comboBox->addItem(name, z); - } - } else { - comboBox->setModel(m_comboBoxModel.data()); + if (!m_combo && parentIsFormEditor(parent)) { + m_combo = createZoomComboBox(parent); + m_combo->setProperty("hideborder", true); + m_combo->setCurrentIndex(indexOf(1.0)); + m_combo->setToolTip(m_combo->currentText()); + + auto currentChanged = QOverload<int>::of(&QComboBox::currentIndexChanged); + connect(m_combo, currentChanged, this, &ZoomAction::emitZoomLevelChanged); + + return m_combo.data(); } + return nullptr; +} - comboBox->setCurrentIndex(m_currentComboBoxIndex); - comboBox->setToolTip(comboBox->currentText()); - connect(this, &ZoomAction::reseted, comboBox, [this, comboBox]() { - blockSignals(true); - comboBox->setCurrentIndex(m_currentComboBoxIndex); - blockSignals(false); - }); - connect(comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), - [this, comboBox](int index) { - m_currentComboBoxIndex = index; - - if (index == -1) - return; - - const QModelIndex modelIndex(m_comboBoxModel.data()->index(index, 0)); - setZoomLevel(m_comboBoxModel.data()->data(modelIndex, Qt::UserRole).toFloat()); - comboBox->setToolTip(modelIndex.data().toString()); - }); - - connect(this, &ZoomAction::indexChanged, comboBox, &QComboBox::setCurrentIndex); - - connect(this, &ZoomAction::zoomLevelChanged, comboBox, [comboBox](double zoom){ - const int index = getZoomIndex(zoom); - if (comboBox->currentIndex() != index) - comboBox->setCurrentIndex(index); - }); - - comboBox->setProperty("hideborder", true); - comboBox->setMaximumWidth(qMax(comboBox->view()->sizeHintForColumn(0) / 2, 16)); - return comboBox; +void ZoomAction::emitZoomLevelChanged(int index) +{ + if (index >= 0 && index < static_cast<int>(m_zooms.size())) + emit zoomLevelChanged(m_zooms[static_cast<size_t>(index)]); } } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/zoomaction.h b/src/plugins/qmldesigner/components/componentcore/zoomaction.h index bd46f915d9..85d855c1d1 100644 --- a/src/plugins/qmldesigner/components/componentcore/zoomaction.h +++ b/src/plugins/qmldesigner/components/componentcore/zoomaction.h @@ -26,11 +26,13 @@ #include <qmldesignercorelib_global.h> -#include <QWidgetAction> #include <QPointer> +#include <QWidgetAction> + +#include <array> QT_BEGIN_NAMESPACE -class QAbstractItemModel; +class QComboBox; QT_END_NAMESPACE namespace QmlDesigner { @@ -39,30 +41,27 @@ class QMLDESIGNERCORE_EXPORT ZoomAction : public QWidgetAction { Q_OBJECT +signals: + void zoomLevelChanged(double zoom); + public: ZoomAction(QObject *parent); - float zoomLevel() const; - - void zoomIn(); - void zoomOut(); - void resetZoomLevel(); - void setZoomLevel(float zoomLevel); - void forceZoomLevel(float zoomLevel); + static std::array<double, 27> zoomLevels(); + static int indexOf(double zoom); - static float getClosestZoomLevel(float zoomLevel); + void setZoomFactor(double zoom); + double setNextZoomFactor(double zoom); + double setPreviousZoomFactor(double zoom); protected: QWidget *createWidget(QWidget *parent) override; -signals: - void zoomLevelChanged(float zoom); - void indexChanged(int); - void reseted(); private: - QPointer<QAbstractItemModel> m_comboBoxModel; - float m_zoomLevel; - int m_currentComboBoxIndex; + void emitZoomLevelChanged(int index); + + static std::array<double, 27> m_zooms; + QPointer<QComboBox> m_combo; }; } // namespace QmlDesigner |