2019-06-18 15:12:21 +02:00
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
|
|
|
|
** Copyright (C) 2019 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 "graphicsview.h"
|
|
|
|
|
#include "curveeditormodel.h"
|
|
|
|
|
#include "curveitem.h"
|
2020-02-10 16:21:23 +01:00
|
|
|
#include "treeitem.h"
|
2019-06-18 15:12:21 +02:00
|
|
|
#include "utils.h"
|
|
|
|
|
|
|
|
|
|
#include <QAction>
|
|
|
|
|
#include <QMenu>
|
|
|
|
|
#include <QResizeEvent>
|
|
|
|
|
#include <QScrollBar>
|
|
|
|
|
|
|
|
|
|
#include <cmath>
|
|
|
|
|
|
|
|
|
|
namespace DesignTools {
|
|
|
|
|
|
|
|
|
|
GraphicsView::GraphicsView(CurveEditorModel *model, QWidget *parent)
|
|
|
|
|
: QGraphicsView(parent)
|
|
|
|
|
, m_zoomX(0.0)
|
|
|
|
|
, m_zoomY(0.0)
|
|
|
|
|
, m_transform()
|
2020-04-06 11:22:30 +02:00
|
|
|
, m_scene(new GraphicsScene())
|
2019-06-18 15:12:21 +02:00
|
|
|
, m_model(model)
|
|
|
|
|
, m_playhead(this)
|
|
|
|
|
, m_selector()
|
|
|
|
|
, m_style(model->style())
|
|
|
|
|
, m_dialog(m_style)
|
|
|
|
|
{
|
|
|
|
|
model->setGraphicsView(this);
|
|
|
|
|
|
2020-04-06 11:22:30 +02:00
|
|
|
setScene(m_scene);
|
2019-06-18 15:12:21 +02:00
|
|
|
setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
|
|
|
|
setResizeAnchor(QGraphicsView::NoAnchor);
|
2019-11-26 14:55:34 +01:00
|
|
|
setRenderHint(QPainter::Antialiasing, true);
|
2019-06-18 15:12:21 +02:00
|
|
|
setTransformationAnchor(QGraphicsView::NoAnchor);
|
|
|
|
|
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
|
|
|
|
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
|
|
|
|
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
|
|
|
|
|
|
|
|
|
|
connect(&m_dialog, &CurveEditorStyleDialog::styleChanged, this, &GraphicsView::setStyle);
|
|
|
|
|
|
|
|
|
|
auto itemSlot = [this](unsigned int id, const AnimationCurve &curve) {
|
|
|
|
|
applyZoom(m_zoomX, m_zoomY);
|
|
|
|
|
m_model->setCurve(id, curve);
|
|
|
|
|
};
|
|
|
|
|
|
2020-04-06 11:22:30 +02:00
|
|
|
connect(m_scene, &GraphicsScene::curveChanged, itemSlot);
|
2019-06-18 15:12:21 +02:00
|
|
|
|
2020-04-06 11:22:30 +02:00
|
|
|
auto pinSlot = [this](PropertyTreeItem *pti) { m_scene->setPinned(pti->id(), pti->pinned()); };
|
2020-02-10 16:21:23 +01:00
|
|
|
connect(m_model, &CurveEditorModel::curveChanged, pinSlot);
|
|
|
|
|
|
2019-06-18 15:12:21 +02:00
|
|
|
applyZoom(m_zoomX, m_zoomY);
|
|
|
|
|
update();
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-06 11:22:30 +02:00
|
|
|
GraphicsView::~GraphicsView()
|
|
|
|
|
{
|
|
|
|
|
if (m_scene) {
|
|
|
|
|
delete m_scene;
|
|
|
|
|
m_scene = nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-18 15:12:21 +02:00
|
|
|
CurveEditorModel *GraphicsView::model() const
|
|
|
|
|
{
|
|
|
|
|
return m_model;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CurveEditorStyle GraphicsView::editorStyle() const
|
|
|
|
|
{
|
|
|
|
|
return m_style;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double GraphicsView::minimumTime() const
|
|
|
|
|
{
|
2020-04-06 11:22:30 +02:00
|
|
|
bool check = m_model->minimumTime() < m_scene->minimumTime();
|
|
|
|
|
return check ? m_model->minimumTime() : m_scene->minimumTime();
|
2019-06-18 15:12:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double GraphicsView::maximumTime() const
|
|
|
|
|
{
|
2020-04-06 11:22:30 +02:00
|
|
|
bool check = m_model->maximumTime() > m_scene->maximumTime();
|
|
|
|
|
return check ? m_model->maximumTime() : m_scene->maximumTime();
|
2019-06-18 15:12:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double GraphicsView::minimumValue() const
|
|
|
|
|
{
|
2020-04-06 11:22:30 +02:00
|
|
|
return m_scene->empty() ? -1.0 : m_scene->minimumValue();
|
2019-06-18 15:12:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double GraphicsView::maximumValue() const
|
|
|
|
|
{
|
2020-04-06 11:22:30 +02:00
|
|
|
return m_scene->empty() ? 1.0 : m_scene->maximumValue();
|
2019-06-18 15:12:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double GraphicsView::zoomX() const
|
|
|
|
|
{
|
|
|
|
|
return m_zoomX;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double GraphicsView::zoomY() const
|
|
|
|
|
{
|
|
|
|
|
return m_zoomY;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QRectF GraphicsView::canvasRect() const
|
|
|
|
|
{
|
|
|
|
|
QRect r = viewport()->rect().adjusted(
|
|
|
|
|
m_style.valueAxisWidth + m_style.canvasMargin,
|
|
|
|
|
m_style.timeAxisHeight + m_style.canvasMargin,
|
|
|
|
|
-m_style.canvasMargin,
|
|
|
|
|
-m_style.canvasMargin);
|
|
|
|
|
|
|
|
|
|
return mapToScene(r).boundingRect();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QRectF GraphicsView::timeScaleRect() const
|
|
|
|
|
{
|
|
|
|
|
QRect vp(viewport()->rect());
|
|
|
|
|
QPoint tl = vp.topLeft() + QPoint(m_style.valueAxisWidth, 0);
|
|
|
|
|
QPoint br = vp.topRight() + QPoint(0, m_style.timeAxisHeight);
|
|
|
|
|
return mapToScene(QRect(tl, br)).boundingRect();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QRectF GraphicsView::valueScaleRect() const
|
|
|
|
|
{
|
|
|
|
|
QRect vp(viewport()->rect());
|
|
|
|
|
QPoint tl = vp.topLeft() + QPoint(0, m_style.timeAxisHeight);
|
|
|
|
|
QPoint br = vp.bottomLeft() + QPoint(m_style.valueAxisWidth, 0);
|
|
|
|
|
return mapToScene(QRect(tl, br)).boundingRect();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QRectF GraphicsView::defaultRasterRect() const
|
|
|
|
|
{
|
|
|
|
|
QPointF topLeft(mapTimeToX(minimumTime()), mapValueToY(maximumValue()));
|
|
|
|
|
QPointF bottomRight(mapTimeToX(maximumTime()), mapValueToY(minimumValue()));
|
|
|
|
|
return QRectF(topLeft, bottomRight);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GraphicsView::setStyle(const CurveEditorStyle &style)
|
|
|
|
|
{
|
|
|
|
|
m_style = style;
|
|
|
|
|
|
2020-04-06 11:22:30 +02:00
|
|
|
const auto curves = m_scene->curves();
|
|
|
|
|
for (auto *curve : curves)
|
|
|
|
|
curve->setStyle(style);
|
2019-06-18 15:12:21 +02:00
|
|
|
|
|
|
|
|
applyZoom(m_zoomX, m_zoomY);
|
|
|
|
|
viewport()->update();
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-10 16:21:23 +01:00
|
|
|
void GraphicsView::setLocked(PropertyTreeItem *item)
|
|
|
|
|
{
|
2020-04-06 11:22:30 +02:00
|
|
|
if (CurveItem *curve = m_scene->findCurve(item->id()))
|
|
|
|
|
curve->setLocked(item->locked());
|
2020-02-10 16:21:23 +01:00
|
|
|
}
|
|
|
|
|
|
2019-06-18 15:12:21 +02:00
|
|
|
void GraphicsView::setZoomX(double zoom, const QPoint &pivot)
|
|
|
|
|
{
|
|
|
|
|
applyZoom(zoom, m_zoomY, pivot);
|
|
|
|
|
viewport()->update();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GraphicsView::setZoomY(double zoom, const QPoint &pivot)
|
|
|
|
|
{
|
|
|
|
|
applyZoom(m_zoomX, zoom, pivot);
|
|
|
|
|
viewport()->update();
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-24 17:09:22 +02:00
|
|
|
void GraphicsView::setCurrentFrame(int frame, bool notify)
|
2019-06-18 15:12:21 +02:00
|
|
|
{
|
|
|
|
|
int clampedFrame = clamp(frame, m_model->minimumTime(), m_model->maximumTime());
|
|
|
|
|
m_playhead.moveToFrame(clampedFrame, this);
|
|
|
|
|
viewport()->update();
|
2019-10-24 17:09:22 +02:00
|
|
|
|
|
|
|
|
if (notify)
|
|
|
|
|
notifyFrameChanged(frame);
|
2019-06-18 15:12:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GraphicsView::scrollContent(double x, double y)
|
|
|
|
|
{
|
|
|
|
|
QScrollBar *hs = horizontalScrollBar();
|
|
|
|
|
QScrollBar *vs = verticalScrollBar();
|
|
|
|
|
hs->setValue(hs->value() + x);
|
|
|
|
|
vs->setValue(vs->value() + y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GraphicsView::reset(const std::vector<CurveItem *> &items)
|
2020-02-21 15:28:31 +01:00
|
|
|
{
|
2020-04-06 11:22:30 +02:00
|
|
|
m_scene->reset();
|
2020-02-21 15:28:31 +01:00
|
|
|
for (auto *item : items)
|
2020-04-06 11:22:30 +02:00
|
|
|
m_scene->addCurveItem(item);
|
2020-02-21 15:28:31 +01:00
|
|
|
|
|
|
|
|
applyZoom(m_zoomX, m_zoomY);
|
|
|
|
|
viewport()->update();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GraphicsView::updateSelection(const std::vector<CurveItem *> &items)
|
2019-06-18 15:12:21 +02:00
|
|
|
{
|
2020-04-06 11:22:30 +02:00
|
|
|
std::vector<CurveItem *> preservedItems = m_scene->takePinnedItems();
|
|
|
|
|
for (auto *curve : items) {
|
|
|
|
|
auto finder = [curve](CurveItem *item) { return curve->id() == item->id(); };
|
|
|
|
|
auto iter = std::find_if(preservedItems.begin(), preservedItems.end(), finder);
|
|
|
|
|
if (iter == preservedItems.end())
|
|
|
|
|
preservedItems.push_back(curve);
|
2020-02-10 16:21:23 +01:00
|
|
|
}
|
2020-04-06 11:22:30 +02:00
|
|
|
reset(preservedItems);
|
2019-06-18 15:12:21 +02:00
|
|
|
}
|
|
|
|
|
|
2019-06-28 10:45:28 +02:00
|
|
|
void GraphicsView::setInterpolation(Keyframe::Interpolation interpol)
|
|
|
|
|
{
|
2020-04-06 11:22:30 +02:00
|
|
|
const auto selectedCurves = m_scene->selectedCurves();
|
|
|
|
|
for (auto *curve : selectedCurves)
|
|
|
|
|
curve->setInterpolation(interpol);
|
2019-06-28 10:45:28 +02:00
|
|
|
|
|
|
|
|
viewport()->update();
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-24 15:05:28 +01:00
|
|
|
void GraphicsView::toggleUnified()
|
|
|
|
|
{
|
2020-04-06 11:22:30 +02:00
|
|
|
const auto selectedCurves = m_scene->selectedCurves();
|
|
|
|
|
for (auto *curve : selectedCurves)
|
|
|
|
|
curve->toggleUnified();
|
|
|
|
|
|
2020-03-24 15:05:28 +01:00
|
|
|
viewport()->update();
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-18 15:12:21 +02:00
|
|
|
void GraphicsView::resizeEvent(QResizeEvent *event)
|
|
|
|
|
{
|
|
|
|
|
QGraphicsView::resizeEvent(event);
|
|
|
|
|
applyZoom(m_zoomX, m_zoomY);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GraphicsView::keyPressEvent(QKeyEvent *event)
|
|
|
|
|
{
|
|
|
|
|
Shortcut shortcut(event->modifiers(), static_cast<Qt::Key>(event->key()));
|
|
|
|
|
if (shortcut == m_style.shortcuts.frameAll)
|
|
|
|
|
applyZoom(0.0, 0.0);
|
2019-06-28 10:45:28 +02:00
|
|
|
else if (shortcut == m_style.shortcuts.deleteKeyframe)
|
2020-04-06 11:22:30 +02:00
|
|
|
m_scene->deleteSelectedKeyframes();
|
2019-06-18 15:12:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GraphicsView::mousePressEvent(QMouseEvent *event)
|
|
|
|
|
{
|
|
|
|
|
if (m_playhead.mousePress(globalToScene(event->globalPos())))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
Shortcut shortcut(event);
|
2019-06-28 10:45:28 +02:00
|
|
|
if (shortcut == m_style.shortcuts.insertKeyframe) {
|
2020-04-06 11:22:30 +02:00
|
|
|
m_scene->insertKeyframe(globalToRaster(event->globalPos()).x());
|
2019-06-28 10:45:28 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-18 15:12:21 +02:00
|
|
|
if (shortcut == Shortcut(Qt::LeftButton)) {
|
|
|
|
|
QPointF pos = mapToScene(event->pos());
|
|
|
|
|
if (timeScaleRect().contains(pos)) {
|
|
|
|
|
setCurrentFrame(std::round(mapXtoTime(pos.x())));
|
2019-10-24 17:09:22 +02:00
|
|
|
m_playhead.setMoving(true);
|
2019-06-18 15:12:21 +02:00
|
|
|
event->accept();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QGraphicsView::mousePressEvent(event);
|
|
|
|
|
|
2020-04-06 11:22:30 +02:00
|
|
|
m_selector.mousePress(event, this, m_scene);
|
2019-06-18 15:12:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GraphicsView::mouseMoveEvent(QMouseEvent *event)
|
|
|
|
|
{
|
|
|
|
|
if (m_playhead.mouseMove(globalToScene(event->globalPos()), this))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
QGraphicsView::mouseMoveEvent(event);
|
|
|
|
|
|
2020-04-06 11:22:30 +02:00
|
|
|
m_selector.mouseMove(event, this, m_scene, m_playhead);
|
2019-06-18 15:12:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GraphicsView::mouseReleaseEvent(QMouseEvent *event)
|
|
|
|
|
{
|
|
|
|
|
QGraphicsView::mouseReleaseEvent(event);
|
|
|
|
|
|
|
|
|
|
m_playhead.mouseRelease(this);
|
2020-04-06 11:22:30 +02:00
|
|
|
m_selector.mouseRelease(event, m_scene);
|
2019-06-18 15:12:21 +02:00
|
|
|
this->viewport()->update();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GraphicsView::wheelEvent(QWheelEvent *event)
|
|
|
|
|
{
|
|
|
|
|
if (event->modifiers().testFlag(Qt::AltModifier))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
QGraphicsView::wheelEvent(event);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GraphicsView::contextMenuEvent(QContextMenuEvent *event)
|
|
|
|
|
{
|
|
|
|
|
if (event->modifiers() != Qt::NoModifier)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
auto openStyleEditor = [this]() { m_dialog.show(); };
|
|
|
|
|
|
|
|
|
|
QMenu menu;
|
2019-09-03 15:41:25 +02:00
|
|
|
|
|
|
|
|
if (qEnvironmentVariableIsSet("QTC_STYLE_CURVE_EDITOR")) {
|
|
|
|
|
QAction *openEditorAction = menu.addAction(tr("Open Style Editor"));
|
|
|
|
|
connect(openEditorAction, &QAction::triggered, openStyleEditor);
|
|
|
|
|
}
|
2019-06-18 15:12:21 +02:00
|
|
|
|
2019-08-12 15:04:32 +02:00
|
|
|
menu.addSeparator();
|
|
|
|
|
auto insertKeyframes = [this, event]() {
|
2020-04-06 11:22:30 +02:00
|
|
|
m_scene->insertKeyframe(globalToRaster(event->globalPos()).x(), true);
|
2019-08-12 15:04:32 +02:00
|
|
|
};
|
|
|
|
|
QAction *insertKeyframeAction = menu.addAction(tr("Insert Keyframe"));
|
|
|
|
|
connect(insertKeyframeAction, &QAction::triggered, insertKeyframes);
|
|
|
|
|
|
2020-04-23 16:27:35 +02:00
|
|
|
auto deleteKeyframes = [this] { m_scene->deleteSelectedKeyframes(); };
|
2020-01-16 13:09:00 +01:00
|
|
|
QAction *deleteKeyframeAction = menu.addAction(tr("Delete Selected Keyframes"));
|
|
|
|
|
connect(deleteKeyframeAction, &QAction::triggered, deleteKeyframes);
|
|
|
|
|
|
2020-04-06 11:22:30 +02:00
|
|
|
if (!m_scene->hasSelectedKeyframe())
|
2020-01-16 13:09:00 +01:00
|
|
|
deleteKeyframeAction->setEnabled(false);
|
|
|
|
|
|
2019-06-18 15:12:21 +02:00
|
|
|
menu.exec(event->globalPos());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GraphicsView::drawForeground(QPainter *painter, const QRectF &rect)
|
|
|
|
|
{
|
|
|
|
|
QRectF abscissa = timeScaleRect();
|
|
|
|
|
if (abscissa.isValid())
|
|
|
|
|
drawTimeScale(painter, abscissa);
|
|
|
|
|
|
|
|
|
|
auto ordinate = valueScaleRect();
|
|
|
|
|
if (ordinate.isValid())
|
|
|
|
|
drawValueScale(painter, ordinate);
|
|
|
|
|
|
|
|
|
|
m_playhead.paint(painter, this);
|
|
|
|
|
|
|
|
|
|
painter->fillRect(QRectF(rect.topLeft(), abscissa.bottomLeft()),
|
|
|
|
|
m_style.backgroundAlternateBrush);
|
|
|
|
|
|
|
|
|
|
m_selector.paint(painter);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GraphicsView::drawBackground(QPainter *painter, const QRectF &rect)
|
|
|
|
|
{
|
|
|
|
|
painter->fillRect(rect, m_style.backgroundBrush);
|
|
|
|
|
painter->fillRect(scene()->sceneRect(), m_style.backgroundAlternateBrush);
|
|
|
|
|
|
|
|
|
|
drawGrid(painter, rect);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int GraphicsView::mapTimeToX(double time) const
|
|
|
|
|
{
|
|
|
|
|
return std::round(time * scaleX(m_transform));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int GraphicsView::mapValueToY(double y) const
|
|
|
|
|
{
|
|
|
|
|
return std::round(y * scaleY(m_transform));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double GraphicsView::mapXtoTime(int x) const
|
|
|
|
|
{
|
|
|
|
|
return static_cast<double>(x) / scaleX(m_transform);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double GraphicsView::mapYtoValue(int y) const
|
|
|
|
|
{
|
|
|
|
|
return static_cast<double>(y) / scaleY(m_transform);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QPointF GraphicsView::globalToScene(const QPoint &point) const
|
|
|
|
|
{
|
|
|
|
|
return mapToScene(viewport()->mapFromGlobal(point));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QPointF GraphicsView::globalToRaster(const QPoint &point) const
|
|
|
|
|
{
|
|
|
|
|
QPointF scene = globalToScene(point);
|
|
|
|
|
return QPointF(mapXtoTime(scene.x()), mapYtoValue(scene.y()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GraphicsView::applyZoom(double x, double y, const QPoint &pivot)
|
|
|
|
|
{
|
2020-04-06 11:22:30 +02:00
|
|
|
m_scene->doNotMoveItems(true);
|
2020-03-24 15:05:28 +01:00
|
|
|
|
2019-06-18 15:12:21 +02:00
|
|
|
QPointF pivotRaster(globalToRaster(pivot));
|
|
|
|
|
|
|
|
|
|
m_zoomX = clamp(x, 0.0, 1.0);
|
|
|
|
|
m_zoomY = clamp(y, 0.0, 1.0);
|
|
|
|
|
|
|
|
|
|
double minTime = minimumTime();
|
|
|
|
|
double maxTime = maximumTime();
|
|
|
|
|
|
2019-08-13 16:07:53 +02:00
|
|
|
double minValue = minimumValue();
|
|
|
|
|
double maxValue = maximumValue();
|
|
|
|
|
|
2019-06-18 15:12:21 +02:00
|
|
|
QRectF canvas = canvasRect();
|
|
|
|
|
|
|
|
|
|
double xZoomedOut = canvas.width() / (maxTime - minTime);
|
|
|
|
|
double xZoomedIn = m_style.zoomInWidth;
|
|
|
|
|
double scaleX = lerp(clamp(m_zoomX, 0.0, 1.0), xZoomedOut, xZoomedIn);
|
|
|
|
|
|
2019-08-13 16:07:53 +02:00
|
|
|
double yZoomedOut = canvas.height() / (maxValue - minValue);
|
2019-06-18 15:12:21 +02:00
|
|
|
double yZoomedIn = m_style.zoomInHeight;
|
|
|
|
|
double scaleY = lerp(clamp(m_zoomY, 0.0, 1.0), -yZoomedOut, -yZoomedIn);
|
|
|
|
|
|
|
|
|
|
m_transform = QTransform::fromScale(scaleX, scaleY);
|
2020-04-06 11:22:30 +02:00
|
|
|
m_scene->setComponentTransform(m_transform);
|
2019-06-18 15:12:21 +02:00
|
|
|
|
2020-04-06 11:22:30 +02:00
|
|
|
QRectF sr = m_scene->rect().adjusted(
|
2019-06-18 15:12:21 +02:00
|
|
|
-m_style.valueAxisWidth - m_style.canvasMargin,
|
|
|
|
|
-m_style.timeAxisHeight - m_style.canvasMargin,
|
|
|
|
|
m_style.canvasMargin,
|
|
|
|
|
m_style.canvasMargin);
|
|
|
|
|
|
|
|
|
|
setSceneRect(sr);
|
|
|
|
|
|
|
|
|
|
m_playhead.resize(this);
|
|
|
|
|
|
|
|
|
|
if (!pivot.isNull()) {
|
|
|
|
|
QPointF deltaTransformed = pivotRaster - globalToRaster(pivot);
|
|
|
|
|
scrollContent(mapTimeToX(deltaTransformed.x()), mapValueToY(deltaTransformed.y()));
|
|
|
|
|
}
|
2020-03-24 15:05:28 +01:00
|
|
|
|
2020-04-06 11:22:30 +02:00
|
|
|
m_scene->doNotMoveItems(false);
|
2019-06-28 10:45:28 +02:00
|
|
|
}
|
|
|
|
|
|
2019-06-18 15:12:21 +02:00
|
|
|
void GraphicsView::drawGrid(QPainter *painter, const QRectF &rect)
|
|
|
|
|
{
|
2019-06-28 10:45:28 +02:00
|
|
|
QRectF gridRect = rect.adjusted(m_style.valueAxisWidth + m_style.canvasMargin,
|
|
|
|
|
m_style.timeAxisHeight + m_style.canvasMargin,
|
|
|
|
|
-m_style.canvasMargin,
|
|
|
|
|
-m_style.canvasMargin);
|
2019-06-18 15:12:21 +02:00
|
|
|
|
|
|
|
|
if (!gridRect.isValid())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
auto drawVerticalLine = [painter, gridRect](double position) {
|
|
|
|
|
painter->drawLine(position, gridRect.top(), position, gridRect.bottom());
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
painter->save();
|
|
|
|
|
painter->setPen(m_style.gridColor);
|
|
|
|
|
|
|
|
|
|
double timeIncrement = timeLabelInterval(painter, m_model->maximumTime());
|
|
|
|
|
for (double i = minimumTime(); i <= maximumTime(); i += timeIncrement)
|
|
|
|
|
drawVerticalLine(mapTimeToX(i));
|
|
|
|
|
|
|
|
|
|
painter->restore();
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-13 17:19:34 +02:00
|
|
|
#if 0
|
2019-06-18 15:12:21 +02:00
|
|
|
void GraphicsView::drawExtremaX(QPainter *painter, const QRectF &rect)
|
|
|
|
|
{
|
|
|
|
|
auto drawVerticalLine = [rect, painter](double position) {
|
|
|
|
|
painter->drawLine(position, rect.top(), position, rect.bottom());
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
painter->save();
|
|
|
|
|
painter->setPen(Qt::red);
|
|
|
|
|
drawVerticalLine(mapTimeToX(m_model->minimumTime()));
|
|
|
|
|
drawVerticalLine(mapTimeToX(m_model->maximumTime()));
|
|
|
|
|
painter->restore();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GraphicsView::drawExtremaY(QPainter *painter, const QRectF &rect)
|
|
|
|
|
{
|
2020-04-06 11:22:30 +02:00
|
|
|
if (m_scene->empty())
|
2019-06-18 15:12:21 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
auto drawHorizontalLine = [rect, painter](double position) {
|
|
|
|
|
painter->drawLine(rect.left(), position, rect.right(), position);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
painter->save();
|
|
|
|
|
painter->setPen(Qt::blue);
|
2020-04-06 11:22:30 +02:00
|
|
|
drawHorizontalLine(mapValueToY(m_scene->minimumValue()));
|
|
|
|
|
drawHorizontalLine(mapValueToY(m_scene->maximumValue()));
|
2019-06-18 15:12:21 +02:00
|
|
|
|
|
|
|
|
painter->restore();
|
|
|
|
|
}
|
2019-08-13 17:19:34 +02:00
|
|
|
#endif
|
2019-06-18 15:12:21 +02:00
|
|
|
|
2020-02-21 15:28:31 +01:00
|
|
|
void GraphicsView::drawRangeBar(QPainter *painter, const QRectF &rect)
|
|
|
|
|
{
|
2020-03-16 14:02:15 +01:00
|
|
|
painter->save();
|
|
|
|
|
|
2020-02-21 15:28:31 +01:00
|
|
|
QFontMetrics fm(painter->font());
|
|
|
|
|
QRectF labelRect = fm.boundingRect(QString("0"));
|
|
|
|
|
labelRect.moveCenter(rect.center());
|
|
|
|
|
|
|
|
|
|
qreal bTick = rect.bottom() - 2;
|
|
|
|
|
qreal tTick = labelRect.bottom() + 2;
|
|
|
|
|
QRectF activeRect = QRectF(QPointF(mapTimeToX(m_model->minimumTime()), tTick),
|
|
|
|
|
QPointF(mapTimeToX(m_model->maximumTime()), bTick));
|
|
|
|
|
|
2020-03-16 14:02:15 +01:00
|
|
|
painter->fillRect(activeRect, m_style.rangeBarColor);
|
2020-02-21 15:28:31 +01:00
|
|
|
|
2020-03-16 14:02:15 +01:00
|
|
|
QColor handleColor(m_style.rangeBarCapsColor);
|
2020-02-21 15:28:31 +01:00
|
|
|
painter->setBrush(handleColor);
|
|
|
|
|
painter->setPen(handleColor);
|
|
|
|
|
|
|
|
|
|
const qreal radius = 5.;
|
|
|
|
|
QRectF minHandle = rangeMinHandle(rect);
|
|
|
|
|
painter->drawRoundedRect(minHandle, radius, radius);
|
|
|
|
|
minHandle.setLeft(minHandle.center().x());
|
2020-03-16 14:02:15 +01:00
|
|
|
painter->fillRect(minHandle, handleColor);
|
2020-02-21 15:28:31 +01:00
|
|
|
|
|
|
|
|
QRectF maxHandle = rangeMaxHandle(rect);
|
|
|
|
|
painter->drawRoundedRect(maxHandle, radius, radius);
|
|
|
|
|
maxHandle.setRight(maxHandle.center().x());
|
2020-03-16 14:02:15 +01:00
|
|
|
painter->fillRect(maxHandle, handleColor);
|
|
|
|
|
|
|
|
|
|
painter->restore();
|
2020-02-21 15:28:31 +01:00
|
|
|
}
|
|
|
|
|
|
2019-06-18 15:12:21 +02:00
|
|
|
void GraphicsView::drawTimeScale(QPainter *painter, const QRectF &rect)
|
|
|
|
|
{
|
|
|
|
|
painter->save();
|
|
|
|
|
painter->setPen(m_style.fontColor);
|
|
|
|
|
painter->fillRect(rect, m_style.backgroundAlternateBrush);
|
|
|
|
|
|
|
|
|
|
QFontMetrics fm(painter->font());
|
|
|
|
|
|
|
|
|
|
auto paintLabeledTick = [this, painter, rect, fm](double time) {
|
|
|
|
|
QString timeText = QString("%1").arg(time);
|
|
|
|
|
|
|
|
|
|
int position = mapTimeToX(time);
|
|
|
|
|
|
|
|
|
|
QRect textRect = fm.boundingRect(timeText);
|
|
|
|
|
textRect.moveCenter(QPoint(position, rect.center().y()));
|
|
|
|
|
|
|
|
|
|
painter->drawText(textRect, Qt::AlignCenter, timeText);
|
|
|
|
|
painter->drawLine(position, rect.bottom() - 2, position, textRect.bottom() + 2);
|
|
|
|
|
};
|
|
|
|
|
|
2020-03-16 14:02:15 +01:00
|
|
|
drawRangeBar(painter, rect);
|
|
|
|
|
|
2019-06-18 15:12:21 +02:00
|
|
|
double timeIncrement = timeLabelInterval(painter, maximumTime());
|
|
|
|
|
for (double i = minimumTime(); i <= maximumTime(); i += timeIncrement)
|
|
|
|
|
paintLabeledTick(i);
|
|
|
|
|
|
2020-02-21 15:28:31 +01:00
|
|
|
drawRangeBar(painter, rect);
|
|
|
|
|
|
2019-06-18 15:12:21 +02:00
|
|
|
painter->restore();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GraphicsView::drawValueScale(QPainter *painter, const QRectF &rect)
|
|
|
|
|
{
|
|
|
|
|
painter->save();
|
|
|
|
|
painter->setPen(m_style.fontColor);
|
|
|
|
|
painter->fillRect(rect, m_style.backgroundAlternateBrush);
|
|
|
|
|
|
|
|
|
|
QFontMetrics fm(painter->font());
|
|
|
|
|
auto paintLabeledTick = [this, painter, rect, fm](double value) {
|
|
|
|
|
QString valueText = QString("%1").arg(value);
|
|
|
|
|
|
|
|
|
|
int position = mapValueToY(value);
|
|
|
|
|
|
|
|
|
|
QRect textRect = fm.boundingRect(valueText);
|
|
|
|
|
textRect.moveCenter(QPoint(rect.center().x(), position));
|
|
|
|
|
painter->drawText(textRect, Qt::AlignCenter, valueText);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
paintLabeledTick(minimumValue());
|
|
|
|
|
paintLabeledTick(maximumValue());
|
|
|
|
|
painter->restore();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double GraphicsView::timeLabelInterval(QPainter *painter, double maxTime)
|
|
|
|
|
{
|
|
|
|
|
QFontMetrics fm(painter->font());
|
2019-08-29 11:40:38 +02:00
|
|
|
int minTextSpacing = fm.horizontalAdvance(QString("X%1X").arg(maxTime));
|
2019-06-18 15:12:21 +02:00
|
|
|
|
|
|
|
|
int deltaTime = 1;
|
|
|
|
|
int nextFactor = 5;
|
|
|
|
|
|
|
|
|
|
double tickDistance = mapTimeToX(deltaTime);
|
|
|
|
|
|
|
|
|
|
while (true) {
|
2019-12-06 11:55:20 +01:00
|
|
|
if (tickDistance == 0 && deltaTime >= maxTime)
|
2019-06-18 15:12:21 +02:00
|
|
|
return maxTime;
|
|
|
|
|
|
|
|
|
|
if (tickDistance > minTextSpacing)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
deltaTime *= nextFactor;
|
|
|
|
|
tickDistance = mapTimeToX(deltaTime);
|
|
|
|
|
|
|
|
|
|
if (nextFactor == 5)
|
|
|
|
|
nextFactor = 2;
|
|
|
|
|
else
|
|
|
|
|
nextFactor = 5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return deltaTime;
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-21 15:28:31 +01:00
|
|
|
QRectF GraphicsView::rangeMinHandle(const QRectF &rect)
|
|
|
|
|
{
|
|
|
|
|
QRectF labelRect = fontMetrics().boundingRect(QString("0"));
|
|
|
|
|
labelRect.moveCenter(rect.center());
|
|
|
|
|
|
|
|
|
|
qreal top = rect.bottom() - 2;
|
|
|
|
|
qreal bottom = labelRect.bottom() + 2;
|
|
|
|
|
QSize size(10, top - bottom);
|
|
|
|
|
|
|
|
|
|
int leftHandleLeft = mapTimeToX(m_model->minimumTime()) - size.width();
|
|
|
|
|
return QRectF(QPointF(leftHandleLeft, bottom), size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QRectF GraphicsView::rangeMaxHandle(const QRectF &rect)
|
|
|
|
|
{
|
|
|
|
|
QRectF labelRect = fontMetrics().boundingRect(QString("0"));
|
|
|
|
|
labelRect.moveCenter(rect.center());
|
|
|
|
|
|
|
|
|
|
qreal bottom = rect.bottom() - 2;
|
|
|
|
|
qreal top = labelRect.bottom() + 2;
|
|
|
|
|
|
|
|
|
|
return QRectF(QPointF(mapTimeToX(m_model->maximumTime()), bottom), QSize(10, top - bottom));
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-18 15:12:21 +02:00
|
|
|
} // End namespace DesignTools.
|