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>
This commit is contained in:
Knud Dollereder
2020-11-12 12:08:22 +01:00
parent ad2d635a09
commit 83187ba54f
15 changed files with 578 additions and 276 deletions

View File

@@ -208,6 +208,7 @@ extend_qtc_plugin(QmlDesigner
modelnodecontextmenu_helper.cpp modelnodecontextmenu_helper.h
modelnodeoperations.cpp modelnodeoperations.h
navigation2d.cpp navigation2d.h
gestures.cpp gestures.h
qmldesignericonprovider.cpp qmldesignericonprovider.h
selectioncontext.cpp selectioncontext.h
theme.cpp theme.h

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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) {
if (m_scrollbar->postEvent(event))
event->ignore();
return false;
} 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.

View File

@@ -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);

View File

@@ -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
};
bool isValidIndex(int index)
{
if (index >= 0 && index < static_cast<int>(ZoomAction::zoomLevels().size()))
return true;
return false;
}
ZoomAction::ZoomAction(QObject *parent)
: QWidgetAction(parent),
m_zoomLevel(1.0),
m_currentComboBoxIndex(defaultZoomIndex)
{
: QWidgetAction(parent)
, m_combo(nullptr)
{}
std::array<double, 27> ZoomAction::zoomLevels()
{
return m_zooms;
}
float ZoomAction::zoomLevel() const
int ZoomAction::indexOf(double zoom)
{
return m_zoomLevel;
}
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::zoomIn()
{
if (m_currentComboBoxIndex < (m_comboBoxModel->rowCount() - 1))
emit indexChanged(m_currentComboBoxIndex + 1);
}
void ZoomAction::zoomOut()
{
if (m_currentComboBoxIndex > 0)
emit indexChanged(m_currentComboBoxIndex - 1);
}
void ZoomAction::resetZoomLevel()
{
m_zoomLevel = 1.0;
m_currentComboBoxIndex = defaultZoomIndex;
emit reseted();
}
void ZoomAction::setZoomLevel(float zoomLevel)
{
if (qFuzzyCompare(m_zoomLevel, zoomLevel))
return;
forceZoomLevel(zoomLevel);
}
void ZoomAction::forceZoomLevel(float zoomLevel)
{
m_zoomLevel = qBound(0.01f, zoomLevel, 16.0f);
emit zoomLevelChanged(m_zoomLevel);
}
//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)
{
for (int i = 0; i < s_zoomFactors.length(); i++) {
if (qFuzzyCompare(s_zoomFactors.at(i), zoom))
return i;
}
return -1;
}
float ZoomAction::getClosestZoomLevel(float zoomLevel)
void ZoomAction::setZoomFactor(double zoom)
{
int i = 0;
while (i < s_zoomFactors.size() && s_zoomFactors[i] < zoomLevel)
++i;
if (int index = indexOf(zoom); index >= 0) {
m_combo->setCurrentIndex(index);
m_combo->setToolTip(m_combo->currentText());
return;
}
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());
}
return s_zoomFactors[qBound(0, i - 1, s_zoomFactors.size() - 1)];
double ZoomAction::setNextZoomFactor(double zoom)
{
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;
}
double ZoomAction::setPreviousZoomFactor(double zoom)
{
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 zoom;
}
bool parentIsFormEditor(QWidget *parent)
{
while (parent) {
if (qobject_cast<FormEditorWidget *>(parent))
return true;
parent = qobject_cast<QWidget *>(parent->parent());
}
return false;
}
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);
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());
/*
* 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());
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

View File

@@ -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;
static std::array<double, 27> zoomLevels();
static int indexOf(double zoom);
void zoomIn();
void zoomOut();
void resetZoomLevel();
void setZoomLevel(float zoomLevel);
void forceZoomLevel(float zoomLevel);
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

View File

@@ -24,20 +24,24 @@
****************************************************************************/
#include "formeditorgraphicsview.h"
#include "formeditoritem.h"
#include "formeditorwidget.h"
#include "navigation2d.h"
#include <QWheelEvent>
#include <QScrollBar>
#include <QGraphicsItem>
#include <QGraphicsWidget>
#include <QGraphicsProxyWidget>
#include <QAction>
#include <QCoreApplication>
#include <QGraphicsItem>
#include <QGraphicsProxyWidget>
#include <QGraphicsWidget>
#include <QScrollBar>
#include <QWheelEvent>
#include <QTimer>
namespace QmlDesigner {
FormEditorGraphicsView::FormEditorGraphicsView(QWidget *parent) :
QGraphicsView(parent)
FormEditorGraphicsView::FormEditorGraphicsView(QWidget *parent)
: QGraphicsView(parent)
{
setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
setResizeAnchor(QGraphicsView::AnchorViewCenter);
@@ -57,6 +61,39 @@ FormEditorGraphicsView::FormEditorGraphicsView(QWidget *parent) :
// as mousetracking only works for mouse key it is better to handle it in the
// eventFilter method so it works also for the space scrolling case as expected
QCoreApplication::instance()->installEventFilter(this);
QmlDesigner::Navigation2dFilter *filter = new QmlDesigner::Navigation2dFilter(this);
connect(filter, &Navigation2dFilter::zoomIn, this, &FormEditorGraphicsView::zoomIn);
connect(filter, &Navigation2dFilter::zoomOut, this, &FormEditorGraphicsView::zoomOut);
auto panChanged = &Navigation2dFilter::panChanged;
connect(filter, panChanged, [this](const QPointF &direction) {
QScrollBar *sbx = horizontalScrollBar();
QScrollBar *sby = verticalScrollBar();
// max - min + pageStep = sceneRect.size * scale
QPointF min(sbx->minimum(), sby->minimum());
QPointF max(sbx->maximum(), sby->maximum());
QPointF step(sbx->pageStep(), sby->pageStep());
QPointF d1 = max - min;
QPointF d2 = d1 + step;
QPoint val = QPointF((direction.x() / d2.x()) * d1.x(), (direction.y() / d2.y()) * d1.y())
.toPoint();
sbx->setValue(sbx->value() - val.x());
sby->setValue(sby->value() - val.y());
});
auto zoomChanged = &Navigation2dFilter::zoomChanged;
connect(filter, zoomChanged, [this](double s, const QPointF &/*pos*/) {
if (auto trans = transform() * QTransform::fromScale(1.0 + s, 1.0 + s); trans.m11() > 0) {
setTransform(trans);
emit this->zoomChanged(transform().m11());
}
});
installEventFilter(filter);
}
bool FormEditorGraphicsView::eventFilter(QObject *watched, QEvent *event)
@@ -67,7 +104,7 @@ bool FormEditorGraphicsView::eventFilter(QObject *watched, QEvent *event)
stopPanning(event);
}
if (event->type() == QEvent::MouseMove) {
auto mouseEvent = static_cast<QMouseEvent*>(event);
auto mouseEvent = static_cast<QMouseEvent *>(event);
if (!m_panningStartPosition.isNull()) {
horizontalScrollBar()->setValue(horizontalScrollBar()->value() -
(mouseEvent->x() - m_panningStartPosition.x()));
@@ -86,7 +123,7 @@ void FormEditorGraphicsView::wheelEvent(QWheelEvent *event)
{
if (event->modifiers().testFlag(Qt::ControlModifier))
event->ignore();
else
else if (event->source() == Qt::MouseEventNotSynthesized)
QGraphicsView::wheelEvent(event);
}
@@ -109,7 +146,7 @@ void FormEditorGraphicsView::mouseReleaseEvent(QMouseEvent *event)
QGraphicsView::mouseReleaseEvent(event);
}
bool isTextInputItem(QGraphicsItem* item)
bool isTextInputItem(QGraphicsItem *item)
{
if (item && item->isWidget()) {
auto graphicsWidget = static_cast<QGraphicsWidget *>(item);
@@ -203,4 +240,15 @@ void FormEditorGraphicsView::drawBackground(QPainter *painter, const QRectF &rec
painter->restore();
}
void FormEditorGraphicsView::frame(const QRectF &boundingRect)
{
fitInView(boundingRect, Qt::KeepAspectRatio);
}
void FormEditorGraphicsView::setZoomFactor(double zoom)
{
resetTransform();
scale(zoom, zoom);
}
} // namespace QmlDesigner

View File

@@ -30,7 +30,13 @@ namespace QmlDesigner {
class FormEditorGraphicsView : public QGraphicsView
{
Q_OBJECT
Q_OBJECT
signals:
void zoomChanged(double zoom);
void zoomIn();
void zoomOut();
public:
explicit FormEditorGraphicsView(QWidget *parent = nullptr);
@@ -41,6 +47,9 @@ public:
void activateColoredBackground(const QColor &color);
void drawBackground(QPainter *painter, const QRectF &rect) override;
void setZoomFactor(double zoom);
void frame(const QRectF &bbox);
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
@@ -48,13 +57,13 @@ protected:
void mouseReleaseEvent(QMouseEvent *event) override;
void keyPressEvent(QKeyEvent *event) override;
void keyReleaseEvent(QKeyEvent *event) override;
private:
enum Panning{
NotStarted, MouseWheelStarted, SpaceKeyStarted
};
enum Panning { NotStarted, MouseWheelStarted, SpaceKeyStarted };
void startPanning(QEvent *event);
void stopPanning(QEvent *event);
Panning m_isPanning = Panning::NotStarted;
QPoint m_panningStartPosition;
QRectF m_rootItemRect;

View File

@@ -443,53 +443,12 @@ void FormEditorView::documentMessagesChanged(const QList<DocumentMessage> &error
m_formEditorWidget->hideErrorMessageBox();
}
void FormEditorView::customNotification(const AbstractView * /*view*/, const QString &identifier, const QList<ModelNode> &nodeList, const QList<QVariant> &/*data*/)
void FormEditorView::customNotification(const AbstractView * /*view*/, const QString &identifier, const QList<ModelNode> &/*nodeList*/, const QList<QVariant> &/*data*/)
{
if (identifier == QLatin1String("puppet crashed"))
m_dragTool->clearMoveDelay();
if (identifier == QLatin1String("reset QmlPuppet"))
temporaryBlockView();
if (identifier == QLatin1String("zoom all")) {
if (QmlItemNode(rootModelNode()).isFlowView()) {
QRectF boundingRect;
for (QGraphicsItem *item : scene()->items()) {
if (auto formEditorItem = FormEditorItem::fromQGraphicsItem(item)) {
if (!formEditorItem->qmlItemNode().modelNode().isRootNode()
&& !formEditorItem->sceneBoundingRect().isNull())
boundingRect = boundingRect.united(formEditorItem->sceneBoundingRect());
}
}
m_formEditorWidget->graphicsView()->fitInView(boundingRect,
Qt::KeepAspectRatio);
} else {
m_formEditorWidget->graphicsView()->fitInView(m_formEditorWidget->rootItemRect(),
Qt::KeepAspectRatio);
}
const qreal scaleFactor = m_formEditorWidget->graphicsView()->viewportTransform().m11();
float zoomLevel = ZoomAction::getClosestZoomLevel(scaleFactor);
m_formEditorWidget->zoomAction()->forceZoomLevel(zoomLevel);
}
if (identifier == QLatin1String("zoom selection")) {
if (nodeList.isEmpty())
return;
QRectF boundingRect;
for (const ModelNode &node : nodeList) {
if (FormEditorItem *item = scene()->itemForQmlItemNode(node))
boundingRect = boundingRect.united(item->sceneBoundingRect());
}
m_formEditorWidget->graphicsView()->fitInView(boundingRect,
Qt::KeepAspectRatio);
const qreal scaleFactor = m_formEditorWidget->graphicsView()->viewportTransform().m11();
float zoomLevel = ZoomAction::getClosestZoomLevel(scaleFactor);
m_formEditorWidget->zoomAction()->forceZoomLevel(zoomLevel);
}
if (identifier == QLatin1String("zoom in"))
m_formEditorWidget->zoomAction()->zoomIn();
if (identifier == QLatin1String("zoom out"))
m_formEditorWidget->zoomAction()->zoomOut();
}
void FormEditorView::currentStateChanged(const ModelNode & /*node*/)
@@ -791,7 +750,7 @@ void FormEditorView::setupFormEditorWidget()
if (QmlItemNode::isValidQmlItemNode(rootModelNode()))
setupFormEditorItemTree(rootModelNode());
m_formEditorWidget->updateActions();
m_formEditorWidget->initialize();
if (!rewriterView()->errors().isEmpty())
m_formEditorWidget->showErrorMessageBox(rewriterView()->errors());

View File

@@ -23,13 +23,13 @@
**
****************************************************************************/
#include "designeractionmanager.h"
#include "formeditorwidget.h"
#include "formeditorscene.h"
#include "qmldesignerplugin.h"
#include "designeractionmanager.h"
#include "designersettings.h"
#include "formeditorscene.h"
#include "qmldesignerconstants.h"
#include "qmldesignericons.h"
#include "qmldesignerplugin.h"
#include "viewmanager.h"
#include <model.h>
#include <theme.h>
@@ -39,16 +39,16 @@
#include <formeditorscene.h>
#include <formeditorview.h>
#include <lineeditaction.h>
#include <zoomaction.h>
#include <toolbox.h>
#include <zoomaction.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
#include <coreplugin/icore.h>
#include <utils/fileutils.h>
#include <utils/utilsicons.h>
#include <utils/stylehelper.h>
#include <utils/utilsicons.h>
#include <QActionGroup>
#include <QFileDialog>
@@ -59,8 +59,8 @@
namespace QmlDesigner {
FormEditorWidget::FormEditorWidget(FormEditorView *view) :
m_formEditorView(view)
FormEditorWidget::FormEditorWidget(FormEditorView *view)
: m_formEditorView(view)
{
Core::Context context(Constants::C_QMLFORMEDITOR);
m_context = new Core::IContext(this);
@@ -72,7 +72,7 @@ FormEditorWidget::FormEditorWidget(FormEditorView *view) :
fillLayout->setSpacing(0);
setLayout(fillLayout);
QList<QAction*> upperActions;
QList<QAction *> upperActions;
m_toolActionGroup = new QActionGroup(this);
@@ -150,9 +150,8 @@ FormEditorWidget::FormEditorWidget(FormEditorView *view) :
const QString fontName = "qtds_propertyIconFont.ttf";
const QColor textColorNormal(Theme::getColor(Theme::MenuItemTextColorNormal));
const QColor textColorDisabled(Theme::getColor(Theme::MenuBarItemTextColorDisabled));
const QIcon zoomAllIcon = Utils::StyleHelper::getIconFromIconFont(fontName,
Theme::getIconUnicode(Theme::Icon::zoomAll),
28, 28, textColorNormal);
const QIcon zoomAllIcon = Utils::StyleHelper::getIconFromIconFont(
fontName, Theme::getIconUnicode(Theme::Icon::zoomAll), 28, 28, textColorNormal);
const QString zoomSelectionUnicode = Theme::getIconUnicode(Theme::Icon::zoomSelection);
const auto zoomSelectionNormal = Utils::StyleHelper::IconFontHelper(zoomSelectionUnicode,
@@ -167,68 +166,101 @@ FormEditorWidget::FormEditorWidget(FormEditorView *view) :
const QIcon zoomSelectionIcon = Utils::StyleHelper::getIconFromIconFont(fontName,
{zoomSelectionNormal,
zoomSelectionDisabeld});
const QIcon zoomInIcon = Utils::StyleHelper::getIconFromIconFont(fontName,
Theme::getIconUnicode(Theme::Icon::zoomIn),
28, 28, textColorNormal);
const QIcon zoomOutIcon = Utils::StyleHelper::getIconFromIconFont(fontName,
Theme::getIconUnicode(Theme::Icon::zoomOut),
28, 28, textColorNormal);
const QIcon zoomInIcon = Utils::StyleHelper::getIconFromIconFont(
fontName, Theme::getIconUnicode(Theme::Icon::zoomIn), 28, 28, textColorNormal);
const QIcon zoomOutIcon = Utils::StyleHelper::getIconFromIconFont(
fontName, Theme::getIconUnicode(Theme::Icon::zoomOut), 28, 28, textColorNormal);
auto writeZoomLevel = [this]() {
double level = m_graphicsView->transform().m11();
if (level == 1.0) {
if (m_formEditorView->rootModelNode().hasAuxiliaryData("formeditorZoom"))
m_formEditorView->rootModelNode().setAuxiliaryData("formeditorZoom", {});
} else {
m_formEditorView->rootModelNode().setAuxiliaryData("formeditorZoom", level);
}
};
auto setZoomLevel = [this, writeZoomLevel](double level) {
if (m_graphicsView) {
m_graphicsView->setZoomFactor(level);
writeZoomLevel();
}
};
auto zoomIn = [this, writeZoomLevel]() {
if (m_graphicsView) {
double zoom = m_graphicsView->transform().m11();
zoom = m_zoomAction->setNextZoomFactor(zoom);
m_graphicsView->setZoomFactor(zoom);
writeZoomLevel();
}
};
auto zoomOut = [this, writeZoomLevel]() {
if (m_graphicsView) {
double zoom = m_graphicsView->transform().m11();
zoom = m_zoomAction->setPreviousZoomFactor(zoom);
m_graphicsView->setZoomFactor(zoom);
writeZoomLevel();
}
};
auto frameAll = [this, zoomOut]() {
if (m_graphicsView) {
m_graphicsView->frame(m_graphicsView->rootItemRect());
zoomOut();
}
};
auto frameSelection = [this, zoomOut]() {
if (m_graphicsView) {
QRectF boundingRect;
const QList<ModelNode> nodeList = m_formEditorView->selectedModelNodes();
for (const ModelNode &node : nodeList) {
if (FormEditorItem *item = m_formEditorView->scene()->itemForQmlItemNode(node))
boundingRect |= item->sceneBoundingRect();
}
m_graphicsView->frame(boundingRect);
zoomOut();
}
};
m_zoomInAction = new QAction(zoomInIcon, tr("Zoom in"), this);
m_zoomInAction->setShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Plus));
connect(m_zoomInAction.data(), &QAction::triggered, this, [this] {
if (!m_formEditorView)
return;
m_formEditorView->emitCustomNotification(QStringLiteral("zoom in"));
});
addAction(m_zoomInAction.data());
upperActions.append(m_zoomInAction.data());
m_toolBox->addRightSideAction(m_zoomInAction.data());
connect(m_zoomInAction.data(), &QAction::triggered, zoomIn);
m_zoomOutAction = new QAction(zoomOutIcon, tr("Zoom out"), this);
m_zoomOutAction->setShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Minus));
connect(m_zoomOutAction.data(), &QAction::triggered, this, [this] {
if (!m_formEditorView)
return;
m_formEditorView->emitCustomNotification(QStringLiteral("zoom out"));
});
addAction(m_zoomOutAction.data());
upperActions.append(m_zoomOutAction.data());
m_toolBox->addRightSideAction(m_zoomOutAction.data());
connect(m_zoomOutAction.data(), &QAction::triggered, zoomOut);
m_zoomAction = new ZoomAction(m_toolActionGroup.data());
connect(m_zoomAction.data(), &ZoomAction::zoomLevelChanged,
this, &FormEditorWidget::setZoomLevel);
addAction(m_zoomAction.data());
upperActions.append(m_zoomAction.data());
m_toolBox->addRightSideAction(m_zoomAction.data());
connect(m_zoomAction.data(), &ZoomAction::zoomLevelChanged, setZoomLevel);
m_zoomAllAction = new QAction(zoomAllIcon, tr("Zoom screen to fit all content"), this);
m_zoomAllAction->setShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_0));
connect(m_zoomAllAction.data(), &QAction::triggered, this, [this] {
if (!m_formEditorView)
return;
m_formEditorView->emitCustomNotification(QStringLiteral("zoom all"));
});
addAction(m_zoomAllAction.data());
upperActions.append(m_zoomAllAction.data());
m_toolBox->addRightSideAction(m_zoomAllAction.data());
connect(m_zoomAllAction.data(), &QAction::triggered, frameAll);
m_zoomSelectionAction = new QAction(zoomSelectionIcon, tr("Zoom screen to fit current selection"), this);
m_zoomSelectionAction = new QAction(zoomSelectionIcon,
tr("Zoom screen to fit current selection"),
this);
m_zoomSelectionAction->setShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_I));
connect(m_zoomSelectionAction.data(), &QAction::triggered, this, [this] {
if (!m_formEditorView)
return;
m_formEditorView->emitCustomNotification(QStringLiteral("zoom selection"),
m_formEditorView->selectedModelNodes());
});
addAction(m_zoomSelectionAction.data());
upperActions.append(m_zoomSelectionAction.data());
m_toolBox->addRightSideAction(m_zoomSelectionAction.data());
connect(m_zoomSelectionAction.data(), &QAction::triggered, frameSelection);
m_resetAction = new QAction(Utils::Icons::RESET_TOOLBAR.icon(), tr("Reset View"), this);
registerActionAsCommand(m_resetAction, Constants::FORMEDITOR_REFRESH, QKeySequence(Qt::Key_R));
@@ -238,6 +270,10 @@ FormEditorWidget::FormEditorWidget(FormEditorView *view) :
m_toolBox->addRightSideAction(m_resetAction.data());
m_graphicsView = new FormEditorGraphicsView(this);
auto applyZoom = [this](double zoom) { zoomAction()->setZoomFactor(zoom); };
connect(m_graphicsView, &FormEditorGraphicsView::zoomChanged, applyZoom);
connect(m_graphicsView, &FormEditorGraphicsView::zoomIn, zoomIn);
connect(m_graphicsView, &FormEditorGraphicsView::zoomOut, zoomOut);
fillLayout->addWidget(m_graphicsView.data());
@@ -293,50 +329,46 @@ void FormEditorWidget::registerActionAsCommand(QAction *action, Utils::Id id, co
command->augmentActionWithShortcutToolTip(action);
}
void FormEditorWidget::wheelEvent(QWheelEvent *event)
void FormEditorWidget::initialize()
{
if (event->modifiers().testFlag(Qt::ControlModifier)) {
if (event->angleDelta().y() > 0)
zoomAction()->zoomIn();
else
zoomAction()->zoomOut();
event->accept();
} else {
QWidget::wheelEvent(event);
double defaultZoom = 1.0;
if (m_formEditorView->model() && m_formEditorView->rootModelNode().isValid()) {
if (m_formEditorView->rootModelNode().hasAuxiliaryData("formeditorZoom"))
defaultZoom = m_formEditorView->rootModelNode().auxiliaryData("formeditorZoom").toDouble();
}
m_graphicsView->setZoomFactor(defaultZoom);
m_zoomAction->setZoomFactor(defaultZoom);
updateActions();
}
void FormEditorWidget::updateActions()
{
if (m_formEditorView->model() && m_formEditorView->rootModelNode().isValid()) {
if (m_formEditorView->rootModelNode().hasAuxiliaryData("width") && m_formEditorView->rootModelNode().auxiliaryData("width").isValid())
m_rootWidthAction->setLineEditText(m_formEditorView->rootModelNode().auxiliaryData("width").toString());
if (m_formEditorView->rootModelNode().hasAuxiliaryData("width")
&& m_formEditorView->rootModelNode().auxiliaryData("width").isValid())
m_rootWidthAction->setLineEditText(
m_formEditorView->rootModelNode().auxiliaryData("width").toString());
else
m_rootWidthAction->clearLineEditText();
if (m_formEditorView->rootModelNode().hasAuxiliaryData("height") && m_formEditorView->rootModelNode().auxiliaryData("height").isValid())
m_rootHeightAction->setLineEditText(m_formEditorView->rootModelNode().auxiliaryData("height").toString());
if (m_formEditorView->rootModelNode().hasAuxiliaryData("height")
&& m_formEditorView->rootModelNode().auxiliaryData("height").isValid())
m_rootHeightAction->setLineEditText(
m_formEditorView->rootModelNode().auxiliaryData("height").toString());
else
m_rootHeightAction->clearLineEditText();
if (m_formEditorView->rootModelNode().hasAuxiliaryData("formeditorColor"))
m_backgroundAction->setColor(m_formEditorView->rootModelNode().auxiliaryData("formeditorColor").value<QColor>());
m_backgroundAction->setColor(
m_formEditorView->rootModelNode().auxiliaryData("formeditorColor").value<QColor>());
else
m_backgroundAction->setColor(Qt::transparent);
if (m_formEditorView->rootModelNode().hasAuxiliaryData("formeditorZoom"))
m_zoomAction->setZoomLevel(m_formEditorView->rootModelNode().auxiliaryData("formeditorZoom").toDouble());
else
m_zoomAction->setZoomLevel(1.0);
} else {
m_rootWidthAction->clearLineEditText();
m_rootHeightAction->clearLineEditText();
}
}
void FormEditorWidget::resetView()
{
setRootItemRect(QRectF());
@@ -408,20 +440,6 @@ QAction *FormEditorWidget::snappingAndAnchoringAction() const
return m_snappingAndAnchoringAction.data();
}
void FormEditorWidget::setZoomLevel(double zoomLevel)
{
m_graphicsView->resetTransform();
m_graphicsView->scale(zoomLevel, zoomLevel);
if (zoomLevel == 1.0) {
if (m_formEditorView->rootModelNode().hasAuxiliaryData("formeditorZoom"))
m_formEditorView->rootModelNode().setAuxiliaryData("formeditorZoom", {});
} else {
m_formEditorView->rootModelNode().setAuxiliaryData("formeditorZoom", zoomLevel);
}
}
void FormEditorWidget::setScene(FormEditorScene *scene)
{
m_graphicsView->setScene(scene);
@@ -510,8 +528,9 @@ DocumentWarningWidget *FormEditorWidget::errorWidget()
{
if (m_documentErrorWidget.isNull()) {
m_documentErrorWidget = new DocumentWarningWidget(this);
connect(m_documentErrorWidget.data(), &DocumentWarningWidget::gotoCodeClicked, [=]
(const QString &, int codeLine, int codeColumn) {
connect(m_documentErrorWidget.data(),
&DocumentWarningWidget::gotoCodeClicked,
[=](const QString &, int codeLine, int codeColumn) {
m_formEditorView->gotoError(codeLine, codeColumn);
});
}

View File

@@ -28,8 +28,8 @@
#include <coreplugin/icontext.h>
#include <QWidget>
#include <QPointer>
#include <QWidget>
QT_BEGIN_NAMESPACE
class QActionGroup;
@@ -70,6 +70,7 @@ public:
void setRootItemRect(const QRectF &rect);
QRectF rootItemRect() const;
void initialize();
void updateActions();
void resetView();
@@ -88,7 +89,6 @@ public:
FormEditorGraphicsView *graphicsView() const;
protected:
void wheelEvent(QWheelEvent *event) override;
QActionGroup *toolActionGroup() const;
DocumentWarningWidget *errorWidget();
void hideEvent(QHideEvent *event) override;
@@ -96,7 +96,6 @@ protected:
private:
void changeTransformTool(bool checked);
void setZoomLevel(double zoomLevel);
void changeRootItemWidth(const QString &widthText);
void changeRootItemHeight(const QString &heightText);
void changeBackgound(const QColor &color);

View File

@@ -32,6 +32,7 @@
#include "openuiqmlfiledialog.h"
#include "generateresource.h"
#include "nodeinstanceview.h"
#include "gestures.h"
#include <metainfo.h>
#include <connectionview.h>
@@ -221,6 +222,8 @@ bool QmlDesignerPlugin::initialize(const QStringList & /*arguments*/, QString *e
if (QFontDatabase::addApplicationFont(fontPath) < 0)
qCWarning(qmldesignerLog) << "Could not add font " << fontPath << "to font database";
TwoFingerSwipe::registerRecognizer();
return true;
}

View File

@@ -452,6 +452,8 @@ Project {
"componentcore/designeractionmanagerview.h",
"componentcore/findimplementation.cpp",
"componentcore/findimplementation.h",
"componentcore/gestures.cpp",
"componentcore/gestures.h",
"componentcore/layoutingridlayout.cpp",
"componentcore/layoutingridlayout.h",
"componentcore/theme.cpp",