Merge branch 'master' into marker
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
add_subdirectory(audiomonitor)
|
||||
add_subdirectory(bezier)
|
||||
add_subdirectory(clickablelabel)
|
||||
add_subdirectory(collapsebutton)
|
||||
add_subdirectory(colorbutton)
|
||||
@@ -52,7 +53,6 @@ add_subdirectory(timelinewidget)
|
||||
add_subdirectory(timeruler)
|
||||
add_subdirectory(timetarget)
|
||||
add_subdirectory(toolbar)
|
||||
add_subdirectory(videoparamedit)
|
||||
add_subdirectory(viewer)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
|
||||
@@ -80,6 +80,10 @@ void AudioMonitor::Stop()
|
||||
|
||||
void AudioMonitor::PushBytes(const QByteArray &d)
|
||||
{
|
||||
if (!params_.channel_count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QVector<double> v(params_.channel_count(), 0);
|
||||
|
||||
BytesToSampleSummary(d, v);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
widget/videoparamedit/videoparamedit.cpp
|
||||
widget/videoparamedit/videoparamedit.h
|
||||
widget/bezier/bezierwidget.cpp
|
||||
widget/bezier/bezierwidget.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,101 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "bezierwidget.h"
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
|
||||
namespace olive {
|
||||
|
||||
BezierWidget::BezierWidget(QWidget *parent) :
|
||||
QWidget{parent}
|
||||
{
|
||||
QGridLayout *layout = new QGridLayout(this);
|
||||
|
||||
int row = 0;
|
||||
|
||||
layout->addWidget(new QLabel(tr("Center:")), row, 0);
|
||||
|
||||
x_slider_ = new FloatSlider();
|
||||
connect(x_slider_, &FloatSlider::ValueChanged, this, &BezierWidget::ValueChanged);
|
||||
layout->addWidget(x_slider_, row, 1);
|
||||
|
||||
y_slider_ = new FloatSlider();
|
||||
connect(y_slider_, &FloatSlider::ValueChanged, this, &BezierWidget::ValueChanged);
|
||||
layout->addWidget(y_slider_, row, 2);
|
||||
|
||||
row++;
|
||||
|
||||
QGroupBox *bezier_group = new QGroupBox(tr("Bezier"));
|
||||
layout->addWidget(bezier_group, row, 0, 1, 3);
|
||||
|
||||
QGridLayout *bezier_layout = new QGridLayout(bezier_group);
|
||||
|
||||
row = 0;
|
||||
|
||||
bezier_layout->addWidget(new QLabel(tr("In:")), row, 0);
|
||||
|
||||
cp1_x_slider_ = new FloatSlider();
|
||||
connect(cp1_x_slider_, &FloatSlider::ValueChanged, this, &BezierWidget::ValueChanged);
|
||||
bezier_layout->addWidget(cp1_x_slider_, row, 1);
|
||||
|
||||
cp1_y_slider_ = new FloatSlider();
|
||||
connect(cp1_y_slider_, &FloatSlider::ValueChanged, this, &BezierWidget::ValueChanged);
|
||||
bezier_layout->addWidget(cp1_y_slider_, row, 2);
|
||||
|
||||
row++;
|
||||
|
||||
bezier_layout->addWidget(new QLabel(tr("Out:")), row, 0);
|
||||
|
||||
cp2_x_slider_ = new FloatSlider();
|
||||
connect(cp2_x_slider_, &FloatSlider::ValueChanged, this, &BezierWidget::ValueChanged);
|
||||
bezier_layout->addWidget(cp2_x_slider_, row, 1);
|
||||
|
||||
cp2_y_slider_ = new FloatSlider();
|
||||
connect(cp2_y_slider_, &FloatSlider::ValueChanged, this, &BezierWidget::ValueChanged);
|
||||
bezier_layout->addWidget(cp2_y_slider_, row, 2);
|
||||
}
|
||||
|
||||
Bezier BezierWidget::GetValue() const
|
||||
{
|
||||
Bezier b;
|
||||
|
||||
b.set_x(x_slider_->GetValue());
|
||||
b.set_y(y_slider_->GetValue());
|
||||
b.set_cp1_x(cp1_x_slider_->GetValue());
|
||||
b.set_cp1_y(cp1_y_slider_->GetValue());
|
||||
b.set_cp2_x(cp2_x_slider_->GetValue());
|
||||
b.set_cp2_y(cp2_y_slider_->GetValue());
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
void BezierWidget::SetValue(const Bezier &b)
|
||||
{
|
||||
x_slider_->SetValue(b.x());
|
||||
y_slider_->SetValue(b.y());
|
||||
cp1_x_slider_->SetValue(b.cp1_x());
|
||||
cp1_y_slider_->SetValue(b.cp1_y());
|
||||
cp2_x_slider_->SetValue(b.cp2_x());
|
||||
cp2_y_slider_->SetValue(b.cp2_y());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef BEZIERWIDGET_H
|
||||
#define BEZIERWIDGET_H
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QWidget>
|
||||
|
||||
#include "common/bezier.h"
|
||||
#include "widget/slider/floatslider.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class BezierWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit BezierWidget(QWidget *parent = nullptr);
|
||||
|
||||
Bezier GetValue() const;
|
||||
|
||||
void SetValue(const Bezier &b);
|
||||
|
||||
FloatSlider *x_slider() const { return x_slider_; }
|
||||
|
||||
FloatSlider *y_slider() const { return y_slider_; }
|
||||
|
||||
FloatSlider *cp1_x_slider() const { return cp1_x_slider_; }
|
||||
|
||||
FloatSlider *cp1_y_slider() const { return cp1_y_slider_; }
|
||||
|
||||
FloatSlider *cp2_x_slider() const { return cp2_x_slider_; }
|
||||
|
||||
FloatSlider *cp2_y_slider() const { return cp2_y_slider_; }
|
||||
|
||||
signals:
|
||||
void ValueChanged();
|
||||
|
||||
private:
|
||||
FloatSlider *x_slider_;
|
||||
|
||||
FloatSlider *y_slider_;
|
||||
|
||||
FloatSlider *cp1_x_slider_;
|
||||
|
||||
FloatSlider *cp1_y_slider_;
|
||||
|
||||
FloatSlider *cp2_x_slider_;
|
||||
|
||||
FloatSlider *cp2_y_slider_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // BEZIERWIDGET_H
|
||||
@@ -76,7 +76,7 @@ void ColorGradientWidget::paintEvent(QPaintEvent *e)
|
||||
p.setPen(QPen(GetUISelectorColor(), qMax(1, selector_radius / 2)));
|
||||
p.setBrush(Qt::NoBrush);
|
||||
|
||||
double clamped_val = clamp(val_, 0.0, 1.0);
|
||||
float clamped_val = clamp(val_, 0.0f, 1.0f);
|
||||
|
||||
if (orientation_ == Qt::Horizontal) {
|
||||
p.drawRect(qRound(width() * (1.0 - clamped_val)) - selector_radius, 0, selector_radius * 2, height() - 1);
|
||||
@@ -87,7 +87,7 @@ void ColorGradientWidget::paintEvent(QPaintEvent *e)
|
||||
|
||||
void ColorGradientWidget::SelectedColorChangedEvent(const Color &c, bool external)
|
||||
{
|
||||
double hue, sat;
|
||||
float hue, sat;
|
||||
|
||||
c.toHsv(&hue, &sat, &val_);
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ private:
|
||||
|
||||
Color end_;
|
||||
|
||||
double val_;
|
||||
float val_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@ void ColorValuesWidget::ColorPickedBtnToggled(bool e)
|
||||
qApp->removeEventFilter(this);
|
||||
|
||||
// Restore original color (or use overridden color from eventFilter)
|
||||
SetColor(picker_end_color_);
|
||||
SetReferenceColor(picker_end_color_);
|
||||
emit ColorChanged(picker_end_color_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ void ColorWheelWidget::SelectedColorChangedEvent(const Color &c, bool external)
|
||||
{
|
||||
if (external) {
|
||||
force_redraw_ = true;
|
||||
val_ = clamp(c.value(), 0.0, 1.0);
|
||||
val_ = clamp(c.value(), 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ Color ColorWheelWidget::GetColorFromTriangle(const ColorWheelWidget::Triangle &t
|
||||
|
||||
QPoint ColorWheelWidget::GetCoordsFromColor(const Color &c) const
|
||||
{
|
||||
double hue, sat, val;
|
||||
float hue, sat, val;
|
||||
c.toHsv(&hue, &sat, &val);
|
||||
|
||||
qreal hypotenuse = sat * GetRadius();
|
||||
|
||||
@@ -16,11 +16,9 @@
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
widget/curvewidget/beziercontrolpointitem.h
|
||||
widget/curvewidget/beziercontrolpointitem.cpp
|
||||
widget/curvewidget/curveview.h
|
||||
widget/curvewidget/curveview.cpp
|
||||
widget/curvewidget/curvewidget.h
|
||||
widget/curvewidget/curveview.h
|
||||
widget/curvewidget/curvewidget.cpp
|
||||
widget/curvewidget/curvewidget.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "beziercontrolpointitem.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QPainter>
|
||||
#include <QStyleOptionGraphicsItem>
|
||||
#include <QWidget>
|
||||
|
||||
#include "common/qtutils.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
BezierControlPointItem::BezierControlPointItem(NodeKeyframe* key, NodeKeyframe::BezierType mode, QGraphicsItem *parent) :
|
||||
QGraphicsRectItem(parent),
|
||||
key_(key),
|
||||
mode_(mode),
|
||||
x_scale_(1.0),
|
||||
y_scale_(1.0)
|
||||
{
|
||||
setFlag(QGraphicsItem::ItemIsMovable);
|
||||
|
||||
connect(key, &NodeKeyframe::TimeChanged, this, &BezierControlPointItem::UpdatePos);
|
||||
|
||||
if (mode_ == NodeKeyframe::kInHandle) {
|
||||
connect(key, &NodeKeyframe::BezierControlInChanged, this, &BezierControlPointItem::UpdatePos);
|
||||
} else {
|
||||
connect(key, &NodeKeyframe::BezierControlOutChanged, this, &BezierControlPointItem::UpdatePos);
|
||||
}
|
||||
|
||||
|
||||
int control_point_size = QtUtils::QFontMetricsWidth(qApp->fontMetrics(), "o");
|
||||
int half_sz = control_point_size / 2;
|
||||
setRect(-half_sz, -half_sz, control_point_size, control_point_size);
|
||||
}
|
||||
|
||||
void BezierControlPointItem::SetXScale(double scale)
|
||||
{
|
||||
x_scale_ = scale;
|
||||
UpdatePos();
|
||||
}
|
||||
|
||||
void BezierControlPointItem::SetYScale(double scale)
|
||||
{
|
||||
y_scale_ = scale;
|
||||
UpdatePos();
|
||||
}
|
||||
|
||||
NodeKeyframe* BezierControlPointItem::key() const
|
||||
{
|
||||
return key_;
|
||||
}
|
||||
|
||||
const NodeKeyframe::BezierType &BezierControlPointItem::mode() const
|
||||
{
|
||||
return mode_;
|
||||
}
|
||||
|
||||
QPointF BezierControlPointItem::GetCorrespondingKeyframeHandle() const
|
||||
{
|
||||
return key_->bezier_control(mode_);
|
||||
}
|
||||
|
||||
void BezierControlPointItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
|
||||
{
|
||||
if (option->state & QStyle::State_Selected) {
|
||||
painter->setPen(widget->palette().highlight().color());
|
||||
} else {
|
||||
painter->setPen(widget->palette().text().color());
|
||||
}
|
||||
|
||||
painter->drawEllipse(rect());
|
||||
}
|
||||
|
||||
void BezierControlPointItem::UpdatePos()
|
||||
{
|
||||
QPointF handle_offset = GetCorrespondingKeyframeHandle();
|
||||
|
||||
// Scale handle offset
|
||||
handle_offset.setX(handle_offset.x() * x_scale_);
|
||||
|
||||
// Flip the Y coordinate because bezier curves are drawn bottom to top
|
||||
handle_offset.setY(-handle_offset.y() * y_scale_);
|
||||
|
||||
setPos(handle_offset - rect().center());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef BEZIERCONTROLPOINTITEM_H
|
||||
#define BEZIERCONTROLPOINTITEM_H
|
||||
|
||||
#include <QGraphicsRectItem>
|
||||
|
||||
#include "node/keyframe.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class BezierControlPointItem : public QObject, public QGraphicsRectItem
|
||||
{
|
||||
public:
|
||||
BezierControlPointItem(NodeKeyframe* key, NodeKeyframe::BezierType mode, QGraphicsItem* parent = nullptr);
|
||||
|
||||
void SetXScale(double scale);
|
||||
|
||||
void SetYScale(double scale);
|
||||
|
||||
NodeKeyframe* key() const;
|
||||
|
||||
const NodeKeyframe::BezierType& mode() const;
|
||||
|
||||
QPointF GetCorrespondingKeyframeHandle() const;
|
||||
|
||||
void SetCorrespondingKeyframeHandle(const QPointF& handle);
|
||||
|
||||
void SetOpposingKeyframeHandle(const QPointF& handle);
|
||||
|
||||
protected:
|
||||
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
|
||||
|
||||
private:
|
||||
NodeKeyframe* key_;
|
||||
|
||||
NodeKeyframe::BezierType mode_;
|
||||
|
||||
double x_scale_;
|
||||
|
||||
double y_scale_;
|
||||
|
||||
private slots:
|
||||
void UpdatePos();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // BEZIERCONTROLPOINTITEM_H
|
||||
@@ -20,47 +20,32 @@
|
||||
|
||||
#include "curveview.h"
|
||||
|
||||
#include <cfloat>
|
||||
#include <QHash>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainterPath>
|
||||
#include <QScrollBar>
|
||||
#include <QtMath>
|
||||
#include <cfloat>
|
||||
|
||||
#include "common/qtutils.h"
|
||||
#include "widget/keyframeview/keyframeviewundo.h"
|
||||
#include "widget/nodeparamview/nodeparamviewundo.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
#define super KeyframeViewBase
|
||||
#define super KeyframeView
|
||||
|
||||
CurveView::CurveView(QWidget *parent) :
|
||||
KeyframeViewBase(parent)
|
||||
KeyframeView(parent),
|
||||
dragging_bezier_pt_(nullptr)
|
||||
{
|
||||
setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
setDragMode(RubberBandDrag);
|
||||
setViewportUpdateMode(FullViewportUpdate);
|
||||
SetYAxisEnabled(true);
|
||||
SetAutoSelectSiblings(false);
|
||||
|
||||
text_padding_ = QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral("i"));
|
||||
|
||||
minimum_grid_space_ = QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral("00000"));
|
||||
|
||||
connect(scene(), &QGraphicsScene::selectionChanged, this, &CurveView::SelectionChanged);
|
||||
}
|
||||
|
||||
CurveView::~CurveView()
|
||||
{
|
||||
// Quick way to avoid segfault when QGraphicsScene::selectionChanged is emitted after other members have been destroyed
|
||||
Clear();
|
||||
}
|
||||
|
||||
void CurveView::Clear()
|
||||
{
|
||||
KeyframeViewBase::Clear();
|
||||
|
||||
foreach (QGraphicsLineItem* line, lines_) {
|
||||
delete line;
|
||||
}
|
||||
lines_.clear();
|
||||
}
|
||||
|
||||
void CurveView::ConnectInput(const NodeKeyframeTrackReference& ref)
|
||||
@@ -71,7 +56,12 @@ void CurveView::ConnectInput(const NodeKeyframeTrackReference& ref)
|
||||
}
|
||||
|
||||
// Add keyframes from track
|
||||
AddKeyframesOfTrack(ref);
|
||||
KeyframeViewInputConnection *track_con = AddKeyframesOfTrack(ref);
|
||||
track_con->SetBrush(keyframe_colors_.value(ref));
|
||||
track_connections_.insert(ref, track_con);
|
||||
|
||||
// Signal to CurveWidget to update its bezier/linear/hold buttons if a key type changes
|
||||
connect(track_con, &KeyframeViewInputConnection::TypeChanged, this, &CurveView::SelectionChanged);
|
||||
|
||||
// Append to the list
|
||||
connected_inputs_.append(ref);
|
||||
@@ -85,7 +75,7 @@ void CurveView::DisconnectInput(const NodeKeyframeTrackReference& ref)
|
||||
}
|
||||
|
||||
// Remove keyframes belonging to this element and track
|
||||
RemoveKeyframesOfTrack(ref);
|
||||
RemoveKeyframesOfTrack(track_connections_.take(ref));
|
||||
|
||||
// Remove from the list
|
||||
connected_inputs_.removeOne(ref);
|
||||
@@ -95,36 +85,21 @@ void CurveView::SelectKeyframesOfInput(const NodeKeyframeTrackReference& ref)
|
||||
{
|
||||
DeselectAll();
|
||||
|
||||
for (auto it=item_map().cbegin(); it!=item_map().cend(); it++) {
|
||||
if (it.key()->key_track_ref() == ref) {
|
||||
it.value()->setSelected(true);
|
||||
foreach (KeyframeViewInputConnection *con, track_connections_) {
|
||||
foreach (NodeKeyframe *key, con->GetKeyframes()) {
|
||||
SelectKeyframe(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CurveView::ZoomToFitInput(const NodeKeyframeTrackReference& ref)
|
||||
{
|
||||
QList<NodeKeyframe*> keys;
|
||||
|
||||
for (auto it=item_map().cbegin(); it!=item_map().cend(); it++) {
|
||||
if (it.key()->key_track_ref() == ref) {
|
||||
keys.append(it.key());
|
||||
}
|
||||
}
|
||||
|
||||
ZoomToFitInternal(keys);
|
||||
}
|
||||
|
||||
void CurveView::SetKeyframeTrackColor(const NodeKeyframeTrackReference &ref, const QColor &color)
|
||||
{
|
||||
// Insert color into hashmap
|
||||
keyframe_colors_.insert(ref, color);
|
||||
|
||||
// Update all keyframes
|
||||
for (auto it=item_map().cbegin(); it!=item_map().cend(); it++) {
|
||||
if (it.key()->key_track_ref() == ref) {
|
||||
it.value()->SetOverrideBrush(color);
|
||||
}
|
||||
if (KeyframeViewInputConnection *con = track_connections_.value(ref)) {
|
||||
// Update all keyframes
|
||||
con->SetBrush(color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +171,7 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect)
|
||||
QPainterPath path;
|
||||
|
||||
// Draw straight line leading to first keyframe
|
||||
QPointF first_key_pos = item_map().value(track.first())->pos();
|
||||
QPointF first_key_pos = GetKeyframePosition(track.first());
|
||||
path.moveTo(QPointF(scene_bottom_left.x(), first_key_pos.y()));
|
||||
path.lineTo(first_key_pos);
|
||||
|
||||
@@ -205,24 +180,24 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect)
|
||||
NodeKeyframe* before = track.at(i-1);
|
||||
NodeKeyframe* after = track.at(i);
|
||||
|
||||
KeyframeViewItem* before_item = item_map().value(before);
|
||||
KeyframeViewItem* after_item = item_map().value(after);
|
||||
QPointF before_pos = GetKeyframePosition(before);
|
||||
QPointF after_pos = GetKeyframePosition(after);
|
||||
|
||||
if (before->type() == NodeKeyframe::kHold) {
|
||||
|
||||
// Draw a hold keyframe (basically a right angle)
|
||||
path.lineTo(after_item->pos().x(), before_item->pos().y());
|
||||
path.lineTo(after_item->pos().x(), after_item->pos().y());
|
||||
path.lineTo(after_pos.x(), before_pos.y());
|
||||
path.lineTo(after_pos.x(), after_pos.y());
|
||||
|
||||
} else if (before->type() == NodeKeyframe::kBezier && after->type() == NodeKeyframe::kBezier) {
|
||||
|
||||
// Draw a cubic bezier
|
||||
|
||||
// Cubic beziers have two control points, so we can just use both
|
||||
QPointF before_control_point = before_item->pos() + ScalePoint(before->valid_bezier_control_out());
|
||||
QPointF after_control_point = after_item->pos() + ScalePoint(after->valid_bezier_control_in());
|
||||
QPointF before_control_point = before_pos + ScalePoint(before->valid_bezier_control_out());
|
||||
QPointF after_control_point = after_pos + ScalePoint(after->valid_bezier_control_in());
|
||||
|
||||
path.cubicTo(before_control_point, after_control_point, after_item->pos());
|
||||
path.cubicTo(before_control_point, after_control_point, after_pos);
|
||||
|
||||
} else if (before->type() == NodeKeyframe::kBezier || after->type() == NodeKeyframe::kBezier) {
|
||||
// Draw a quadratic bezier
|
||||
@@ -232,10 +207,10 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect)
|
||||
QPointF control_point;
|
||||
|
||||
if (before->type() == NodeKeyframe::kBezier) {
|
||||
key_anchor = before_item->pos();
|
||||
key_anchor = before_pos;
|
||||
control_point = before->valid_bezier_control_out();
|
||||
} else {
|
||||
key_anchor = after_item->pos();
|
||||
key_anchor = after_pos;
|
||||
control_point = after->valid_bezier_control_in();
|
||||
}
|
||||
|
||||
@@ -243,66 +218,31 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect)
|
||||
control_point = key_anchor + ScalePoint(control_point);
|
||||
|
||||
// Create the path from both keyframes
|
||||
path.quadTo(control_point, after_item->pos());
|
||||
path.quadTo(control_point, after_pos);
|
||||
|
||||
} else {
|
||||
|
||||
// Linear to linear
|
||||
path.lineTo(after_item->pos());
|
||||
path.lineTo(after_pos);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Draw straight line leading from end keyframe
|
||||
QPointF last_key_pos = item_map().value(track.last())->pos();
|
||||
QPointF last_key_pos = GetKeyframePosition(track.last());
|
||||
path.lineTo(QPointF(scene_top_right.x(), last_key_pos.y()));
|
||||
|
||||
painter->drawPath(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw bezier control point lines
|
||||
if (!bezier_control_points_.isEmpty()) {
|
||||
painter->setPen(QPen(palette().text().color(), 1));
|
||||
|
||||
QVector<QLineF> bezier_lines;
|
||||
foreach (BezierControlPointItem* item, bezier_control_points_) {
|
||||
// All BezierControlPointItems should be children of a KeyframeViewItem
|
||||
KeyframeViewItem* par = static_cast<KeyframeViewItem*>(item->parentItem());
|
||||
|
||||
bezier_lines.append(QLineF(par->pos(), par->pos() + item->pos()));
|
||||
}
|
||||
painter->drawLines(bezier_lines);
|
||||
}
|
||||
}
|
||||
|
||||
void CurveView::KeyframeAboutToBeRemoved(NodeKeyframe *key)
|
||||
void CurveView::drawForeground(QPainter *painter, const QRectF &rect)
|
||||
{
|
||||
disconnect(key, &NodeKeyframe::ValueChanged, this, &CurveView::KeyframeValueChanged);
|
||||
disconnect(key, &NodeKeyframe::TypeChanged, this, &CurveView::KeyframeTypeChanged);
|
||||
}
|
||||
bezier_pts_.clear();
|
||||
|
||||
void CurveView::ScaleChangedEvent(const double& scale)
|
||||
{
|
||||
KeyframeViewBase::ScaleChangedEvent(scale);
|
||||
|
||||
foreach (BezierControlPointItem* item, bezier_control_points_) {
|
||||
item->SetXScale(scale);
|
||||
}
|
||||
}
|
||||
|
||||
void CurveView::VerticalScaleChangedEvent(double scale)
|
||||
{
|
||||
Q_UNUSED(scale)
|
||||
|
||||
for (auto iterator=item_map().begin();iterator!=item_map().end();iterator++) {
|
||||
SetItemYFromKeyframeValue(iterator.value()->key(), iterator.value());
|
||||
}
|
||||
|
||||
foreach (BezierControlPointItem* item, bezier_control_points_) {
|
||||
item->SetYScale(scale);
|
||||
}
|
||||
super::drawForeground(painter, rect);
|
||||
}
|
||||
|
||||
void CurveView::ContextMenuEvent(Menu &m)
|
||||
@@ -320,62 +260,334 @@ void CurveView::ContextMenuEvent(Menu &m)
|
||||
|
||||
void CurveView::SceneRectUpdateEvent(QRectF &r)
|
||||
{
|
||||
r.setTop(r.top() - this->height());
|
||||
r.setBottom(r.bottom() + this->height());
|
||||
double min_val, max_val;
|
||||
bool got_val = false;
|
||||
|
||||
foreach (KeyframeViewInputConnection *con, track_connections_) {
|
||||
foreach (NodeKeyframe *key, con->GetKeyframes()) {
|
||||
qreal key_y = GetItemYFromKeyframeValue(key);
|
||||
|
||||
if (got_val) {
|
||||
min_val = qMin(key_y, min_val);
|
||||
max_val = qMax(key_y, max_val);
|
||||
} else {
|
||||
min_val = key_y;
|
||||
max_val = key_y;
|
||||
got_val = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (got_val) {
|
||||
r.setTop(min_val - this->height());
|
||||
r.setBottom(max_val + this->height());
|
||||
}
|
||||
}
|
||||
|
||||
void CurveView::ZoomToFitInternal(const QList<NodeKeyframe *> &keys)
|
||||
qreal CurveView::GetKeyframeSceneY(KeyframeViewInputConnection *track, NodeKeyframe *key)
|
||||
{
|
||||
if (keys.isEmpty()) {
|
||||
// Prevent scaling to DBL_MIN/DBL_MAX
|
||||
return GetItemYFromKeyframeValue(key);
|
||||
}
|
||||
|
||||
void CurveView::DrawKeyframe(QPainter *painter, NodeKeyframe *key, KeyframeViewInputConnection *track, const QRectF &key_rect)
|
||||
{
|
||||
if (IsKeyframeSelected(key) && key->type() == NodeKeyframe::kBezier) {
|
||||
// Draw bezier control points if keyframe is selected
|
||||
int control_point_size = QtUtils::QFontMetricsWidth(fontMetrics(), "o");
|
||||
int half_sz = control_point_size / 2;
|
||||
QRectF control_point_rect(-half_sz, -half_sz, control_point_size, control_point_size);
|
||||
|
||||
painter->setPen(palette().text().color());
|
||||
painter->setBrush(Qt::NoBrush);
|
||||
|
||||
QRectF cp_in = control_point_rect.translated(key_rect.center() + ScalePoint(key->bezier_control_in()));
|
||||
QRectF cp_out = control_point_rect.translated(key_rect.center() + ScalePoint(key->bezier_control_out()));
|
||||
|
||||
painter->drawLine(key_rect.center(), cp_in.center());
|
||||
painter->drawLine(key_rect.center(), cp_out.center());
|
||||
|
||||
painter->drawEllipse(cp_in);
|
||||
painter->drawEllipse(cp_out);
|
||||
|
||||
bezier_pts_.append({cp_in, key, NodeKeyframe::kInHandle});
|
||||
bezier_pts_.append({cp_out, key, NodeKeyframe::kOutHandle});
|
||||
}
|
||||
|
||||
super::DrawKeyframe(painter, key, track, key_rect);
|
||||
}
|
||||
|
||||
bool CurveView::FirstChanceMousePress(QMouseEvent *event)
|
||||
{
|
||||
dragging_bezier_pt_ = nullptr;
|
||||
QPointF scene_pt = mapToScene(event->pos());
|
||||
foreach (const BezierPoint &b, bezier_pts_) {
|
||||
if (b.rect.contains(scene_pt)) {
|
||||
dragging_bezier_pt_ = &b;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (dragging_bezier_pt_) {
|
||||
NodeKeyframe *key = dragging_bezier_pt_->keyframe;
|
||||
dragging_bezier_point_start_ = (dragging_bezier_pt_->type == NodeKeyframe::kInHandle) ? key->bezier_control_in() : key->bezier_control_out();
|
||||
dragging_bezier_point_opposing_start_ = (dragging_bezier_pt_->type == NodeKeyframe::kInHandle) ? key->bezier_control_out() : key->bezier_control_in();
|
||||
|
||||
drag_start_ = mapToScene(event->pos());
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void CurveView::FirstChanceMouseMove(QMouseEvent *event)
|
||||
{
|
||||
// Calculate cursor difference and scale it
|
||||
QPointF scene_pos = mapToScene(event->pos());
|
||||
QPointF mouse_diff_scaled = GetScaledCursorPos(scene_pos - drag_start_);
|
||||
|
||||
if (event->modifiers() & Qt::ShiftModifier) {
|
||||
// If holding shift, only move one axis
|
||||
mouse_diff_scaled.setY(0);
|
||||
}
|
||||
|
||||
// Flip the mouse Y because bezier control points are drawn bottom to top, not top to bottom
|
||||
mouse_diff_scaled.setY(-mouse_diff_scaled.y());
|
||||
|
||||
QPointF new_bezier_pos = GenerateBezierControlPosition(dragging_bezier_pt_->type,
|
||||
dragging_bezier_point_start_,
|
||||
mouse_diff_scaled);
|
||||
|
||||
// If the user is NOT holding control, we set the other handle to the exact negative of this handle
|
||||
QPointF new_opposing_pos;
|
||||
NodeKeyframe::BezierType opposing_type = NodeKeyframe::get_opposing_bezier_type(dragging_bezier_pt_->type);
|
||||
|
||||
|
||||
if (!(event->modifiers() & Qt::ControlModifier)) {
|
||||
new_opposing_pos = GenerateBezierControlPosition(opposing_type,
|
||||
dragging_bezier_point_opposing_start_,
|
||||
-mouse_diff_scaled);
|
||||
} else {
|
||||
new_opposing_pos = dragging_bezier_point_opposing_start_;
|
||||
}
|
||||
|
||||
dragging_bezier_pt_->keyframe->set_bezier_control(dragging_bezier_pt_->type,
|
||||
new_bezier_pos);
|
||||
|
||||
dragging_bezier_pt_->keyframe->set_bezier_control(opposing_type,
|
||||
new_opposing_pos);
|
||||
|
||||
Redraw();
|
||||
}
|
||||
|
||||
void CurveView::FirstChanceMouseRelease(QMouseEvent *event)
|
||||
{
|
||||
MultiUndoCommand* command = new MultiUndoCommand();
|
||||
|
||||
// Create undo command with the current bezier point and the old one
|
||||
command->add_child(new KeyframeSetBezierControlPoint(dragging_bezier_pt_->keyframe,
|
||||
dragging_bezier_pt_->type,
|
||||
dragging_bezier_pt_->keyframe->bezier_control(dragging_bezier_pt_->type),
|
||||
dragging_bezier_point_start_));
|
||||
|
||||
if (!(event->modifiers() & Qt::ControlModifier)) {
|
||||
auto opposing_type = NodeKeyframe::get_opposing_bezier_type(dragging_bezier_pt_->type);
|
||||
|
||||
command->add_child(new KeyframeSetBezierControlPoint(dragging_bezier_pt_->keyframe,
|
||||
opposing_type,
|
||||
dragging_bezier_pt_->keyframe->bezier_control(opposing_type),
|
||||
dragging_bezier_point_opposing_start_));
|
||||
}
|
||||
|
||||
dragging_bezier_pt_ = nullptr;
|
||||
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
}
|
||||
|
||||
void CurveView::KeyframeDragStart(QMouseEvent *event)
|
||||
{
|
||||
drag_keyframe_values_.resize(GetSelectedKeyframes().size());
|
||||
for (int i=0; i<GetSelectedKeyframes().size(); i++) {
|
||||
NodeKeyframe *key = GetSelectedKeyframes().at(i);
|
||||
drag_keyframe_values_[i] = key->value();
|
||||
}
|
||||
|
||||
drag_start_ = mapToScene(event->pos());
|
||||
}
|
||||
|
||||
void CurveView::KeyframeDragMove(QMouseEvent *event, QString &tip)
|
||||
{
|
||||
if (event->modifiers() & Qt::ShiftModifier) {
|
||||
// Lock to X axis only and set original values on all keys
|
||||
for (int i=0; i<GetSelectedKeyframes().size(); i++) {
|
||||
NodeKeyframe *key = GetSelectedKeyframes().at(i);
|
||||
key->set_value(drag_keyframe_values_.at(i));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
rational min_time = RATIONAL_MAX;
|
||||
rational max_time = RATIONAL_MIN;
|
||||
// Calculate cursor difference
|
||||
double scaled_diff = (mapToScene(event->pos()).y() - drag_start_.y()) / GetYScale();
|
||||
|
||||
double min_val = DBL_MAX;
|
||||
double max_val = DBL_MIN;
|
||||
// Validate movement - ensure no keyframe goes above its max point or below its min point
|
||||
for (int i=0; i<GetSelectedKeyframes().size(); i++) {
|
||||
NodeKeyframe *key = GetSelectedKeyframes().at(i);
|
||||
|
||||
foreach (NodeKeyframe* key, keys) {
|
||||
rational transformed_time = GetAdjustedTime(key->parent(),
|
||||
GetTimeTarget(),
|
||||
key->time(),
|
||||
false);
|
||||
FloatSlider::DisplayType display = GetFloatDisplayTypeFromKeyframe(key);
|
||||
Node* node = key->parent();
|
||||
double original_val = FloatSlider::TransformValueToDisplay(drag_keyframe_values_.at(i).toDouble(), display);
|
||||
const QString& input = key->input();
|
||||
double new_val = FloatSlider::TransformDisplayToValue(original_val - scaled_diff, display);
|
||||
double limited = new_val;
|
||||
|
||||
min_time = qMin(transformed_time, min_time);
|
||||
max_time = qMax(transformed_time, max_time);
|
||||
if (node->HasInputProperty(input, QStringLiteral("min"))) {
|
||||
limited = qMax(limited, node->GetInputProperty(input, QStringLiteral("min")).toDouble());
|
||||
}
|
||||
|
||||
min_val = qMin(key->value().toDouble(), min_val);
|
||||
max_val = qMax(key->value().toDouble(), max_val);
|
||||
if (node->HasInputProperty(input, QStringLiteral("max"))) {
|
||||
limited = qMin(limited, node->GetInputProperty(input, QStringLiteral("max")).toDouble());
|
||||
}
|
||||
|
||||
if (limited != new_val) {
|
||||
scaled_diff = original_val - limited;
|
||||
}
|
||||
}
|
||||
|
||||
double time_range = max_time.toDouble() - min_time.toDouble();
|
||||
double new_x_scale = CalculateScaleFromDimensions(this->width(), time_range);
|
||||
double new_y_scale = CalculateScaleFromDimensions(this->height(), max_val - min_val);
|
||||
// Set values
|
||||
for (int i=0; i<GetSelectedKeyframes().size(); i++) {
|
||||
NodeKeyframe *key = GetSelectedKeyframes().at(i);
|
||||
FloatSlider::DisplayType display = GetFloatDisplayTypeFromKeyframe(key);
|
||||
key->set_value(FloatSlider::TransformDisplayToValue(FloatSlider::TransformValueToDisplay(drag_keyframe_values_.at(i).toDouble(), display) - scaled_diff, display));
|
||||
}
|
||||
|
||||
emit ScaleChanged(new_x_scale);
|
||||
SetYScale(new_y_scale);
|
||||
NodeKeyframe *tip_item = GetSelectedKeyframes().first();
|
||||
|
||||
QMetaObject::invokeMethod(horizontalScrollBar(), "setValue", Qt::QueuedConnection,
|
||||
Q_ARG(int, TimeToScene(min_time) - CalculatePaddingFromDimensionScale(this->width())));
|
||||
QMetaObject::invokeMethod(verticalScrollBar(), "setValue", Qt::QueuedConnection,
|
||||
Q_ARG(int, GetItemYFromKeyframeValue(max_val) - CalculatePaddingFromDimensionScale(this->height())));
|
||||
bool ok;
|
||||
double num_value = tip_item->value().toDouble(&ok);
|
||||
|
||||
if (ok) {
|
||||
tip = QStringLiteral("%1\n");
|
||||
tip.append(FloatSlider::ValueToString(num_value + GetOffsetFromKeyframe(tip_item), GetFloatDisplayTypeFromKeyframe(tip_item), 2, true));
|
||||
}
|
||||
}
|
||||
|
||||
void CurveView::KeyframeDragRelease(QMouseEvent *event, MultiUndoCommand *command)
|
||||
{
|
||||
for (int i=0; i<GetSelectedKeyframes().size(); i++) {
|
||||
NodeKeyframe *k = GetSelectedKeyframes().at(i);
|
||||
if (!qFuzzyCompare(k->value().toDouble(), drag_keyframe_values_.at(i).toDouble())) {
|
||||
command->add_child(new NodeParamSetKeyframeValueCommand(k, k->value(), drag_keyframe_values_.at(i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QPointF CurveView::GenerateBezierControlPosition(const NodeKeyframe::BezierType mode, const QPointF &start_point, const QPointF &scaled_cursor_diff)
|
||||
{
|
||||
QPointF new_bezier_pos = start_point;
|
||||
|
||||
new_bezier_pos += scaled_cursor_diff;
|
||||
|
||||
// LIMIT bezier handles from overlapping each other
|
||||
if (mode == NodeKeyframe::kInHandle) {
|
||||
if (new_bezier_pos.x() > 0) {
|
||||
new_bezier_pos.setX(0);
|
||||
}
|
||||
} else {
|
||||
if (new_bezier_pos.x() < 0) {
|
||||
new_bezier_pos.setX(0);
|
||||
}
|
||||
}
|
||||
|
||||
return new_bezier_pos;
|
||||
}
|
||||
|
||||
QPointF CurveView::GetScaledCursorPos(const QPointF &cursor_pos)
|
||||
{
|
||||
return QPointF(cursor_pos.x() / GetScale(),
|
||||
cursor_pos.y() / GetYScale());
|
||||
}
|
||||
|
||||
void CurveView::ZoomToFitInternal(bool selected_only)
|
||||
{
|
||||
bool got_val = false;
|
||||
|
||||
rational min_time, max_time;
|
||||
double min_val, max_val;
|
||||
|
||||
foreach (KeyframeViewInputConnection *con, track_connections_) {
|
||||
foreach (NodeKeyframe *key, con->GetKeyframes()) {
|
||||
if (!selected_only || IsKeyframeSelected(key)) {
|
||||
rational transformed_time = GetAdjustedTime(key->parent(),
|
||||
GetTimeTarget(),
|
||||
key->time(),
|
||||
false);
|
||||
|
||||
qreal key_y = GetUnscaledItemYFromKeyframeValue(key);
|
||||
|
||||
if (got_val) {
|
||||
min_time = qMin(transformed_time, min_time);
|
||||
max_time = qMax(transformed_time, max_time);
|
||||
|
||||
min_val = qMin(key_y, min_val);
|
||||
max_val = qMax(key_y, max_val);
|
||||
} else {
|
||||
min_time = transformed_time;
|
||||
max_time = transformed_time;
|
||||
|
||||
min_val = key_y;
|
||||
max_val = key_y;
|
||||
|
||||
got_val = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent scaling if no keyframes were found
|
||||
if (got_val) {
|
||||
QRectF desired(QPointF(min_time.toDouble(), min_val), QPointF(max_time.toDouble(), max_val));
|
||||
|
||||
const double scale_divider = 0.5;
|
||||
double scale_half_divider = scale_divider*0.5;
|
||||
|
||||
double new_x_scale = viewport()->width() / desired.width() * scale_divider;
|
||||
double new_y_scale;
|
||||
|
||||
if (qFuzzyIsNull(desired.height())) {
|
||||
// Catch divide by zero
|
||||
new_y_scale = 1.0;
|
||||
scale_half_divider = 0.5;
|
||||
} else {
|
||||
// Use height as normal
|
||||
new_y_scale = viewport()->height() / desired.height() * scale_divider;
|
||||
}
|
||||
|
||||
emit ScaleChanged(new_x_scale);
|
||||
SetYScale(new_y_scale);
|
||||
|
||||
UpdateSceneRect();
|
||||
|
||||
int sb_x = desired.left() * new_x_scale - viewport()->width() * scale_half_divider;
|
||||
QMetaObject::invokeMethod(horizontalScrollBar(), "setValue", Qt::QueuedConnection, Q_ARG(int, sb_x));
|
||||
|
||||
int sb_y = desired.top() * new_y_scale - viewport()->height() * scale_half_divider;
|
||||
QMetaObject::invokeMethod(verticalScrollBar(), "setValue", Qt::QueuedConnection, Q_ARG(int, sb_y));
|
||||
}
|
||||
}
|
||||
|
||||
qreal CurveView::GetItemYFromKeyframeValue(NodeKeyframe *key)
|
||||
{
|
||||
return GetItemYFromKeyframeValue(key->value().toDouble());
|
||||
return GetUnscaledItemYFromKeyframeValue(key) * GetYScale();
|
||||
}
|
||||
|
||||
qreal CurveView::GetItemYFromKeyframeValue(double value)
|
||||
qreal CurveView::GetUnscaledItemYFromKeyframeValue(NodeKeyframe *key)
|
||||
{
|
||||
return -value * GetYScale();
|
||||
}
|
||||
double val = key->value().toDouble();
|
||||
|
||||
void CurveView::SetItemYFromKeyframeValue(NodeKeyframe *key, KeyframeViewItem *item)
|
||||
{
|
||||
item->SetOverrideY(GetItemYFromKeyframeValue(key));
|
||||
val = FloatSlider::TransformValueToDisplay(val, GetFloatDisplayTypeFromKeyframe(key));
|
||||
|
||||
val += GetOffsetFromKeyframe(key);
|
||||
|
||||
return -val;
|
||||
}
|
||||
|
||||
QPointF CurveView::ScalePoint(const QPointF &point)
|
||||
@@ -384,80 +596,48 @@ QPointF CurveView::ScalePoint(const QPointF &point)
|
||||
return QPointF(point.x() * GetScale(), - point.y() * GetYScale());
|
||||
}
|
||||
|
||||
void CurveView::CreateBezierControlPoints(KeyframeViewItem* item)
|
||||
FloatSlider::DisplayType CurveView::GetFloatDisplayTypeFromKeyframe(NodeKeyframe *key)
|
||||
{
|
||||
BezierControlPointItem* bezier_in_pt = new BezierControlPointItem(item->key(), NodeKeyframe::kInHandle, item);
|
||||
bezier_in_pt->SetXScale(GetScale());
|
||||
bezier_in_pt->SetYScale(GetYScale());
|
||||
bezier_control_points_.append(bezier_in_pt);
|
||||
connect(bezier_in_pt, &QObject::destroyed, this, &CurveView::BezierControlPointDestroyed, Qt::DirectConnection);
|
||||
|
||||
BezierControlPointItem* bezier_out_pt = new BezierControlPointItem(item->key(), NodeKeyframe::kOutHandle, item);
|
||||
bezier_out_pt->SetXScale(GetScale());
|
||||
bezier_out_pt->SetYScale(GetYScale());
|
||||
bezier_control_points_.append(bezier_out_pt);
|
||||
connect(bezier_out_pt, &QObject::destroyed, this, &CurveView::BezierControlPointDestroyed, Qt::DirectConnection);
|
||||
}
|
||||
|
||||
void CurveView::KeyframeValueChanged()
|
||||
{
|
||||
NodeKeyframe* key = static_cast<NodeKeyframe*>(sender());
|
||||
KeyframeViewItem* item = item_map().value(key);
|
||||
|
||||
SetItemYFromKeyframeValue(key, item);
|
||||
}
|
||||
|
||||
void CurveView::KeyframeTypeChanged()
|
||||
{
|
||||
NodeKeyframe* key = static_cast<NodeKeyframe*>(sender());
|
||||
KeyframeViewItem* item = item_map().value(key);
|
||||
|
||||
if (item->isSelected()) {
|
||||
item->setSelected(false);
|
||||
item->setSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
void CurveView::SelectionChanged()
|
||||
{
|
||||
// Clear current bezier handles
|
||||
while (!bezier_control_points_.isEmpty()) {
|
||||
delete bezier_control_points_.first();
|
||||
Node* node = key->parent();
|
||||
const QString& input = key->input();
|
||||
if (node->HasInputProperty(input, QStringLiteral("view"))) {
|
||||
// Try to get view from input (which will be normal if unset)
|
||||
return static_cast<FloatSlider::DisplayType>(node->GetInputProperty(input, QStringLiteral("view")).toInt());
|
||||
}
|
||||
|
||||
QList<QGraphicsItem*> selected = scene()->selectedItems();
|
||||
|
||||
foreach (QGraphicsItem* item, selected) {
|
||||
KeyframeViewItem* this_item = static_cast<KeyframeViewItem*>(item);
|
||||
|
||||
if (this_item->key()->type() == NodeKeyframe::kBezier) {
|
||||
CreateBezierControlPoints(this_item);
|
||||
}
|
||||
}
|
||||
// Fallback to normal
|
||||
return FloatSlider::kNormal;
|
||||
}
|
||||
|
||||
void CurveView::BezierControlPointDestroyed()
|
||||
double CurveView::GetOffsetFromKeyframe(NodeKeyframe *key)
|
||||
{
|
||||
BezierControlPointItem* item = static_cast<BezierControlPointItem*>(sender());
|
||||
bezier_control_points_.removeOne(item);
|
||||
Node *node = key->parent();
|
||||
const QString &input = key->input();
|
||||
if (node->HasInputProperty(input, QStringLiteral("offset"))) {
|
||||
QVariant v = node->GetInputProperty(input, QStringLiteral("offset"));
|
||||
|
||||
// NOTE: Implement getting correct offset for the track based on the data type
|
||||
QVector<QVariant> track_vals = NodeValue::split_normal_value_into_track_values(node->GetInputDataType(input), v);
|
||||
|
||||
return track_vals.at(key->track()).toDouble();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
QPointF CurveView::GetKeyframePosition(NodeKeyframe *key)
|
||||
{
|
||||
return QPointF(GetKeyframeSceneX(key), GetItemYFromKeyframeValue(key));
|
||||
}
|
||||
|
||||
void CurveView::ZoomToFit()
|
||||
{
|
||||
ZoomToFitInternal(item_map().keys());
|
||||
ZoomToFitInternal(false);
|
||||
}
|
||||
|
||||
void CurveView::ZoomToFitSelected()
|
||||
{
|
||||
QList<NodeKeyframe*> selected_keys;
|
||||
|
||||
for (auto it=item_map().cbegin(); it!=item_map().cend(); it++) {
|
||||
if (it.value()->isSelected()) {
|
||||
selected_keys.append(it.key());
|
||||
}
|
||||
}
|
||||
|
||||
ZoomToFitInternal(selected_keys);
|
||||
ZoomToFitInternal(true);
|
||||
}
|
||||
|
||||
void CurveView::ResetZoom()
|
||||
@@ -466,16 +646,4 @@ void CurveView::ResetZoom()
|
||||
SetYScale(1.0);
|
||||
}
|
||||
|
||||
KeyframeViewItem* CurveView::AddKeyframe(NodeKeyframe* key)
|
||||
{
|
||||
KeyframeViewItem* item = super::AddKeyframe(key);
|
||||
SetItemYFromKeyframeValue(key, item);
|
||||
item->SetOverrideBrush(keyframe_colors_.value(key->key_track_ref()));
|
||||
|
||||
connect(key, &NodeKeyframe::ValueChanged, this, &CurveView::KeyframeValueChanged);
|
||||
connect(key, &NodeKeyframe::TypeChanged, this, &CurveView::KeyframeTypeChanged);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,36 +21,27 @@
|
||||
#ifndef CURVEVIEW_H
|
||||
#define CURVEVIEW_H
|
||||
|
||||
#include "beziercontrolpointitem.h"
|
||||
#include "node/keyframe.h"
|
||||
#include "widget/keyframeview/keyframeview.h"
|
||||
#include "widget/keyframeview/keyframeviewitem.h"
|
||||
#include "widget/slider/floatslider.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class CurveView : public KeyframeViewBase
|
||||
class CurveView : public KeyframeView
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
CurveView(QWidget* parent = nullptr);
|
||||
|
||||
virtual ~CurveView() override;
|
||||
|
||||
virtual void Clear() override;
|
||||
|
||||
void ConnectInput(const NodeKeyframeTrackReference &ref);
|
||||
|
||||
void DisconnectInput(const NodeKeyframeTrackReference &ref);
|
||||
|
||||
void SelectKeyframesOfInput(const NodeKeyframeTrackReference &ref);
|
||||
|
||||
void ZoomToFitInput(const NodeKeyframeTrackReference &ref);
|
||||
|
||||
void SetKeyframeTrackColor(const NodeKeyframeTrackReference& ref, const QColor& color);
|
||||
|
||||
public slots:
|
||||
virtual KeyframeViewItem* AddKeyframe(NodeKeyframe* key) override;
|
||||
|
||||
void ZoomToFit();
|
||||
|
||||
void ZoomToFitSelected();
|
||||
@@ -59,51 +50,70 @@ public slots:
|
||||
|
||||
protected:
|
||||
virtual void drawBackground(QPainter* painter, const QRectF& rect) override;
|
||||
|
||||
virtual void KeyframeAboutToBeRemoved(NodeKeyframe *key) override;
|
||||
|
||||
virtual void ScaleChangedEvent(const double &scale) override;
|
||||
|
||||
virtual void VerticalScaleChangedEvent(double scale) override;
|
||||
virtual void drawForeground(QPainter *painter, const QRectF &rect) override;
|
||||
|
||||
virtual void ContextMenuEvent(Menu &m) override;
|
||||
|
||||
virtual void SceneRectUpdateEvent(QRectF &r) override;
|
||||
|
||||
virtual qreal GetKeyframeSceneY(KeyframeViewInputConnection *track, NodeKeyframe *key) override;
|
||||
|
||||
virtual void DrawKeyframe(QPainter *painter, NodeKeyframe *key, KeyframeViewInputConnection *track, const QRectF &key_rect) override;
|
||||
|
||||
virtual bool FirstChanceMousePress(QMouseEvent *event) override;
|
||||
virtual void FirstChanceMouseMove(QMouseEvent *event) override;
|
||||
virtual void FirstChanceMouseRelease(QMouseEvent *event) override;
|
||||
|
||||
virtual void KeyframeDragStart(QMouseEvent *event) override;
|
||||
virtual void KeyframeDragMove(QMouseEvent *event, QString &tip) override;
|
||||
virtual void KeyframeDragRelease(QMouseEvent *event, MultiUndoCommand *command) override;
|
||||
|
||||
private:
|
||||
void ZoomToFitInternal(const QList<NodeKeyframe *> &keys);
|
||||
void ZoomToFitInternal(bool selected_only);
|
||||
|
||||
qreal GetItemYFromKeyframeValue(NodeKeyframe* key);
|
||||
qreal GetItemYFromKeyframeValue(double value);
|
||||
|
||||
void SetItemYFromKeyframeValue(NodeKeyframe* key, KeyframeViewItem* item);
|
||||
qreal GetUnscaledItemYFromKeyframeValue(NodeKeyframe* key);
|
||||
|
||||
QPointF ScalePoint(const QPointF& point);
|
||||
|
||||
static FloatSlider::DisplayType GetFloatDisplayTypeFromKeyframe(NodeKeyframe *key);
|
||||
|
||||
static double GetOffsetFromKeyframe(NodeKeyframe *key);
|
||||
|
||||
void AdjustLines();
|
||||
|
||||
void CreateBezierControlPoints(KeyframeViewItem *item);
|
||||
QPointF GetKeyframePosition(NodeKeyframe *key);
|
||||
|
||||
static QPointF GenerateBezierControlPosition(const NodeKeyframe::BezierType mode,
|
||||
const QPointF& start_point,
|
||||
const QPointF& scaled_cursor_diff);
|
||||
|
||||
QPointF GetScaledCursorPos(const QPointF &cursor_pos);
|
||||
|
||||
QHash<NodeKeyframeTrackReference, QColor> keyframe_colors_;
|
||||
QHash<NodeKeyframeTrackReference, KeyframeViewInputConnection*> track_connections_;
|
||||
|
||||
int text_padding_;
|
||||
|
||||
int minimum_grid_space_;
|
||||
|
||||
QVector<QGraphicsLineItem*> lines_;
|
||||
|
||||
QVector<BezierControlPointItem*> bezier_control_points_;
|
||||
|
||||
QVector<NodeKeyframeTrackReference> connected_inputs_;
|
||||
|
||||
private slots:
|
||||
void KeyframeValueChanged();
|
||||
struct BezierPoint
|
||||
{
|
||||
QRectF rect;
|
||||
NodeKeyframe *keyframe;
|
||||
NodeKeyframe::BezierType type;
|
||||
};
|
||||
|
||||
void KeyframeTypeChanged();
|
||||
QVector<BezierPoint> bezier_pts_;
|
||||
const BezierPoint *dragging_bezier_pt_;
|
||||
|
||||
void SelectionChanged();
|
||||
QPointF dragging_bezier_point_start_;
|
||||
QPointF dragging_bezier_point_opposing_start_;
|
||||
QPointF drag_start_;
|
||||
|
||||
void BezierControlPointDestroyed();
|
||||
QVector<QVariant> drag_keyframe_values_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -34,8 +34,10 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
#define super TimeBasedWidget
|
||||
|
||||
CurveWidget::CurveWidget(QWidget *parent) :
|
||||
TimeBasedWidget(parent)
|
||||
super(parent)
|
||||
{
|
||||
QHBoxLayout* outer_layout = new QHBoxLayout(this);
|
||||
|
||||
@@ -45,10 +47,7 @@ CurveWidget::CurveWidget(QWidget *parent) :
|
||||
tree_view_ = new NodeTreeView();
|
||||
tree_view_->SetOnlyShowKeyframable(true);
|
||||
tree_view_->SetShowKeyframeTracksAsRows(true);
|
||||
connect(tree_view_, &NodeTreeView::NodeEnableChanged, this, &CurveWidget::NodeEnabledChanged);
|
||||
connect(tree_view_, &NodeTreeView::InputEnableChanged, this, &CurveWidget::InputEnabledChanged);
|
||||
connect(tree_view_, &NodeTreeView::InputSelectionChanged, this, &CurveWidget::InputSelectionChanged);
|
||||
connect(tree_view_, &NodeTreeView::InputDoubleClicked, this, &CurveWidget::InputDoubleClicked);
|
||||
splitter->addWidget(tree_view_);
|
||||
|
||||
QWidget* workarea = new QWidget();
|
||||
@@ -99,7 +98,7 @@ CurveWidget::CurveWidget(QWidget *parent) :
|
||||
|
||||
// Connect ruler and view together
|
||||
connect(view_, &CurveView::TimeChanged, this, &CurveWidget::SetTimeAndSignal);
|
||||
connect(view_->scene(), &QGraphicsScene::selectionChanged, this, &CurveWidget::SelectionChanged);
|
||||
connect(view_, &CurveView::SelectionChanged, this, &CurveWidget::SelectionChanged);
|
||||
connect(view_, &CurveView::ScaleChanged, this, &CurveWidget::SetScale);
|
||||
connect(view_, &CurveView::Dragged, this, &CurveWidget::KeyframeViewDragged);
|
||||
|
||||
@@ -113,12 +112,6 @@ CurveWidget::CurveWidget(QWidget *parent) :
|
||||
SetScale(120.0);
|
||||
}
|
||||
|
||||
CurveWidget::~CurveWidget()
|
||||
{
|
||||
// Quick way to avoid segfault when QGraphicsScene::selectionChanged is emitted after other members have been destroyed
|
||||
view_->Clear();
|
||||
}
|
||||
|
||||
const double &CurveWidget::GetVerticalScale()
|
||||
{
|
||||
return view_->GetYScale();
|
||||
@@ -138,27 +131,38 @@ void CurveWidget::SetNodes(const QVector<Node *> &nodes)
|
||||
{
|
||||
tree_view_->SetNodes(nodes);
|
||||
|
||||
// Detect removed nodes
|
||||
foreach (Node* n, nodes_) {
|
||||
if (!nodes.contains(n)) {
|
||||
ConnectNode(n, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Detect added nodes
|
||||
foreach (Node* n, nodes) {
|
||||
if (tree_view_->IsNodeEnabled(n) && !nodes_.contains(n)) {
|
||||
ConnectNode(n, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Save new node list
|
||||
nodes_ = nodes;
|
||||
|
||||
// Generate colors
|
||||
foreach (Node *node, nodes_) {
|
||||
foreach (const QString& input, node->inputs()) {
|
||||
if (node->IsInputKeyframable(input) && !node->IsInputHidden(input)) {
|
||||
int arr_sz = node->InputArraySize(input);
|
||||
for (int i=-1; i<arr_sz; i++) {
|
||||
// Generate a random color for this input
|
||||
const QVector<NodeKeyframeTrack>& tracks = node->GetKeyframeTracks(input, i);
|
||||
|
||||
for (int j=0; j<tracks.size(); j++) {
|
||||
NodeKeyframeTrackReference ref(NodeInput(node, input, i), j);
|
||||
|
||||
if (!keyframe_colors_.contains(ref)) {
|
||||
QColor c = QColor::fromHsl(std::rand()%360, 255, 160);
|
||||
|
||||
keyframe_colors_.insert(ref, c);
|
||||
tree_view_->SetKeyframeTrackColor(ref, c);
|
||||
view_->SetKeyframeTrackColor(ref, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CurveWidget::TimeChangedEvent(const rational &time)
|
||||
{
|
||||
TimeBasedWidget::TimeChangedEvent(time);
|
||||
super::TimeChangedEvent(time);
|
||||
|
||||
view_->SetTime(time);
|
||||
UpdateBridgeTime(time);
|
||||
@@ -166,20 +170,22 @@ void CurveWidget::TimeChangedEvent(const rational &time)
|
||||
|
||||
void CurveWidget::TimebaseChangedEvent(const rational &timebase)
|
||||
{
|
||||
TimeBasedWidget::TimebaseChangedEvent(timebase);
|
||||
super::TimebaseChangedEvent(timebase);
|
||||
|
||||
view_->SetTimebase(timebase);
|
||||
}
|
||||
|
||||
void CurveWidget::ScaleChangedEvent(const double &scale)
|
||||
{
|
||||
TimeBasedWidget::ScaleChangedEvent(scale);
|
||||
super::ScaleChangedEvent(scale);
|
||||
|
||||
view_->SetScale(scale);
|
||||
}
|
||||
|
||||
void CurveWidget::TimeTargetChangedEvent(Node *target)
|
||||
{
|
||||
TimeTargetObject::TimeTargetChangedEvent(target);
|
||||
|
||||
key_control_->SetTimeTarget(target);
|
||||
|
||||
view_->SetTimeTarget(target);
|
||||
@@ -187,6 +193,8 @@ void CurveWidget::TimeTargetChangedEvent(Node *target)
|
||||
|
||||
void CurveWidget::ConnectedNodeChangeEvent(ViewerOutput *n)
|
||||
{
|
||||
super::ConnectedNodeChangeEvent(n);
|
||||
|
||||
SetTimeTarget(n);
|
||||
}
|
||||
|
||||
@@ -216,91 +224,47 @@ void CurveWidget::UpdateBridgeTime(const rational &time)
|
||||
key_control_->SetTime(time);
|
||||
}
|
||||
|
||||
void CurveWidget::ConnectNode(Node *node, bool connect)
|
||||
void CurveWidget::ConnectInput(Node *node, const QString &input, int element)
|
||||
{
|
||||
foreach (const QString& input, node->inputs()) {
|
||||
if (node->IsInputKeyframable(input)) {
|
||||
ConnectInput(node, input, connect);
|
||||
if (element == -1 && node->InputIsArray(input)) {
|
||||
// This is the root element, connect all elements (if applicable)
|
||||
int arr_sz = node->InputArraySize(input);
|
||||
for (int i=-1; i<arr_sz; i++) {
|
||||
ConnectInputInternal(node, input, i);
|
||||
}
|
||||
}
|
||||
|
||||
// Connect add/remove signals
|
||||
if (connect) {
|
||||
QObject::connect(node, &Node::KeyframeAdded, this, &CurveWidget::AddKeyframe);
|
||||
QObject::connect(node, &Node::KeyframeRemoved, this, &CurveWidget::RemoveKeyframe);
|
||||
} else {
|
||||
QObject::disconnect(node, &Node::KeyframeAdded, this, &CurveWidget::AddKeyframe);
|
||||
QObject::disconnect(node, &Node::KeyframeRemoved, this, &CurveWidget::RemoveKeyframe);
|
||||
// This is a single element, just connect it as-is
|
||||
ConnectInputInternal(node, input, element);
|
||||
}
|
||||
}
|
||||
|
||||
void CurveWidget::ConnectInput(Node *node, const QString &input, bool connect)
|
||||
void CurveWidget::ConnectInputInternal(Node *node, const QString &input, int element)
|
||||
{
|
||||
if (!node->IsInputKeyframable(input)) {
|
||||
qWarning() << "Tried to connect input that isn't keyframable";
|
||||
return;
|
||||
}
|
||||
|
||||
int track_count = NodeValue::get_number_of_keyframe_tracks(node->GetInputDataType(input));
|
||||
bool multiple_tracks = track_count > 1;
|
||||
|
||||
int arr_sz = node->InputArraySize(input);
|
||||
for (int i=-1; i<arr_sz; i++) {
|
||||
// Generate a random color for this input
|
||||
const QVector<NodeKeyframeTrack>& tracks = node->GetKeyframeTracks(input, i);
|
||||
|
||||
for (int j=0; j<tracks.size(); j++) {
|
||||
NodeKeyframeTrackReference ref(NodeInput(node, input, i), j);
|
||||
|
||||
if (!keyframe_colors_.contains(ref)) {
|
||||
QColor c = QColor::fromHsv(std::rand()%360, std::rand()%255, 255);
|
||||
|
||||
keyframe_colors_.insert(ref, c);
|
||||
tree_view_->SetKeyframeTrackColor(ref, c);
|
||||
view_->SetKeyframeTrackColor(ref, c);
|
||||
}
|
||||
}
|
||||
|
||||
if (tree_view_->IsInputEnabled(NodeKeyframeTrackReference(NodeInput(node, input, i), multiple_tracks ? -1 : 0))) {
|
||||
if (multiple_tracks) {
|
||||
for (int j=0; j<track_count; j++) {
|
||||
NodeKeyframeTrackReference ref(NodeInput(node, input, i), j);
|
||||
if (tree_view_->IsInputEnabled(ref)) {
|
||||
if (connect) {
|
||||
view_->ConnectInput(ref);
|
||||
} else {
|
||||
view_->DisconnectInput(ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NodeKeyframeTrackReference ref(NodeInput(node, input, i), 0);
|
||||
if (connect) {
|
||||
view_->ConnectInput(ref);
|
||||
} else {
|
||||
view_->DisconnectInput(ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
NodeInput input_ref(node, input, element);
|
||||
int track_count = NodeValue::get_number_of_keyframe_tracks(input_ref.GetDataType());
|
||||
for (int i=0; i<track_count; i++) {
|
||||
NodeKeyframeTrackReference track_ref(input_ref, i);
|
||||
view_->ConnectInput(track_ref);
|
||||
selected_tracks_.append(track_ref);
|
||||
}
|
||||
}
|
||||
|
||||
void CurveWidget::SelectionChanged()
|
||||
{
|
||||
QList<QGraphicsItem*> selected = view_->scene()->selectedItems();
|
||||
const QVector<NodeKeyframe*> &selected = view_->GetSelectedKeyframes();
|
||||
|
||||
SetKeyframeButtonChecked(false);
|
||||
SetKeyframeButtonEnabled(!selected.isEmpty());
|
||||
|
||||
if (!selected.isEmpty()) {
|
||||
bool all_same_type = true;
|
||||
NodeKeyframe::Type type = static_cast<KeyframeViewItem*>(selected.first())->key()->type();
|
||||
NodeKeyframe::Type type = selected.first()->type();
|
||||
|
||||
for (int i=1;i<selected.size();i++) {
|
||||
KeyframeViewItem* prev_item = static_cast<KeyframeViewItem*>(selected.at(i-1));
|
||||
KeyframeViewItem* this_item = static_cast<KeyframeViewItem*>(selected.at(i));
|
||||
NodeKeyframe* prev_item = selected.at(i-1);
|
||||
NodeKeyframe* this_item = selected.at(i);
|
||||
|
||||
if (prev_item->key()->type() != this_item->key()->type()) {
|
||||
if (prev_item->type() != this_item->type()) {
|
||||
all_same_type = false;
|
||||
break;
|
||||
}
|
||||
@@ -323,7 +287,7 @@ void CurveWidget::KeyframeTypeButtonTriggered(bool checked)
|
||||
}
|
||||
|
||||
// Get selected items and do nothing if there are none
|
||||
QList<QGraphicsItem*> selected = view_->scene()->selectedItems();
|
||||
const QVector<NodeKeyframe*> &selected = view_->GetSelectedKeyframes();
|
||||
if (selected.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@@ -345,51 +309,40 @@ void CurveWidget::KeyframeTypeButtonTriggered(bool checked)
|
||||
|
||||
MultiUndoCommand* command = new MultiUndoCommand();
|
||||
|
||||
foreach (QGraphicsItem* item, selected) {
|
||||
KeyframeViewItem* key_item = static_cast<KeyframeViewItem*>(item);
|
||||
|
||||
command->add_child(new KeyframeSetTypeCommand(key_item->key(), new_type));
|
||||
foreach (NodeKeyframe* item, selected) {
|
||||
command->add_child(new KeyframeSetTypeCommand(item, new_type));
|
||||
}
|
||||
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
}
|
||||
|
||||
void CurveWidget::NodeEnabledChanged(Node* n, bool e)
|
||||
{
|
||||
ConnectNode(n, e);
|
||||
}
|
||||
|
||||
void CurveWidget::InputEnabledChanged(const NodeKeyframeTrackReference& ref, bool e)
|
||||
{
|
||||
if (e) {
|
||||
view_->ConnectInput(ref);
|
||||
} else {
|
||||
view_->DisconnectInput(ref);
|
||||
}
|
||||
}
|
||||
|
||||
void CurveWidget::AddKeyframe(NodeKeyframe *key)
|
||||
{
|
||||
view_->AddKeyframe(key);
|
||||
}
|
||||
|
||||
void CurveWidget::RemoveKeyframe(NodeKeyframe *key)
|
||||
{
|
||||
view_->RemoveKeyframe(key);
|
||||
}
|
||||
|
||||
void CurveWidget::InputSelectionChanged(const NodeKeyframeTrackReference& ref)
|
||||
{
|
||||
key_control_->SetInput(ref.input());
|
||||
|
||||
if (ref.IsValid()) {
|
||||
view_->SelectKeyframesOfInput(ref);
|
||||
foreach (const NodeKeyframeTrackReference &c, selected_tracks_) {
|
||||
view_->DisconnectInput(c);
|
||||
}
|
||||
}
|
||||
|
||||
void CurveWidget::InputDoubleClicked(const NodeKeyframeTrackReference& ref)
|
||||
{
|
||||
view_->ZoomToFitInput(ref);
|
||||
selected_tracks_.clear();
|
||||
|
||||
if (ref.IsValid() && !ref.input().IsArray()) {
|
||||
// This reference is a track, connect it only
|
||||
view_->ConnectInput(ref);
|
||||
selected_tracks_.append(ref);
|
||||
} else if (ref.input().IsValid()) {
|
||||
// This reference is a input, connect all tracks
|
||||
ConnectInput(ref.input().node(), ref.input().input(), ref.input().element());
|
||||
} else if (Node *node = ref.input().node()) {
|
||||
// This is a node, add all inputs
|
||||
foreach (const QString &input, node->inputs()) {
|
||||
if (node->IsInputKeyframable(input) && !node->IsInputHidden(input)) {
|
||||
ConnectInput(node, input, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view_->ZoomToFit();
|
||||
}
|
||||
|
||||
void CurveWidget::KeyframeViewDragged(int x, int y)
|
||||
|
||||
@@ -40,8 +40,6 @@ class CurveWidget : public TimeBasedWidget, public TimeTargetObject
|
||||
public:
|
||||
CurveWidget(QWidget* parent = nullptr);
|
||||
|
||||
virtual ~CurveWidget() override;
|
||||
|
||||
const double& GetVerticalScale();
|
||||
void SetVerticalScale(const double& vscale);
|
||||
|
||||
@@ -78,9 +76,9 @@ private:
|
||||
|
||||
void UpdateBridgeTime(const rational &time);
|
||||
|
||||
void ConnectNode(Node* node, bool connect);
|
||||
void ConnectInput(Node *node, const QString &input, int element);
|
||||
|
||||
void ConnectInput(Node* node, const QString& input, bool connect);
|
||||
void ConnectInputInternal(Node *node, const QString &input, int element);
|
||||
|
||||
QHash<NodeKeyframeTrackReference, QColor> keyframe_colors_;
|
||||
|
||||
@@ -98,23 +96,15 @@ private:
|
||||
|
||||
QVector<Node*> nodes_;
|
||||
|
||||
QVector<NodeKeyframeTrackReference> selected_tracks_;
|
||||
|
||||
private slots:
|
||||
void SelectionChanged();
|
||||
|
||||
void KeyframeTypeButtonTriggered(bool checked);
|
||||
|
||||
void NodeEnabledChanged(Node* n, bool e);
|
||||
|
||||
void InputEnabledChanged(const NodeKeyframeTrackReference &ref, bool e);
|
||||
|
||||
void AddKeyframe(NodeKeyframe* key);
|
||||
|
||||
void RemoveKeyframe(NodeKeyframe* key);
|
||||
|
||||
void InputSelectionChanged(const NodeKeyframeTrackReference& ref);
|
||||
|
||||
void InputDoubleClicked(const NodeKeyframeTrackReference& ref);
|
||||
|
||||
void KeyframeViewDragged(int x, int y);
|
||||
|
||||
void CatchUpYScrollToPoint(int point);
|
||||
|
||||
@@ -16,13 +16,11 @@
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
widget/keyframeview/keyframeview.h
|
||||
widget/keyframeview/keyframeview.cpp
|
||||
widget/keyframeview/keyframeviewbase.h
|
||||
widget/keyframeview/keyframeviewbase.cpp
|
||||
widget/keyframeview/keyframeviewitem.h
|
||||
widget/keyframeview/keyframeviewitem.cpp
|
||||
widget/keyframeview/keyframeviewundo.h
|
||||
widget/keyframeview/keyframeview.h
|
||||
widget/keyframeview/keyframeviewinputconnection.cpp
|
||||
widget/keyframeview/keyframeviewinputconnection.h
|
||||
widget/keyframeview/keyframeviewundo.cpp
|
||||
widget/keyframeview/keyframeviewundo.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
@@ -20,40 +20,537 @@
|
||||
|
||||
#include "keyframeview.h"
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QToolTip>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "common/qtutils.h"
|
||||
#include "dialog/keyframeproperties/keyframeproperties.h"
|
||||
#include "keyframeviewundo.h"
|
||||
#include "node/node.h"
|
||||
#include "widget/menu/menu.h"
|
||||
#include "widget/menu/menushared.h"
|
||||
#include "widget/nodeparamview/nodeparamviewundo.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
#define super KeyframeViewBase
|
||||
#define super TimeBasedView
|
||||
|
||||
KeyframeView::KeyframeView(QWidget *parent) :
|
||||
KeyframeViewBase(parent),
|
||||
max_scroll_(0)
|
||||
super(parent),
|
||||
selection_manager_(this),
|
||||
autoselect_siblings_(true),
|
||||
max_scroll_(0),
|
||||
first_chance_mouse_event_(false)
|
||||
{
|
||||
setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||
SetDefaultDragMode(RubberBandDrag);
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
connect(this, &KeyframeView::customContextMenuRequested, this, &KeyframeView::ShowContextMenu);
|
||||
}
|
||||
|
||||
void KeyframeView::SetElementY(const NodeInput &c, int y)
|
||||
void KeyframeView::DeleteSelected()
|
||||
{
|
||||
qreal scene_y = mapToScene(mapFromGlobal(QPoint(0, y))).y();
|
||||
MultiUndoCommand* command = new MultiUndoCommand();
|
||||
|
||||
element_y_.insert(c, scene_y);
|
||||
foreach (NodeKeyframe *key, GetSelectedKeyframes()) {
|
||||
command->add_child(new NodeParamRemoveKeyframeCommand(key));
|
||||
}
|
||||
|
||||
for (auto it=item_map().cbegin(); it!=item_map().cend(); it++) {
|
||||
if (it.key()->key_track_ref().input() == c) {
|
||||
it.value()->SetOverrideY(scene_y);
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
}
|
||||
|
||||
KeyframeView::NodeConnections KeyframeView::AddKeyframesOfNode(Node *n)
|
||||
{
|
||||
NodeConnections map;
|
||||
|
||||
foreach (const QString& i, n->inputs()) {
|
||||
map.insert(i, AddKeyframesOfInput(n, i));
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
KeyframeView::InputConnections KeyframeView::AddKeyframesOfInput(Node* on, const QString& oinput)
|
||||
{
|
||||
InputConnections vec;
|
||||
|
||||
NodeInput resolved = NodeGroup::ResolveInput(NodeInput(on, oinput));
|
||||
Node *n = resolved.node();
|
||||
const QString &input = resolved.input();
|
||||
|
||||
if (n->IsInputKeyframable(input)) {
|
||||
int arr_sz = n->InputArraySize(input);
|
||||
vec.resize(arr_sz + 1);
|
||||
for (int i=-1; i<arr_sz; i++) {
|
||||
vec[i+1] = AddKeyframesOfElement(NodeInput(n, input, i));
|
||||
}
|
||||
}
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
KeyframeView::ElementConnections KeyframeView::AddKeyframesOfElement(const NodeInput& input)
|
||||
{
|
||||
const QVector<NodeKeyframeTrack>& tracks = input.node()->GetKeyframeTracks(input);
|
||||
ElementConnections vec(tracks.size());
|
||||
|
||||
for (int i=0; i<tracks.size(); i++) {
|
||||
vec[i] = AddKeyframesOfTrack(NodeKeyframeTrackReference(input, i));
|
||||
}
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
KeyframeViewInputConnection *KeyframeView::AddKeyframesOfTrack(const NodeKeyframeTrackReference& ref)
|
||||
{
|
||||
KeyframeViewInputConnection *track = new KeyframeViewInputConnection(ref, this);
|
||||
connect(track, &KeyframeViewInputConnection::RequireUpdate, this, &KeyframeView::Redraw);
|
||||
tracks_.append(track);
|
||||
Redraw();
|
||||
return track;
|
||||
}
|
||||
|
||||
void KeyframeView::RemoveKeyframesOfTrack(KeyframeViewInputConnection *connection)
|
||||
{
|
||||
if (tracks_.removeOne(connection)) {
|
||||
foreach (NodeKeyframe *key, connection->GetKeyframes()) {
|
||||
selection_manager_.Deselect(key);
|
||||
}
|
||||
delete connection;
|
||||
Redraw();
|
||||
emit SelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeView::SelectAll()
|
||||
{
|
||||
foreach (KeyframeViewInputConnection *track, tracks_) {
|
||||
foreach (NodeKeyframe *key, track->GetKeyframes()) {
|
||||
SelectKeyframe(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeView::DeselectAll()
|
||||
{
|
||||
selection_manager_.ClearSelection();
|
||||
|
||||
Redraw();
|
||||
}
|
||||
|
||||
void KeyframeView::Clear()
|
||||
{
|
||||
if (!tracks_.isEmpty()) {
|
||||
qDeleteAll(tracks_);
|
||||
tracks_.clear();
|
||||
Redraw();
|
||||
}
|
||||
|
||||
selection_manager_.ClearSelection();
|
||||
}
|
||||
|
||||
void KeyframeView::SelectionManagerSelectEvent(void *obj)
|
||||
{
|
||||
if (autoselect_siblings_) {
|
||||
NodeKeyframe *key = static_cast<NodeKeyframe*>(obj);
|
||||
QVector<NodeKeyframe*> keys = key->parent()->GetKeyframesAtTime(key->input(), key->time(), key->element());
|
||||
foreach (NodeKeyframe* k, keys) {
|
||||
if (k != key) {
|
||||
SelectKeyframe(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit SelectionChanged();
|
||||
}
|
||||
|
||||
void KeyframeView::SelectionManagerDeselectEvent(void *obj)
|
||||
{
|
||||
if (autoselect_siblings_) {
|
||||
NodeKeyframe *key = static_cast<NodeKeyframe*>(obj);
|
||||
QVector<NodeKeyframe*> keys = key->parent()->GetKeyframesAtTime(key->input(), key->time(), key->element());
|
||||
foreach (NodeKeyframe* k, keys) {
|
||||
if (k != key) {
|
||||
DeselectKeyframe(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit SelectionChanged();
|
||||
}
|
||||
|
||||
void KeyframeView::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
NodeKeyframe *key_under_cursor = selection_manager_.GetObjectAtPoint(event->pos());
|
||||
|
||||
if (HandPress(event) || (!key_under_cursor && PlayheadPress(event))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do mouse press things
|
||||
if (FirstChanceMousePress(event)) {
|
||||
first_chance_mouse_event_ = true;
|
||||
} else if (NodeKeyframe *initial_key = selection_manager_.MousePress(event)) {
|
||||
selection_manager_.DragStart(initial_key, event);
|
||||
KeyframeDragStart(event);
|
||||
} else {
|
||||
selection_manager_.RubberBandStart(event);
|
||||
}
|
||||
|
||||
// Update view
|
||||
Redraw();
|
||||
}
|
||||
|
||||
void KeyframeView::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (HandMove(event) || PlayheadMove(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (first_chance_mouse_event_) {
|
||||
FirstChanceMouseMove(event);
|
||||
} else if (selection_manager_.IsDragging()) {
|
||||
QString tip;
|
||||
KeyframeDragMove(event, tip);
|
||||
selection_manager_.DragMove(event, tip);
|
||||
} else if (selection_manager_.IsRubberBanding()) {
|
||||
selection_manager_.RubberBandMove(event);
|
||||
Redraw();
|
||||
}
|
||||
|
||||
if (event->buttons()) {
|
||||
// Signal cursor pos in case we should scroll to catch up to it
|
||||
QPointF scene_pos = mapToScene(event->pos());
|
||||
emit Dragged(scene_pos.x(), scene_pos.y());
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeView::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (HandRelease(event) || PlayheadRelease(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (first_chance_mouse_event_) {
|
||||
FirstChanceMouseRelease(event);
|
||||
first_chance_mouse_event_ = false;
|
||||
} else if (selection_manager_.IsDragging()) {
|
||||
MultiUndoCommand* command = new MultiUndoCommand();
|
||||
selection_manager_.DragStop(command);
|
||||
KeyframeDragRelease(event, command);
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
} else if (selection_manager_.IsRubberBanding()) {
|
||||
selection_manager_.RubberBandStop();
|
||||
Redraw();
|
||||
emit SelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
int BinarySearchFirstKeyframeAfterOrAt(const QVector<NodeKeyframe*> &keys, const rational &time)
|
||||
{
|
||||
int low = 0;
|
||||
int high = keys.size()-1;
|
||||
|
||||
while (low <= high) {
|
||||
int mid = low + (high-low)/2;
|
||||
NodeKeyframe *test_key = keys.at(mid);
|
||||
|
||||
if (test_key->time() == time || (test_key->time() > time && (mid == 0 || keys.at(mid-1)->time() < time))) {
|
||||
return mid;
|
||||
} else if (test_key->time() < time) {
|
||||
low = mid + 1;
|
||||
} else {
|
||||
high = mid - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return keys.size();
|
||||
}
|
||||
|
||||
void KeyframeView::drawForeground(QPainter *painter, const QRectF &rect)
|
||||
{
|
||||
int key_sz = QtUtils::QFontMetricsWidth(fontMetrics(), "Oi");
|
||||
int key_rad = key_sz/2;
|
||||
|
||||
selection_manager_.ClearDrawnObjects();
|
||||
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
foreach (KeyframeViewInputConnection *track, tracks_) {
|
||||
const QVector<NodeKeyframe*> &keys = track->GetKeyframes();
|
||||
|
||||
if (keys.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!IsYAxisEnabled()) {
|
||||
// Filter out if the keyframes are offscreen Y
|
||||
qreal y = GetKeyframeSceneY(track, keys.first());
|
||||
if (y + key_rad < rect.top() || y - key_rad >= rect.bottom()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Find first keyframe to show with binary search
|
||||
rational left_time = GetUnadjustedKeyframeTime(keys.first(), SceneToTime(rect.left() - key_sz));
|
||||
int using_index = BinarySearchFirstKeyframeAfterOrAt(keys, left_time);
|
||||
|
||||
rational next_key = RATIONAL_MIN;
|
||||
NodeKeyframe::Type last_type = NodeKeyframe::kInvalid;
|
||||
for (int i=using_index; i<keys.size(); i++) {
|
||||
NodeKeyframe *key = keys.at(i);
|
||||
|
||||
if (key->time() < next_key && key->type() == last_type) {
|
||||
// This key will be drawn at exactly the same location as the last one and therefore
|
||||
// doesn't need to be drawn. See if the next one will be drawn.
|
||||
i++;
|
||||
if (i == keys.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
key = keys.at(i);
|
||||
|
||||
if (key->time() < next_key) {
|
||||
// Next key still won't be drawn, so we'll switch to a binary search
|
||||
i = BinarySearchFirstKeyframeAfterOrAt(keys, next_key);
|
||||
|
||||
if (i == keys.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
key = keys.at(i);
|
||||
}
|
||||
}
|
||||
|
||||
QRectF key_rect(-key_rad, -key_rad, key_sz, key_sz);
|
||||
qreal key_x = GetKeyframeSceneX(key);
|
||||
key_rect.translate(key_x, GetKeyframeSceneY(track, key));
|
||||
|
||||
if (key_rect.left() >= rect.right()) {
|
||||
// Break after last keyframe
|
||||
break;
|
||||
}
|
||||
|
||||
DrawKeyframe(painter, key, track, key_rect);
|
||||
|
||||
next_key = GetUnadjustedKeyframeTime(key, SceneToTime(key_x + 1));
|
||||
last_type = key->type();
|
||||
}
|
||||
}
|
||||
|
||||
super::drawForeground(painter, rect);
|
||||
}
|
||||
|
||||
void KeyframeView::DrawKeyframe(QPainter *painter, NodeKeyframe *key, KeyframeViewInputConnection *track, const QRectF &key_rect)
|
||||
{
|
||||
painter->setPen(Qt::black);
|
||||
|
||||
if (IsKeyframeSelected(key)) {
|
||||
painter->setBrush(palette().highlight());
|
||||
} else {
|
||||
painter->setBrush(track->GetBrush());
|
||||
}
|
||||
|
||||
selection_manager_.DeclareDrawnObject(key, key_rect);
|
||||
|
||||
switch (key->type()) {
|
||||
case NodeKeyframe::kInvalid:
|
||||
break;
|
||||
case NodeKeyframe::kLinear:
|
||||
{
|
||||
QPointF points[] = {
|
||||
QPointF(key_rect.center().x(), key_rect.top()),
|
||||
QPointF(key_rect.right(), key_rect.center().y()),
|
||||
QPointF(key_rect.center().x(), key_rect.bottom()),
|
||||
QPointF(key_rect.left(), key_rect.center().y())
|
||||
};
|
||||
|
||||
painter->drawPolygon(points, 4);
|
||||
break;
|
||||
}
|
||||
case NodeKeyframe::kBezier:
|
||||
painter->drawEllipse(key_rect);
|
||||
break;
|
||||
case NodeKeyframe::kHold:
|
||||
painter->drawRect(key_rect);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeView::ScaleChangedEvent(const double &scale)
|
||||
{
|
||||
super::ScaleChangedEvent(scale);
|
||||
|
||||
Redraw();
|
||||
}
|
||||
|
||||
void KeyframeView::TimeTargetChangedEvent(Node *target)
|
||||
{
|
||||
Redraw();
|
||||
}
|
||||
|
||||
void KeyframeView::TimebaseChangedEvent(const rational &timebase)
|
||||
{
|
||||
super::TimebaseChangedEvent(timebase);
|
||||
|
||||
selection_manager_.SetTimebase(timebase);
|
||||
}
|
||||
|
||||
void KeyframeView::ContextMenuEvent(Menu& m)
|
||||
{
|
||||
Q_UNUSED(m)
|
||||
}
|
||||
|
||||
void KeyframeView::SelectKeyframe(NodeKeyframe *key)
|
||||
{
|
||||
if (selection_manager_.Select(key)) {
|
||||
Redraw();
|
||||
|
||||
emit SelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeView::DeselectKeyframe(NodeKeyframe *key)
|
||||
{
|
||||
if (selection_manager_.Deselect(key)) {
|
||||
Redraw();
|
||||
|
||||
emit SelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
rational KeyframeView::GetUnadjustedKeyframeTime(NodeKeyframe *key, const rational &time)
|
||||
{
|
||||
return GetAdjustedTime(GetTimeTarget(), key->parent(), time, true);
|
||||
}
|
||||
|
||||
rational KeyframeView::GetAdjustedKeyframeTime(NodeKeyframe *key)
|
||||
{
|
||||
return GetAdjustedTime(key->parent(), GetTimeTarget(), key->time(), false);
|
||||
}
|
||||
|
||||
double KeyframeView::GetKeyframeSceneX(NodeKeyframe *key)
|
||||
{
|
||||
return TimeToScene(GetAdjustedKeyframeTime(key));
|
||||
}
|
||||
|
||||
qreal KeyframeView::GetKeyframeSceneY(KeyframeViewInputConnection *track, NodeKeyframe *key)
|
||||
{
|
||||
return mapFromGlobal(QPoint(0, track->GetKeyframeY())).y();
|
||||
}
|
||||
|
||||
void KeyframeView::SceneRectUpdateEvent(QRectF &rect)
|
||||
{
|
||||
rect.setY(0);
|
||||
rect.setHeight(max_scroll_);
|
||||
}
|
||||
|
||||
KeyframeViewItem* KeyframeView::AddKeyframe(NodeKeyframe* key)
|
||||
rational KeyframeView::CalculateNewTimeFromScreen(const rational &old_time, double cursor_diff)
|
||||
{
|
||||
KeyframeViewItem* item = super::AddKeyframe(key);
|
||||
item->SetOverrideY(element_y_.value(key->key_track_ref().input()));
|
||||
return item;
|
||||
return rational::fromDouble(old_time.toDouble() + cursor_diff);
|
||||
}
|
||||
|
||||
void KeyframeView::ShowContextMenu()
|
||||
{
|
||||
Menu m;
|
||||
|
||||
MenuShared::instance()->AddItemsForEditMenu(&m, false);
|
||||
|
||||
QAction* linear_key_action = nullptr;
|
||||
QAction* bezier_key_action = nullptr;
|
||||
QAction* hold_key_action = nullptr;
|
||||
|
||||
if (!GetSelectedKeyframes().isEmpty()) {
|
||||
bool all_keys_are_same_type = true;
|
||||
NodeKeyframe::Type type = GetSelectedKeyframes().first()->type();
|
||||
|
||||
for (int i=1;i<GetSelectedKeyframes().size();i++) {
|
||||
NodeKeyframe* key_item = GetSelectedKeyframes().at(i);
|
||||
NodeKeyframe* prev_item = GetSelectedKeyframes().at(i-1);
|
||||
|
||||
if (key_item->type() != prev_item->type()) {
|
||||
all_keys_are_same_type = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m.addSeparator();
|
||||
|
||||
linear_key_action = m.addAction(tr("Linear"));
|
||||
bezier_key_action = m.addAction(tr("Bezier"));
|
||||
hold_key_action = m.addAction(tr("Hold"));
|
||||
|
||||
if (all_keys_are_same_type) {
|
||||
switch (type) {
|
||||
case NodeKeyframe::kInvalid:
|
||||
break;
|
||||
case NodeKeyframe::kLinear:
|
||||
linear_key_action->setChecked(true);
|
||||
break;
|
||||
case NodeKeyframe::kBezier:
|
||||
bezier_key_action->setChecked(true);
|
||||
break;
|
||||
case NodeKeyframe::kHold:
|
||||
hold_key_action->setChecked(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m.addSeparator();
|
||||
|
||||
AddSetScrollZoomsByDefaultActionToMenu(&m);
|
||||
|
||||
m.addSeparator();
|
||||
|
||||
ContextMenuEvent(m);
|
||||
|
||||
if (!GetSelectedKeyframes().isEmpty()) {
|
||||
m.addSeparator();
|
||||
|
||||
QAction* properties_action = m.addAction(tr("P&roperties"));
|
||||
connect(properties_action, &QAction::triggered, this, &KeyframeView::ShowKeyframePropertiesDialog);
|
||||
}
|
||||
|
||||
QAction* selected = m.exec(QCursor::pos());
|
||||
|
||||
// Process keyframe type changes
|
||||
if (selected) {
|
||||
if (selected == linear_key_action
|
||||
|| selected == bezier_key_action
|
||||
|| selected == hold_key_action) {
|
||||
NodeKeyframe::Type new_type;
|
||||
|
||||
if (selected == hold_key_action) {
|
||||
new_type = NodeKeyframe::kHold;
|
||||
} else if (selected == bezier_key_action) {
|
||||
new_type = NodeKeyframe::kBezier;
|
||||
} else {
|
||||
new_type = NodeKeyframe::kLinear;
|
||||
}
|
||||
|
||||
MultiUndoCommand* command = new MultiUndoCommand();
|
||||
foreach (NodeKeyframe* item, GetSelectedKeyframes()) {
|
||||
command->add_child(new KeyframeSetTypeCommand(item, new_type));
|
||||
}
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeView::ShowKeyframePropertiesDialog()
|
||||
{
|
||||
if (!GetSelectedKeyframes().isEmpty()) {
|
||||
KeyframePropertiesDialog kd(GetSelectedKeyframes(), timebase(), this);
|
||||
kd.exec();
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeView::Redraw()
|
||||
{
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,39 +18,141 @@
|
||||
|
||||
***/
|
||||
|
||||
#ifndef KEYFRAMEVIEW_H
|
||||
#define KEYFRAMEVIEW_H
|
||||
#ifndef KEYFRAMEVIEWBASE_H
|
||||
#define KEYFRAMEVIEWBASE_H
|
||||
|
||||
#include "keyframeviewbase.h"
|
||||
#include "keyframeviewinputconnection.h"
|
||||
#include "node/keyframe.h"
|
||||
#include "widget/menu/menu.h"
|
||||
#include "widget/timebased/timebasedview.h"
|
||||
#include "widget/timebased/timebasedviewselectionmanager.h"
|
||||
#include "widget/timetarget/timetarget.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class KeyframeView : public KeyframeViewBase
|
||||
class KeyframeView : public TimeBasedView, public TimeTargetObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
KeyframeView(QWidget* parent = nullptr);
|
||||
|
||||
void DeleteSelected();
|
||||
|
||||
using ElementConnections = QVector<KeyframeViewInputConnection *>;
|
||||
using InputConnections = QVector<ElementConnections>;
|
||||
using NodeConnections = QMap<QString, InputConnections>;
|
||||
|
||||
NodeConnections AddKeyframesOfNode(Node* n);
|
||||
|
||||
InputConnections AddKeyframesOfInput(Node *n, const QString &input);
|
||||
|
||||
ElementConnections AddKeyframesOfElement(const NodeInput &input);
|
||||
|
||||
KeyframeViewInputConnection *AddKeyframesOfTrack(const NodeKeyframeTrackReference &ref);
|
||||
|
||||
void RemoveKeyframesOfTrack(KeyframeViewInputConnection *connection);
|
||||
|
||||
void SelectAll();
|
||||
|
||||
void DeselectAll();
|
||||
|
||||
void Clear();
|
||||
|
||||
const QVector<NodeKeyframe*> &GetSelectedKeyframes() const
|
||||
{
|
||||
return selection_manager_.GetSelectedObjects();
|
||||
}
|
||||
|
||||
virtual void SelectionManagerSelectEvent(void *obj) override;
|
||||
virtual void SelectionManagerDeselectEvent(void *obj) override;
|
||||
|
||||
void SetMaxScroll(int i)
|
||||
{
|
||||
max_scroll_ = i;
|
||||
UpdateSceneRect();
|
||||
}
|
||||
|
||||
void SetElementY(const NodeInput& c, int y);
|
||||
signals:
|
||||
void Dragged(int current_x, int current_y);
|
||||
|
||||
void SelectionChanged();
|
||||
|
||||
protected:
|
||||
virtual void mousePressEvent(QMouseEvent *event) override;
|
||||
virtual void mouseMoveEvent(QMouseEvent *event) override;
|
||||
virtual void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
|
||||
virtual void drawForeground(QPainter *painter, const QRectF &rect) override;
|
||||
|
||||
virtual void DrawKeyframe(QPainter *painter, NodeKeyframe *key, KeyframeViewInputConnection *track, const QRectF &key_rect);
|
||||
|
||||
virtual void ScaleChangedEvent(const double& scale) override;
|
||||
|
||||
virtual void TimeTargetChangedEvent(Node*) override;
|
||||
|
||||
virtual void TimebaseChangedEvent(const rational &timebase) override;
|
||||
|
||||
virtual void ContextMenuEvent(Menu &m);
|
||||
|
||||
virtual bool FirstChanceMousePress(QMouseEvent *event){return false;}
|
||||
virtual void FirstChanceMouseMove(QMouseEvent *event){}
|
||||
virtual void FirstChanceMouseRelease(QMouseEvent *event){}
|
||||
|
||||
virtual void KeyframeDragStart(QMouseEvent *event){}
|
||||
virtual void KeyframeDragMove(QMouseEvent *event, QString &tip){}
|
||||
virtual void KeyframeDragRelease(QMouseEvent *event, MultiUndoCommand *command){}
|
||||
|
||||
void SelectKeyframe(NodeKeyframe *key);
|
||||
|
||||
void DeselectKeyframe(NodeKeyframe *key);
|
||||
|
||||
bool IsKeyframeSelected(NodeKeyframe *key) const
|
||||
{
|
||||
return selection_manager_.IsSelected(key);
|
||||
}
|
||||
|
||||
rational GetUnadjustedKeyframeTime(NodeKeyframe *key, const rational &time);
|
||||
rational GetUnadjustedKeyframeTime(NodeKeyframe *key)
|
||||
{
|
||||
return GetUnadjustedKeyframeTime(key, key->time());
|
||||
}
|
||||
|
||||
rational GetAdjustedKeyframeTime(NodeKeyframe *key);
|
||||
|
||||
double GetKeyframeSceneX(NodeKeyframe *key);
|
||||
|
||||
virtual qreal GetKeyframeSceneY(KeyframeViewInputConnection *track, NodeKeyframe *key);
|
||||
|
||||
void SetAutoSelectSiblings(bool e)
|
||||
{
|
||||
autoselect_siblings_ = e;
|
||||
}
|
||||
|
||||
virtual void SceneRectUpdateEvent(QRectF& rect) override;
|
||||
|
||||
public slots:
|
||||
virtual KeyframeViewItem* AddKeyframe(NodeKeyframe* key) override;
|
||||
protected slots:
|
||||
void Redraw();
|
||||
|
||||
private:
|
||||
QHash<NodeInput, qreal> element_y_;
|
||||
rational CalculateNewTimeFromScreen(const rational& old_time, double cursor_diff);
|
||||
|
||||
QVector<KeyframeViewInputConnection*> tracks_;
|
||||
|
||||
TimeBasedViewSelectionManager<NodeKeyframe> selection_manager_;
|
||||
|
||||
bool autoselect_siblings_;
|
||||
|
||||
int max_scroll_;
|
||||
|
||||
bool first_chance_mouse_event_;
|
||||
|
||||
private slots:
|
||||
void ShowContextMenu();
|
||||
|
||||
void ShowKeyframePropertiesDialog();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // KEYFRAMEVIEW_H
|
||||
#endif // KEYFRAMEVIEWBASE_H
|
||||
|
||||
@@ -1,623 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "keyframeviewbase.h"
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QToolTip>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "dialog/keyframeproperties/keyframeproperties.h"
|
||||
#include "keyframeviewundo.h"
|
||||
#include "node/node.h"
|
||||
#include "widget/menu/menu.h"
|
||||
#include "widget/menu/menushared.h"
|
||||
#include "widget/nodeparamview/nodeparamviewundo.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
KeyframeViewBase::KeyframeViewBase(QWidget *parent) :
|
||||
TimeBasedView(parent),
|
||||
dragging_bezier_point_(nullptr),
|
||||
currently_autoselecting_(false),
|
||||
dragging_(false)
|
||||
{
|
||||
SetDefaultDragMode(RubberBandDrag);
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
connect(this, &KeyframeViewBase::customContextMenuRequested, this, &KeyframeViewBase::ShowContextMenu);
|
||||
connect(scene(), &QGraphicsScene::selectionChanged, this, &KeyframeViewBase::AutoSelectKeyTimeNeighbors);
|
||||
}
|
||||
|
||||
void KeyframeViewBase::Clear()
|
||||
{
|
||||
QMap<NodeKeyframe*, KeyframeViewItem*>::iterator iterator;
|
||||
|
||||
for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) {
|
||||
delete iterator.value();
|
||||
}
|
||||
|
||||
item_map_.clear();
|
||||
}
|
||||
|
||||
void KeyframeViewBase::DeleteSelected()
|
||||
{
|
||||
MultiUndoCommand* command = new MultiUndoCommand();
|
||||
|
||||
QMap<NodeKeyframe*, KeyframeViewItem*>::const_iterator i;
|
||||
|
||||
for (i=item_map_.constBegin(); i!=item_map_.constEnd(); i++) {
|
||||
if (i.value()->isSelected()) {
|
||||
command->add_child(new NodeParamRemoveKeyframeCommand(i.key()));
|
||||
}
|
||||
}
|
||||
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
}
|
||||
|
||||
void KeyframeViewBase::AddKeyframesOfNode(Node *n)
|
||||
{
|
||||
foreach (const QString& i, n->inputs()) {
|
||||
AddKeyframesOfInput(n, i);
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeViewBase::AddKeyframesOfInput(Node* n, const QString& input)
|
||||
{
|
||||
if (!n->IsInputKeyframable(input)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int arr_sz = n->InputArraySize(input);
|
||||
for (int i=-1; i<arr_sz; i++) {
|
||||
AddKeyframesOfElement(NodeInput(n, input, i));
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeViewBase::AddKeyframesOfElement(const NodeInput& input)
|
||||
{
|
||||
const QVector<NodeKeyframeTrack>& tracks = input.node()->GetKeyframeTracks(input);
|
||||
|
||||
for (int i=0; i<tracks.size(); i++) {
|
||||
AddKeyframesOfTrack(NodeKeyframeTrackReference(input, i));
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeViewBase::AddKeyframesOfTrack(const NodeKeyframeTrackReference& ref)
|
||||
{
|
||||
const QVector<NodeKeyframeTrack>& tracks = ref.input().node()->GetKeyframeTracks(ref.input());
|
||||
const NodeKeyframeTrack& t = tracks.at(ref.track());
|
||||
|
||||
foreach (NodeKeyframe* key, t) {
|
||||
AddKeyframe(key);
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeViewBase::RemoveKeyframesOfNode(Node *n)
|
||||
{
|
||||
foreach (const QString& i, n->inputs()) {
|
||||
RemoveKeyframesOfInput(n, i);
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeViewBase::RemoveKeyframesOfInput(Node* n, const QString& input)
|
||||
{
|
||||
if (!n->IsInputKeyframable(input)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int arr_sz = n->InputArraySize(input);
|
||||
for (int i=-1; i<arr_sz; i++) {
|
||||
RemoveKeyframesOfElement(NodeInput(n, input, i));
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeViewBase::RemoveKeyframesOfElement(const NodeInput& input)
|
||||
{
|
||||
const QVector<NodeKeyframeTrack>& tracks = input.node()->GetKeyframeTracks(input);
|
||||
|
||||
for (int i=0; i<tracks.size(); i++) {
|
||||
RemoveKeyframesOfTrack(NodeKeyframeTrackReference(input, i));
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeViewBase::RemoveKeyframesOfTrack(const NodeKeyframeTrackReference& ref)
|
||||
{
|
||||
const QVector<NodeKeyframeTrack>& tracks = ref.input().node()->GetKeyframeTracks(ref.input());
|
||||
const NodeKeyframeTrack& t = tracks.at(ref.track());
|
||||
|
||||
foreach (NodeKeyframe* key, t) {
|
||||
RemoveKeyframe(key);
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeViewBase::SelectAll()
|
||||
{
|
||||
for (auto it=item_map_.cbegin(); it!=item_map_.cend(); it++) {
|
||||
it.value()->setSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeViewBase::DeselectAll()
|
||||
{
|
||||
for (auto it=item_map_.cbegin(); it!=item_map_.cend(); it++) {
|
||||
it.value()->setSelected(false);
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeViewBase::RemoveKeyframe(NodeKeyframe* key)
|
||||
{
|
||||
KeyframeAboutToBeRemoved(key);
|
||||
|
||||
delete item_map_.take(key);
|
||||
}
|
||||
|
||||
KeyframeViewItem *KeyframeViewBase::AddKeyframe(NodeKeyframe* key)
|
||||
{
|
||||
KeyframeViewItem* item = item_map_.value(key);
|
||||
|
||||
if (!item) {
|
||||
item = new KeyframeViewItem(key);
|
||||
item->SetTimeTarget(GetTimeTarget());
|
||||
item->SetScale(GetScale());
|
||||
item_map_.insert(key, item);
|
||||
scene()->addItem(item);
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void KeyframeViewBase::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
QGraphicsItem* item_under_cursor = itemAt(event->pos());
|
||||
|
||||
if (HandPress(event) || (!item_under_cursor && PlayheadPress(event))) {
|
||||
return;
|
||||
}
|
||||
|
||||
active_tool_ = Core::instance()->tool();
|
||||
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
QGraphicsView::mousePressEvent(event);
|
||||
|
||||
if (active_tool_ == Tool::kPointer) {
|
||||
if (item_under_cursor) {
|
||||
|
||||
dragging_ = true;
|
||||
drag_start_ = mapToScene(event->pos());
|
||||
|
||||
// Determine what type of item is under the cursor
|
||||
dragging_bezier_point_ = dynamic_cast<BezierControlPointItem*>(item_under_cursor);
|
||||
|
||||
if (dragging_bezier_point_) {
|
||||
|
||||
dragging_bezier_point_start_ = dragging_bezier_point_->GetCorrespondingKeyframeHandle();
|
||||
dragging_bezier_point_opposing_start_ = dragging_bezier_point_->key()->bezier_control(NodeKeyframe::get_opposing_bezier_type(dragging_bezier_point_->mode()));
|
||||
|
||||
} else {
|
||||
|
||||
QList<QGraphicsItem*> selected_items = scene()->selectedItems();
|
||||
|
||||
selected_keys_.resize(selected_items.size());
|
||||
|
||||
initial_drag_item_ = static_cast<KeyframeViewItem*>(item_under_cursor);
|
||||
|
||||
for (int i=0;i<selected_items.size();i++) {
|
||||
KeyframeViewItem* key = static_cast<KeyframeViewItem*>(selected_items.at(i));
|
||||
|
||||
selected_keys_.replace(i, {key,
|
||||
key->x(),
|
||||
GetAdjustedTime(key->key()->parent(), GetTimeTarget(), key->key()->time(), false),
|
||||
key->key()->value().toDouble()});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeViewBase::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (HandMove(event) || PlayheadMove(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->buttons() & Qt::LeftButton) {
|
||||
QGraphicsView::mouseMoveEvent(event);
|
||||
|
||||
if (dragging_) {
|
||||
// Calculate cursor difference and scale it
|
||||
QPointF mouse_diff_scaled = GetScaledCursorPos(mapToScene(event->pos()) - drag_start_);
|
||||
|
||||
if (event->modifiers() & Qt::ShiftModifier) {
|
||||
// If holding shift, only move one axis
|
||||
mouse_diff_scaled.setY(0);
|
||||
}
|
||||
|
||||
if (dragging_bezier_point_) {
|
||||
|
||||
// Flip the mouse Y because bezier control points are drawn bottom to top, not top to bottom
|
||||
mouse_diff_scaled.setY(-mouse_diff_scaled.y());
|
||||
|
||||
QPointF new_bezier_pos = GenerateBezierControlPosition(dragging_bezier_point_->mode(),
|
||||
dragging_bezier_point_start_,
|
||||
mouse_diff_scaled);
|
||||
|
||||
// If the user is NOT holding control, we set the other handle to the exact negative of this handle
|
||||
QPointF new_opposing_pos;
|
||||
NodeKeyframe::BezierType opposing_type = NodeKeyframe::get_opposing_bezier_type(dragging_bezier_point_->mode());
|
||||
|
||||
|
||||
if (!(event->modifiers() & Qt::ControlModifier)) {
|
||||
new_opposing_pos = GenerateBezierControlPosition(opposing_type,
|
||||
dragging_bezier_point_opposing_start_,
|
||||
-mouse_diff_scaled);
|
||||
} else {
|
||||
new_opposing_pos = dragging_bezier_point_opposing_start_;
|
||||
}
|
||||
|
||||
dragging_bezier_point_->key()->set_bezier_control(dragging_bezier_point_->mode(),
|
||||
new_bezier_pos);
|
||||
|
||||
dragging_bezier_point_->key()->set_bezier_control(opposing_type,
|
||||
new_opposing_pos);
|
||||
|
||||
// Bezier control points are parented to keyframe items making their positions relative
|
||||
// to those items. We need to map them to the scene coordinates for this to work properly.
|
||||
QPointF bezier_pos = dragging_bezier_point_->pos() + dragging_bezier_point_->parentItem()->pos();
|
||||
emit Dragged(qRound(bezier_pos.x()), qRound(bezier_pos.y()));
|
||||
|
||||
} else if (!selected_keys_.isEmpty()) {
|
||||
|
||||
// Validate movement - ensure no keyframe goes above its max point or below its min point
|
||||
FloatSlider::DisplayType display_type = FloatSlider::kNormal;
|
||||
|
||||
if (IsYAxisEnabled()) {
|
||||
foreach (const KeyframeItemAndTime& keypair, selected_keys_) {
|
||||
Node* node = keypair.key->key()->parent();
|
||||
const QString& input = keypair.key->key()->input();
|
||||
double new_val = keypair.value - mouse_diff_scaled.y();
|
||||
double limited = new_val;
|
||||
|
||||
if (node->HasInputProperty(input, QStringLiteral("min"))) {
|
||||
limited = qMax(limited, node->GetInputProperty(input, QStringLiteral("min")).toDouble());
|
||||
}
|
||||
|
||||
if (node->HasInputProperty(input, QStringLiteral("max"))) {
|
||||
limited = qMin(limited, node->GetInputProperty(input, QStringLiteral("max")).toDouble());
|
||||
}
|
||||
|
||||
if (limited != new_val) {
|
||||
mouse_diff_scaled.setY(keypair.value - limited);
|
||||
}
|
||||
}
|
||||
|
||||
Node* initial_drag_input = initial_drag_item_->key()->parent();
|
||||
const QString& initial_drag_input_id = initial_drag_item_->key()->input();
|
||||
if (initial_drag_input->HasInputProperty(initial_drag_input_id, QStringLiteral("view"))) {
|
||||
display_type = static_cast<FloatSlider::DisplayType>(initial_drag_input->GetInputProperty(initial_drag_input_id, QStringLiteral("view")).toInt());
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const KeyframeItemAndTime& keypair, selected_keys_) {
|
||||
rational node_time = GetAdjustedTime(GetTimeTarget(),
|
||||
keypair.key->key()->parent(),
|
||||
CalculateNewTimeFromScreen(keypair.time, mouse_diff_scaled.x()),
|
||||
true);
|
||||
|
||||
keypair.key->key()->set_time(node_time);
|
||||
|
||||
if (IsYAxisEnabled()) {
|
||||
keypair.key->key()->set_value(keypair.value - mouse_diff_scaled.y());
|
||||
}
|
||||
}
|
||||
|
||||
// Show information about this keyframe
|
||||
QString tip = Timecode::time_to_timecode(initial_drag_item_->key()->time(), timebase(),
|
||||
Core::instance()->GetTimecodeDisplay(), false);
|
||||
|
||||
if (IsYAxisEnabled()) {
|
||||
bool ok;
|
||||
double num_value = initial_drag_item_->key()->value().toDouble(&ok);
|
||||
|
||||
if (ok) {
|
||||
tip.append('\n');
|
||||
tip.append(FloatSlider::ValueToString(num_value, display_type, 2, true));
|
||||
}
|
||||
|
||||
// Force viewport to update since Qt might try to optimize it out if the keyframe is
|
||||
// offscreen
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
QToolTip::hideText();
|
||||
QToolTip::showText(QCursor::pos(), tip);
|
||||
|
||||
emit Dragged(qRound(initial_drag_item_->x()), qRound(initial_drag_item_->y()));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeViewBase::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (HandRelease(event) || PlayheadRelease(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
QGraphicsView::mouseReleaseEvent(event);
|
||||
|
||||
if (dragging_) {
|
||||
if (dragging_bezier_point_) {
|
||||
MultiUndoCommand* command = new MultiUndoCommand();
|
||||
|
||||
// Create undo command with the current bezier point and the old one
|
||||
command->add_child(new KeyframeSetBezierControlPoint(dragging_bezier_point_->key(),
|
||||
dragging_bezier_point_->mode(),
|
||||
dragging_bezier_point_->key()->bezier_control(dragging_bezier_point_->mode()),
|
||||
dragging_bezier_point_start_));
|
||||
|
||||
if (!(event->modifiers() & Qt::ControlModifier)) {
|
||||
auto opposing_type = NodeKeyframe::get_opposing_bezier_type(dragging_bezier_point_->mode());
|
||||
|
||||
command->add_child(new KeyframeSetBezierControlPoint(dragging_bezier_point_->key(),
|
||||
opposing_type,
|
||||
dragging_bezier_point_->key()->bezier_control(opposing_type),
|
||||
dragging_bezier_point_opposing_start_));
|
||||
}
|
||||
|
||||
dragging_bezier_point_ = nullptr;
|
||||
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
} else if (!selected_keys_.isEmpty()) {
|
||||
MultiUndoCommand* command = new MultiUndoCommand();
|
||||
|
||||
foreach (const KeyframeItemAndTime& keypair, selected_keys_) {
|
||||
NodeKeyframe* item = keypair.key->key();
|
||||
|
||||
// Commit movement
|
||||
command->add_child(new NodeParamSetKeyframeTimeCommand(item,
|
||||
item->time(),
|
||||
keypair.time));
|
||||
|
||||
// Commit value if we're setting a value
|
||||
if (IsYAxisEnabled()) {
|
||||
command->add_child(new NodeParamSetKeyframeValueCommand(item,
|
||||
item->value(),
|
||||
keypair.value));
|
||||
}
|
||||
}
|
||||
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
}
|
||||
|
||||
selected_keys_.clear();
|
||||
|
||||
dragging_ = false;
|
||||
|
||||
QToolTip::hideText();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeViewBase::ScaleChangedEvent(const double &scale)
|
||||
{
|
||||
TimeBasedView::ScaleChangedEvent(scale);
|
||||
|
||||
for (auto iterator=item_map_.begin();iterator!=item_map_.end();iterator++) {
|
||||
iterator.value()->SetScale(scale);
|
||||
}
|
||||
}
|
||||
|
||||
const QMap<NodeKeyframe *, KeyframeViewItem *> &KeyframeViewBase::item_map() const
|
||||
{
|
||||
return item_map_;
|
||||
}
|
||||
|
||||
void KeyframeViewBase::KeyframeAboutToBeRemoved(NodeKeyframe *)
|
||||
{
|
||||
}
|
||||
|
||||
void KeyframeViewBase::TimeTargetChangedEvent(Node *target)
|
||||
{
|
||||
QMap<NodeKeyframe*, KeyframeViewItem*>::const_iterator i;
|
||||
|
||||
for (i=item_map_.begin();i!=item_map_.end();i++) {
|
||||
i.value()->SetTimeTarget(target);
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeViewBase::ContextMenuEvent(Menu& m)
|
||||
{
|
||||
Q_UNUSED(m)
|
||||
}
|
||||
|
||||
rational KeyframeViewBase::CalculateNewTimeFromScreen(const rational &old_time, double cursor_diff)
|
||||
{
|
||||
return rational::fromDouble(old_time.toDouble() + cursor_diff);
|
||||
}
|
||||
|
||||
QPointF KeyframeViewBase::GenerateBezierControlPosition(const NodeKeyframe::BezierType mode, const QPointF &start_point, const QPointF &scaled_cursor_diff)
|
||||
{
|
||||
QPointF new_bezier_pos = start_point;
|
||||
|
||||
new_bezier_pos += scaled_cursor_diff;
|
||||
|
||||
// LIMIT bezier handles from overlapping each other
|
||||
if (mode == NodeKeyframe::kInHandle) {
|
||||
if (new_bezier_pos.x() > 0) {
|
||||
new_bezier_pos.setX(0);
|
||||
}
|
||||
} else {
|
||||
if (new_bezier_pos.x() < 0) {
|
||||
new_bezier_pos.setX(0);
|
||||
}
|
||||
}
|
||||
|
||||
return new_bezier_pos;
|
||||
}
|
||||
|
||||
QPointF KeyframeViewBase::GetScaledCursorPos(const QPointF &cursor_pos)
|
||||
{
|
||||
return QPointF(cursor_pos.x() / GetScale(),
|
||||
cursor_pos.y() / GetYScale());
|
||||
}
|
||||
|
||||
void KeyframeViewBase::ShowContextMenu()
|
||||
{
|
||||
Menu m;
|
||||
|
||||
MenuShared::instance()->AddItemsForEditMenu(&m, false);
|
||||
|
||||
QAction* linear_key_action = nullptr;
|
||||
QAction* bezier_key_action = nullptr;
|
||||
QAction* hold_key_action = nullptr;
|
||||
|
||||
QList<QGraphicsItem*> items = scene()->selectedItems();
|
||||
if (!items.isEmpty()) {
|
||||
bool all_keys_are_same_type = true;
|
||||
NodeKeyframe::Type type = static_cast<KeyframeViewItem*>(items.first())->key()->type();
|
||||
|
||||
for (int i=1;i<items.size();i++) {
|
||||
KeyframeViewItem* key_item = static_cast<KeyframeViewItem*>(items.at(i));
|
||||
KeyframeViewItem* prev_item = static_cast<KeyframeViewItem*>(items.at(i-1));
|
||||
|
||||
if (key_item->key()->type() != prev_item->key()->type()) {
|
||||
all_keys_are_same_type = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m.addSeparator();
|
||||
|
||||
linear_key_action = m.addAction(tr("Linear"));
|
||||
bezier_key_action = m.addAction(tr("Bezier"));
|
||||
hold_key_action = m.addAction(tr("Hold"));
|
||||
|
||||
if (all_keys_are_same_type) {
|
||||
switch (type) {
|
||||
case NodeKeyframe::kLinear:
|
||||
linear_key_action->setChecked(true);
|
||||
break;
|
||||
case NodeKeyframe::kBezier:
|
||||
bezier_key_action->setChecked(true);
|
||||
break;
|
||||
case NodeKeyframe::kHold:
|
||||
hold_key_action->setChecked(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m.addSeparator();
|
||||
|
||||
AddSetScrollZoomsByDefaultActionToMenu(&m);
|
||||
|
||||
m.addSeparator();
|
||||
|
||||
ContextMenuEvent(m);
|
||||
|
||||
if (!items.isEmpty()) {
|
||||
m.addSeparator();
|
||||
|
||||
QAction* properties_action = m.addAction(tr("P&roperties"));
|
||||
connect(properties_action, &QAction::triggered, this, &KeyframeViewBase::ShowKeyframePropertiesDialog);
|
||||
}
|
||||
|
||||
QAction* selected = m.exec(QCursor::pos());
|
||||
|
||||
// Process keyframe type changes
|
||||
if (!items.isEmpty()) {
|
||||
if (selected == linear_key_action
|
||||
|| selected == bezier_key_action
|
||||
|| selected == hold_key_action) {
|
||||
NodeKeyframe::Type new_type;
|
||||
|
||||
if (selected == hold_key_action) {
|
||||
new_type = NodeKeyframe::kHold;
|
||||
} else if (selected == bezier_key_action) {
|
||||
new_type = NodeKeyframe::kBezier;
|
||||
} else {
|
||||
new_type = NodeKeyframe::kLinear;
|
||||
}
|
||||
|
||||
MultiUndoCommand* command = new MultiUndoCommand();
|
||||
foreach (QGraphicsItem* item, items) {
|
||||
command->add_child(new KeyframeSetTypeCommand(static_cast<KeyframeViewItem*>(item)->key(),
|
||||
new_type));
|
||||
}
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeViewBase::ShowKeyframePropertiesDialog()
|
||||
{
|
||||
QList<QGraphicsItem*> items = scene()->selectedItems();
|
||||
QVector<NodeKeyframe*> keys;
|
||||
|
||||
foreach (QGraphicsItem* item, items) {
|
||||
keys.append(static_cast<KeyframeViewItem*>(item)->key());
|
||||
}
|
||||
|
||||
if (!keys.isEmpty()) {
|
||||
KeyframePropertiesDialog kd(keys, timebase(), this);
|
||||
kd.exec();
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeViewBase::AutoSelectKeyTimeNeighbors()
|
||||
{
|
||||
if (currently_autoselecting_ || IsYAxisEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevents infinite loop
|
||||
currently_autoselecting_ = true;
|
||||
|
||||
QList<QGraphicsItem*> selected_items = scene()->selectedItems();
|
||||
|
||||
foreach (QGraphicsItem* g, selected_items) {
|
||||
KeyframeViewItem* key_item = static_cast<KeyframeViewItem*>(g);
|
||||
|
||||
rational key_time = key_item->key()->time();
|
||||
|
||||
QVector<NodeKeyframe*> keys = key_item->key()->parent()->GetKeyframesAtTime(key_item->key()->input(), key_time, key_item->key()->element());
|
||||
|
||||
foreach (NodeKeyframe* k, keys) {
|
||||
if (k == key_item->key()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure this key is not already selected
|
||||
KeyframeViewItem* item = item_map_.value(k);
|
||||
|
||||
if (item) {
|
||||
item->setSelected(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currently_autoselecting_ = false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef KEYFRAMEVIEWBASE_H
|
||||
#define KEYFRAMEVIEWBASE_H
|
||||
|
||||
#include "keyframeviewitem.h"
|
||||
#include "node/keyframe.h"
|
||||
#include "widget/curvewidget/beziercontrolpointitem.h"
|
||||
#include "widget/menu/menu.h"
|
||||
#include "widget/timebased/timebasedview.h"
|
||||
#include "widget/timetarget/timetarget.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class KeyframeViewBase : public TimeBasedView, public TimeTargetObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
KeyframeViewBase(QWidget* parent = nullptr);
|
||||
|
||||
virtual void Clear();
|
||||
|
||||
void DeleteSelected();
|
||||
|
||||
void AddKeyframesOfNode(Node* n);
|
||||
|
||||
void AddKeyframesOfInput(Node *n, const QString &input);
|
||||
|
||||
void AddKeyframesOfElement(const NodeInput &input);
|
||||
|
||||
void AddKeyframesOfTrack(const NodeKeyframeTrackReference &ref);
|
||||
|
||||
void RemoveKeyframesOfNode(Node* n);
|
||||
|
||||
void RemoveKeyframesOfInput(Node *n, const QString &input);
|
||||
|
||||
void RemoveKeyframesOfElement(const NodeInput &input);
|
||||
|
||||
void RemoveKeyframesOfTrack(const NodeKeyframeTrackReference &ref);
|
||||
|
||||
void SelectAll();
|
||||
|
||||
void DeselectAll();
|
||||
|
||||
signals:
|
||||
void Dragged(int current_x, int current_y);
|
||||
|
||||
public slots:
|
||||
virtual KeyframeViewItem* AddKeyframe(NodeKeyframe* key);
|
||||
|
||||
void RemoveKeyframe(NodeKeyframe* key);
|
||||
|
||||
protected:
|
||||
virtual void mousePressEvent(QMouseEvent *event) override;
|
||||
virtual void mouseMoveEvent(QMouseEvent *event) override;
|
||||
virtual void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
|
||||
virtual void ScaleChangedEvent(const double& scale) override;
|
||||
|
||||
const QMap<NodeKeyframe*, KeyframeViewItem*>& item_map() const;
|
||||
|
||||
virtual void KeyframeAboutToBeRemoved(NodeKeyframe* key);
|
||||
|
||||
virtual void TimeTargetChangedEvent(Node*) override;
|
||||
|
||||
virtual void ContextMenuEvent(Menu &m);
|
||||
|
||||
bool IsDragging() const
|
||||
{
|
||||
return dragging_;
|
||||
}
|
||||
|
||||
private:
|
||||
rational CalculateNewTimeFromScreen(const rational& old_time, double cursor_diff);
|
||||
|
||||
static QPointF GenerateBezierControlPosition(const NodeKeyframe::BezierType mode,
|
||||
const QPointF& start_point,
|
||||
const QPointF& scaled_cursor_diff);
|
||||
|
||||
QPointF GetScaledCursorPos(const QPointF &cursor_pos);
|
||||
|
||||
struct KeyframeItemAndTime {
|
||||
KeyframeViewItem* key;
|
||||
qreal item_x;
|
||||
rational time;
|
||||
double value;
|
||||
};
|
||||
|
||||
QMap<NodeKeyframe*, KeyframeViewItem*> item_map_;
|
||||
|
||||
Tool::Item active_tool_;
|
||||
|
||||
QPointF drag_start_;
|
||||
|
||||
BezierControlPointItem* dragging_bezier_point_;
|
||||
QPointF dragging_bezier_point_start_;
|
||||
QPointF dragging_bezier_point_opposing_start_;
|
||||
|
||||
KeyframeViewItem* initial_drag_item_;
|
||||
|
||||
QVector<KeyframeItemAndTime> selected_keys_;
|
||||
|
||||
bool currently_autoselecting_;
|
||||
|
||||
bool dragging_;
|
||||
|
||||
private slots:
|
||||
void ShowContextMenu();
|
||||
|
||||
void ShowKeyframePropertiesDialog();
|
||||
|
||||
void AutoSelectKeyTimeNeighbors();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // KEYFRAMEVIEWBASE_H
|
||||
@@ -0,0 +1,100 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "keyframeviewinputconnection.h"
|
||||
|
||||
#include "keyframeview.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
KeyframeViewInputConnection::KeyframeViewInputConnection(const NodeKeyframeTrackReference &input, KeyframeView *parent) :
|
||||
QObject(parent),
|
||||
keyframe_view_(parent),
|
||||
input_(input),
|
||||
y_(0),
|
||||
y_behavior_(kSingleRow),
|
||||
brush_(Qt::white)
|
||||
{
|
||||
Node *n = input.input().node();
|
||||
|
||||
connect(n, &Node::KeyframeAdded, this, &KeyframeViewInputConnection::AddKeyframe);
|
||||
connect(n, &Node::KeyframeRemoved, this, &KeyframeViewInputConnection::RemoveKeyframe);
|
||||
connect(n, &Node::KeyframeTimeChanged, this, &KeyframeViewInputConnection::KeyframeChanged);
|
||||
connect(n, &Node::KeyframeTypeChanged, this, &KeyframeViewInputConnection::KeyframeChanged);
|
||||
connect(n, &Node::KeyframeTypeChanged, this, &KeyframeViewInputConnection::KeyframeTypeChanged);
|
||||
connect(n, &Node::KeyframeValueChanged, this, &KeyframeViewInputConnection::KeyframeChanged);
|
||||
}
|
||||
|
||||
void KeyframeViewInputConnection::SetKeyframeY(int y)
|
||||
{
|
||||
if (y_ != y) {
|
||||
y_ = y;
|
||||
|
||||
emit RequireUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeViewInputConnection::SetYBehavior(YBehavior e)
|
||||
{
|
||||
if (y_behavior_ != e) {
|
||||
y_behavior_ = e;
|
||||
|
||||
emit RequireUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeViewInputConnection::SetBrush(const QBrush &brush)
|
||||
{
|
||||
if (brush_ != brush) {
|
||||
brush_ = brush;
|
||||
|
||||
emit RequireUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeViewInputConnection::AddKeyframe(NodeKeyframe *key)
|
||||
{
|
||||
if (key->key_track_ref() == input_) {
|
||||
emit RequireUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeViewInputConnection::RemoveKeyframe(NodeKeyframe *key)
|
||||
{
|
||||
if (key->key_track_ref() == input_) {
|
||||
emit RequireUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeViewInputConnection::KeyframeChanged(NodeKeyframe *key)
|
||||
{
|
||||
if (key->key_track_ref() == input_) {
|
||||
emit RequireUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeViewInputConnection::KeyframeTypeChanged(NodeKeyframe *key)
|
||||
{
|
||||
if (key->key_track_ref() == input_) {
|
||||
emit TypeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef KEYFRAMEVIEWINPUTCONNECTION_H
|
||||
#define KEYFRAMEVIEWINPUTCONNECTION_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "node/node.h"
|
||||
#include "node/param.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class KeyframeView;
|
||||
|
||||
class KeyframeViewInputConnection : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
KeyframeViewInputConnection(const NodeKeyframeTrackReference &input, KeyframeView *parent);
|
||||
|
||||
const int &GetKeyframeY() const
|
||||
{
|
||||
return y_;
|
||||
}
|
||||
|
||||
void SetKeyframeY(int y);
|
||||
|
||||
enum YBehavior {
|
||||
kSingleRow,
|
||||
kValueIsHeight
|
||||
};
|
||||
|
||||
void SetYBehavior(YBehavior e);
|
||||
|
||||
const QVector<NodeKeyframe*> GetKeyframes() const
|
||||
{
|
||||
return input_.input().node()->GetKeyframeTracks(input_.input()).at(input_.track());
|
||||
}
|
||||
|
||||
const QBrush &GetBrush() const
|
||||
{
|
||||
return brush_;
|
||||
}
|
||||
|
||||
void SetBrush(const QBrush &brush);
|
||||
|
||||
signals:
|
||||
void RequireUpdate();
|
||||
|
||||
void TypeChanged();
|
||||
|
||||
private:
|
||||
KeyframeView *keyframe_view_;
|
||||
|
||||
NodeKeyframeTrackReference input_;
|
||||
|
||||
int y_;
|
||||
|
||||
YBehavior y_behavior_;
|
||||
|
||||
QBrush brush_;
|
||||
|
||||
private slots:
|
||||
void AddKeyframe(NodeKeyframe *key);
|
||||
|
||||
void RemoveKeyframe(NodeKeyframe *key);
|
||||
|
||||
void KeyframeChanged(NodeKeyframe *key);
|
||||
|
||||
void KeyframeTypeChanged(NodeKeyframe *key);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // KEYFRAMEVIEWINPUTCONNECTION_H
|
||||
@@ -1,129 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "keyframeviewitem.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QPainter>
|
||||
#include <QStyleOptionGraphicsItem>
|
||||
#include <QWidget>
|
||||
|
||||
#include "common/qtutils.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
KeyframeViewItem::KeyframeViewItem(NodeKeyframe* key, QGraphicsItem *parent) :
|
||||
QGraphicsRectItem(parent),
|
||||
key_(key),
|
||||
scale_(1.0),
|
||||
vert_center_(0),
|
||||
use_custom_brush_(false)
|
||||
{
|
||||
setFlag(QGraphicsItem::ItemIsSelectable);
|
||||
|
||||
connect(key, &NodeKeyframe::TimeChanged, this, &KeyframeViewItem::UpdatePos);
|
||||
connect(key, &NodeKeyframe::TypeChanged, this, &KeyframeViewItem::Redraw);
|
||||
|
||||
int keyframe_size = QtUtils::QFontMetricsWidth(qApp->fontMetrics(), "Oi");
|
||||
int half_sz = keyframe_size/2;
|
||||
setRect(-half_sz, -half_sz, keyframe_size, keyframe_size);
|
||||
|
||||
UpdatePos();
|
||||
|
||||
// Set default brush
|
||||
}
|
||||
|
||||
void KeyframeViewItem::SetOverrideY(qreal vertical_center)
|
||||
{
|
||||
vert_center_ = vertical_center;
|
||||
UpdatePos();
|
||||
}
|
||||
|
||||
void KeyframeViewItem::SetScale(double scale)
|
||||
{
|
||||
scale_ = scale;
|
||||
UpdatePos();
|
||||
}
|
||||
|
||||
void KeyframeViewItem::SetOverrideBrush(const QBrush &b)
|
||||
{
|
||||
use_custom_brush_ = true;
|
||||
setBrush(b);
|
||||
}
|
||||
|
||||
NodeKeyframe* KeyframeViewItem::key() const
|
||||
{
|
||||
return key_;
|
||||
}
|
||||
|
||||
void KeyframeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
|
||||
{
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
painter->setPen(Qt::black);
|
||||
|
||||
if (option->state & QStyle::State_Selected) {
|
||||
painter->setBrush(widget->palette().highlight());
|
||||
} else if (use_custom_brush_) {
|
||||
painter->setBrush(brush());
|
||||
} else {
|
||||
painter->setBrush(widget->palette().text());
|
||||
}
|
||||
|
||||
switch (key_->type()) {
|
||||
case NodeKeyframe::kLinear:
|
||||
{
|
||||
QPointF points[] = {
|
||||
QPointF(rect().center().x(), rect().top()),
|
||||
QPointF(rect().right(), rect().center().y()),
|
||||
QPointF(rect().center().x(), rect().bottom()),
|
||||
QPointF(rect().left(), rect().center().y())
|
||||
};
|
||||
|
||||
painter->drawPolygon(points, 4);
|
||||
break;
|
||||
}
|
||||
case NodeKeyframe::kBezier:
|
||||
painter->drawEllipse(rect());
|
||||
break;
|
||||
case NodeKeyframe::kHold:
|
||||
painter->drawRect(rect());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void KeyframeViewItem::TimeTargetChangedEvent(Node *)
|
||||
{
|
||||
UpdatePos();
|
||||
}
|
||||
|
||||
void KeyframeViewItem::UpdatePos()
|
||||
{
|
||||
rational adjusted = GetAdjustedTime(key_->parent(), GetTimeTarget(), key_->time(), false);
|
||||
|
||||
setPos(adjusted.toDouble() * scale_, vert_center_);
|
||||
}
|
||||
|
||||
void KeyframeViewItem::Redraw()
|
||||
{
|
||||
QGraphicsItem::update();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef KEYFRAMEVIEWITEM_H
|
||||
#define KEYFRAMEVIEWITEM_H
|
||||
|
||||
#include <QGraphicsRectItem>
|
||||
|
||||
#include "node/keyframe.h"
|
||||
#include "widget/timetarget/timetarget.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class KeyframeViewItem : public QObject, public QGraphicsRectItem, public TimeTargetObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
KeyframeViewItem(NodeKeyframe* key, QGraphicsItem *parent = nullptr);
|
||||
|
||||
void SetOverrideY(qreal vertical_center);
|
||||
|
||||
void SetScale(double scale);
|
||||
|
||||
void SetOverrideBrush(const QBrush& b);
|
||||
|
||||
NodeKeyframe* key() const;
|
||||
|
||||
protected:
|
||||
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
|
||||
|
||||
virtual void TimeTargetChangedEvent(Node* ) override;
|
||||
|
||||
private:
|
||||
NodeKeyframe* key_;
|
||||
|
||||
double scale_;
|
||||
|
||||
qreal vert_center_;
|
||||
|
||||
bool use_custom_brush_;
|
||||
|
||||
private slots:
|
||||
void UpdatePos();
|
||||
|
||||
void Redraw();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // KEYFRAMEVIEWITEM_H
|
||||
@@ -16,23 +16,29 @@
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
widget/nodeparamview/nodeparamview.h
|
||||
widget/nodeparamview/nodeparamview.cpp
|
||||
widget/nodeparamview/nodeparamviewarraywidget.h
|
||||
widget/nodeparamview/nodeparamview.h
|
||||
widget/nodeparamview/nodeparamviewarraywidget.cpp
|
||||
widget/nodeparamview/nodeparamviewconnectedlabel.h
|
||||
widget/nodeparamview/nodeparamviewarraywidget.h
|
||||
widget/nodeparamview/nodeparamviewconnectedlabel.cpp
|
||||
widget/nodeparamview/nodeparamviewdockarea.h
|
||||
widget/nodeparamview/nodeparamviewconnectedlabel.h
|
||||
widget/nodeparamview/nodeparamviewcontext.cpp
|
||||
widget/nodeparamview/nodeparamviewcontext.h
|
||||
widget/nodeparamview/nodeparamviewdockarea.cpp
|
||||
widget/nodeparamview/nodeparamviewitem.h
|
||||
widget/nodeparamview/nodeparamviewdockarea.h
|
||||
widget/nodeparamview/nodeparamviewitem.cpp
|
||||
widget/nodeparamview/nodeparamviewkeyframecontrol.h
|
||||
widget/nodeparamview/nodeparamviewitem.h
|
||||
widget/nodeparamview/nodeparamviewitembase.cpp
|
||||
widget/nodeparamview/nodeparamviewitembase.h
|
||||
widget/nodeparamview/nodeparamviewitemtitlebar.cpp
|
||||
widget/nodeparamview/nodeparamviewitemtitlebar.h
|
||||
widget/nodeparamview/nodeparamviewkeyframecontrol.cpp
|
||||
widget/nodeparamview/nodeparamviewtextedit.h
|
||||
widget/nodeparamview/nodeparamviewkeyframecontrol.h
|
||||
widget/nodeparamview/nodeparamviewtextedit.cpp
|
||||
widget/nodeparamview/nodeparamviewundo.h
|
||||
widget/nodeparamview/nodeparamviewtextedit.h
|
||||
widget/nodeparamview/nodeparamviewundo.cpp
|
||||
widget/nodeparamview/nodeparamviewwidgetbridge.h
|
||||
widget/nodeparamview/nodeparamviewundo.h
|
||||
widget/nodeparamview/nodeparamviewwidgetbridge.cpp
|
||||
widget/nodeparamview/nodeparamviewwidgetbridge.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <QScrollBar>
|
||||
#include <QSplitter>
|
||||
|
||||
#include "common/functiontimer.h"
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "node/output/viewer/viewer.h"
|
||||
|
||||
@@ -32,10 +33,13 @@ namespace olive {
|
||||
|
||||
#define super TimeBasedWidget
|
||||
|
||||
NodeParamView::NodeParamView(QWidget *parent) :
|
||||
NodeParamView::NodeParamView(bool create_keyframe_view, QWidget *parent) :
|
||||
super(true, false, parent),
|
||||
last_scroll_val_(0),
|
||||
focused_node_(nullptr)
|
||||
focused_node_(nullptr),
|
||||
create_checkboxes_(kNoCheckBoxes),
|
||||
time_target_(nullptr),
|
||||
ignore_flags_(false)
|
||||
{
|
||||
// Create horizontal layout to place scroll area in (and keyframe editing eventually)
|
||||
QHBoxLayout* layout = new QHBoxLayout(this);
|
||||
@@ -46,25 +50,18 @@ NodeParamView::NodeParamView(QWidget *parent) :
|
||||
layout->addWidget(splitter);
|
||||
|
||||
// Set up scroll area for params
|
||||
QScrollArea* scroll_area = new QScrollArea();
|
||||
scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
||||
scroll_area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
scroll_area->setWidgetResizable(true);
|
||||
splitter->addWidget(scroll_area);
|
||||
param_scroll_area_ = new QScrollArea();
|
||||
param_scroll_area_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
||||
param_scroll_area_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
param_scroll_area_->setWidgetResizable(true);
|
||||
splitter->addWidget(param_scroll_area_);
|
||||
|
||||
// Param widget
|
||||
param_widget_container_ = new NodeParamViewParamContainer();
|
||||
connect(param_widget_container_, &NodeParamViewParamContainer::Resized, this, &NodeParamView::UpdateGlobalScrollBar);
|
||||
scroll_area->setWidget(param_widget_container_);
|
||||
param_widget_container_ = new QWidget();
|
||||
param_scroll_area_->setWidget(param_widget_container_);
|
||||
|
||||
param_widget_area_ = new NodeParamViewDockArea();
|
||||
|
||||
// Disable dock widgets from tabbing and disable glitchy animations
|
||||
param_widget_area_->setDockOptions(static_cast<QMainWindow::DockOption>(0));
|
||||
|
||||
// HACK: Hide the main window separators (unfortunately the cursors still appear)
|
||||
param_widget_area_->setStyleSheet(QStringLiteral("QMainWindow::separator {background: rgba(0, 0, 0, 0)}"));
|
||||
|
||||
QVBoxLayout* param_widget_container_layout = new QVBoxLayout(param_widget_container_);
|
||||
QMargins param_widget_margin = param_widget_container_layout->contentsMargins();
|
||||
param_widget_margin.setTop(ruler()->height());
|
||||
@@ -74,34 +71,24 @@ NodeParamView::NodeParamView(QWidget *parent) :
|
||||
|
||||
param_widget_container_layout->addStretch(INT_MAX);
|
||||
|
||||
// Set up keyframe view
|
||||
QWidget* keyframe_area = new QWidget();
|
||||
QVBoxLayout* keyframe_area_layout = new QVBoxLayout(keyframe_area);
|
||||
keyframe_area_layout->setSpacing(0);
|
||||
keyframe_area_layout->setMargin(0);
|
||||
// Create contexts for three different types
|
||||
context_items_.resize(Track::kCount + 1);
|
||||
for (int i=0; i<context_items_.size(); i++) {
|
||||
NodeParamViewContext *c = new NodeParamViewContext;
|
||||
c->setVisible(false);
|
||||
|
||||
// Create ruler object
|
||||
keyframe_area_layout->addWidget(ruler());
|
||||
NodeParamViewItemTitleBar *title_bar = static_cast<NodeParamViewItemTitleBar*>(c->titleBarWidget());
|
||||
|
||||
// Create keyframe view
|
||||
keyframe_view_ = new KeyframeView();
|
||||
keyframe_view_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
ConnectTimelineView(keyframe_view_);
|
||||
keyframe_area_layout->addWidget(keyframe_view_);
|
||||
if (i == Track::kVideo || i == Track::kAudio) {
|
||||
title_bar->SetAddEffectButtonVisible(true);
|
||||
title_bar->SetText(tr("%1 Nodes").arg(Footage::GetStreamTypeName(static_cast<Track::Type>(i))));
|
||||
} else {
|
||||
title_bar->SetText(tr("Other"));
|
||||
}
|
||||
|
||||
// Connect ruler and keyframe view together
|
||||
connect(ruler(), &TimeRuler::TimeChanged, keyframe_view_, &KeyframeView::SetTime);
|
||||
connect(keyframe_view_, &KeyframeView::TimeChanged, ruler(), &TimeRuler::SetTime);
|
||||
connect(keyframe_view_, &KeyframeView::TimeChanged, this, &NodeParamView::SetTime);
|
||||
connect(keyframe_view_, &KeyframeView::Dragged, this, &NodeParamView::KeyframeViewDragged);
|
||||
|
||||
// Connect keyframe view scaling to this
|
||||
connect(keyframe_view_, &KeyframeView::ScaleChanged, this, &NodeParamView::SetScale);
|
||||
|
||||
splitter->addWidget(keyframe_area);
|
||||
|
||||
// Set both widgets to 50/50
|
||||
splitter->setSizes({INT_MAX, INT_MAX});
|
||||
context_items_[i] = c;
|
||||
param_widget_area_->AddItem(c);
|
||||
}
|
||||
|
||||
// Disable collapsing param view (but collapsing keyframe view is permitted)
|
||||
splitter->setCollapsible(0, false);
|
||||
@@ -112,18 +99,54 @@ NodeParamView::NodeParamView(QWidget *parent) :
|
||||
layout->addWidget(vertical_scrollbar_);
|
||||
|
||||
// Connect scrollbars together
|
||||
connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue);
|
||||
connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, scroll_area->verticalScrollBar(), &QScrollBar::setValue);
|
||||
connect(scroll_area->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue);
|
||||
connect(scroll_area->verticalScrollBar(), &QScrollBar::valueChanged, keyframe_view_->verticalScrollBar(), &QScrollBar::setValue);
|
||||
connect(vertical_scrollbar_, &QScrollBar::valueChanged, scroll_area->verticalScrollBar(), &QScrollBar::setValue);
|
||||
connect(vertical_scrollbar_, &QScrollBar::valueChanged, keyframe_view_->verticalScrollBar(), &QScrollBar::setValue);
|
||||
connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue);
|
||||
connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::rangeChanged, vertical_scrollbar_, &QScrollBar::setRange);
|
||||
connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::rangeChanged, this, &NodeParamView::UpdateGlobalScrollBar);
|
||||
connect(vertical_scrollbar_, &QScrollBar::valueChanged, param_scroll_area_->verticalScrollBar(), &QScrollBar::setValue);
|
||||
|
||||
// TimeBasedWidget's scrollbar has extra functionality that we can take advantage of
|
||||
keyframe_view_->setHorizontalScrollBar(scrollbar());
|
||||
keyframe_view_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
||||
if (create_keyframe_view) {
|
||||
// Set up keyframe view
|
||||
QWidget* keyframe_area = new QWidget();
|
||||
QVBoxLayout* keyframe_area_layout = new QVBoxLayout(keyframe_area);
|
||||
keyframe_area_layout->setSpacing(0);
|
||||
keyframe_area_layout->setMargin(0);
|
||||
|
||||
connect(keyframe_view_->horizontalScrollBar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll);
|
||||
// Create ruler object
|
||||
keyframe_area_layout->addWidget(ruler());
|
||||
|
||||
// Create keyframe view
|
||||
keyframe_view_ = new KeyframeView();
|
||||
keyframe_view_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
ConnectTimelineView(keyframe_view_);
|
||||
keyframe_area_layout->addWidget(keyframe_view_);
|
||||
|
||||
// Connect ruler and keyframe view together
|
||||
connect(ruler(), &TimeRuler::TimeChanged, keyframe_view_, &KeyframeView::SetTime);
|
||||
connect(keyframe_view_, &KeyframeView::TimeChanged, ruler(), &TimeRuler::SetTime);
|
||||
connect(keyframe_view_, &KeyframeView::TimeChanged, this, &NodeParamView::SetTime);
|
||||
connect(keyframe_view_, &KeyframeView::Dragged, this, &NodeParamView::KeyframeViewDragged);
|
||||
|
||||
// Connect keyframe view scaling to this
|
||||
connect(keyframe_view_, &KeyframeView::ScaleChanged, this, &NodeParamView::SetScale);
|
||||
|
||||
splitter->addWidget(keyframe_area);
|
||||
|
||||
// Set both widgets to 50/50
|
||||
splitter->setSizes({INT_MAX, INT_MAX});
|
||||
|
||||
connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue);
|
||||
connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, param_scroll_area_->verticalScrollBar(), &QScrollBar::setValue);
|
||||
connect(param_scroll_area_->verticalScrollBar(), &QScrollBar::valueChanged, keyframe_view_->verticalScrollBar(), &QScrollBar::setValue);
|
||||
connect(vertical_scrollbar_, &QScrollBar::valueChanged, keyframe_view_->verticalScrollBar(), &QScrollBar::setValue);
|
||||
|
||||
// TimeBasedWidget's scrollbar has extra functionality that we can take advantage of
|
||||
keyframe_view_->setHorizontalScrollBar(scrollbar());
|
||||
keyframe_view_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
||||
|
||||
connect(keyframe_view_->horizontalScrollBar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll);
|
||||
} else {
|
||||
keyframe_view_ = nullptr;
|
||||
}
|
||||
|
||||
// Set a default scale - FIXME: Hardcoded
|
||||
SetScale(120);
|
||||
@@ -135,8 +158,28 @@ NodeParamView::NodeParamView(QWidget *parent) :
|
||||
&NodeParamView::FocusChanged);
|
||||
}
|
||||
|
||||
void NodeParamView::SelectNodes(const QVector<Node *> &nodes)
|
||||
NodeParamView::~NodeParamView()
|
||||
{
|
||||
qDeleteAll(context_items_);
|
||||
}
|
||||
|
||||
void NodeParamView::CloseContextsBelongingToProject(Project *p)
|
||||
{
|
||||
QVector<Node*> new_contexts = contexts_;
|
||||
|
||||
for (int i=0; i<new_contexts.size(); i++) {
|
||||
if (new_contexts.at(i)->project() == p) {
|
||||
new_contexts.removeAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
SetContexts(new_contexts);
|
||||
}
|
||||
|
||||
/*void NodeParamView::SelectNodes(const QVector<Node *> &nodes)
|
||||
{
|
||||
return;
|
||||
int original_node_count = items_.size();
|
||||
|
||||
foreach (Node* n, nodes) {
|
||||
@@ -149,7 +192,7 @@ void NodeParamView::SelectNodes(const QVector<Node *> &nodes)
|
||||
active_nodes_.append(n);
|
||||
|
||||
// Create node UI
|
||||
AddNode(n);
|
||||
AddNode(n, param_widget_area_);
|
||||
}
|
||||
|
||||
if (items_.size() > original_node_count ) {
|
||||
@@ -164,6 +207,7 @@ void NodeParamView::SelectNodes(const QVector<Node *> &nodes)
|
||||
|
||||
void NodeParamView::DeselectNodes(const QVector<Node *> &nodes)
|
||||
{
|
||||
return;
|
||||
// Remove item from map and delete the widget
|
||||
int original_node_count = items_.size();
|
||||
|
||||
@@ -190,6 +234,64 @@ void NodeParamView::DeselectNodes(const QVector<Node *> &nodes)
|
||||
|
||||
SignalNodeOrder();
|
||||
}
|
||||
}*/
|
||||
|
||||
void NodeParamView::SetInputChecked(const NodeInput &input, bool e)
|
||||
{
|
||||
input_checked_.insert(input, e);
|
||||
foreach (NodeParamViewContext *ctx, context_items_) {
|
||||
ctx->SetInputChecked(input, e);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamView::SetContexts(const QVector<Node *> &contexts)
|
||||
{
|
||||
//TIME_THIS_FUNCTION;
|
||||
|
||||
foreach (NodeParamViewContext *ctx, context_items_) {
|
||||
ctx->Clear();
|
||||
ctx->setVisible(false);
|
||||
}
|
||||
|
||||
foreach (Node *ctx, contexts_) {
|
||||
disconnect(ctx, &Node::NodeAddedToContext, this, &NodeParamView::NodeAddedToContext);
|
||||
}
|
||||
|
||||
contexts_ = contexts;
|
||||
|
||||
foreach (Node *ctx, contexts_) {
|
||||
// Queued so that if any further work is done in connecting this node to the context, it'll be
|
||||
// done before our sorting function is called
|
||||
connect(ctx, &Node::NodeAddedToContext, this, &NodeParamView::NodeAddedToContext, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
if (keyframe_view_) {
|
||||
keyframe_view_->Clear();
|
||||
}
|
||||
|
||||
if (focused_node_) {
|
||||
focused_node_ = nullptr;
|
||||
emit FocusedNodeChanged(nullptr);
|
||||
}
|
||||
|
||||
foreach (Node *ctx, contexts) {
|
||||
NodeParamViewContext *item = GetContextItemFromContext(ctx);
|
||||
|
||||
item->AddContext(ctx);
|
||||
item->setVisible(true);
|
||||
|
||||
for (auto it=ctx->GetContextPositions().cbegin(); it!=ctx->GetContextPositions().cend(); it++) {
|
||||
AddNode(it.key(), item);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (NodeParamViewContext *ctx, context_items_) {
|
||||
SortItemsInContext(ctx);
|
||||
}
|
||||
|
||||
if (keyframe_view_) {
|
||||
QueueKeyframePositionUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamView::resizeEvent(QResizeEvent *event)
|
||||
@@ -197,25 +299,27 @@ void NodeParamView::resizeEvent(QResizeEvent *event)
|
||||
super::resizeEvent(event);
|
||||
|
||||
vertical_scrollbar_->setPageStep(vertical_scrollbar_->height());
|
||||
|
||||
UpdateGlobalScrollBar();
|
||||
}
|
||||
|
||||
void NodeParamView::ScaleChangedEvent(const double &scale)
|
||||
{
|
||||
super::ScaleChangedEvent(scale);
|
||||
|
||||
keyframe_view_->SetScale(scale);
|
||||
if (keyframe_view_) {
|
||||
keyframe_view_->SetScale(scale);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamView::TimebaseChangedEvent(const rational &timebase)
|
||||
{
|
||||
super::TimebaseChangedEvent(timebase);
|
||||
|
||||
keyframe_view_->SetTimebase(timebase);
|
||||
if (keyframe_view_) {
|
||||
keyframe_view_->SetTimebase(timebase);
|
||||
}
|
||||
|
||||
foreach (NodeParamViewItem* item, items_) {
|
||||
item->SetTimebase(timebase);
|
||||
foreach (NodeParamViewContext* ctx, context_items_) {
|
||||
ctx->SetTimebase(timebase);
|
||||
}
|
||||
|
||||
UpdateItemTime(GetTime());
|
||||
@@ -225,34 +329,52 @@ void NodeParamView::TimeChangedEvent(const rational &time)
|
||||
{
|
||||
super::TimeChangedEvent(time);
|
||||
|
||||
keyframe_view_->SetTime(time);
|
||||
if (keyframe_view_) {
|
||||
keyframe_view_->SetTime(time);
|
||||
}
|
||||
|
||||
UpdateItemTime(time);
|
||||
}
|
||||
|
||||
void NodeParamView::ConnectedNodeChangeEvent(ViewerOutput *n)
|
||||
{
|
||||
// Set viewer as a time target
|
||||
keyframe_view_->SetTimeTarget(n);
|
||||
if (keyframe_view_) {
|
||||
// Set viewer as a time target
|
||||
keyframe_view_->SetTimeTarget(n);
|
||||
}
|
||||
|
||||
foreach (NodeParamViewItem* item, items_) {
|
||||
foreach (NodeParamViewContext* item, context_items_) {
|
||||
item->SetTimeTarget(n);
|
||||
}
|
||||
|
||||
time_target_ = n;
|
||||
}
|
||||
|
||||
Node *NodeParamView::GetTimeTarget() const
|
||||
{
|
||||
return keyframe_view_->GetTimeTarget();
|
||||
return time_target_;
|
||||
}
|
||||
|
||||
void NodeParamView::DeleteSelected()
|
||||
{
|
||||
keyframe_view_->DeleteSelected();
|
||||
if (keyframe_view_) {
|
||||
keyframe_view_->DeleteSelected();
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamView::SelectNodes(const QVector<Node *> &nodes)
|
||||
{
|
||||
// Do nothing, this is a placeholder if we ever need this to do anything in the future
|
||||
}
|
||||
|
||||
void NodeParamView::DeselectNodes(const QVector<Node *> &nodes)
|
||||
{
|
||||
// Do nothing, this is a placeholder if we ever need this to do anything in the future
|
||||
}
|
||||
|
||||
void NodeParamView::UpdateItemTime(const rational &time)
|
||||
{
|
||||
foreach (NodeParamViewItem* item, items_) {
|
||||
foreach (NodeParamViewContext* item, context_items_) {
|
||||
item->SetTime(time);
|
||||
}
|
||||
}
|
||||
@@ -262,104 +384,125 @@ void NodeParamView::QueueKeyframePositionUpdate()
|
||||
QMetaObject::invokeMethod(this, &NodeParamView::UpdateElementY, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void NodeParamView::SignalNodeOrder()
|
||||
void NodeParamView::AddNode(Node *n, NodeParamViewContext *context)
|
||||
{
|
||||
// Sort by item Y (apparently there's no way in Qt to get the order of dock widgets)
|
||||
QVector<Node*> nodes;
|
||||
QVector<int> item_ys;
|
||||
if ((n->GetFlags() & Node::kDontShowInParamView) && !ignore_flags_) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto it=items_.cbegin(); it!=items_.cend(); it++) {
|
||||
int item_y = it.value()->pos().y();
|
||||
NodeParamViewItem* item = new NodeParamViewItem(n, create_checkboxes_, context);
|
||||
|
||||
connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::SetTimeAndSignal);
|
||||
connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::RequestSelectNode);
|
||||
connect(item, &NodeParamViewItem::PinToggled, this, &NodeParamView::PinNode);
|
||||
connect(item, &NodeParamViewItem::InputCheckedChanged, this, &NodeParamView::SetInputChecked);
|
||||
|
||||
if (create_checkboxes_) {
|
||||
for (auto it=input_checked_.cbegin(); it!=input_checked_.cend(); it++) {
|
||||
if (it.key().node() == n) {
|
||||
item->SetInputChecked(it.key(), it.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item->SetTimeTarget(GetTimeTarget());
|
||||
item->SetTimebase(timebase());
|
||||
item->SetTime(GetTime());
|
||||
|
||||
context->AddNode(item);
|
||||
|
||||
if (!focused_node_ && n->HasGizmos()) {
|
||||
// We'll focus this node now
|
||||
item->SetHighlighted(true);
|
||||
focused_node_ = item;
|
||||
emit FocusedNodeChanged(n);
|
||||
}
|
||||
|
||||
if (keyframe_view_) {
|
||||
connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::QueueKeyframePositionUpdate);
|
||||
connect(item, &NodeParamViewItem::ArrayExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate);
|
||||
connect(item, &NodeParamViewItem::ExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate);
|
||||
connect(item, &NodeParamViewItem::Moved, this, &NodeParamView::QueueKeyframePositionUpdate);
|
||||
|
||||
item->SetKeyframeConnections(keyframe_view_->AddKeyframesOfNode(n));
|
||||
}
|
||||
}
|
||||
|
||||
int GetDistanceBetweenNodes(Node *start, Node *end)
|
||||
{
|
||||
if (start == end) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (auto it=start->input_connections().cbegin(); it!=start->input_connections().cend(); it++) {
|
||||
int this_node_dist = GetDistanceBetweenNodes(it->second, end);
|
||||
if (this_node_dist != -1) {
|
||||
return 1 + this_node_dist;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void NodeParamView::SortItemsInContext(NodeParamViewContext *context_item)
|
||||
{
|
||||
QVector<QPair<NodeParamViewItem*, int> > distances;
|
||||
|
||||
for (auto it=context_item->GetItems().cbegin(); it!=context_item->GetItems().cend(); it++) {
|
||||
int distance = -1;
|
||||
foreach (Node *ctx, context_item->GetContexts()) {
|
||||
distance = qMax(distance, GetDistanceBetweenNodes(ctx, it.key()));
|
||||
}
|
||||
|
||||
if (distance == -1) {
|
||||
distance = INT_MAX;
|
||||
}
|
||||
|
||||
bool inserted = false;
|
||||
QPair<NodeParamViewItem*, int> dist(it.value(), distance);
|
||||
|
||||
for (int i=0; i<item_ys.size(); i++) {
|
||||
if (item_ys.at(i) > item_y) {
|
||||
item_ys.insert(i, item_y);
|
||||
nodes.insert(i, it.key());
|
||||
for (int i=0; i<distances.size(); i++) {
|
||||
if (distances.at(i).second < distance) {
|
||||
distances.insert(i, dist);
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inserted) {
|
||||
item_ys.append(item_y);
|
||||
nodes.append(it.key());
|
||||
distances.append(dist);
|
||||
}
|
||||
}
|
||||
|
||||
emit NodeOrderChanged(nodes);
|
||||
}
|
||||
|
||||
void NodeParamView::AddNode(Node *n)
|
||||
{
|
||||
NodeParamViewItem* item = new NodeParamViewItem(n, param_widget_area_);
|
||||
|
||||
item->setAllowedAreas(Qt::LeftDockWidgetArea);
|
||||
item->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
|
||||
item->SetExpanded(node_expanded_state_.value(n, true));
|
||||
|
||||
connect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe);
|
||||
connect(n, &Node::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe);
|
||||
|
||||
connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::SetTimeAndSignal);
|
||||
connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::RequestSelectNode);
|
||||
connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::QueueKeyframePositionUpdate);
|
||||
connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::SignalNodeOrder);
|
||||
connect(item, &NodeParamViewItem::PinToggled, this, &NodeParamView::PinNode);
|
||||
connect(item, &NodeParamViewItem::ArrayExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate);
|
||||
connect(item, &NodeParamViewItem::ExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate);
|
||||
connect(item, &NodeParamViewItem::Moved, this, &NodeParamView::QueueKeyframePositionUpdate);
|
||||
|
||||
// Set time target
|
||||
item->SetTimeTarget(GetTimeTarget());
|
||||
|
||||
// Set the timebase
|
||||
item->SetTimebase(timebase());
|
||||
|
||||
items_.insert(n, item);
|
||||
param_widget_area_->addDockWidget(Qt::LeftDockWidgetArea, item);
|
||||
|
||||
if (!focused_node_ && n->HasGizmos()) {
|
||||
// We'll focus this node now
|
||||
item->SetHighlighted(true);
|
||||
focused_node_ = n;
|
||||
emit FocusedNodeChanged(focused_node_);
|
||||
foreach (auto info, distances) {
|
||||
context_item->GetDockArea()->AddItem(info.first);
|
||||
}
|
||||
|
||||
keyframe_view_->AddKeyframesOfNode(n);
|
||||
}
|
||||
|
||||
void NodeParamView::RemoveNode(Node *n)
|
||||
NodeParamViewContext *NodeParamView::GetContextItemFromContext(Node *ctx)
|
||||
{
|
||||
keyframe_view_->RemoveKeyframesOfNode(n);
|
||||
Track::Type ctx_type = Track::kCount;
|
||||
|
||||
disconnect(n, &Node::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe);
|
||||
disconnect(n, &Node::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe);
|
||||
|
||||
delete items_.take(n);
|
||||
|
||||
if (focused_node_ == n) {
|
||||
// Try to find new node with gizmos to focus
|
||||
focused_node_ = nullptr;
|
||||
for (auto it=items_.cbegin(); it!=items_.cend(); it++) {
|
||||
if (it.key()->HasGizmos()) {
|
||||
focused_node_ = it.key();
|
||||
it.value()->SetHighlighted(true);
|
||||
break;
|
||||
if (ClipBlock *clip = dynamic_cast<ClipBlock*>(ctx)) {
|
||||
if (clip->track()) {
|
||||
if (clip->track()->type() != Track::kNone) {
|
||||
ctx_type = clip->track()->type();
|
||||
}
|
||||
}
|
||||
|
||||
emit FocusedNodeChanged(focused_node_);
|
||||
} else if (Track *track = dynamic_cast<Track*>(ctx)) {
|
||||
if (track->type() != Track::kNone) {
|
||||
ctx_type = track->type();
|
||||
}
|
||||
}
|
||||
|
||||
return context_items_.at(ctx_type);
|
||||
}
|
||||
|
||||
void NodeParamView::UpdateGlobalScrollBar()
|
||||
{
|
||||
int height_offscreen = param_widget_container_->height() - ruler()->height() + scrollbar()->height();
|
||||
|
||||
keyframe_view_->SetMaxScroll(height_offscreen);
|
||||
vertical_scrollbar_->setRange(0, height_offscreen - keyframe_view_->height());
|
||||
if (keyframe_view_) {
|
||||
keyframe_view_->SetMaxScroll(param_widget_container_->height() - ruler()->height());
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamView::PinNode(bool pin)
|
||||
@@ -373,8 +516,7 @@ void NodeParamView::PinNode(bool pin)
|
||||
pinned_nodes_.removeOne(node);
|
||||
|
||||
if (!active_nodes_.contains(node)) {
|
||||
RemoveNode(node);
|
||||
SignalNodeOrder();
|
||||
//RemoveNode(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -384,26 +526,36 @@ void NodeParamView::FocusChanged(QWidget* old, QWidget* now)
|
||||
Q_UNUSED(old)
|
||||
|
||||
QObject* parent = now;
|
||||
NodeParamViewItem* item;
|
||||
|
||||
while (parent) {
|
||||
item = dynamic_cast<NodeParamViewItem*>(parent);
|
||||
if (NodeParamViewItem* item = dynamic_cast<NodeParamViewItem*>(parent)) {
|
||||
if (item != focused_node_) {
|
||||
// Found a NodeParamViewItem that isn't already focused, see if it belongs to us
|
||||
bool ours = false;
|
||||
|
||||
if (item) {
|
||||
// Found it!
|
||||
if (item->GetNode() != focused_node_) {
|
||||
if (focused_node_) {
|
||||
// De-focus current node
|
||||
items_.value(focused_node_)->SetHighlighted(false);
|
||||
do {
|
||||
parent = parent->parent();
|
||||
|
||||
if (parent == this) {
|
||||
ours = true;
|
||||
break;
|
||||
}
|
||||
} while (parent);
|
||||
|
||||
if (ours) {
|
||||
// This item is ours,
|
||||
if (focused_node_) {
|
||||
// De-focus current node
|
||||
focused_node_->SetHighlighted(false);
|
||||
}
|
||||
|
||||
focused_node_ = item;
|
||||
|
||||
item->SetHighlighted(true);
|
||||
|
||||
emit FocusedNodeChanged(item->GetNode());
|
||||
}
|
||||
|
||||
focused_node_ = item->GetNode();
|
||||
|
||||
item->SetHighlighted(true);
|
||||
|
||||
emit FocusedNodeChanged(focused_node_);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -421,18 +573,47 @@ void NodeParamView::KeyframeViewDragged(int x, int y)
|
||||
|
||||
void NodeParamView::UpdateElementY()
|
||||
{
|
||||
for (auto it=items_.cbegin(); it!=items_.cend(); it++) {
|
||||
foreach (const QString& input, it.key()->inputs()) {
|
||||
int arr_sz = it.key()->InputArraySize(input);
|
||||
foreach (NodeParamViewContext *ctx, context_items_) {
|
||||
for (auto it=ctx->GetItems().cbegin(); it!=ctx->GetItems().cend(); it++) {
|
||||
const KeyframeView::NodeConnections &connections = it.value()->GetKeyframeConnections();
|
||||
|
||||
for (int i=-1; i<arr_sz; i++) {
|
||||
NodeInput ic = {it.key(), input, i};
|
||||
if (!connections.isEmpty()) {
|
||||
foreach (const QString& input, it.key()->inputs()) {
|
||||
if (!(it.key()->GetInputFlags(input) & kInputFlagHidden)) {
|
||||
int arr_sz = NodeGroup::ResolveInput(NodeInput(it.key(), input)).GetArraySize();
|
||||
|
||||
int y = it.value()->GetElementY(ic);
|
||||
keyframe_view_->SetElementY(ic, y);
|
||||
for (int i=-1; i<arr_sz; i++) {
|
||||
NodeInput ic = {it.key(), input, i};
|
||||
|
||||
int y = it.value()->GetElementY(ic);
|
||||
|
||||
// For some reason Qt's mapToGlobal doesn't seem to handle this, so we offset here
|
||||
y += vertical_scrollbar_->value();
|
||||
|
||||
const KeyframeView::InputConnections &input_con = connections.value(input);
|
||||
int use_index = i + 1;
|
||||
if (use_index < input_con.size()) {
|
||||
const KeyframeView::ElementConnections &ele_con = input_con.at(ic.element()+1);
|
||||
foreach (KeyframeViewInputConnection *track, ele_con) {
|
||||
track->SetKeyframeY(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamView::NodeAddedToContext(Node *n)
|
||||
{
|
||||
Node *ctx = static_cast<Node*>(sender());
|
||||
NodeParamViewContext *item = GetContextItemFromContext(ctx);
|
||||
|
||||
AddNode(n, item);
|
||||
|
||||
SortItemsInContext(item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <QWidget>
|
||||
|
||||
#include "node/node.h"
|
||||
#include "nodeparamviewcontext.h"
|
||||
#include "nodeparamviewdockarea.h"
|
||||
#include "nodeparamviewitem.h"
|
||||
#include "widget/keyframeview/keyframeview.h"
|
||||
@@ -32,42 +33,30 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
class NodeParamViewParamContainer : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
NodeParamViewParamContainer(QWidget* parent = nullptr) :
|
||||
QWidget(parent)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void resizeEvent(QResizeEvent *event) override
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
|
||||
emit Resized(event->size().height());
|
||||
}
|
||||
|
||||
signals:
|
||||
void Resized(int new_height);
|
||||
|
||||
};
|
||||
|
||||
class NodeParamView : public TimeBasedWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
NodeParamView(QWidget* parent = nullptr);
|
||||
|
||||
void SelectNodes(const QVector<Node *> &nodes);
|
||||
void DeselectNodes(const QVector<Node*>& nodes);
|
||||
|
||||
const QMap<Node*, NodeParamViewItem*>& GetItemMap() const
|
||||
NodeParamView(bool create_keyframe_view, QWidget* parent = nullptr);
|
||||
NodeParamView(QWidget* parent = nullptr) :
|
||||
NodeParamView(true, parent)
|
||||
{
|
||||
return items_;
|
||||
}
|
||||
|
||||
virtual ~NodeParamView() override;
|
||||
|
||||
void SetCreateCheckBoxes(NodeParamViewCheckBoxBehavior e)
|
||||
{
|
||||
create_checkboxes_ = e;
|
||||
}
|
||||
|
||||
bool IsInputChecked(const NodeInput &input) const
|
||||
{
|
||||
return input_checked_.value(input);
|
||||
}
|
||||
|
||||
void CloseContextsBelongingToProject(Project *p);
|
||||
|
||||
Node* GetTimeTarget() const;
|
||||
|
||||
void DeleteSelected();
|
||||
@@ -82,11 +71,29 @@ public:
|
||||
keyframe_view_->DeselectAll();
|
||||
}
|
||||
|
||||
void SetIgnoreNodeFlags(bool e)
|
||||
{
|
||||
ignore_flags_ = e;
|
||||
}
|
||||
|
||||
void SelectNodes(const QVector<Node*> &nodes);
|
||||
void DeselectNodes(const QVector<Node*> &nodes);
|
||||
|
||||
const QVector<Node*> &GetContexts() const
|
||||
{
|
||||
return contexts_;
|
||||
}
|
||||
|
||||
public slots:
|
||||
void SetInputChecked(const NodeInput &input, bool e);
|
||||
|
||||
void SetContexts(const QVector<Node*> &contexts);
|
||||
|
||||
void UpdateElementY();
|
||||
|
||||
signals:
|
||||
void RequestSelectNode(const QVector<Node*>& target);
|
||||
|
||||
void NodeOrderChanged(const QVector<Node*>& nodes);
|
||||
|
||||
void FocusedNodeChanged(Node* n);
|
||||
|
||||
protected:
|
||||
@@ -103,33 +110,41 @@ private:
|
||||
|
||||
void QueueKeyframePositionUpdate();
|
||||
|
||||
void SignalNodeOrder();
|
||||
void AddNode(Node* n, NodeParamViewContext *context);
|
||||
|
||||
void AddNode(Node* n);
|
||||
void SortItemsInContext(NodeParamViewContext *context);
|
||||
|
||||
void RemoveNode(Node* n);
|
||||
NodeParamViewContext *GetContextItemFromContext(Node *context);
|
||||
|
||||
KeyframeView* keyframe_view_;
|
||||
|
||||
QMap<Node*, NodeParamViewItem*> items_;
|
||||
QVector<NodeParamViewContext*> context_items_;
|
||||
|
||||
QScrollBar* vertical_scrollbar_;
|
||||
|
||||
int last_scroll_val_;
|
||||
|
||||
NodeParamViewParamContainer* param_widget_container_;
|
||||
QScrollArea* param_scroll_area_;
|
||||
|
||||
QWidget* param_widget_container_;
|
||||
|
||||
// This may look weird, but QMainWindow is just a QWidget with a fancy layout that allows
|
||||
// docking windows
|
||||
NodeParamViewDockArea* param_widget_area_;
|
||||
|
||||
QVector<Node*> pinned_nodes_;
|
||||
|
||||
QVector<Node*> active_nodes_;
|
||||
|
||||
QMap<Node*, bool> node_expanded_state_;
|
||||
NodeParamViewItem* focused_node_;
|
||||
|
||||
Node* focused_node_;
|
||||
NodeParamViewCheckBoxBehavior create_checkboxes_;
|
||||
|
||||
Node *time_target_;
|
||||
|
||||
QHash<NodeInput, bool> input_checked_;
|
||||
|
||||
bool ignore_flags_;
|
||||
|
||||
QVector<Node*> contexts_;
|
||||
|
||||
private slots:
|
||||
void UpdateGlobalScrollBar();
|
||||
@@ -140,7 +155,7 @@ private slots:
|
||||
|
||||
void KeyframeViewDragged(int x, int y);
|
||||
|
||||
void UpdateElementY();
|
||||
void NodeAddedToContext(Node *n);
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "nodeparamviewcontext.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
#include "node/block/clip/clip.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
#define super NodeParamViewItemBase
|
||||
|
||||
NodeParamViewContext::NodeParamViewContext(QWidget *parent) :
|
||||
super(parent)
|
||||
{
|
||||
QWidget *body = new QWidget();
|
||||
QHBoxLayout *body_layout = new QHBoxLayout(body);
|
||||
SetBody(body);
|
||||
|
||||
dock_area_ = new NodeParamViewDockArea();
|
||||
body_layout->addWidget(dock_area_);
|
||||
|
||||
setBackgroundRole(QPalette::Base);
|
||||
|
||||
Retranslate();
|
||||
|
||||
connect(title_bar(), &NodeParamViewItemTitleBar::AddEffectButtonClicked, this, &NodeParamViewContext::AddEffectButtonClicked);
|
||||
}
|
||||
|
||||
void NodeParamViewContext::AddNode(NodeParamViewItem *item)
|
||||
{
|
||||
items_.insert(item->GetNode(), item);
|
||||
dock_area_->AddItem(item);
|
||||
}
|
||||
|
||||
void NodeParamViewContext::RemoveNode(Node *node)
|
||||
{
|
||||
}
|
||||
|
||||
void NodeParamViewContext::Clear()
|
||||
{
|
||||
qDeleteAll(items_);
|
||||
items_.clear();
|
||||
|
||||
contexts_.clear();
|
||||
}
|
||||
|
||||
void NodeParamViewContext::SetInputChecked(const NodeInput &input, bool e)
|
||||
{
|
||||
if (NodeParamViewItem *item = items_.value(input.node())) {
|
||||
item->SetInputChecked(input, e);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewContext::SetTimebase(const rational &timebase)
|
||||
{
|
||||
foreach (NodeParamViewItem* item, items_) {
|
||||
item->SetTimebase(timebase);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewContext::SetTimeTarget(Node *n)
|
||||
{
|
||||
foreach (NodeParamViewItem* item, items_) {
|
||||
item->SetTimeTarget(n);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewContext::SetTime(const rational &time)
|
||||
{
|
||||
foreach (NodeParamViewItem* item, items_) {
|
||||
item->SetTime(time);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewContext::Retranslate()
|
||||
{
|
||||
}
|
||||
|
||||
void NodeParamViewContext::AddEffectButtonClicked()
|
||||
{
|
||||
QMessageBox::information(this, tr("STUB"), tr("This feature is coming soon. Thanks for testing development builds of Olive :)"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef NODEPARAMVIEWCONTEXT_H
|
||||
#define NODEPARAMVIEWCONTEXT_H
|
||||
|
||||
#include "nodeparamviewdockarea.h"
|
||||
#include "nodeparamviewitembase.h"
|
||||
#include "nodeparamviewitem.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class NodeParamViewContext : public NodeParamViewItemBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
NodeParamViewContext(QWidget *parent = nullptr);
|
||||
|
||||
NodeParamViewDockArea *GetDockArea() const
|
||||
{
|
||||
return dock_area_;
|
||||
}
|
||||
|
||||
const QVector<Node*> &GetContexts() const
|
||||
{
|
||||
return contexts_;
|
||||
}
|
||||
|
||||
const QMap<Node*, NodeParamViewItem*> &GetItems() const
|
||||
{
|
||||
return items_;
|
||||
}
|
||||
|
||||
void AddNode(NodeParamViewItem *item);
|
||||
|
||||
void RemoveNode(Node *node);
|
||||
|
||||
void Clear();
|
||||
|
||||
void SetInputChecked(const NodeInput &input, bool e);
|
||||
|
||||
void SetTimebase(const rational &timebase);
|
||||
|
||||
void SetTimeTarget(Node *n);
|
||||
|
||||
void SetTime(const rational &time);
|
||||
|
||||
public slots:
|
||||
void AddContext(Node *node)
|
||||
{
|
||||
contexts_.append(node);
|
||||
}
|
||||
|
||||
void RemoveContext(Node *node)
|
||||
{
|
||||
contexts_.removeOne(node);
|
||||
}
|
||||
|
||||
protected slots:
|
||||
virtual void Retranslate() override;
|
||||
|
||||
private:
|
||||
NodeParamViewDockArea *dock_area_;
|
||||
|
||||
QVector<Node*> contexts_;
|
||||
|
||||
QMap<Node*, NodeParamViewItem*> items_;
|
||||
|
||||
private slots:
|
||||
void AddEffectButtonClicked();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // NODEPARAMVIEWCONTEXT_H
|
||||
@@ -20,11 +20,18 @@
|
||||
|
||||
#include "nodeparamviewdockarea.h"
|
||||
|
||||
#include <QDockWidget>
|
||||
|
||||
namespace olive {
|
||||
|
||||
NodeParamViewDockArea::NodeParamViewDockArea(QWidget *parent) :
|
||||
QMainWindow(parent)
|
||||
{
|
||||
// Disable dock widgets from tabbing and disable glitchy animations
|
||||
setDockOptions(static_cast<QMainWindow::DockOption>(0));
|
||||
|
||||
// HACK: Hide the main window separators (unfortunately the cursors still appear)
|
||||
setStyleSheet(QStringLiteral("QMainWindow::separator {background: rgba(0, 0, 0, 0)}"));
|
||||
}
|
||||
|
||||
QMenu *NodeParamViewDockArea::createPopupMenu()
|
||||
@@ -32,4 +39,11 @@ QMenu *NodeParamViewDockArea::createPopupMenu()
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void NodeParamViewDockArea::AddItem(QDockWidget *item)
|
||||
{
|
||||
item->setAllowedAreas(Qt::LeftDockWidgetArea);
|
||||
item->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
|
||||
addDockWidget(Qt::LeftDockWidgetArea, item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
// This may look weird, but QMainWindow is just a QWidget with a fancy layout that allows
|
||||
// for docking QDockWidgets
|
||||
class NodeParamViewDockArea : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -33,6 +35,8 @@ public:
|
||||
|
||||
virtual QMenu *createPopupMenu() override;
|
||||
|
||||
void AddItem(QDockWidget *item);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDebug>
|
||||
#include <QEvent>
|
||||
#include <QPainter>
|
||||
|
||||
#include "common/qtutils.h"
|
||||
#include "core.h"
|
||||
@@ -38,233 +36,115 @@ const int NodeParamViewItemBody::kArrayInsertColumn = kKeyControlColumn-1;
|
||||
const int NodeParamViewItemBody::kArrayRemoveColumn = kArrayInsertColumn-1;
|
||||
const int NodeParamViewItemBody::kExtraButtonColumn = kKeyControlColumn-1;
|
||||
|
||||
// 0 is for the array collapse button, 1 is for the main label, widgets start at 2
|
||||
const int NodeParamViewItemBody::kWidgetStartColumn = 2;
|
||||
const int NodeParamViewItemBody::kOptionalCheckBox = 0;
|
||||
const int NodeParamViewItemBody::kArrayCollapseBtnColumn = 1;
|
||||
const int NodeParamViewItemBody::kLabelColumn = 2;
|
||||
const int NodeParamViewItemBody::kWidgetStartColumn = 3;
|
||||
|
||||
#define super QDockWidget
|
||||
#define super NodeParamViewItemBase
|
||||
|
||||
NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) :
|
||||
NodeParamViewItem::NodeParamViewItem(Node *node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget *parent) :
|
||||
super(parent),
|
||||
node_(node),
|
||||
highlighted_(false)
|
||||
node_(node)
|
||||
{
|
||||
// Create title bar widget
|
||||
title_bar_ = new NodeParamViewItemTitleBar(this);
|
||||
|
||||
// Add title bar to widget
|
||||
this->setTitleBarWidget(title_bar_);
|
||||
node_->Retranslate();
|
||||
|
||||
// Create and add contents widget
|
||||
body_ = new NodeParamViewItemBody(node_);
|
||||
body_ = new NodeParamViewItemBody(node_, create_checkboxes);
|
||||
connect(body_, &NodeParamViewItemBody::RequestSelectNode, this, &NodeParamViewItem::RequestSelectNode);
|
||||
connect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime);
|
||||
connect(body_, &NodeParamViewItemBody::ArrayExpandedChanged, this, &NodeParamViewItem::ArrayExpandedChanged);
|
||||
connect(title_bar_, &NodeParamViewItemTitleBar::ExpandedStateChanged, this, &NodeParamViewItem::SetExpanded);
|
||||
connect(title_bar_, &NodeParamViewItemTitleBar::PinToggled, this, &NodeParamViewItem::PinToggled);
|
||||
|
||||
this->setWidget(body_);
|
||||
|
||||
// Use dummy QWidget to retain width when not expanded (QDockWidget seems to ignore the titlebar
|
||||
// size hints and will shrink as small as possible if the body is hidden)
|
||||
hidden_body_ = new QWidget(this);
|
||||
connect(body_, &NodeParamViewItemBody::InputCheckedChanged, this, &NodeParamViewItem::InputCheckedChanged);
|
||||
SetBody(body_);
|
||||
|
||||
connect(node_, &Node::LabelChanged, this, &NodeParamViewItem::Retranslate);
|
||||
|
||||
setBackgroundRole(QPalette::Base);
|
||||
setAutoFillBackground(true);
|
||||
|
||||
setFocusPolicy(Qt::ClickFocus);
|
||||
setBackgroundRole(QPalette::Window);
|
||||
|
||||
Retranslate();
|
||||
}
|
||||
|
||||
void NodeParamViewItem::SetTimeTarget(Node *target)
|
||||
{
|
||||
body_->SetTimeTarget(target);
|
||||
}
|
||||
|
||||
void NodeParamViewItem::SetTime(const rational &time)
|
||||
{
|
||||
time_ = time;
|
||||
|
||||
body_->SetTime(time_);
|
||||
}
|
||||
|
||||
void NodeParamViewItem::SetTimebase(const rational& timebase)
|
||||
{
|
||||
body_->SetTimebase(timebase);
|
||||
}
|
||||
|
||||
Node *NodeParamViewItem::GetNode() const
|
||||
{
|
||||
return node_;
|
||||
}
|
||||
|
||||
void NodeParamViewItem::changeEvent(QEvent *e)
|
||||
{
|
||||
if (e->type() == QEvent::LanguageChange) {
|
||||
Retranslate();
|
||||
}
|
||||
|
||||
super::changeEvent(e);
|
||||
}
|
||||
|
||||
void NodeParamViewItem::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
super::paintEvent(event);
|
||||
|
||||
// Draw border if focused
|
||||
if (highlighted_) {
|
||||
QPainter p(this);
|
||||
p.setBrush(Qt::NoBrush);
|
||||
p.setPen(palette().highlight().color());
|
||||
p.drawRect(rect().adjusted(0, 0, -1, -1));
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewItem::moveEvent(QMoveEvent *event)
|
||||
{
|
||||
super::moveEvent(event);
|
||||
|
||||
emit Moved();
|
||||
}
|
||||
|
||||
void NodeParamViewItem::Retranslate()
|
||||
{
|
||||
node_->Retranslate();
|
||||
|
||||
if (node_->GetLabel().isEmpty()) {
|
||||
title_bar_->SetText(node_->Name());
|
||||
} else {
|
||||
title_bar_->SetText(tr("%1 (%2)").arg(node_->GetLabel(), node_->Name()));
|
||||
}
|
||||
title_bar()->SetText(GetTitleBarTextFromNode(node_));
|
||||
|
||||
body_->Retranslate();
|
||||
}
|
||||
|
||||
void NodeParamViewItem::SetExpanded(bool e)
|
||||
{
|
||||
setWidget(e ? body_ : hidden_body_);
|
||||
title_bar_->SetExpanded(e);
|
||||
|
||||
emit ExpandedChanged(e);
|
||||
}
|
||||
|
||||
bool NodeParamViewItem::IsExpanded() const
|
||||
{
|
||||
return body_->isVisible();
|
||||
}
|
||||
|
||||
int NodeParamViewItem::GetElementY(const NodeInput &c) const
|
||||
{
|
||||
if (IsExpanded()) {
|
||||
return body_->GetElementY(c);
|
||||
} else {
|
||||
// Not expanded, put keyframes at the titlebar Y
|
||||
return mapToGlobal(title_bar_->rect().center()).y();
|
||||
return mapToGlobal(title_bar()->rect().center()).y();
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewItem::ToggleExpanded()
|
||||
void NodeParamViewItem::SetInputChecked(const NodeInput &input, bool e)
|
||||
{
|
||||
SetExpanded(!IsExpanded());
|
||||
body_->SetInputChecked(input, e);
|
||||
}
|
||||
|
||||
NodeParamViewItemTitleBar::NodeParamViewItemTitleBar(QWidget *parent) :
|
||||
NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget *parent) :
|
||||
QWidget(parent),
|
||||
draw_border_(true)
|
||||
{
|
||||
QHBoxLayout* layout = new QHBoxLayout(this);
|
||||
|
||||
collapse_btn_ = new CollapseButton();
|
||||
connect(collapse_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::ExpandedStateChanged);
|
||||
layout->addWidget(collapse_btn_);
|
||||
|
||||
lbl_ = new QLabel();
|
||||
layout->addWidget(lbl_);
|
||||
|
||||
// Place next buttons on the far side
|
||||
layout->addStretch();
|
||||
|
||||
QPushButton* pin_btn = new QPushButton(QStringLiteral("P"));
|
||||
pin_btn->setCheckable(true);
|
||||
pin_btn->setFixedSize(pin_btn->sizeHint().height(), pin_btn->sizeHint().height());
|
||||
layout->addWidget(pin_btn);
|
||||
connect(pin_btn, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::PinToggled);
|
||||
}
|
||||
|
||||
void NodeParamViewItemTitleBar::SetExpanded(bool e)
|
||||
{
|
||||
draw_border_ = e;
|
||||
collapse_btn_->setChecked(e);
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void NodeParamViewItemTitleBar::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QWidget::paintEvent(event);
|
||||
|
||||
if (draw_border_) {
|
||||
QPainter p(this);
|
||||
|
||||
// Draw bottom border using text color
|
||||
int bottom = height() - 1;
|
||||
p.setPen(palette().text().color());
|
||||
p.drawLine(0, bottom, width(), bottom);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewItemTitleBar::mouseDoubleClickEvent(QMouseEvent *event)
|
||||
{
|
||||
QWidget::mouseDoubleClickEvent(event);
|
||||
|
||||
collapse_btn_->click();
|
||||
}
|
||||
|
||||
NodeParamViewItemBody::NodeParamViewItemBody(Node* node, QWidget *parent) :
|
||||
QWidget(parent),
|
||||
node_(node)
|
||||
node_(node),
|
||||
create_checkboxes_(create_checkboxes)
|
||||
{
|
||||
QGridLayout* root_layout = new QGridLayout(this);
|
||||
|
||||
int insert_row = 0;
|
||||
|
||||
QVector<Node*> connected_signals;
|
||||
|
||||
// Create widgets all root level components
|
||||
foreach (const QString& input, node->inputs()) {
|
||||
CreateWidgets(root_layout, node, input, -1, insert_row);
|
||||
foreach (QString input, node->inputs()) {
|
||||
Node *n = node;
|
||||
|
||||
insert_row++;
|
||||
NodeInput resolved = NodeGroup::ResolveInput(NodeInput(n, input));
|
||||
if (!connected_signals.contains(resolved.node())) {
|
||||
connect(resolved.node(), &Node::InputArraySizeChanged, this, &NodeParamViewItemBody::InputArraySizeChanged);
|
||||
connect(resolved.node(), &Node::InputConnected, this, &NodeParamViewItemBody::EdgeChanged);
|
||||
connect(resolved.node(), &Node::InputDisconnected, this, &NodeParamViewItemBody::EdgeChanged);
|
||||
|
||||
if (node->InputIsArray(input)) {
|
||||
// Insert here
|
||||
QWidget* array_widget = new QWidget();
|
||||
connected_signals.append(resolved.node());
|
||||
}
|
||||
|
||||
QGridLayout* array_layout = new QGridLayout(array_widget);
|
||||
array_layout->setContentsMargins(QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral(" ")), 0, 0, 0);
|
||||
input_group_lookup_.insert({resolved.node(), resolved.input()}, {n, input});
|
||||
|
||||
root_layout->addWidget(array_widget, insert_row, 1, 1, 10);
|
||||
|
||||
// Start with zero elements for efficiency. We will make the widgets for them if the user
|
||||
// requests the array UI to be expanded
|
||||
int arr_sz = 0;
|
||||
|
||||
// Add one last add button for appending to the array
|
||||
NodeParamViewArrayButton* append_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kAdd);
|
||||
connect(append_btn, &NodeParamViewArrayButton::clicked, this, &NodeParamViewItemBody::ArrayAppendClicked);
|
||||
array_layout->addWidget(append_btn, arr_sz, kArrayInsertColumn);
|
||||
|
||||
array_widget->setVisible(false);
|
||||
|
||||
array_ui_.insert({node, input}, {array_widget, arr_sz, append_btn});
|
||||
if (!(n->GetInputFlags(input) & kInputFlagHidden)) {
|
||||
CreateWidgets(root_layout, n, input, -1, insert_row);
|
||||
|
||||
insert_row++;
|
||||
|
||||
if (n->InputIsArray(input)) {
|
||||
// Insert here
|
||||
QWidget* array_widget = new QWidget();
|
||||
|
||||
QGridLayout* array_layout = new QGridLayout(array_widget);
|
||||
array_layout->setContentsMargins(QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral(" ")), 0, 0, 0);
|
||||
|
||||
root_layout->addWidget(array_widget, insert_row, 1, 1, 10);
|
||||
|
||||
// Start with zero elements for efficiency. We will make the widgets for them if the user
|
||||
// requests the array UI to be expanded
|
||||
int arr_sz = 0;
|
||||
|
||||
// Add one last add button for appending to the array
|
||||
NodeParamViewArrayButton* append_btn = new NodeParamViewArrayButton(NodeParamViewArrayButton::kAdd);
|
||||
connect(append_btn, &NodeParamViewArrayButton::clicked, this, &NodeParamViewItemBody::ArrayAppendClicked);
|
||||
array_layout->addWidget(append_btn, arr_sz, kArrayInsertColumn);
|
||||
|
||||
array_widget->setVisible(false);
|
||||
|
||||
array_ui_.insert({n, input}, {array_widget, arr_sz, append_btn});
|
||||
|
||||
insert_row++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
connect(node, &Node::InputArraySizeChanged, this, &NodeParamViewItemBody::InputArraySizeChanged);
|
||||
connect(node, &Node::InputConnected, this, &NodeParamViewItemBody::EdgeChanged);
|
||||
connect(node, &Node::InputDisconnected, this, &NodeParamViewItemBody::EdgeChanged);
|
||||
}
|
||||
|
||||
void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const QString &input, int element, int row)
|
||||
@@ -277,11 +157,22 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const
|
||||
ui_objects.layout = layout;
|
||||
ui_objects.row = row;
|
||||
|
||||
// Create optional checkbox if requested
|
||||
if (create_checkboxes_) {
|
||||
ui_objects.optional_checkbox = new QCheckBox();
|
||||
connect(ui_objects.optional_checkbox, &QCheckBox::clicked, this, &NodeParamViewItemBody::OptionalCheckBoxClicked);
|
||||
layout->addWidget(ui_objects.optional_checkbox, row, kOptionalCheckBox);
|
||||
|
||||
if (create_checkboxes_ == kCheckBoxesOnNonConnected && input_ref.IsConnected()) {
|
||||
ui_objects.optional_checkbox->setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Add descriptor label
|
||||
ui_objects.main_label = new QLabel();
|
||||
|
||||
// Label always goes into column 1 (array collapse button goes into 0 if applicable)
|
||||
layout->addWidget(ui_objects.main_label, row, 1);
|
||||
// Create input label
|
||||
layout->addWidget(ui_objects.main_label, row, kLabelColumn);
|
||||
|
||||
if (node->InputIsArray(input)) {
|
||||
if (element == -1) {
|
||||
@@ -292,8 +183,8 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const
|
||||
// Default to collapsed
|
||||
array_collapse_btn->setChecked(false);
|
||||
|
||||
// Collapse button always goes into column 0
|
||||
layout->addWidget(array_collapse_btn, row, 0);
|
||||
// Add collapse button to layout
|
||||
layout->addWidget(array_collapse_btn, row, kArrayCollapseBtnColumn);
|
||||
|
||||
// Connect signal to show/hide array params when toggled
|
||||
connect(array_collapse_btn, &CollapseButton::toggled, this, &NodeParamViewItemBody::ArrayCollapseBtnPressed);
|
||||
@@ -315,14 +206,6 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const
|
||||
connect(remove_element_btn, &NodeParamViewArrayButton::clicked, this, &NodeParamViewItemBody::ArrayRemoveClicked);
|
||||
|
||||
}
|
||||
} else if (dynamic_cast<ClipBlock*>(node) && input == ClipBlock::kSpeedInput) {
|
||||
// Special behavior - this was the most preferable way to do this so we could support multiple
|
||||
// nodes per item one day
|
||||
QPushButton *btn = new QPushButton(tr("..."));
|
||||
btn->setFixedWidth(btn->sizeHint().height());
|
||||
connect(btn, &QPushButton::clicked, this, &NodeParamViewItemBody::ShowSpeedDurationDialogForNode);
|
||||
layout->addWidget(btn, row, kExtraButtonColumn);
|
||||
ui_objects.extra_btn = btn;
|
||||
}
|
||||
|
||||
// Create a widget/input bridge for this input
|
||||
@@ -340,9 +223,12 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const
|
||||
layout->addWidget(w, row, i+kWidgetStartColumn);
|
||||
}
|
||||
|
||||
// In case this input is a group, resolve that actual input to use for connected labels
|
||||
NodeInput resolved = NodeGroup::ResolveInput(input_ref);
|
||||
|
||||
if (node->IsInputConnectable(input)) {
|
||||
// Create clickable label used when an input is connected
|
||||
ui_objects.connected_label = new NodeParamViewConnectedLabel(input_ref);
|
||||
ui_objects.connected_label = new NodeParamViewConnectedLabel(resolved);
|
||||
connect(ui_objects.connected_label, &NodeParamViewConnectedLabel::RequestSelectNode, this, &NodeParamViewItemBody::RequestSelectNode);
|
||||
layout->addWidget(ui_objects.connected_label, row, kWidgetStartColumn, 1, kKeyControlColumn - kWidgetStartColumn);
|
||||
}
|
||||
@@ -350,7 +236,7 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const
|
||||
// Add keyframe control to this layout if parameter is keyframable
|
||||
if (node->IsInputKeyframable(input)) {
|
||||
ui_objects.key_control = new NodeParamViewKeyframeControl();
|
||||
ui_objects.key_control->SetInput(input_ref);
|
||||
ui_objects.key_control->SetInput(resolved);
|
||||
layout->addWidget(ui_objects.key_control, row, kKeyControlColumn);
|
||||
connect(ui_objects.key_control, &NodeParamViewKeyframeControl::RequestSetTime, this, &NodeParamViewItemBody::RequestSetTime);
|
||||
}
|
||||
@@ -412,6 +298,8 @@ int NodeParamViewItemBody::GetElementY(NodeInput c) const
|
||||
c.set_element(-1);
|
||||
}
|
||||
|
||||
//c = NodeGroup::ResolveInput(c);
|
||||
|
||||
// Find its row in the parameters
|
||||
QLabel* lbl = input_ui_map_.value(c).main_label;
|
||||
|
||||
@@ -429,7 +317,10 @@ void NodeParamViewItemBody::EdgeChanged(Node *output, const NodeInput& input)
|
||||
{
|
||||
Q_UNUSED(output)
|
||||
|
||||
UpdateUIForEdgeConnection(input);
|
||||
const NodeInputPair &pair = input_group_lookup_.value({input.node(), input.input()});
|
||||
NodeInput resolved(pair.node, pair.input, input.element());
|
||||
|
||||
UpdateUIForEdgeConnection(resolved);
|
||||
}
|
||||
|
||||
void NodeParamViewItemBody::UpdateUIForEdgeConnection(const NodeInput& input)
|
||||
@@ -438,15 +329,22 @@ void NodeParamViewItemBody::UpdateUIForEdgeConnection(const NodeInput& input)
|
||||
if (input_ui_map_.contains(input)) {
|
||||
const InputUI& ui_objects = input_ui_map_[input];
|
||||
|
||||
bool is_connected = NodeGroup::ResolveInput(input).IsConnected();
|
||||
|
||||
foreach (QWidget* w, ui_objects.widget_bridge->widgets()) {
|
||||
w->setVisible(!input.IsConnected());
|
||||
w->setVisible(!is_connected);
|
||||
}
|
||||
|
||||
// Show/hide connection label
|
||||
ui_objects.connected_label->setVisible(input.IsConnected());
|
||||
ui_objects.connected_label->setVisible(is_connected);
|
||||
|
||||
if (ui_objects.key_control) {
|
||||
ui_objects.key_control->setVisible(!input.IsConnected());
|
||||
ui_objects.key_control->setVisible(!is_connected);
|
||||
}
|
||||
|
||||
// Show/hide optional checkbox if requested
|
||||
if (create_checkboxes_ == kCheckBoxesOnNonConnected) {
|
||||
ui_objects.optional_checkbox->setVisible(!is_connected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -463,7 +361,13 @@ void NodeParamViewItemBody::PlaceWidgetsFromBridge(QGridLayout* layout, NodePara
|
||||
|
||||
void NodeParamViewItemBody::InputArraySizeChangedInternal(Node *node, const QString &input, int size)
|
||||
{
|
||||
ArrayUI& array_ui = array_ui_[{node, input}];
|
||||
NodeInputPair nip = {node, input};
|
||||
|
||||
if (!array_ui_.contains(nip)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayUI& array_ui = array_ui_[nip];
|
||||
|
||||
if (size != array_ui.count) {
|
||||
QGridLayout* grid = static_cast<QGridLayout*>(array_ui.widget->layout());
|
||||
@@ -504,7 +408,8 @@ void NodeParamViewItemBody::ArrayCollapseBtnPressed(bool checked)
|
||||
array_ui_.value(input).widget->setVisible(checked);
|
||||
if (checked) {
|
||||
// Ensure widgets are created (the signal will be ignored if they are)
|
||||
InputArraySizeChangedInternal(input.node, input.input, input.node->InputArraySize(input.input));
|
||||
NodeInput resolved = NodeGroup::ResolveInput(NodeInput(input.node, input.input));
|
||||
InputArraySizeChangedInternal(input.node, input.input, resolved.GetArraySize());
|
||||
}
|
||||
|
||||
emit ArrayExpandedChanged(checked);
|
||||
@@ -514,16 +419,17 @@ void NodeParamViewItemBody::InputArraySizeChanged(const QString& input, int old_
|
||||
{
|
||||
Q_UNUSED(old_sz)
|
||||
|
||||
Node* node = static_cast<Node*>(sender());
|
||||
NodeInputPair nip = input_group_lookup_.value({static_cast<Node*>(sender()), input});
|
||||
|
||||
InputArraySizeChangedInternal(node, input, size);
|
||||
InputArraySizeChangedInternal(nip.node, nip.input, size);
|
||||
}
|
||||
|
||||
void NodeParamViewItemBody::ArrayAppendClicked()
|
||||
{
|
||||
for (auto it=array_ui_.cbegin(); it!=array_ui_.cend(); it++) {
|
||||
if (it.value().append_btn == sender()) {
|
||||
it.key().node->InputArrayAppend(it.key().input, true);
|
||||
NodeInput real_input = NodeGroup::ResolveInput(NodeInput(it.key().node, it.key().input));
|
||||
real_input.node()->InputArrayAppend(real_input.input(), true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -534,7 +440,7 @@ void NodeParamViewItemBody::ArrayInsertClicked()
|
||||
for (auto it=input_ui_map_.cbegin(); it!=input_ui_map_.cend(); it++) {
|
||||
if (it.value().array_insert_btn == sender()) {
|
||||
// Found our input and element
|
||||
const NodeInput& ic = it.key();
|
||||
NodeInput ic = NodeGroup::ResolveInput(it.key());
|
||||
ic.node()->InputArrayInsert(ic.input(), ic.element(), true);
|
||||
break;
|
||||
}
|
||||
@@ -546,7 +452,7 @@ void NodeParamViewItemBody::ArrayRemoveClicked()
|
||||
for (auto it=input_ui_map_.cbegin(); it!=input_ui_map_.cend(); it++) {
|
||||
if (it.value().array_remove_btn == sender()) {
|
||||
// Found our input and element
|
||||
const NodeInput& ic = it.key();
|
||||
NodeInput ic = NodeGroup::ResolveInput(it.key());
|
||||
ic.node()->InputArrayRemove(ic.input(), ic.element(), true);
|
||||
break;
|
||||
}
|
||||
@@ -575,6 +481,16 @@ void NodeParamViewItemBody::SetTimebase(const rational& timebase)
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewItemBody::SetInputChecked(const NodeInput &input, bool e)
|
||||
{
|
||||
if (input_ui_map_.contains(input)) {
|
||||
QCheckBox *cb = input_ui_map_.value(input).optional_checkbox;
|
||||
if (cb) {
|
||||
cb->setChecked(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewItemBody::ReplaceWidgets(const NodeInput &input)
|
||||
{
|
||||
InputUI ui = input_ui_map_.value(input);
|
||||
@@ -588,12 +504,25 @@ void NodeParamViewItemBody::ShowSpeedDurationDialogForNode()
|
||||
sdd.exec();
|
||||
}
|
||||
|
||||
void NodeParamViewItemBody::OptionalCheckBoxClicked(bool e)
|
||||
{
|
||||
QCheckBox *cb = static_cast<QCheckBox*>(sender());
|
||||
|
||||
for (auto it=input_ui_map_.cbegin(); it!=input_ui_map_.cend(); it++) {
|
||||
if (it.value().optional_checkbox == cb) {
|
||||
emit InputCheckedChanged(it.key(), e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NodeParamViewItemBody::InputUI::InputUI() :
|
||||
main_label(nullptr),
|
||||
widget_bridge(nullptr),
|
||||
connected_label(nullptr),
|
||||
key_control(nullptr),
|
||||
extra_btn(nullptr),
|
||||
optional_checkbox(nullptr),
|
||||
array_insert_btn(nullptr),
|
||||
array_remove_btn(nullptr)
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
#ifndef NODEPARAMVIEWITEM_H
|
||||
#define NODEPARAMVIEWITEM_H
|
||||
|
||||
#include <QDockWidget>
|
||||
#include <QCheckBox>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
@@ -32,50 +32,24 @@
|
||||
#include "nodeparamviewarraywidget.h"
|
||||
#include "nodeparamviewconnectedlabel.h"
|
||||
#include "nodeparamviewkeyframecontrol.h"
|
||||
#include "nodeparamviewitembase.h"
|
||||
#include "nodeparamviewwidgetbridge.h"
|
||||
#include "widget/clickablelabel/clickablelabel.h"
|
||||
#include "widget/collapsebutton/collapsebutton.h"
|
||||
#include "widget/keyframeview/keyframeview.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class NodeParamViewItemTitleBar : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
NodeParamViewItemTitleBar(QWidget* parent = nullptr);
|
||||
|
||||
void SetExpanded(bool e);
|
||||
|
||||
void SetText(const QString& s)
|
||||
{
|
||||
lbl_->setText(s);
|
||||
lbl_->setToolTip(s);
|
||||
lbl_->setMinimumWidth(1);
|
||||
}
|
||||
|
||||
signals:
|
||||
void ExpandedStateChanged(bool e);
|
||||
|
||||
void PinToggled(bool e);
|
||||
|
||||
protected:
|
||||
virtual void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||
|
||||
private:
|
||||
bool draw_border_;
|
||||
|
||||
QLabel* lbl_;
|
||||
|
||||
CollapseButton* collapse_btn_;
|
||||
|
||||
enum NodeParamViewCheckBoxBehavior {
|
||||
kNoCheckBoxes,
|
||||
kCheckBoxesOn,
|
||||
kCheckBoxesOnNonConnected
|
||||
};
|
||||
|
||||
class NodeParamViewItemBody : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
NodeParamViewItemBody(Node* node, QWidget* parent = nullptr);
|
||||
NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget* parent = nullptr);
|
||||
|
||||
void SetTimeTarget(Node* target);
|
||||
|
||||
@@ -86,7 +60,9 @@ public:
|
||||
int GetElementY(NodeInput c) const;
|
||||
|
||||
// Set the timebase of any timebased widgets contained here
|
||||
void SetTimebase(const rational& timebase);
|
||||
void SetTimebase(const rational& timebase);
|
||||
|
||||
void SetInputChecked(const NodeInput &input, bool e);
|
||||
|
||||
signals:
|
||||
void RequestSetTime(const rational& time);
|
||||
@@ -95,6 +71,8 @@ signals:
|
||||
|
||||
void ArrayExpandedChanged(bool e);
|
||||
|
||||
void InputCheckedChanged(const NodeInput &input, bool e);
|
||||
|
||||
private:
|
||||
void CreateWidgets(QGridLayout *layout, Node* node, const QString& input, int element, int row_index);
|
||||
|
||||
@@ -114,6 +92,7 @@ private:
|
||||
QGridLayout* layout;
|
||||
int row;
|
||||
QPushButton *extra_btn;
|
||||
QCheckBox *optional_checkbox;
|
||||
|
||||
NodeParamViewArrayButton* array_insert_btn;
|
||||
NodeParamViewArrayButton* array_remove_btn;
|
||||
@@ -135,6 +114,10 @@ private:
|
||||
|
||||
rational timebase_;
|
||||
|
||||
NodeParamViewCheckBoxBehavior create_checkboxes_;
|
||||
|
||||
QHash<NodeInputPair, NodeInputPair> input_group_lookup_;
|
||||
|
||||
/**
|
||||
* @brief The column to place the keyframe controls in
|
||||
*
|
||||
@@ -147,6 +130,9 @@ private:
|
||||
static const int kArrayRemoveColumn;
|
||||
static const int kExtraButtonColumn;
|
||||
|
||||
static const int kOptionalCheckBox;
|
||||
static const int kArrayCollapseBtnColumn;
|
||||
static const int kLabelColumn;
|
||||
static const int kWidgetStartColumn;
|
||||
|
||||
private slots:
|
||||
@@ -168,74 +154,72 @@ private slots:
|
||||
|
||||
void ShowSpeedDurationDialogForNode();
|
||||
|
||||
void OptionalCheckBoxClicked(bool e);
|
||||
|
||||
};
|
||||
|
||||
class NodeParamViewItem : public QDockWidget
|
||||
class NodeParamViewItem : public NodeParamViewItemBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
NodeParamViewItem(Node* node, QWidget* parent = nullptr);
|
||||
NodeParamViewItem(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget* parent = nullptr);
|
||||
|
||||
void SetTimeTarget(Node* target);
|
||||
|
||||
void SetTime(const rational& time);
|
||||
|
||||
// Set the timebase of the NodeParamViewItemBody
|
||||
void SetTimebase(const rational& timebase);
|
||||
|
||||
Node* GetNode() const;
|
||||
|
||||
bool IsExpanded() const;
|
||||
|
||||
void SetHighlighted(bool e)
|
||||
void SetTimeTarget(Node* target)
|
||||
{
|
||||
highlighted_ = e;
|
||||
body_->SetTimeTarget(target);
|
||||
}
|
||||
|
||||
update();
|
||||
void SetTime(const rational& time)
|
||||
{
|
||||
time_ = time;
|
||||
|
||||
body_->SetTime(time_);
|
||||
}
|
||||
|
||||
void SetTimebase(const rational& timebase)
|
||||
{
|
||||
body_->SetTimebase(timebase);
|
||||
}
|
||||
|
||||
Node* GetNode() const
|
||||
{
|
||||
return node_;
|
||||
}
|
||||
|
||||
int GetElementY(const NodeInput& c) const;
|
||||
|
||||
public slots:
|
||||
void SetExpanded(bool e);
|
||||
void SetInputChecked(const NodeInput &input, bool e);
|
||||
|
||||
void ToggleExpanded();
|
||||
const KeyframeView::NodeConnections &GetKeyframeConnections() const
|
||||
{
|
||||
return keyframe_connections_;
|
||||
}
|
||||
|
||||
void SetKeyframeConnections(const KeyframeView::NodeConnections &c)
|
||||
{
|
||||
keyframe_connections_ = c;
|
||||
}
|
||||
|
||||
signals:
|
||||
void RequestSetTime(const rational& time);
|
||||
|
||||
void RequestSelectNode(const QVector<Node*>& node);
|
||||
|
||||
void PinToggled(bool e);
|
||||
|
||||
void ExpandedChanged(bool e);
|
||||
|
||||
void ArrayExpandedChanged(bool e);
|
||||
|
||||
void Moved();
|
||||
void InputCheckedChanged(const NodeInput &input, bool e);
|
||||
|
||||
protected:
|
||||
virtual void changeEvent(QEvent *e) override;
|
||||
|
||||
virtual void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
virtual void moveEvent(QMoveEvent *event) override;
|
||||
protected slots:
|
||||
virtual void Retranslate() override;
|
||||
|
||||
private:
|
||||
NodeParamViewItemTitleBar* title_bar_;
|
||||
|
||||
NodeParamViewItemBody* body_;
|
||||
|
||||
QWidget *hidden_body_;
|
||||
|
||||
Node* node_;
|
||||
|
||||
rational time_;
|
||||
|
||||
bool highlighted_;
|
||||
|
||||
private slots:
|
||||
void Retranslate();
|
||||
KeyframeView::NodeConnections keyframe_connections_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "nodeparamviewitembase.h"
|
||||
|
||||
#include <QEvent>
|
||||
#include <QPainter>
|
||||
|
||||
namespace olive {
|
||||
|
||||
#define super QDockWidget
|
||||
|
||||
NodeParamViewItemBase::NodeParamViewItemBase(QWidget *parent) :
|
||||
super(parent),
|
||||
highlighted_(false)
|
||||
{
|
||||
// Create title bar widget
|
||||
title_bar_ = new NodeParamViewItemTitleBar(this);
|
||||
|
||||
// Add title bar to widget
|
||||
this->setTitleBarWidget(title_bar_);
|
||||
|
||||
// Connect title bar to this
|
||||
connect(title_bar_, &NodeParamViewItemTitleBar::ExpandedStateChanged, this, &NodeParamViewItemBase::SetExpanded);
|
||||
connect(title_bar_, &NodeParamViewItemTitleBar::PinToggled, this, &NodeParamViewItemBase::PinToggled);
|
||||
|
||||
// Use dummy QWidget to retain width when not expanded (QDockWidget seems to ignore the titlebar
|
||||
// size hints and will shrink as small as possible if the body is hidden)
|
||||
hidden_body_ = new QWidget(this);
|
||||
|
||||
// Default to hidden body, this also seems to fix an issue with clicks being intermittent
|
||||
// on the titlebar
|
||||
setWidget(hidden_body_);
|
||||
|
||||
setAutoFillBackground(true);
|
||||
|
||||
setFocusPolicy(Qt::ClickFocus);
|
||||
}
|
||||
|
||||
bool NodeParamViewItemBase::IsExpanded() const
|
||||
{
|
||||
return title_bar_->IsExpanded();
|
||||
}
|
||||
|
||||
QString NodeParamViewItemBase::GetTitleBarTextFromNode(Node *n)
|
||||
{
|
||||
if (n->GetLabel().isEmpty()) {
|
||||
return n->Name();
|
||||
} else {
|
||||
return tr("%1 (%2)").arg(n->GetLabel(), n->Name());
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewItemBase::SetBody(QWidget *body)
|
||||
{
|
||||
body_ = body;
|
||||
body_->setParent(this);
|
||||
|
||||
if (title_bar_->IsExpanded()) {
|
||||
setWidget(body_);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewItemBase::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
super::paintEvent(event);
|
||||
|
||||
// Draw border if focused
|
||||
if (highlighted_) {
|
||||
QPainter p(this);
|
||||
p.setBrush(Qt::NoBrush);
|
||||
p.setPen(palette().highlight().color());
|
||||
p.drawRect(rect().adjusted(0, 0, -1, -1));
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewItemBase::SetExpanded(bool e)
|
||||
{
|
||||
setWidget(e ? body_ : hidden_body_);
|
||||
title_bar_->SetExpanded(e);
|
||||
|
||||
emit ExpandedChanged(e);
|
||||
}
|
||||
|
||||
void NodeParamViewItemBase::changeEvent(QEvent *e)
|
||||
{
|
||||
if (e->type() == QEvent::LanguageChange) {
|
||||
Retranslate();
|
||||
}
|
||||
|
||||
super::changeEvent(e);
|
||||
}
|
||||
|
||||
void NodeParamViewItemBase::moveEvent(QMoveEvent *event)
|
||||
{
|
||||
super::moveEvent(event);
|
||||
|
||||
emit Moved();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef NODEPARAMVIEWITEMBASE_H
|
||||
#define NODEPARAMVIEWITEMBASE_H
|
||||
|
||||
#include <QDockWidget>
|
||||
|
||||
#include "nodeparamviewitemtitlebar.h"
|
||||
#include "node/node.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class NodeParamViewItemBase : public QDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
NodeParamViewItemBase(QWidget* parent = nullptr);
|
||||
|
||||
void SetHighlighted(bool e)
|
||||
{
|
||||
highlighted_ = e;
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
bool IsExpanded() const;
|
||||
|
||||
static QString GetTitleBarTextFromNode(Node *n);
|
||||
|
||||
public slots:
|
||||
void SetExpanded(bool e);
|
||||
|
||||
void ToggleExpanded()
|
||||
{
|
||||
SetExpanded(!IsExpanded());
|
||||
}
|
||||
|
||||
signals:
|
||||
void PinToggled(bool e);
|
||||
|
||||
void ExpandedChanged(bool e);
|
||||
|
||||
void Moved();
|
||||
|
||||
protected:
|
||||
void SetBody(QWidget *body);
|
||||
|
||||
virtual void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
NodeParamViewItemTitleBar* title_bar() const
|
||||
{
|
||||
return title_bar_;
|
||||
}
|
||||
|
||||
virtual void changeEvent(QEvent *e) override;
|
||||
|
||||
virtual void moveEvent(QMoveEvent *event) override;
|
||||
|
||||
protected slots:
|
||||
virtual void Retranslate(){}
|
||||
|
||||
private:
|
||||
NodeParamViewItemTitleBar* title_bar_;
|
||||
|
||||
QWidget *body_;
|
||||
|
||||
QWidget *hidden_body_;
|
||||
|
||||
bool highlighted_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // NODEPARAMVIEWITEMBASE_H
|
||||
@@ -0,0 +1,90 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "nodeparamviewitemtitlebar.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QPainter>
|
||||
|
||||
#include "ui/icons/icons.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
NodeParamViewItemTitleBar::NodeParamViewItemTitleBar(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
draw_border_(true)
|
||||
{
|
||||
QHBoxLayout* layout = new QHBoxLayout(this);
|
||||
|
||||
collapse_btn_ = new CollapseButton();
|
||||
connect(collapse_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::ExpandedStateChanged);
|
||||
layout->addWidget(collapse_btn_);
|
||||
|
||||
lbl_ = new QLabel();
|
||||
layout->addWidget(lbl_);
|
||||
|
||||
// Place next buttons on the far side
|
||||
layout->addStretch();
|
||||
|
||||
add_fx_btn_ = new QPushButton();
|
||||
add_fx_btn_->setIcon(icon::AddEffect);
|
||||
add_fx_btn_->setFixedSize(add_fx_btn_->sizeHint().height(), add_fx_btn_->sizeHint().height());
|
||||
add_fx_btn_->setVisible(false);
|
||||
layout->addWidget(add_fx_btn_);
|
||||
connect(add_fx_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::AddEffectButtonClicked);
|
||||
|
||||
pin_btn_ = new QPushButton(QStringLiteral("P"));
|
||||
pin_btn_->setCheckable(true);
|
||||
pin_btn_->setFixedSize(pin_btn_->sizeHint().height(), pin_btn_->sizeHint().height());
|
||||
pin_btn_->setVisible(false);
|
||||
layout->addWidget(pin_btn_);
|
||||
connect(pin_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::PinToggled);
|
||||
}
|
||||
|
||||
void NodeParamViewItemTitleBar::SetExpanded(bool e)
|
||||
{
|
||||
draw_border_ = e;
|
||||
collapse_btn_->setChecked(e);
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void NodeParamViewItemTitleBar::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QWidget::paintEvent(event);
|
||||
|
||||
if (draw_border_) {
|
||||
QPainter p(this);
|
||||
|
||||
// Draw bottom border using text color
|
||||
int bottom = height() - 1;
|
||||
p.setPen(palette().text().color());
|
||||
p.drawLine(0, bottom, width(), bottom);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewItemTitleBar::mouseDoubleClickEvent(QMouseEvent *event)
|
||||
{
|
||||
QWidget::mouseDoubleClickEvent(event);
|
||||
|
||||
collapse_btn_->click();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef NODEPARAMVIEWITEMTITLEBAR_H
|
||||
#define NODEPARAMVIEWITEMTITLEBAR_H
|
||||
|
||||
#include <QLabel>
|
||||
#include <QWidget>
|
||||
|
||||
#include "widget/collapsebutton/collapsebutton.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class NodeParamViewItemTitleBar : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
NodeParamViewItemTitleBar(QWidget* parent = nullptr);
|
||||
|
||||
bool IsExpanded() const
|
||||
{
|
||||
return collapse_btn_->isChecked();
|
||||
}
|
||||
|
||||
public slots:
|
||||
void SetExpanded(bool e);
|
||||
|
||||
void SetText(const QString& s)
|
||||
{
|
||||
lbl_->setText(s);
|
||||
lbl_->setToolTip(s);
|
||||
lbl_->setMinimumWidth(1);
|
||||
}
|
||||
|
||||
void SetPinButtonVisible(bool e)
|
||||
{
|
||||
pin_btn_->setVisible(e);
|
||||
}
|
||||
|
||||
void SetAddEffectButtonVisible(bool e)
|
||||
{
|
||||
add_fx_btn_->setVisible(e);
|
||||
}
|
||||
|
||||
signals:
|
||||
void ExpandedStateChanged(bool e);
|
||||
|
||||
void PinToggled(bool e);
|
||||
|
||||
void AddEffectButtonClicked();
|
||||
|
||||
protected:
|
||||
virtual void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||
|
||||
private:
|
||||
bool draw_border_;
|
||||
|
||||
QLabel* lbl_;
|
||||
|
||||
CollapseButton* collapse_btn_;
|
||||
|
||||
QPushButton *pin_btn_;
|
||||
|
||||
QPushButton *add_fx_btn_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // NODEPARAMVIEWITEMTITLEBAR_H
|
||||
@@ -33,33 +33,34 @@
|
||||
#include "nodeparamviewtextedit.h"
|
||||
#include "nodeparamviewundo.h"
|
||||
#include "undo/undostack.h"
|
||||
#include "widget/bezier/bezierwidget.h"
|
||||
#include "widget/colorbutton/colorbutton.h"
|
||||
#include "widget/filefield/filefield.h"
|
||||
#include "widget/slider/floatslider.h"
|
||||
#include "widget/slider/integerslider.h"
|
||||
#include "widget/slider/rationalslider.h"
|
||||
#include "widget/videoparamedit/videoparamedit.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
NodeParamViewWidgetBridge::NodeParamViewWidgetBridge(const NodeInput &input, QObject *parent) :
|
||||
QObject(parent),
|
||||
input_(input)
|
||||
NodeParamViewWidgetBridge::NodeParamViewWidgetBridge(NodeInput input, QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
CreateWidgets();
|
||||
do {
|
||||
input_hierarchy_.append(input);
|
||||
|
||||
connect(input_.node(), &Node::ValueChanged, this, &NodeParamViewWidgetBridge::InputValueChanged);
|
||||
connect(input_.node(), &Node::InputPropertyChanged, this, &NodeParamViewWidgetBridge::PropertyChanged);
|
||||
connect(input_.node(), &Node::InputDataTypeChanged, this, &NodeParamViewWidgetBridge::InputDataTypeChanged);
|
||||
connect(input.node(), &Node::ValueChanged, this, &NodeParamViewWidgetBridge::InputValueChanged);
|
||||
connect(input.node(), &Node::InputPropertyChanged, this, &NodeParamViewWidgetBridge::PropertyChanged);
|
||||
connect(input.node(), &Node::InputDataTypeChanged, this, &NodeParamViewWidgetBridge::InputDataTypeChanged);
|
||||
} while (NodeGroup::GetInner(&input));
|
||||
|
||||
CreateWidgets();
|
||||
}
|
||||
|
||||
void NodeParamViewWidgetBridge::SetTime(const rational &time)
|
||||
{
|
||||
time_ = time;
|
||||
|
||||
if (input_.IsValid()) {
|
||||
UpdateWidgetValues();
|
||||
}
|
||||
UpdateWidgetValues();
|
||||
}
|
||||
|
||||
int GetSliderCount(NodeValue::Type type)
|
||||
@@ -69,16 +70,16 @@ int GetSliderCount(NodeValue::Type type)
|
||||
|
||||
void NodeParamViewWidgetBridge::CreateWidgets()
|
||||
{
|
||||
if (input_.IsArray() && input_.element() == -1) {
|
||||
if (GetInnerInput().IsArray() && GetInnerInput().element() == -1) {
|
||||
|
||||
NodeParamViewArrayWidget* w = new NodeParamViewArrayWidget(input_.node(), input_.input());
|
||||
NodeParamViewArrayWidget* w = new NodeParamViewArrayWidget(GetInnerInput().node(), GetInnerInput().input());
|
||||
connect(w, &NodeParamViewArrayWidget::DoubleClicked, this, &NodeParamViewWidgetBridge::ArrayWidgetDoubleClicked);
|
||||
widgets_.append(w);
|
||||
|
||||
} else {
|
||||
|
||||
// We assume the first data type is the "primary" type
|
||||
NodeValue::Type t = input_.GetDataType();
|
||||
NodeValue::Type t = GetDataType();
|
||||
switch (t) {
|
||||
// None of these inputs have applicable UI widgets
|
||||
case NodeValue::kNone:
|
||||
@@ -89,6 +90,8 @@ void NodeParamViewWidgetBridge::CreateWidgets()
|
||||
case NodeValue::kShaderJob:
|
||||
case NodeValue::kSampleJob:
|
||||
case NodeValue::kGenerateJob:
|
||||
case NodeValue::kVideoParams:
|
||||
case NodeValue::kAudioParams:
|
||||
break;
|
||||
case NodeValue::kInt:
|
||||
{
|
||||
@@ -112,7 +115,7 @@ void NodeParamViewWidgetBridge::CreateWidgets()
|
||||
{
|
||||
QComboBox* combobox = new QComboBox();
|
||||
|
||||
QStringList items = input_.GetComboBoxStrings();
|
||||
QStringList items = GetInnerInput().GetComboBoxStrings();
|
||||
foreach (const QString& s, items) {
|
||||
combobox->addItem(s);
|
||||
}
|
||||
@@ -130,7 +133,7 @@ void NodeParamViewWidgetBridge::CreateWidgets()
|
||||
}
|
||||
case NodeValue::kColor:
|
||||
{
|
||||
ColorButton* color_button = new ColorButton(input_.node()->project()->color_manager());
|
||||
ColorButton* color_button = new ColorButton(GetInnerInput().node()->project()->color_manager());
|
||||
widgets_.append(color_button);
|
||||
connect(color_button, &ColorButton::ColorChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
||||
break;
|
||||
@@ -156,26 +159,23 @@ void NodeParamViewWidgetBridge::CreateWidgets()
|
||||
connect(font_combobox, &QFontComboBox::currentFontChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
||||
break;
|
||||
}
|
||||
case NodeValue::kVideoParams:
|
||||
case NodeValue::kBezier:
|
||||
{
|
||||
VideoParamEdit* edit = new VideoParamEdit();
|
||||
edit->SetColorManager(input_.node()->project()->color_manager());
|
||||
widgets_.append(edit);
|
||||
connect(edit, &VideoParamEdit::Changed, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
||||
break;
|
||||
}
|
||||
case NodeValue::kAudioParams:
|
||||
{
|
||||
// FIXME: Create audio param widget
|
||||
BezierWidget *bezier = new BezierWidget();
|
||||
widgets_.append(bezier);
|
||||
|
||||
connect(bezier->x_slider(), &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
||||
connect(bezier->y_slider(), &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
||||
connect(bezier->cp1_x_slider(), &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
||||
connect(bezier->cp1_y_slider(), &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
||||
connect(bezier->cp2_x_slider(), &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
||||
connect(bezier->cp2_y_slider(), &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check all properties
|
||||
auto input_properties = input_.node()->GetInputProperties(input_.input());
|
||||
for (auto it=input_properties.cbegin(); it!=input_properties.cend(); it++) {
|
||||
PropertyChanged(input_.input(), it.key(), it.value());
|
||||
}
|
||||
UpdateProperties();
|
||||
|
||||
UpdateWidgetValues();
|
||||
|
||||
@@ -198,16 +198,16 @@ void NodeParamViewWidgetBridge::SetInputValue(const QVariant &value, int track)
|
||||
|
||||
void NodeParamViewWidgetBridge::SetInputValueInternal(const QVariant &value, int track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key)
|
||||
{
|
||||
if (input_.IsKeyframing()) {
|
||||
if (GetInnerInput().IsKeyframing()) {
|
||||
rational node_time = GetCurrentTimeAsNodeTime();
|
||||
|
||||
NodeKeyframe* existing_key = input_.GetKeyframeAtTimeOnTrack(node_time, track);
|
||||
NodeKeyframe* existing_key = GetInnerInput().GetKeyframeAtTimeOnTrack(node_time, track);
|
||||
|
||||
if (existing_key) {
|
||||
command->add_child(new NodeParamSetKeyframeValueCommand(existing_key, value));
|
||||
} else {
|
||||
// No existing key, create a new one
|
||||
int nb_tracks = NodeValue::get_number_of_keyframe_tracks(input_.node()->GetInputDataType(input_.input()));
|
||||
int nb_tracks = NodeValue::get_number_of_keyframe_tracks(GetInnerInput().node()->GetInputDataType(GetInnerInput().input()));
|
||||
for (int i=0; i<nb_tracks; i++) {
|
||||
QVariant track_value;
|
||||
|
||||
@@ -216,35 +216,33 @@ void NodeParamViewWidgetBridge::SetInputValueInternal(const QVariant &value, int
|
||||
} else if (!insert_on_all_tracks_if_no_key) {
|
||||
continue;
|
||||
} else {
|
||||
track_value = input_.node()->GetSplitValueAtTimeOnTrack(input_.input(), node_time, i, input_.element());
|
||||
track_value = GetInnerInput().node()->GetSplitValueAtTimeOnTrack(GetInnerInput().input(), node_time, i, GetInnerInput().element());
|
||||
}
|
||||
|
||||
NodeKeyframe* new_key = new NodeKeyframe(node_time,
|
||||
track_value,
|
||||
input_.node()->GetBestKeyframeTypeForTimeOnTrack(NodeKeyframeTrackReference(input_, i), node_time),
|
||||
GetInnerInput().node()->GetBestKeyframeTypeForTimeOnTrack(NodeKeyframeTrackReference(GetInnerInput(), i), node_time),
|
||||
i,
|
||||
input_.element(),
|
||||
input_.input());
|
||||
GetInnerInput().element(),
|
||||
GetInnerInput().input());
|
||||
|
||||
command->add_child(new NodeParamInsertKeyframeCommand(input_.node(), new_key));
|
||||
command->add_child(new NodeParamInsertKeyframeCommand(GetInnerInput().node(), new_key));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(input_, track), value));
|
||||
command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(GetInnerInput(), track), value));
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewWidgetBridge::ProcessSlider(NumericSliderBase *slider, const QVariant &value)
|
||||
void NodeParamViewWidgetBridge::ProcessSlider(NumericSliderBase *slider, int slider_track, const QVariant &value)
|
||||
{
|
||||
int slider_track = widgets_.indexOf(slider);
|
||||
|
||||
if (slider->IsDragging()) {
|
||||
|
||||
// While we're dragging, we block the input's normal signalling and create our own
|
||||
if (!dragger_.IsStarted()) {
|
||||
rational node_time = GetCurrentTimeAsNodeTime();
|
||||
|
||||
dragger_.Start(NodeKeyframeTrackReference(input_, slider_track), node_time);
|
||||
dragger_.Start(NodeKeyframeTrackReference(GetInnerInput(), slider_track), node_time);
|
||||
}
|
||||
|
||||
dragger_.Drag(value);
|
||||
@@ -268,7 +266,7 @@ void NodeParamViewWidgetBridge::ProcessSlider(NumericSliderBase *slider, const Q
|
||||
|
||||
void NodeParamViewWidgetBridge::WidgetCallback()
|
||||
{
|
||||
switch (input_.GetDataType()) {
|
||||
switch (GetDataType()) {
|
||||
// None of these inputs have applicable UI widgets
|
||||
case NodeValue::kNone:
|
||||
case NodeValue::kTexture:
|
||||
@@ -278,6 +276,8 @@ void NodeParamViewWidgetBridge::WidgetCallback()
|
||||
case NodeValue::kShaderJob:
|
||||
case NodeValue::kSampleJob:
|
||||
case NodeValue::kGenerateJob:
|
||||
case NodeValue::kVideoParams:
|
||||
case NodeValue::kAudioParams:
|
||||
break;
|
||||
case NodeValue::kInt:
|
||||
{
|
||||
@@ -343,12 +343,12 @@ void NodeParamViewWidgetBridge::WidgetCallback()
|
||||
SetInputValueInternal(c.blue(), 2, command, false);
|
||||
SetInputValueInternal(c.alpha(), 3, command, false);
|
||||
|
||||
Node* n = input_.node();
|
||||
Node* n = GetInnerInput().node();
|
||||
n->blockSignals(true);
|
||||
n->SetInputProperty(input_.input(), QStringLiteral("col_input"), c.color_input());
|
||||
n->SetInputProperty(input_.input(), QStringLiteral("col_display"), c.color_output().display());
|
||||
n->SetInputProperty(input_.input(), QStringLiteral("col_view"), c.color_output().view());
|
||||
n->SetInputProperty(input_.input(), QStringLiteral("col_look"), c.color_output().look());
|
||||
n->SetInputProperty(GetInnerInput().input(), QStringLiteral("col_input"), c.color_input());
|
||||
n->SetInputProperty(GetInnerInput().input(), QStringLiteral("col_display"), c.color_output().display());
|
||||
n->SetInputProperty(GetInnerInput().input(), QStringLiteral("col_view"), c.color_output().view());
|
||||
n->SetInputProperty(GetInnerInput().input(), QStringLiteral("col_look"), c.color_output().look());
|
||||
n->blockSignals(false);
|
||||
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
@@ -383,20 +383,38 @@ void NodeParamViewWidgetBridge::WidgetCallback()
|
||||
if (cb->itemData(i, Qt::AccessibleDescriptionRole).toString() == QStringLiteral("separator")) {
|
||||
index--;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SetInputValue(index, 0);
|
||||
break;
|
||||
}
|
||||
case NodeValue::kVideoParams:
|
||||
case NodeValue::kBezier:
|
||||
{
|
||||
VideoParamEdit* edit = static_cast<VideoParamEdit*>(sender());
|
||||
SetInputValue(QVariant::fromValue(edit->GetVideoParams()), 0);
|
||||
// Widget is a FloatSlider (child of BezierWidget)
|
||||
BezierWidget *bw = static_cast<BezierWidget*>(widgets_.first());
|
||||
FloatSlider *fs = static_cast<FloatSlider*>(sender());
|
||||
|
||||
int index = -1;
|
||||
if (fs == bw->x_slider()) {
|
||||
index = 0;
|
||||
} else if (fs == bw->y_slider()) {
|
||||
index = 1;
|
||||
} else if (fs == bw->cp1_x_slider()) {
|
||||
index = 2;
|
||||
} else if (fs == bw->cp1_y_slider()) {
|
||||
index = 3;
|
||||
} else if (fs == bw->cp2_x_slider()) {
|
||||
index = 4;
|
||||
} else if (fs == bw->cp2_y_slider()) {
|
||||
index = 5;
|
||||
}
|
||||
|
||||
if (index != -1) {
|
||||
ProcessSlider(fs, index, fs->GetValue());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NodeValue::kAudioParams:
|
||||
// FIXME: No audio param widget yet
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,7 +423,7 @@ void NodeParamViewWidgetBridge::CreateSliders(int count)
|
||||
{
|
||||
for (int i=0;i<count;i++) {
|
||||
T* fs = new T();
|
||||
fs->SliderBase::SetDefaultValue(input_.GetSplitDefaultValueForTrack(i));
|
||||
fs->SliderBase::SetDefaultValue(GetInnerInput().GetSplitDefaultValueForTrack(i));
|
||||
fs->SetLadderElementCount(2);
|
||||
widgets_.append(fs);
|
||||
connect(fs, &T::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
||||
@@ -414,17 +432,17 @@ void NodeParamViewWidgetBridge::CreateSliders(int count)
|
||||
|
||||
void NodeParamViewWidgetBridge::UpdateWidgetValues()
|
||||
{
|
||||
if (input_.IsArray() && input_.element() == -1) {
|
||||
if (GetInnerInput().IsArray() && GetInnerInput().element() == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
rational node_time;
|
||||
if (input_.IsKeyframing()) {
|
||||
if (GetInnerInput().IsKeyframing()) {
|
||||
node_time = GetCurrentTimeAsNodeTime();
|
||||
}
|
||||
|
||||
// We assume the first data type is the "primary" type
|
||||
switch (input_.GetDataType()) {
|
||||
switch (GetDataType()) {
|
||||
// None of these inputs have applicable UI widgets
|
||||
case NodeValue::kNone:
|
||||
case NodeValue::kTexture:
|
||||
@@ -434,25 +452,27 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues()
|
||||
case NodeValue::kShaderJob:
|
||||
case NodeValue::kSampleJob:
|
||||
case NodeValue::kGenerateJob:
|
||||
case NodeValue::kVideoParams:
|
||||
case NodeValue::kAudioParams:
|
||||
break;
|
||||
case NodeValue::kInt:
|
||||
{
|
||||
static_cast<IntegerSlider*>(widgets_.first())->SetValue(input_.GetValueAtTime(node_time).toLongLong());
|
||||
static_cast<IntegerSlider*>(widgets_.first())->SetValue(GetInnerInput().GetValueAtTime(node_time).toLongLong());
|
||||
break;
|
||||
}
|
||||
case NodeValue::kFloat:
|
||||
{
|
||||
static_cast<FloatSlider*>(widgets_.first())->SetValue(input_.GetValueAtTime(node_time).toDouble());
|
||||
static_cast<FloatSlider*>(widgets_.first())->SetValue(GetInnerInput().GetValueAtTime(node_time).toDouble());
|
||||
break;
|
||||
}
|
||||
case NodeValue::kRational:
|
||||
{
|
||||
static_cast<RationalSlider*>(widgets_.first())->SetValue(input_.GetValueAtTime(node_time).value<rational>());
|
||||
static_cast<RationalSlider*>(widgets_.first())->SetValue(GetInnerInput().GetValueAtTime(node_time).value<rational>());
|
||||
break;
|
||||
}
|
||||
case NodeValue::kVec2:
|
||||
{
|
||||
QVector2D vec2 = input_.GetValueAtTime(node_time).value<QVector2D>();
|
||||
QVector2D vec2 = GetInnerInput().GetValueAtTime(node_time).value<QVector2D>();
|
||||
|
||||
static_cast<FloatSlider*>(widgets_.at(0))->SetValue(static_cast<double>(vec2.x()));
|
||||
static_cast<FloatSlider*>(widgets_.at(1))->SetValue(static_cast<double>(vec2.y()));
|
||||
@@ -460,7 +480,7 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues()
|
||||
}
|
||||
case NodeValue::kVec3:
|
||||
{
|
||||
QVector3D vec3 = input_.GetValueAtTime(node_time).value<QVector3D>();
|
||||
QVector3D vec3 = GetInnerInput().GetValueAtTime(node_time).value<QVector3D>();
|
||||
|
||||
static_cast<FloatSlider*>(widgets_.at(0))->SetValue(static_cast<double>(vec3.x()));
|
||||
static_cast<FloatSlider*>(widgets_.at(1))->SetValue(static_cast<double>(vec3.y()));
|
||||
@@ -469,7 +489,7 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues()
|
||||
}
|
||||
case NodeValue::kVec4:
|
||||
{
|
||||
QVector4D vec4 = input_.GetValueAtTime(node_time).value<QVector4D>();
|
||||
QVector4D vec4 = GetInnerInput().GetValueAtTime(node_time).value<QVector4D>();
|
||||
|
||||
static_cast<FloatSlider*>(widgets_.at(0))->SetValue(static_cast<double>(vec4.x()));
|
||||
static_cast<FloatSlider*>(widgets_.at(1))->SetValue(static_cast<double>(vec4.y()));
|
||||
@@ -480,18 +500,18 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues()
|
||||
case NodeValue::kFile:
|
||||
{
|
||||
FileField* ff = static_cast<FileField*>(widgets_.first());
|
||||
ff->SetFilename(input_.GetValueAtTime(node_time).toString());
|
||||
ff->SetFilename(GetInnerInput().GetValueAtTime(node_time).toString());
|
||||
break;
|
||||
}
|
||||
case NodeValue::kColor:
|
||||
{
|
||||
ManagedColor mc = input_.GetValueAtTime(node_time).value<Color>();
|
||||
ManagedColor mc = GetInnerInput().GetValueAtTime(node_time).value<Color>();
|
||||
|
||||
mc.set_color_input(input_.GetProperty("col_input").toString());
|
||||
mc.set_color_input(GetInnerInput().GetProperty("col_input").toString());
|
||||
|
||||
QString d = input_.GetProperty("col_display").toString();
|
||||
QString v = input_.GetProperty("col_view").toString();
|
||||
QString l = input_.GetProperty("col_look").toString();
|
||||
QString d = GetInnerInput().GetProperty("col_display").toString();
|
||||
QString v = GetInnerInput().GetProperty("col_view").toString();
|
||||
QString l = GetInnerInput().GetProperty("col_look").toString();
|
||||
|
||||
mc.set_color_output(ColorTransform(d, v, l));
|
||||
|
||||
@@ -501,17 +521,17 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues()
|
||||
case NodeValue::kText:
|
||||
{
|
||||
NodeParamViewTextEdit* e = static_cast<NodeParamViewTextEdit*>(widgets_.first());
|
||||
e->setTextPreservingCursor(input_.GetValueAtTime(node_time).toString());
|
||||
e->setTextPreservingCursor(GetInnerInput().GetValueAtTime(node_time).toString());
|
||||
break;
|
||||
}
|
||||
case NodeValue::kBoolean:
|
||||
static_cast<QCheckBox*>(widgets_.first())->setChecked(input_.GetValueAtTime(node_time).toBool());
|
||||
static_cast<QCheckBox*>(widgets_.first())->setChecked(GetInnerInput().GetValueAtTime(node_time).toBool());
|
||||
break;
|
||||
case NodeValue::kFont:
|
||||
{
|
||||
QFontComboBox* fc = static_cast<QFontComboBox*>(widgets_.first());
|
||||
fc->blockSignals(true);
|
||||
fc->setCurrentFont(input_.GetValueAtTime(node_time).toString());
|
||||
fc->setCurrentFont(GetInnerInput().GetValueAtTime(node_time).toString());
|
||||
fc->blockSignals(false);
|
||||
break;
|
||||
}
|
||||
@@ -519,47 +539,39 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues()
|
||||
{
|
||||
QComboBox* cb = static_cast<QComboBox*>(widgets_.first());
|
||||
cb->blockSignals(true);
|
||||
int index = input_.GetValueAtTime(node_time).toInt();
|
||||
int real_row = -1;
|
||||
int index = GetInnerInput().GetValueAtTime(node_time).toInt();
|
||||
for (int i=0; i<cb->count(); i++) {
|
||||
if (!cb->itemText(i).isEmpty()) {
|
||||
real_row++;
|
||||
}
|
||||
|
||||
if (real_row == index) {
|
||||
if (cb->itemData(i).toInt() == index) {
|
||||
cb->setCurrentIndex(i);
|
||||
}
|
||||
}
|
||||
cb->blockSignals(false);
|
||||
break;
|
||||
}
|
||||
case NodeValue::kVideoParams:
|
||||
case NodeValue::kBezier:
|
||||
{
|
||||
VideoParamEdit* edit = static_cast<VideoParamEdit*>(widgets_.first());
|
||||
edit->SetVideoParams(input_.GetValueAtTime(node_time).value<VideoParams>());
|
||||
BezierWidget* bw = static_cast<BezierWidget*>(widgets_.first());
|
||||
bw->SetValue(GetInnerInput().GetValueAtTime(node_time).value<Bezier>());
|
||||
break;
|
||||
}
|
||||
case NodeValue::kAudioParams:
|
||||
// FIXME: No audio param widget
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rational NodeParamViewWidgetBridge::GetCurrentTimeAsNodeTime() const
|
||||
{
|
||||
return GetAdjustedTime(GetTimeTarget(), input_.node(), time_, true);
|
||||
return GetAdjustedTime(GetTimeTarget(), GetInnerInput().node(), time_, true);
|
||||
}
|
||||
|
||||
void NodeParamViewWidgetBridge::SetTimebase(const rational& timebase)
|
||||
{
|
||||
if (input_.GetDataType() == NodeValue::kRational) {
|
||||
if (GetDataType() == NodeValue::kRational) {
|
||||
static_cast<RationalSlider*>(widgets_.first())->SetTimebase(timebase);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewWidgetBridge::InputValueChanged(const NodeInput &input, const TimeRange &range)
|
||||
{
|
||||
if (input_ == input
|
||||
if (GetInnerInput() == input
|
||||
&& !dragger_.IsStarted()
|
||||
&& range.in() <= time_ && range.out() >= time_) {
|
||||
// We'll need to update the widgets because the values have changed on our current time
|
||||
@@ -567,13 +579,9 @@ void NodeParamViewWidgetBridge::InputValueChanged(const NodeInput &input, const
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewWidgetBridge::PropertyChanged(const QString& input, const QString &key, const QVariant &value)
|
||||
void NodeParamViewWidgetBridge::SetProperty(const QString &key, const QVariant &value)
|
||||
{
|
||||
if (input != input_.input() || (input_.IsArray() && input_.element() == -1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
NodeValue::Type data_type = input_.GetDataType();
|
||||
NodeValue::Type data_type = GetDataType();
|
||||
|
||||
// Parameters for all types
|
||||
if (key == QStringLiteral("enabled")) {
|
||||
@@ -674,42 +682,12 @@ void NodeParamViewWidgetBridge::PropertyChanged(const QString& input, const QStr
|
||||
break;
|
||||
}
|
||||
} else if (key == QStringLiteral("offset")) {
|
||||
switch (data_type) {
|
||||
case NodeValue::kInt:
|
||||
static_cast<IntegerSlider*>(widgets_.first())->SetOffset(value);
|
||||
break;
|
||||
case NodeValue::kFloat:
|
||||
static_cast<FloatSlider*>(widgets_.first())->SetOffset(value);
|
||||
break;
|
||||
case NodeValue::kRational:
|
||||
static_cast<RationalSlider*>(widgets_.first())->SetOffset(value);
|
||||
break;
|
||||
case NodeValue::kVec2:
|
||||
{
|
||||
QVector2D offs = value.value<QVector2D>();
|
||||
static_cast<FloatSlider*>(widgets_.at(0))->SetOffset(offs.x());
|
||||
static_cast<FloatSlider*>(widgets_.at(1))->SetOffset(offs.y());
|
||||
break;
|
||||
}
|
||||
case NodeValue::kVec3:
|
||||
{
|
||||
QVector3D offs = value.value<QVector3D>();
|
||||
static_cast<FloatSlider*>(widgets_.at(0))->SetOffset(offs.x());
|
||||
static_cast<FloatSlider*>(widgets_.at(1))->SetOffset(offs.y());
|
||||
static_cast<FloatSlider*>(widgets_.at(2))->SetOffset(offs.z());
|
||||
break;
|
||||
}
|
||||
case NodeValue::kVec4:
|
||||
{
|
||||
QVector4D offs = value.value<QVector4D>();
|
||||
static_cast<FloatSlider*>(widgets_.at(0))->SetOffset(offs.x());
|
||||
static_cast<FloatSlider*>(widgets_.at(1))->SetOffset(offs.y());
|
||||
static_cast<FloatSlider*>(widgets_.at(2))->SetOffset(offs.z());
|
||||
static_cast<FloatSlider*>(widgets_.at(3))->SetOffset(offs.w());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
int tracks = NodeValue::get_number_of_keyframe_tracks(data_type);
|
||||
|
||||
QVector<QVariant> offsets = NodeValue::split_normal_value_into_track_values(data_type, value);
|
||||
|
||||
for (int i=0; i<tracks; i++) {
|
||||
static_cast<NumericSliderBase*>(widgets_.at(i))->SetOffset(offsets.at(i));
|
||||
}
|
||||
|
||||
UpdateWidgetValues();
|
||||
@@ -718,32 +696,37 @@ void NodeParamViewWidgetBridge::PropertyChanged(const QString& input, const QStr
|
||||
|
||||
// ComboBox strings changing
|
||||
if (data_type == NodeValue::kCombo) {
|
||||
QComboBox* cb = static_cast<QComboBox*>(widgets_.first());
|
||||
if (key == QStringLiteral("combo_str")) {
|
||||
QComboBox* cb = static_cast<QComboBox*>(widgets_.first());
|
||||
|
||||
int old_index = cb->currentIndex();
|
||||
int old_index = cb->currentIndex();
|
||||
|
||||
// Block the combobox changed signals since we anticipate the index will be the same and not require a re-render
|
||||
cb->blockSignals(true);
|
||||
// Block the combobox changed signals since we anticipate the index will be the same and not require a re-render
|
||||
cb->blockSignals(true);
|
||||
|
||||
cb->clear();
|
||||
cb->clear();
|
||||
|
||||
QStringList items = input_.GetComboBoxStrings();
|
||||
foreach (const QString& s, items) {
|
||||
if (s.isEmpty()) {
|
||||
cb->insertSeparator(cb->count());
|
||||
} else {
|
||||
cb->addItem(s);
|
||||
QStringList items = value.toStringList();
|
||||
int index = 0;
|
||||
foreach (const QString& s, items) {
|
||||
if (s.isEmpty()) {
|
||||
cb->insertSeparator(cb->count());
|
||||
cb->setItemData(cb->count()-1, -1);
|
||||
} else {
|
||||
cb->addItem(s, index);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cb->setCurrentIndex(old_index);
|
||||
cb->setCurrentIndex(old_index);
|
||||
|
||||
cb->blockSignals(false);
|
||||
cb->blockSignals(false);
|
||||
|
||||
// In case the amount of items is LESS and the previous index cannot be set, NOW we trigger a re-cache since the
|
||||
// value has changed
|
||||
if (cb->currentIndex() != old_index) {
|
||||
WidgetCallback();
|
||||
// In case the amount of items is LESS and the previous index cannot be set, NOW we trigger a re-cache since the
|
||||
// value has changed
|
||||
if (cb->currentIndex() != old_index) {
|
||||
WidgetCallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -796,21 +779,11 @@ void NodeParamViewWidgetBridge::PropertyChanged(const QString& input, const QStr
|
||||
ff->SetDirectoryMode(value.toBool());
|
||||
}
|
||||
}
|
||||
|
||||
// Parameters for video param objects
|
||||
if (data_type == NodeValue::kVideoParams) {
|
||||
VideoParamEdit* edit = static_cast<VideoParamEdit*>(widgets_.first());
|
||||
|
||||
if (key == QStringLiteral("mask")) {
|
||||
edit->SetParameterMask(value.toULongLong());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewWidgetBridge::InputDataTypeChanged(const QString &input, NodeValue::Type type)
|
||||
{
|
||||
Q_UNUSED(type)
|
||||
if (input == this->input_.input()) {
|
||||
if (sender() == GetOuterInput().node() && input == GetOuterInput().input()) {
|
||||
// Delete all widgets
|
||||
qDeleteAll(widgets_);
|
||||
widgets_.clear();
|
||||
@@ -819,7 +792,34 @@ void NodeParamViewWidgetBridge::InputDataTypeChanged(const QString &input, NodeV
|
||||
CreateWidgets();
|
||||
|
||||
// Signal that widgets are new
|
||||
emit WidgetsRecreated(input_);
|
||||
emit WidgetsRecreated(GetOuterInput());
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewWidgetBridge::PropertyChanged(const QString &input, const QString &key, const QVariant &value)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
for (auto it=input_hierarchy_.cbegin(); it!=input_hierarchy_.cend(); it++) {
|
||||
if (it->input() == input) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
UpdateProperties();
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewWidgetBridge::UpdateProperties()
|
||||
{
|
||||
// Set properties from the last entry (the innermost input) to the first (the outermost)
|
||||
for (auto it=input_hierarchy_.crbegin(); it!=input_hierarchy_.crend(); it++) {
|
||||
auto input_properties = it->node()->GetInputProperties(it->input());
|
||||
for (auto jt=input_properties.cbegin(); jt!=input_properties.cend(); jt++) {
|
||||
SetProperty(jt.key(), jt.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ class NodeParamViewWidgetBridge : public QObject, public TimeTargetObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
NodeParamViewWidgetBridge(const NodeInput& input, QObject* parent);
|
||||
NodeParamViewWidgetBridge(NodeInput input, QObject* parent);
|
||||
|
||||
void SetTime(const rational& time);
|
||||
|
||||
@@ -64,7 +64,13 @@ private:
|
||||
|
||||
void SetInputValueInternal(const QVariant& value, int track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key);
|
||||
|
||||
void ProcessSlider(NumericSliderBase* slider, const QVariant& value);
|
||||
void ProcessSlider(NumericSliderBase* slider, int slider_track, const QVariant& value);
|
||||
void ProcessSlider(NumericSliderBase* slider, const QVariant& value)
|
||||
{
|
||||
ProcessSlider(slider, widgets_.indexOf(slider), value);
|
||||
}
|
||||
|
||||
void SetProperty(const QString &key, const QVariant &value);
|
||||
|
||||
template <typename T>
|
||||
void CreateSliders(int count);
|
||||
@@ -73,7 +79,24 @@ private:
|
||||
|
||||
rational GetCurrentTimeAsNodeTime() const;
|
||||
|
||||
NodeInput input_;
|
||||
const NodeInput &GetOuterInput() const
|
||||
{
|
||||
return input_hierarchy_.first();
|
||||
}
|
||||
|
||||
const NodeInput &GetInnerInput() const
|
||||
{
|
||||
return input_hierarchy_.last();
|
||||
}
|
||||
|
||||
NodeValue::Type GetDataType() const
|
||||
{
|
||||
return GetOuterInput().GetDataType();
|
||||
}
|
||||
|
||||
void UpdateProperties();
|
||||
|
||||
QVector<NodeInput> input_hierarchy_;
|
||||
|
||||
QVector<QWidget*> widgets_;
|
||||
|
||||
@@ -88,10 +111,10 @@ private slots:
|
||||
|
||||
void InputValueChanged(const NodeInput& input, const TimeRange& range);
|
||||
|
||||
void PropertyChanged(const QString &input, const QString& key, const QVariant& value);
|
||||
|
||||
void InputDataTypeChanged(const QString& input, NodeValue::Type type);
|
||||
|
||||
void PropertyChanged(const QString &input, const QString &key, const QVariant &value);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -27,7 +27,8 @@ namespace olive {
|
||||
NodeTreeView::NodeTreeView(QWidget *parent) :
|
||||
QTreeWidget(parent),
|
||||
only_show_keyframable_(false),
|
||||
show_keyframe_tracks_as_rows_(false)
|
||||
show_keyframe_tracks_as_rows_(false),
|
||||
checkboxes_enabled_(false)
|
||||
{
|
||||
connect(this, &NodeTreeView::itemChanged, this, &NodeTreeView::ItemCheckStateChanged);
|
||||
connect(this, &NodeTreeView::itemSelectionChanged, this, &NodeTreeView::SelectionChanged);
|
||||
@@ -67,12 +68,14 @@ void NodeTreeView::SetNodes(const QVector<Node *> &nodes)
|
||||
foreach (Node* n, nodes_) {
|
||||
QTreeWidgetItem* node_item = new QTreeWidgetItem();
|
||||
node_item->setText(0, n->Name());
|
||||
node_item->setCheckState(0, disabled_nodes_.contains(n) ? Qt::Unchecked : Qt::Checked);
|
||||
if (checkboxes_enabled_) {
|
||||
node_item->setCheckState(0, disabled_nodes_.contains(n) ? Qt::Unchecked : Qt::Checked);
|
||||
}
|
||||
node_item->setData(0, kItemType, kItemTypeNode);
|
||||
node_item->setData(0, kItemNodePointer, Node::PtrToValue(n));
|
||||
|
||||
foreach (const QString& input, n->inputs()) {
|
||||
if (only_show_keyframable_ && !n->IsInputKeyframable(input)) {
|
||||
if (n->IsInputHidden(input) || (only_show_keyframable_ && !n->IsInputKeyframable(input))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -105,7 +108,6 @@ void NodeTreeView::SetNodes(const QVector<Node *> &nodes)
|
||||
CreateItemsForTracks(element_item, input_ref, key_tracks.size());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Add at the end to prevent unnecessary signalling while we're setting these objects up
|
||||
@@ -115,6 +117,8 @@ void NodeTreeView::SetNodes(const QVector<Node *> &nodes)
|
||||
delete node_item;
|
||||
}
|
||||
}
|
||||
|
||||
expandAll();
|
||||
}
|
||||
|
||||
void NodeTreeView::changeEvent(QEvent *e)
|
||||
@@ -153,6 +157,8 @@ NodeKeyframeTrackReference NodeTreeView::GetSelectedInput()
|
||||
|
||||
if (item->data(0, kItemType).toInt() == kItemTypeInput) {
|
||||
selected_ref = item->data(0, kItemInputReference).value<NodeKeyframeTrackReference>();
|
||||
} else {
|
||||
selected_ref = NodeKeyframeTrackReference(NodeInput(Node::ValueToPtr<Node>(item->data(0, kItemNodePointer)), QString()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,21 +170,27 @@ QTreeWidgetItem* NodeTreeView::CreateItem(QTreeWidgetItem *parent, const NodeKey
|
||||
QTreeWidgetItem* input_item = new QTreeWidgetItem(parent);
|
||||
|
||||
QString item_name;
|
||||
if (ref.track() == -1 || NodeValue::get_number_of_keyframe_tracks(ref.input().GetDataType()) == 1) {
|
||||
item_name = ref.input().name();
|
||||
if (ref.track() == -1
|
||||
|| NodeValue::get_number_of_keyframe_tracks(ref.input().GetDataType()) == 1
|
||||
|| (ref.input().IsArray() && ref.input().element() == -1)) {
|
||||
if (ref.input().element() == -1) {
|
||||
item_name = ref.input().name();
|
||||
} else {
|
||||
item_name = QString::number(ref.input().element());
|
||||
}
|
||||
} else {
|
||||
switch (ref.track()) {
|
||||
case 0:
|
||||
item_name = tr("X");
|
||||
item_name = UseRGBAOverXYZW(ref) ? tr("R") : tr("X");
|
||||
break;
|
||||
case 1:
|
||||
item_name = tr("Y");
|
||||
item_name = UseRGBAOverXYZW(ref) ? tr("G") : tr("Y");
|
||||
break;
|
||||
case 2:
|
||||
item_name = tr("Z");
|
||||
item_name = UseRGBAOverXYZW(ref) ? tr("B") : tr("Z");
|
||||
break;
|
||||
case 3:
|
||||
item_name = tr("W");
|
||||
item_name = UseRGBAOverXYZW(ref) ? tr("A") : tr("W");
|
||||
break;
|
||||
default:
|
||||
item_name = QString::number(ref.track());
|
||||
@@ -186,7 +198,9 @@ QTreeWidgetItem* NodeTreeView::CreateItem(QTreeWidgetItem *parent, const NodeKey
|
||||
}
|
||||
input_item->setText(0, item_name);
|
||||
|
||||
input_item->setCheckState(0, disabled_inputs_.contains(ref) ? Qt::Unchecked : Qt::Checked);
|
||||
if (checkboxes_enabled_) {
|
||||
input_item->setCheckState(0, disabled_inputs_.contains(ref) ? Qt::Unchecked : Qt::Checked);
|
||||
}
|
||||
input_item->setData(0, kItemType, kItemTypeInput);
|
||||
input_item->setData(0, kItemInputReference, QVariant::fromValue(ref));
|
||||
|
||||
@@ -206,6 +220,11 @@ void NodeTreeView::CreateItemsForTracks(QTreeWidgetItem *parent, const NodeInput
|
||||
}
|
||||
}
|
||||
|
||||
bool NodeTreeView::UseRGBAOverXYZW(const NodeKeyframeTrackReference &ref)
|
||||
{
|
||||
return ref.input().GetDataType() == NodeValue::kColor;
|
||||
}
|
||||
|
||||
void NodeTreeView::ItemCheckStateChanged(QTreeWidgetItem *item, int column)
|
||||
{
|
||||
Q_UNUSED(column)
|
||||
|
||||
@@ -37,6 +37,11 @@ public:
|
||||
|
||||
bool IsInputEnabled(const NodeKeyframeTrackReference& ref) const;
|
||||
|
||||
void SetCheckBoxesEnabled(bool e)
|
||||
{
|
||||
checkboxes_enabled_ = e;
|
||||
}
|
||||
|
||||
void SetKeyframeTrackColor(const NodeKeyframeTrackReference& ref, const QColor& color);
|
||||
|
||||
void SetOnlyShowKeyframable(bool e)
|
||||
@@ -75,6 +80,8 @@ private:
|
||||
|
||||
void CreateItemsForTracks(QTreeWidgetItem* parent, const NodeInput& input, int track_count);
|
||||
|
||||
static bool UseRGBAOverXYZW(const NodeKeyframeTrackReference &ref);
|
||||
|
||||
enum ItemType {
|
||||
kItemTypeNode,
|
||||
kItemTypeInput
|
||||
@@ -98,6 +105,8 @@ private:
|
||||
|
||||
QHash<NodeKeyframeTrackReference, QColor> keyframe_colors_;
|
||||
|
||||
bool checkboxes_enabled_;
|
||||
|
||||
private slots:
|
||||
void ItemCheckStateChanged(QTreeWidgetItem* item, int column);
|
||||
|
||||
|
||||
@@ -19,10 +19,14 @@ set(OLIVE_SOURCES
|
||||
widget/nodeview/nodeview.cpp
|
||||
widget/nodeview/nodeview.h
|
||||
widget/nodeview/nodeviewcommon.h
|
||||
widget/nodeview/nodeviewcontext.cpp
|
||||
widget/nodeview/nodeviewcontext.h
|
||||
widget/nodeview/nodeviewedge.cpp
|
||||
widget/nodeview/nodeviewedge.h
|
||||
widget/nodeview/nodeviewitem.cpp
|
||||
widget/nodeview/nodeviewitem.h
|
||||
widget/nodeview/nodeviewitemconnector.cpp
|
||||
widget/nodeview/nodeviewitemconnector.h
|
||||
widget/nodeview/nodeviewminimap.cpp
|
||||
widget/nodeview/nodeviewminimap.h
|
||||
widget/nodeview/nodeviewscene.cpp
|
||||
@@ -31,5 +35,7 @@ set(OLIVE_SOURCES
|
||||
widget/nodeview/nodeviewtoolbar.h
|
||||
widget/nodeview/nodeviewundo.cpp
|
||||
widget/nodeview/nodeviewundo.h
|
||||
widget/nodeview/nodewidget.cpp
|
||||
widget/nodeview/nodewidget.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
+739
-1036
File diff suppressed because it is too large
Load Diff
+57
-108
@@ -24,9 +24,11 @@
|
||||
#include <QGraphicsView>
|
||||
#include <QTimer>
|
||||
|
||||
#include "core.h"
|
||||
#include "node/graph.h"
|
||||
#include "node/nodecopypaste.h"
|
||||
#include "nodeviewedge.h"
|
||||
#include "nodeviewcontext.h"
|
||||
#include "nodeviewminimap.h"
|
||||
#include "nodeviewscene.h"
|
||||
#include "widget/handmovableview/handmovableview.h"
|
||||
@@ -44,16 +46,18 @@ class NodeView : public HandMovableView, public NodeCopyPasteService
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
NodeView(QWidget* parent);
|
||||
NodeView(QWidget* parent = nullptr);
|
||||
|
||||
virtual ~NodeView() override;
|
||||
|
||||
NodeGraph* GetGraph() const
|
||||
void SetContexts(const QVector<Node *> &nodes);
|
||||
|
||||
const QVector<Node*> &GetContexts() const
|
||||
{
|
||||
return graph_;
|
||||
return contexts_;
|
||||
}
|
||||
|
||||
void SetGraph(NodeGraph *graph, const QVector<Node *> &nodes);
|
||||
void CloseContextsBelongingToProject(Project *project);
|
||||
|
||||
void ClearGraph();
|
||||
|
||||
@@ -65,8 +69,7 @@ public:
|
||||
void SelectAll();
|
||||
void DeselectAll();
|
||||
|
||||
void Select(QVector<Node *> nodes, bool center_view_on_item);
|
||||
void SelectWithDependencies(QVector<Node *> nodes, bool center_view_on_item);
|
||||
void Select(const QVector<Node *> &nodes, bool center_view_on_item);
|
||||
|
||||
void CopySelected(bool cut);
|
||||
void Paste();
|
||||
@@ -81,7 +84,7 @@ public:
|
||||
|
||||
const QVector<Node*> &GetCurrentContexts() const
|
||||
{
|
||||
return filter_nodes_;
|
||||
return contexts_;
|
||||
}
|
||||
|
||||
public slots:
|
||||
@@ -97,17 +100,24 @@ public slots:
|
||||
delete m;
|
||||
}
|
||||
|
||||
void CenterOnItemsBoundingRect();
|
||||
|
||||
void CenterOnNode(olive::Node *n);
|
||||
|
||||
signals:
|
||||
void NodesSelected(const QVector<Node*>& nodes);
|
||||
|
||||
void NodesDeselected(const QVector<Node*>& nodes);
|
||||
|
||||
void NodeGroupOpenRequested(NodeGroup *group);
|
||||
|
||||
protected:
|
||||
virtual void keyPressEvent(QKeyEvent *event) override;
|
||||
|
||||
virtual void mousePressEvent(QMouseEvent *event) override;
|
||||
virtual void mouseMoveEvent(QMouseEvent *event) override;
|
||||
virtual void mouseReleaseEvent(QMouseEvent* event) override;
|
||||
virtual void mouseDoubleClickEvent(QMouseEvent* event) override;
|
||||
|
||||
virtual void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
@@ -117,15 +127,13 @@ protected:
|
||||
|
||||
virtual bool eventFilter(QObject *object, QEvent *event) override;
|
||||
|
||||
virtual void CopyNodesToClipboardInternal(QXmlStreamWriter *writer, const QVector<Node*> &nodes, void* userdata) override;
|
||||
virtual void PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data, void* userdata) override;
|
||||
virtual void CopyNodesToClipboardCallback(const QVector<Node *> &nodes, ProjectSerializer::SaveData *data, void* userdata) override;
|
||||
virtual void PasteNodesToClipboardCallback(const QVector<Node*> &nodes, const ProjectSerializer::LoadData &ldata, void *userdata) override;
|
||||
|
||||
virtual void changeEvent(QEvent *e) override;
|
||||
|
||||
private:
|
||||
void AttachNodesToCursor(const QVector<Node *> &nodes);
|
||||
|
||||
void AttachItemsToCursor(const QVector<NodeViewItem *> &items);
|
||||
|
||||
void DetachItemsFromCursor();
|
||||
void DetachItemsFromCursor(bool delete_nodes_too = true);
|
||||
|
||||
void SetFlowDirection(NodeViewCommon::FlowDirection dir);
|
||||
|
||||
@@ -136,120 +144,66 @@ private:
|
||||
|
||||
void ZoomFromKeyboard(double multiplier);
|
||||
|
||||
bool DetermineIfNodeIsFloatingInContext(Node *node, Node *context, Node *source, const Node::OutputConnections &removed_edges, const Node::OutputConnection &added_edge);
|
||||
void UpdateContextsFromEdgeRemove(MultiUndoCommand *command, const Node::OutputConnections &remove_edges);
|
||||
void UpdateContextsFromEdgeAdd(MultiUndoCommand *command, const Node::OutputConnection &added_edge, const Node::OutputConnections &removed_edges = Node::OutputConnections());
|
||||
void RecursivelyAddNodeToContext(MultiUndoCommand *command, Node *node, Node *context);
|
||||
void RecursivelyRemoveFloatingNodeFromContext(MultiUndoCommand *command, Node *node, Node *context, Node *source, const Node::OutputConnections &removed_edges, const Node::OutputConnection &added_edge, bool prevent_removing);
|
||||
void ClearCreateEdgeInputIfNecessary();
|
||||
|
||||
QPointF GetEstimatedPositionForContext(NodeViewItem *item, Node *context) const;
|
||||
|
||||
Menu *CreateAddMenu(Menu *parent);
|
||||
NodeViewItem *GetAssumedItemForSelectedNode(Node *node);
|
||||
Node::Position GetAssumedPositionForSelectedNode(Node *node);
|
||||
|
||||
void CreateNewEdge(NodeViewItem *output_item, const QPoint &mouse_pos);
|
||||
Menu *CreateAddMenu(Menu *parent);
|
||||
|
||||
void PositionNewEdge(const QPoint &pos);
|
||||
|
||||
NodeViewItem *UpdateNodeItem(Node *node, bool ignore_own_context = false);
|
||||
void AddContext(Node *n);
|
||||
|
||||
void PasteNodesInternal(const QVector<Node*> &duplicate_nodes = QVector<Node *>());
|
||||
void RemoveContext(Node *n);
|
||||
|
||||
class NodeViewAttachNodesToCursor : public UndoCommand
|
||||
{
|
||||
public:
|
||||
NodeViewAttachNodesToCursor(NodeView* view, const QVector<Node*>& nodes);
|
||||
bool IsItemAttachedToCursor(NodeViewItem *item) const;
|
||||
|
||||
virtual Project * GetRelevantProject() const override;
|
||||
void ExpandItem(NodeViewItem *item);
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
void CollapseItem(NodeViewItem *item);
|
||||
|
||||
virtual void undo() override;
|
||||
void EndEdgeDrag(bool cancel = false);
|
||||
|
||||
private:
|
||||
NodeView* view_;
|
||||
|
||||
QVector<Node*> nodes_;
|
||||
|
||||
};
|
||||
void PostPaste(const QVector<Node*> &new_nodes, const Node::PositionMap &map);
|
||||
|
||||
NodeViewMiniMap *minimap_;
|
||||
|
||||
NodeGraph* graph_;
|
||||
|
||||
struct AttachedItem {
|
||||
NodeViewItem* item;
|
||||
Node *node;
|
||||
QPointF original_pos;
|
||||
};
|
||||
|
||||
class NodeViewItemPreventRemovingCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
NodeViewItemPreventRemovingCommand(NodeView *view, Node *node, bool prevent_removing) :
|
||||
view_(view),
|
||||
node_(node),
|
||||
new_prevent_removing_(prevent_removing)
|
||||
{}
|
||||
|
||||
virtual Project * GetRelevantProject() const override
|
||||
{
|
||||
return node_->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
NodeView *view_;
|
||||
Node *node_;
|
||||
bool new_prevent_removing_;
|
||||
bool old_prevent_removing_;
|
||||
|
||||
};
|
||||
|
||||
QList<AttachedItem> attached_items_;
|
||||
void SetAttachedItems(const QVector<AttachedItem> &items);
|
||||
QVector<AttachedItem> attached_items_;
|
||||
|
||||
NodeViewEdge* drop_edge_;
|
||||
NodeInput drop_input_;
|
||||
|
||||
NodeViewEdge* create_edge_;
|
||||
NodeViewItem* create_edge_src_;
|
||||
NodeViewItem* create_edge_dst_;
|
||||
NodeInput create_edge_dst_input_;
|
||||
bool create_edge_dst_temp_expanded_;
|
||||
NodeViewItem* create_edge_output_item_;
|
||||
NodeViewItem* create_edge_input_item_;
|
||||
NodeInput create_edge_input_;
|
||||
bool create_edge_already_exists_;
|
||||
bool create_edge_from_output_;
|
||||
|
||||
QVector<NodeViewItem*> create_edge_expanded_items_;
|
||||
|
||||
NodeViewScene scene_;
|
||||
|
||||
MultiUndoCommand* paste_command_;
|
||||
|
||||
QVector<Node*> selected_nodes_;
|
||||
|
||||
enum FilterMode {
|
||||
kFilterShowAll,
|
||||
kFilterShowSelective
|
||||
};
|
||||
|
||||
struct Position {
|
||||
Node *node;
|
||||
QPointF original_item_pos;
|
||||
};
|
||||
|
||||
QMap<NodeViewItem *, Position> positions_;
|
||||
|
||||
FilterMode filter_mode_;
|
||||
|
||||
QVector<Node*> filter_nodes_;
|
||||
QVector<Node*> contexts_;
|
||||
QVector<Node*> last_set_filter_nodes_;
|
||||
QMap<Node*, QPointF> context_offsets_;
|
||||
|
||||
QMap<NodeViewItem*, QPointF> dragging_items_;
|
||||
|
||||
double scale_;
|
||||
|
||||
bool create_edge_already_exists_;
|
||||
|
||||
bool queue_reposition_contexts_;
|
||||
|
||||
static const double kMinimumScale;
|
||||
|
||||
private slots:
|
||||
@@ -273,35 +227,30 @@ private slots:
|
||||
*/
|
||||
void ContextMenuSetDirection(QAction* action);
|
||||
|
||||
/**
|
||||
* @brief Receiver for the user changing the filter
|
||||
*/
|
||||
void ContextMenuFilterChanged(QAction* action);
|
||||
|
||||
/**
|
||||
* @brief Opens the selected node in a Viewer
|
||||
*/
|
||||
void OpenSelectedNodeInViewer();
|
||||
|
||||
//void AddNode(Node *node);
|
||||
void RemoveNode(Node *node);
|
||||
void AddEdge(Node *output, const NodeInput& input);
|
||||
void RemoveEdge(Node *output, const NodeInput& input);
|
||||
|
||||
void AddNodePosition(Node *node, Node *relative);
|
||||
void RemoveNodePosition(Node *node, Node *relative);
|
||||
|
||||
void UpdateSceneBoundingRect();
|
||||
|
||||
void CenterOnItemsBoundingRect();
|
||||
|
||||
void RepositionMiniMap();
|
||||
|
||||
void UpdateViewportOnMiniMap();
|
||||
|
||||
void MoveToScenePoint(const QPointF &pos);
|
||||
|
||||
void RepositionContexts();
|
||||
void NodeRemovedFromGraph();
|
||||
|
||||
void GroupNodes();
|
||||
|
||||
void UngroupNodes();
|
||||
|
||||
void ShowNodeProperties();
|
||||
|
||||
void LabelSelectedNodes();
|
||||
|
||||
void ItemAboutToBeDeleted(NodeViewItem *item);
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace olive {
|
||||
class NodeViewCommon {
|
||||
public:
|
||||
enum FlowDirection {
|
||||
kInvalidDirection = -1,
|
||||
kTopToBottom,
|
||||
kBottomToTop,
|
||||
kLeftToRight,
|
||||
@@ -44,6 +45,16 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsFlowVertical(FlowDirection dir)
|
||||
{
|
||||
return dir == kTopToBottom || dir == kBottomToTop;
|
||||
}
|
||||
|
||||
static bool IsFlowHorizontal(FlowDirection dir)
|
||||
{
|
||||
return dir == kLeftToRight || dir == kRightToLeft;
|
||||
}
|
||||
|
||||
static bool DirectionsAreOpposing(FlowDirection a, FlowDirection b) {
|
||||
return ((a == NodeViewCommon::kLeftToRight && b == NodeViewCommon::kRightToLeft)
|
||||
|| (a == NodeViewCommon::kRightToLeft && b == NodeViewCommon::kLeftToRight)
|
||||
|
||||
@@ -0,0 +1,353 @@
|
||||
#include "nodeviewcontext.h"
|
||||
|
||||
#include <QBrush>
|
||||
#include <QCoreApplication>
|
||||
#include <QGraphicsScene>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QPen>
|
||||
#include <QStyleOptionGraphicsItem>
|
||||
|
||||
#include "core.h"
|
||||
#include "node/block/block.h"
|
||||
#include "node/graph.h"
|
||||
#include "node/output/track/track.h"
|
||||
#include "node/project/sequence/sequence.h"
|
||||
#include "nodeviewitem.h"
|
||||
#include "ui/colorcoding.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
#define super QGraphicsRectItem
|
||||
|
||||
NodeViewContext::NodeViewContext(Node *context, QGraphicsItem *item) :
|
||||
super(item),
|
||||
context_(context)
|
||||
{
|
||||
Block *block = dynamic_cast<Block*>(context_);
|
||||
if (block && block->track() && block->track()->sequence()) {
|
||||
rational timebase = block->track()->sequence()->GetVideoParams().frame_rate_as_time_base();
|
||||
lbl_ = QCoreApplication::translate("NodeViewContext",
|
||||
"%1 [%2] :: %3 - %4").arg(block->GetLabelAndName(),
|
||||
Track::Reference::TypeToTranslatedString(block->track()->type()),
|
||||
Timecode::time_to_timecode(block->in(), timebase, Core::instance()->GetTimecodeDisplay()),
|
||||
Timecode::time_to_timecode(block->out(), timebase, Core::instance()->GetTimecodeDisplay()));
|
||||
} else {
|
||||
lbl_ = context_->GetLabelAndName();
|
||||
}
|
||||
|
||||
const Node::PositionMap &map = context_->GetContextPositions();
|
||||
for (auto it=map.cbegin(); it!=map.cend(); it++) {
|
||||
AddChild(it.key());
|
||||
}
|
||||
|
||||
connect(context_, &Node::NodeAddedToContext, this, &NodeViewContext::AddChild, Qt::DirectConnection);
|
||||
connect(context_, &Node::NodePositionInContextChanged, this, &NodeViewContext::SetChildPosition, Qt::DirectConnection);
|
||||
connect(context_, &Node::NodeRemovedFromContext, this, &NodeViewContext::RemoveChild, Qt::DirectConnection);
|
||||
}
|
||||
|
||||
NodeViewContext::~NodeViewContext()
|
||||
{
|
||||
// Delete edges before items, because the edge constructor references the items
|
||||
qDeleteAll(edges_);
|
||||
edges_.clear();
|
||||
}
|
||||
|
||||
void NodeViewContext::AddChild(Node *node)
|
||||
{
|
||||
if (!context_) {
|
||||
return;
|
||||
}
|
||||
|
||||
NodeViewItem *item = new NodeViewItem(node, context_, this);
|
||||
item->SetFlowDirection(flow_dir_);
|
||||
|
||||
AddNodeInternal(node, item);
|
||||
|
||||
if (NodeGroup *group = dynamic_cast<NodeGroup*>(node)) {
|
||||
for (auto it=group->GetContextPositions().cbegin(); it!=group->GetContextPositions().cend(); it++) {
|
||||
// Use this item as the representative for all of these nodes too
|
||||
AddNodeInternal(it.key(), item);
|
||||
}
|
||||
|
||||
connect(group, &NodeGroup::NodeAddedToContext, this, &NodeViewContext::GroupAddedNode);
|
||||
connect(group, &NodeGroup::NodeRemovedFromContext, this, &NodeViewContext::GroupRemovedNode);
|
||||
}
|
||||
|
||||
UpdateRect();
|
||||
}
|
||||
|
||||
void NodeViewContext::SetChildPosition(Node *node, const QPointF &pos)
|
||||
{
|
||||
item_map_.value(node)->SetNodePosition(pos);
|
||||
}
|
||||
|
||||
void NodeViewContext::RemoveChild(Node *node)
|
||||
{
|
||||
disconnect(node, &Node::InputConnected, this, &NodeViewContext::ChildInputConnected);
|
||||
disconnect(node, &Node::InputDisconnected, this, &NodeViewContext::ChildInputDisconnected);
|
||||
|
||||
if (NodeGroup *group = dynamic_cast<NodeGroup*>(node)) {
|
||||
disconnect(group, &NodeGroup::NodeAddedToContext, this, &NodeViewContext::GroupAddedNode);
|
||||
disconnect(group, &NodeGroup::NodeRemovedFromContext, this, &NodeViewContext::GroupRemovedNode);
|
||||
}
|
||||
|
||||
NodeViewItem *item = item_map_.take(node);
|
||||
|
||||
// Remove from scene before emitting signal so that any drag functions that might be happening
|
||||
// now can be handled before the item is destroyed
|
||||
scene()->removeItem(item);
|
||||
|
||||
emit ItemAboutToBeDeleted(item);
|
||||
|
||||
// Delete edges first because the edge destructor will try to reference item (maybe that should
|
||||
// be changed...)
|
||||
QVector<NodeViewEdge*> edges_to_remove = item->GetAllEdgesRecursively();
|
||||
foreach (NodeViewEdge *edge, edges_to_remove) {
|
||||
if (node == item->GetNode() || edge->output() == node || edge->input().node() == node) {
|
||||
ChildInputDisconnected(edge->output(), edge->input());
|
||||
}
|
||||
}
|
||||
|
||||
// Check if this item is specifically for this node and the node is a group. If so, remove it for
|
||||
// all other entries in the map.
|
||||
if (item->GetNode() == node) {
|
||||
if (dynamic_cast<NodeGroup*>(item->GetNode())) {
|
||||
for (auto it=item_map_.begin(); it!=item_map_.end(); ) {
|
||||
if (it.value() == item) {
|
||||
it = item_map_.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete item;
|
||||
}
|
||||
|
||||
UpdateRect();
|
||||
}
|
||||
|
||||
void NodeViewContext::ChildInputConnected(Node *output, const NodeInput &input)
|
||||
{
|
||||
// Add edge
|
||||
if (!input.IsHidden()) {
|
||||
if (NodeViewItem* output_item = item_map_.value(output)) {
|
||||
AddEdgeInternal(output, input, output_item, item_map_.value(input.node())->GetItemForInput(input));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool NodeViewContext::ChildInputDisconnected(Node *output, const NodeInput &input)
|
||||
{
|
||||
// Remove edge
|
||||
for (int i=0; i<edges_.size(); i++) {
|
||||
NodeViewEdge *e = edges_.at(i);
|
||||
if (e->output() == output && e->input() == input) {
|
||||
delete e;
|
||||
edges_.removeAt(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
qreal GetTextOffset(const QFontMetricsF &fm)
|
||||
{
|
||||
return fm.height()/2;
|
||||
}
|
||||
|
||||
void NodeViewContext::UpdateRect()
|
||||
{
|
||||
QFont f;
|
||||
QFontMetricsF fm(f);
|
||||
qreal lbl_offset = GetTextOffset(fm);
|
||||
|
||||
QRectF cbr = childrenBoundingRect();
|
||||
QRectF rect = cbr;
|
||||
int pad = NodeViewItem::DefaultItemHeight();
|
||||
rect.adjust(-pad, - lbl_offset*2 - fm.height() - pad, pad, pad);
|
||||
setRect(rect);
|
||||
|
||||
last_titlebar_height_ = rect.y() + (cbr.y() - rect.y()) - pad;
|
||||
}
|
||||
|
||||
void NodeViewContext::SetFlowDirection(NodeViewCommon::FlowDirection dir)
|
||||
{
|
||||
flow_dir_ = dir;
|
||||
|
||||
foreach (NodeViewItem *item, item_map_) {
|
||||
item->SetFlowDirection(dir);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewContext::SetCurvedEdges(bool e)
|
||||
{
|
||||
curved_edges_ = e;
|
||||
|
||||
foreach (NodeViewEdge *edge, edges_) {
|
||||
edge->SetCurved(e);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewContext::DeleteSelected(NodeViewDeleteCommand *command)
|
||||
{
|
||||
// Delete any selected edges
|
||||
foreach (NodeViewEdge *edge, edges_) {
|
||||
if (edge->isSelected()) {
|
||||
command->AddEdge(edge->output(), edge->input());
|
||||
}
|
||||
}
|
||||
|
||||
// Delete any selected nodes
|
||||
foreach (NodeViewItem *node, item_map_) {
|
||||
if (node->isSelected()) {
|
||||
command->AddNode(node->GetNode(), context_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewContext::Select(const QVector<Node *> &nodes)
|
||||
{
|
||||
foreach (Node *n, nodes) {
|
||||
if (NodeViewItem *item = item_map_.value(n)) {
|
||||
item->setSelected(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVector<NodeViewItem *> NodeViewContext::GetSelectedItems() const
|
||||
{
|
||||
QVector<NodeViewItem *> items;
|
||||
|
||||
for (auto it=item_map_.cbegin(); it!=item_map_.cend(); it++) {
|
||||
if (it.value()->isSelected()) {
|
||||
if (!items.contains(it.value())) {
|
||||
items.append(it.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
QPointF NodeViewContext::MapScenePosToNodePosInContext(const QPointF &pos) const
|
||||
{
|
||||
for (auto it=item_map_.cbegin(); it!=item_map_.cend(); it++) {
|
||||
QPointF pos_inside_parent = it.value()->mapToParent(it.value()->mapFromScene(pos));
|
||||
return NodeViewItem::ScreenToNodePoint(pos_inside_parent, flow_dir_);
|
||||
}
|
||||
return QPointF(0, 0);
|
||||
}
|
||||
|
||||
void NodeViewContext::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
|
||||
{
|
||||
// Set pen and brush
|
||||
Color color = context_->color();
|
||||
QColor c = color.toQColor();
|
||||
QPen pen(c, 2);
|
||||
if (option->state & QStyle::State_Selected) {
|
||||
pen.setStyle(Qt::DotLine);
|
||||
}
|
||||
painter->setPen(pen);
|
||||
|
||||
QColor bg = c;
|
||||
bg.setAlpha(128);
|
||||
painter->setBrush(bg);
|
||||
|
||||
// Draw semi-transparent rect for whole item
|
||||
int rounded = painter->fontMetrics().height();
|
||||
painter->drawRoundedRect(rect(), rounded, rounded);
|
||||
|
||||
// Draw solid background for titlebar
|
||||
QRectF titlebar_rect = rect();
|
||||
titlebar_rect.setHeight(last_titlebar_height_ - rect().top());
|
||||
painter->setClipRect(titlebar_rect);
|
||||
painter->setBrush(c);
|
||||
painter->drawRoundedRect(rect(), rounded, rounded);
|
||||
painter->setClipping(false);
|
||||
|
||||
// Draw titlebar text
|
||||
painter->setPen(ColorCoding::GetUISelectorColor(color));
|
||||
|
||||
int offset = GetTextOffset(painter->fontMetrics());
|
||||
|
||||
QRectF text_rect = rect();
|
||||
text_rect.adjust(offset, offset, -offset, -offset);
|
||||
painter->drawText(text_rect, lbl_);
|
||||
}
|
||||
|
||||
QVariant NodeViewContext::itemChange(GraphicsItemChange change, const QVariant &value)
|
||||
{
|
||||
return super::itemChange(change, value);
|
||||
}
|
||||
|
||||
void NodeViewContext::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
bool clicked_inside_titlebar = (event->pos().y() < last_titlebar_height_);
|
||||
|
||||
setFlag(ItemIsMovable, clicked_inside_titlebar);
|
||||
setFlag(ItemIsSelectable, clicked_inside_titlebar);
|
||||
|
||||
super::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void NodeViewContext::AddNodeInternal(Node *node, NodeViewItem *item)
|
||||
{
|
||||
connect(node, &Node::InputConnected, this, &NodeViewContext::ChildInputConnected);
|
||||
connect(node, &Node::InputDisconnected, this, &NodeViewContext::ChildInputDisconnected);
|
||||
|
||||
item_map_.insert(node, item);
|
||||
|
||||
if (node == context_) {
|
||||
item->SetLabelAsOutput(true);
|
||||
}
|
||||
|
||||
for (auto it=node->output_connections().cbegin(); it!=node->output_connections().cend(); it++) {
|
||||
if (!it->second.IsHidden()) {
|
||||
if (NodeViewItem *other_item = item_map_.value(it->second.node())) {
|
||||
AddEdgeInternal(node, it->second, item, other_item->GetItemForInput(it->second));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) {
|
||||
if (!it->first.IsHidden()) {
|
||||
if (NodeViewItem *other_item = item_map_.value(it->second)) {
|
||||
AddEdgeInternal(it->second, it->first, other_item, item->GetItemForInput(it->first));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewContext::AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to)
|
||||
{
|
||||
if (from == to) {
|
||||
return;
|
||||
}
|
||||
|
||||
NodeViewEdge* edge_ui = new NodeViewEdge(output, input, from, to, this);
|
||||
|
||||
edge_ui->Adjust();
|
||||
edge_ui->SetCurved(curved_edges_);
|
||||
|
||||
edges_.append(edge_ui);
|
||||
}
|
||||
|
||||
void NodeViewContext::GroupAddedNode(Node *node)
|
||||
{
|
||||
NodeGroup *group = static_cast<NodeGroup*>(sender());
|
||||
|
||||
AddNodeInternal(node, item_map_.value(group));
|
||||
}
|
||||
|
||||
void NodeViewContext::GroupRemovedNode(Node *node)
|
||||
{
|
||||
NodeGroup *group = static_cast<NodeGroup*>(sender());
|
||||
|
||||
if (item_map_.value(node) == item_map_.value(group)) {
|
||||
item_map_.remove(node);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
#ifndef NODEVIEWCONTEXT_H
|
||||
#define NODEVIEWCONTEXT_H
|
||||
|
||||
#include <QGraphicsRectItem>
|
||||
#include <QGraphicsTextItem>
|
||||
|
||||
#include "node/node.h"
|
||||
#include "nodeviewcommon.h"
|
||||
#include "nodeviewedge.h"
|
||||
#include "nodeviewundo.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class NodeViewContext : public QObject, public QGraphicsRectItem
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
NodeViewContext(Node *context, QGraphicsItem *item = nullptr);
|
||||
|
||||
virtual ~NodeViewContext() override;
|
||||
|
||||
Node *GetContext() const
|
||||
{
|
||||
return context_;
|
||||
}
|
||||
|
||||
void UpdateRect();
|
||||
|
||||
void SetFlowDirection(NodeViewCommon::FlowDirection dir);
|
||||
|
||||
void SetCurvedEdges(bool e);
|
||||
|
||||
void DeleteSelected(NodeViewDeleteCommand *command);
|
||||
|
||||
void Select(const QVector<Node*> &nodes);
|
||||
|
||||
QVector<NodeViewItem*> GetSelectedItems() const;
|
||||
|
||||
QPointF MapScenePosToNodePosInContext(const QPointF &pos) const;
|
||||
|
||||
NodeViewItem *GetItemFromMap(Node *node) const
|
||||
{
|
||||
return item_map_.value(node);
|
||||
}
|
||||
|
||||
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
|
||||
|
||||
public slots:
|
||||
void AddChild(Node *node);
|
||||
|
||||
void SetChildPosition(Node *node, const QPointF &pos);
|
||||
|
||||
void RemoveChild(Node *node);
|
||||
|
||||
void ChildInputConnected(Node *output, const NodeInput& input);
|
||||
|
||||
bool ChildInputDisconnected(Node *output, const NodeInput& input);
|
||||
|
||||
signals:
|
||||
void ItemAboutToBeDeleted(NodeViewItem *item);
|
||||
|
||||
protected:
|
||||
virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
|
||||
|
||||
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
|
||||
private:
|
||||
void AddNodeInternal(Node *node, NodeViewItem *item);
|
||||
|
||||
void AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to);
|
||||
|
||||
Node *context_;
|
||||
|
||||
QString lbl_;
|
||||
|
||||
NodeViewCommon::FlowDirection flow_dir_;
|
||||
|
||||
bool curved_edges_;
|
||||
|
||||
int last_titlebar_height_;
|
||||
|
||||
QMap<Node*, NodeViewItem*> item_map_;
|
||||
|
||||
QVector<NodeViewEdge*> edges_;
|
||||
|
||||
private slots:
|
||||
void GroupAddedNode(Node *node);
|
||||
|
||||
void GroupRemovedNode(Node *node);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // NODEVIEWCONTEXT_H
|
||||
@@ -46,6 +46,9 @@ NodeViewEdge::NodeViewEdge(Node *output, const NodeInput &input,
|
||||
{
|
||||
Init();
|
||||
SetConnected(true);
|
||||
|
||||
from_item_->AddEdge(this);
|
||||
to_item_->AddEdge(this);
|
||||
}
|
||||
|
||||
NodeViewEdge::NodeViewEdge(QGraphicsItem *parent) :
|
||||
@@ -56,12 +59,51 @@ NodeViewEdge::NodeViewEdge(QGraphicsItem *parent) :
|
||||
Init();
|
||||
}
|
||||
|
||||
NodeViewEdge::~NodeViewEdge()
|
||||
{
|
||||
if (from_item_) {
|
||||
from_item_->RemoveEdge(this);
|
||||
}
|
||||
|
||||
if (to_item_) {
|
||||
to_item_->RemoveEdge(this);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewEdge::set_from_item(NodeViewItem *i)
|
||||
{
|
||||
if (from_item_) {
|
||||
from_item_->RemoveEdge(this);
|
||||
}
|
||||
|
||||
from_item_ = i;
|
||||
|
||||
if (from_item_) {
|
||||
from_item_->AddEdge(this);
|
||||
}
|
||||
|
||||
Adjust();
|
||||
}
|
||||
|
||||
void NodeViewEdge::set_to_item(NodeViewItem *i)
|
||||
{
|
||||
if (to_item_) {
|
||||
to_item_->RemoveEdge(this);
|
||||
}
|
||||
|
||||
to_item_ = i;
|
||||
|
||||
if (to_item_) {
|
||||
to_item_->AddEdge(this);
|
||||
}
|
||||
|
||||
Adjust();
|
||||
}
|
||||
|
||||
void NodeViewEdge::Adjust()
|
||||
{
|
||||
// Draw a line between the two
|
||||
SetPoints(from_item()->GetOutputPoint(),
|
||||
to_item()->GetInputPoint(input_.input(), input_.element(), from_item()->pos()),
|
||||
to_item()->IsExpanded());
|
||||
SetPoints(from_item()->GetOutputPoint(), to_item()->GetInputPoint());
|
||||
}
|
||||
|
||||
void NodeViewEdge::SetConnected(bool c)
|
||||
@@ -78,24 +120,14 @@ void NodeViewEdge::SetHighlighted(bool e)
|
||||
update();
|
||||
}
|
||||
|
||||
void NodeViewEdge::SetPoints(const QPointF &start, const QPointF &end, bool input_is_expanded)
|
||||
void NodeViewEdge::SetPoints(const QPointF &start, const QPointF &end)
|
||||
{
|
||||
cached_start_ = start;
|
||||
cached_end_ = end;
|
||||
cached_input_is_expanded_ = input_is_expanded;
|
||||
|
||||
UpdateCurve();
|
||||
}
|
||||
|
||||
void NodeViewEdge::SetFlowDirection(NodeViewCommon::FlowDirection dir)
|
||||
{
|
||||
flow_dir_ = dir;
|
||||
|
||||
if (from_item_ && to_item_) {
|
||||
Adjust();
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewEdge::SetCurved(bool e)
|
||||
{
|
||||
curved_ = e;
|
||||
@@ -126,18 +158,12 @@ void NodeViewEdge::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti
|
||||
painter->setPen(QPen(edge_color, edge_width_));
|
||||
painter->setBrush(Qt::NoBrush);
|
||||
painter->drawPath(path());
|
||||
|
||||
// Draw arrow
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setBrush(edge_color);
|
||||
painter->drawPolygon(arrow_);
|
||||
}
|
||||
|
||||
void NodeViewEdge::Init()
|
||||
{
|
||||
connected_ = false;
|
||||
highlighted_ = false;
|
||||
flow_dir_ = NodeViewCommon::kLeftToRight;
|
||||
curved_ = true;
|
||||
|
||||
setFlag(QGraphicsItem::ItemIsSelectable);
|
||||
@@ -147,14 +173,12 @@ void NodeViewEdge::Init()
|
||||
|
||||
// Use font metrics to set edge width for basic high DPI support
|
||||
edge_width_ = QFontMetrics(QFont()).height() / 12;
|
||||
arrow_size_ = QFontMetrics(QFont()).height() / 2;
|
||||
}
|
||||
|
||||
void NodeViewEdge::UpdateCurve()
|
||||
{
|
||||
const QPointF &start = cached_start_;
|
||||
const QPointF &end = cached_end_;
|
||||
const bool input_is_expanded = cached_input_is_expanded_;
|
||||
|
||||
QPainterPath path;
|
||||
path.moveTo(start);
|
||||
@@ -168,13 +192,26 @@ void NodeViewEdge::UpdateCurve()
|
||||
|
||||
QPointF cp1, cp2;
|
||||
|
||||
if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal) {
|
||||
NodeViewCommon::FlowDirection from_flow = from_item_ ? from_item_->GetFlowDirection() : NodeViewCommon::kInvalidDirection;
|
||||
NodeViewCommon::FlowDirection to_flow = to_item_ ? to_item_->GetFlowDirection() : NodeViewCommon::kInvalidDirection;
|
||||
|
||||
if (from_flow == NodeViewCommon::kInvalidDirection && to_flow == NodeViewCommon::kInvalidDirection) {
|
||||
// This is a technically unsupported scenario, but to avoid issues, we'll use a fallback
|
||||
from_flow = NodeViewCommon::kLeftToRight;
|
||||
to_flow = NodeViewCommon::kLeftToRight;
|
||||
} else if (from_flow == NodeViewCommon::kInvalidDirection) {
|
||||
from_flow = to_flow;
|
||||
} else if (to_flow == NodeViewCommon::kInvalidDirection) {
|
||||
to_flow = from_flow;
|
||||
}
|
||||
|
||||
if (NodeViewCommon::GetFlowOrientation(from_flow) == Qt::Horizontal) {
|
||||
cp1 = QPointF(half_x, start.y());
|
||||
} else {
|
||||
cp1 = QPointF(start.x(), half_y);
|
||||
}
|
||||
|
||||
if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal || input_is_expanded) {
|
||||
if (NodeViewCommon::GetFlowOrientation(to_flow) == Qt::Horizontal) {
|
||||
cp2 = QPointF(half_x, end.y());
|
||||
} else {
|
||||
cp2 = QPointF(end.x(), half_y);
|
||||
@@ -183,7 +220,7 @@ void NodeViewEdge::UpdateCurve()
|
||||
path.cubicTo(cp1, cp2, end);
|
||||
|
||||
if (!qFuzzyCompare(start.x(), end.x())) {
|
||||
double continue_x = end.x() - qCos(angle)*arrow_size_;
|
||||
double continue_x = end.x() - qCos(angle);
|
||||
|
||||
double x1 = start.x();
|
||||
double x2 = cp1.x();
|
||||
@@ -213,18 +250,7 @@ void NodeViewEdge::UpdateCurve()
|
||||
|
||||
}
|
||||
|
||||
setPath(path);
|
||||
|
||||
const double arrow_angle = 150.0 * M_PI / 180.0;
|
||||
QVector<QPointF> arrow_points(4);
|
||||
arrow_points[0] = end;
|
||||
arrow_points[1] = end + QPointF(qCos(angle + arrow_angle) * arrow_size_, qSin(angle + arrow_angle) * arrow_size_);
|
||||
arrow_points[2] = end + QPointF(qCos(angle - arrow_angle) * arrow_size_, qSin(angle - arrow_angle) * arrow_size_);
|
||||
arrow_points[3] = end;
|
||||
|
||||
arrow_ = QPolygonF(arrow_points);
|
||||
arrow_bounding_rect_ = arrow_.boundingRect();
|
||||
arrow_bounding_rect_.adjust(-arrow_size_, -arrow_size_, arrow_size_, arrow_size_);
|
||||
setPath(mapFromScene(path));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ public:
|
||||
|
||||
NodeViewEdge(QGraphicsItem* parent = nullptr);
|
||||
|
||||
virtual ~NodeViewEdge() override;
|
||||
|
||||
Node *output() const
|
||||
{
|
||||
return output_;
|
||||
@@ -70,10 +72,9 @@ public:
|
||||
return to_item_;
|
||||
}
|
||||
|
||||
const QRectF arrow_bounding_rect() const
|
||||
{
|
||||
return arrow_bounding_rect_;
|
||||
}
|
||||
void set_from_item(NodeViewItem *i);
|
||||
|
||||
void set_to_item(NodeViewItem *i);
|
||||
|
||||
void Adjust();
|
||||
|
||||
@@ -104,12 +105,7 @@ public:
|
||||
/**
|
||||
* @brief Set points to create curve from
|
||||
*/
|
||||
void SetPoints(const QPointF& start, const QPointF& end, bool input_is_expanded);
|
||||
|
||||
/**
|
||||
* @brief Sets the direction nodes are flowing
|
||||
*/
|
||||
void SetFlowDirection(NodeViewCommon::FlowDirection dir);
|
||||
void SetPoints(const QPointF& start, const QPointF& end);
|
||||
|
||||
/**
|
||||
* @brief Set whether edges should be drawn as curved or as straight lines
|
||||
@@ -140,19 +136,10 @@ private:
|
||||
|
||||
bool highlighted_;
|
||||
|
||||
NodeViewCommon::FlowDirection flow_dir_;
|
||||
|
||||
bool curved_;
|
||||
|
||||
QPolygonF arrow_;
|
||||
|
||||
int arrow_size_;
|
||||
|
||||
QRectF arrow_bounding_rect_;
|
||||
|
||||
QPointF cached_start_;
|
||||
QPointF cached_end_;
|
||||
bool cached_input_is_expanded_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -39,20 +39,18 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
NodeViewItem::NodeViewItem(QGraphicsItem *parent) :
|
||||
NodeViewItem::NodeViewItem(Node *node, const QString &input, int element, Node *context, QGraphicsItem *parent) :
|
||||
QGraphicsRectItem(parent),
|
||||
node_(nullptr),
|
||||
node_(node),
|
||||
input_(input),
|
||||
element_(element),
|
||||
context_(context),
|
||||
expanded_(false),
|
||||
hide_titlebar_(false),
|
||||
highlighted_index_(-1),
|
||||
flow_dir_(NodeViewCommon::kLeftToRight),
|
||||
prevent_removing_(false)
|
||||
highlighted_(false),
|
||||
flow_dir_(NodeViewCommon::kInvalidDirection),
|
||||
arrow_click_(false),
|
||||
label_as_output_(false)
|
||||
{
|
||||
// Set flags for this widget
|
||||
setFlag(QGraphicsItem::ItemIsMovable);
|
||||
setFlag(QGraphicsItem::ItemIsSelectable);
|
||||
setFlag(QGraphicsItem::ItemSendsGeometryChanges);
|
||||
|
||||
//
|
||||
// We use font metrics to set all the UI measurements for DPI-awareness
|
||||
//
|
||||
@@ -60,13 +58,48 @@ NodeViewItem::NodeViewItem(QGraphicsItem *parent) :
|
||||
// Set border width
|
||||
node_border_width_ = DefaultItemBorder();
|
||||
|
||||
int widget_width = DefaultItemWidth();
|
||||
int widget_height = DefaultItemHeight();
|
||||
// Set rect size to default
|
||||
SetRectSize();
|
||||
|
||||
title_bar_rect_ = QRectF(-widget_width/2, -widget_height/2, widget_width, widget_height);
|
||||
setRect(title_bar_rect_);
|
||||
// Create connector
|
||||
input_connector_ = new NodeViewItemConnector(false, this);
|
||||
output_connector_ = new NodeViewItemConnector(true, this);
|
||||
|
||||
output_triangle_.resize(3);
|
||||
connect(node_, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged);
|
||||
connect(node_, &Node::ColorChanged, this, &NodeViewItem::NodeAppearanceChanged);
|
||||
|
||||
if (IsOutputItem()) {
|
||||
connect(node_, &Node::InputAdded, this, &NodeViewItem::RepopulateInputs);
|
||||
connect(node_, &Node::InputRemoved, this, &NodeViewItem::RepopulateInputs);
|
||||
RepopulateInputs();
|
||||
|
||||
// Set flags for this widget
|
||||
setFlag(QGraphicsItem::ItemSendsGeometryChanges);
|
||||
setFlag(QGraphicsItem::ItemIsMovable);
|
||||
setFlag(QGraphicsItem::ItemIsSelectable);
|
||||
|
||||
if (context_) {
|
||||
SetNodePosition(context_->GetNodePositionDataInContext(node_));
|
||||
}
|
||||
} else {
|
||||
output_connector_->setVisible(false);
|
||||
|
||||
connect(node_, &Node::InputArraySizeChanged, this, &NodeViewItem::InputArraySizeChanged);
|
||||
connect(node_, &Node::InputArraySizeChanged, this, &NodeViewItem::InputArraySizeChanged);
|
||||
}
|
||||
|
||||
// This should be set during runtime, but just in case here's a default fallback
|
||||
SetFlowDirection(NodeViewCommon::kLeftToRight);
|
||||
}
|
||||
|
||||
NodeViewItem::~NodeViewItem()
|
||||
{
|
||||
Q_ASSERT(edges_.isEmpty());
|
||||
}
|
||||
|
||||
Node::Position NodeViewItem::GetNodePositionData() const
|
||||
{
|
||||
return Node::Position(GetNodePosition(), IsExpanded());
|
||||
}
|
||||
|
||||
QPointF NodeViewItem::GetNodePosition() const
|
||||
@@ -81,6 +114,23 @@ void NodeViewItem::SetNodePosition(const QPointF &pos)
|
||||
UpdateNodePosition();
|
||||
}
|
||||
|
||||
void NodeViewItem::SetNodePosition(const Node::Position &pos)
|
||||
{
|
||||
SetNodePosition(pos.position);
|
||||
SetExpanded(pos.expanded);
|
||||
}
|
||||
|
||||
QVector<NodeViewEdge *> NodeViewItem::GetAllEdgesRecursively() const
|
||||
{
|
||||
QVector<NodeViewEdge *> list = edges_;
|
||||
|
||||
foreach (NodeViewItem *item, children_) {
|
||||
list.append(item->GetAllEdgesRecursively());
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
int NodeViewItem::DefaultTextPadding()
|
||||
{
|
||||
return QFontMetrics(QFont()).height() / 4;
|
||||
@@ -119,6 +169,8 @@ QPointF NodeViewItem::NodeToScreenPoint(QPointF p, NodeViewCommon::FlowDirection
|
||||
// Swap X/Y and invert Y
|
||||
p = QPointF(p.y(), -p.x());
|
||||
break;
|
||||
case NodeViewCommon::kInvalidDirection:
|
||||
break;
|
||||
}
|
||||
|
||||
// Multiply by item sizes for this direction
|
||||
@@ -144,12 +196,14 @@ QPointF NodeViewItem::ScreenToNodePoint(QPointF p, NodeViewCommon::FlowDirection
|
||||
break;
|
||||
case NodeViewCommon::kTopToBottom:
|
||||
// Swap X/Y
|
||||
p = QPointF(p.y(), p.x());
|
||||
p = QPointF(p.y(), p.x());
|
||||
break;
|
||||
case NodeViewCommon::kBottomToTop:
|
||||
// Swap X/Y and invert Y
|
||||
p = QPointF(-p.y(), p.x());
|
||||
break;
|
||||
case NodeViewCommon::kInvalidDirection:
|
||||
break;
|
||||
}
|
||||
|
||||
return p;
|
||||
@@ -193,66 +247,81 @@ void NodeViewItem::RemoveEdge(NodeViewEdge *edge)
|
||||
edges_.removeOne(edge);
|
||||
}
|
||||
|
||||
int NodeViewItem::GetIndexAt(QPointF pt) const
|
||||
{
|
||||
pt -= pos();
|
||||
|
||||
for (int i=0; i<node_inputs_.size(); i++) {
|
||||
if (GetInputRect(i).contains(pt)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void NodeViewItem::SetNode(Node *n)
|
||||
{
|
||||
node_ = n;
|
||||
|
||||
node_inputs_.clear();
|
||||
|
||||
if (node_) {
|
||||
node_->Retranslate();
|
||||
|
||||
foreach (const QString& input, node_->inputs()) {
|
||||
if (node_->IsInputConnectable(input)) {
|
||||
node_inputs_.append(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void NodeViewItem::SetExpanded(bool e, bool hide_titlebar)
|
||||
{
|
||||
if (node_inputs_.isEmpty()
|
||||
|| (expanded_ == e && hide_titlebar_ == hide_titlebar)) {
|
||||
if (!CanBeExpanded() || (expanded_ == e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
expanded_ = e;
|
||||
hide_titlebar_ = hide_titlebar;
|
||||
|
||||
if (expanded_ && !node_inputs_.isEmpty()) {
|
||||
// Create new rect
|
||||
QRectF new_rect = title_bar_rect_;
|
||||
|
||||
if (hide_titlebar_) {
|
||||
new_rect.setHeight(new_rect.height() * node_inputs_.size());
|
||||
} else {
|
||||
new_rect.setHeight(new_rect.height() * (node_inputs_.size() + 1));
|
||||
}
|
||||
|
||||
setRect(new_rect);
|
||||
} else {
|
||||
setRect(title_bar_rect_);
|
||||
if (context_) {
|
||||
context_->SetNodeExpandedInContext(node_, e);
|
||||
}
|
||||
|
||||
update();
|
||||
if (IsOutputItem()) {
|
||||
// We don't have to check has_connectable_inputs_ here because we did it at the top
|
||||
input_connector_->setVisible(!expanded_);
|
||||
}
|
||||
|
||||
if (expanded_) {
|
||||
node_->Retranslate();
|
||||
|
||||
if (IsOutputItem()) {
|
||||
// Create items for each input of the node
|
||||
int i = 1;
|
||||
foreach (const QString &input, node_->inputs()) {
|
||||
if (IsInputValid(input)) {
|
||||
NodeViewItem *item = new NodeViewItem(node_, input, -1, context_, this);
|
||||
children_.append(item);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
QVector<NodeViewEdge*> edges = edges_;
|
||||
for (auto it=edges.cbegin(); it!=edges.cend(); it++) {
|
||||
if ((*it)->to_item() == this) {
|
||||
(*it)->set_to_item(GetItemForInput((*it)->input()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Create items for each element of the input array
|
||||
int arr_sz = node_->InputArraySize(input_);
|
||||
children_.resize(arr_sz);
|
||||
for (int i=0; i<arr_sz; i++) {
|
||||
NodeViewItem *item = new NodeViewItem(node_, input_, i, context_, this);
|
||||
children_[i] = item;
|
||||
}
|
||||
|
||||
QVector<NodeViewEdge*> edges = edges_;
|
||||
for (auto it=edges.cbegin(); it!=edges.cend(); it++) {
|
||||
if ((*it)->to_item() == this) {
|
||||
(*it)->set_to_item(GetItemForInput((*it)->input()));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach (NodeViewItem *child, children_) {
|
||||
QVector<NodeViewEdge*> child_edges = child->edges();
|
||||
foreach (NodeViewEdge *edge, child_edges) {
|
||||
edge->set_to_item(this);
|
||||
}
|
||||
delete child;
|
||||
}
|
||||
children_.clear();
|
||||
}
|
||||
|
||||
UpdateChildrenPositions();
|
||||
|
||||
if (flow_dir_ == NodeViewCommon::kTopToBottom) {
|
||||
UpdateOutputConnectorPosition();
|
||||
}
|
||||
|
||||
ReadjustAllEdges();
|
||||
|
||||
UpdateContextRect();
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void NodeViewItem::ToggleExpanded()
|
||||
@@ -266,114 +335,105 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti
|
||||
// has been slightly modified
|
||||
QPalette app_pal = Core::instance()->main_window()->palette();
|
||||
|
||||
// Draw background rect if expanded
|
||||
if (IsExpanded()) {
|
||||
// We only draw a single unit's worth
|
||||
QRectF single_unit_rect = rect();
|
||||
single_unit_rect.setHeight(DefaultItemHeight());
|
||||
|
||||
if (IsOutputItem()) {
|
||||
// Set output item colors
|
||||
painter->setPen(Qt::black);
|
||||
painter->setBrush(node_->brush(single_unit_rect.top(), single_unit_rect.bottom()));
|
||||
} else {
|
||||
// Set input item colors
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setBrush(app_pal.color(QPalette::Window));
|
||||
painter->setBrush(element_ == -1 ? app_pal.color(QPalette::Window) : app_pal.color(QPalette::Base));
|
||||
}
|
||||
|
||||
painter->drawRect(single_unit_rect);
|
||||
|
||||
// Draw highlight if applicable
|
||||
if (highlighted_) {
|
||||
QColor highlight_col = app_pal.color(QPalette::Text);
|
||||
highlight_col.setAlpha(64);
|
||||
painter->setBrush(highlight_col);
|
||||
painter->drawRect(rect());
|
||||
}
|
||||
|
||||
// Determine what text to draw and whether to draw an arrow
|
||||
QString node_label, node_name;
|
||||
|
||||
if (IsOutputItem()) {
|
||||
if (label_as_output_) {
|
||||
node_name = QCoreApplication::translate("NodeViewItem", "Output");
|
||||
} else {
|
||||
node_label = node_->GetLabel();
|
||||
node_name = node_->ShortName();
|
||||
}
|
||||
} else {
|
||||
if (element_ == -1) {
|
||||
node_name = node_->GetInputName(input_);
|
||||
} else {
|
||||
node_name = QString::number(element_);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw arrow if necessary
|
||||
int arrow_size = CanBeExpanded() ? DrawExpandArrow(painter) : 0;
|
||||
|
||||
if (IsOutputItem()) {
|
||||
// Determine the text color (automatically calculate from node background color)
|
||||
painter->setPen(ColorCoding::GetUISelectorColor(node_->color()));
|
||||
} else {
|
||||
// Just use text item
|
||||
painter->setPen(app_pal.text().color());
|
||||
}
|
||||
|
||||
if (node_label.isEmpty()) {
|
||||
// Draw name only
|
||||
DrawNodeTitle(painter, node_name, single_unit_rect, Qt::AlignVCenter, arrow_size);
|
||||
} else {
|
||||
int text_pad = DefaultTextPadding()/2;
|
||||
QRectF safe_label_bounds = single_unit_rect.adjusted(text_pad, text_pad, -text_pad, -text_pad);
|
||||
QFont f;
|
||||
qreal font_sz = f.pointSizeF();
|
||||
|
||||
// Draw label as larger/upper text
|
||||
f.setPointSizeF(font_sz * 0.8);
|
||||
painter->setFont(f);
|
||||
DrawNodeTitle(painter, node_label, safe_label_bounds, Qt::AlignTop, arrow_size);
|
||||
|
||||
// Draw node name as smaller/lower text
|
||||
f.setPointSizeF(font_sz * 0.6);
|
||||
painter->setFont(f);
|
||||
DrawNodeTitle(painter, node_name, safe_label_bounds, Qt::AlignBottom, arrow_size);
|
||||
}
|
||||
|
||||
// Draw final border (output only)
|
||||
if (IsOutputItem()) {
|
||||
QPen border_pen;
|
||||
border_pen.setWidth(node_border_width_);
|
||||
|
||||
if (option->state & QStyle::State_Selected) {
|
||||
border_pen.setColor(app_pal.color(QPalette::Highlight));
|
||||
} else {
|
||||
border_pen.setColor(Qt::black);
|
||||
}
|
||||
|
||||
painter->setPen(border_pen);
|
||||
painter->setBrush(Qt::NoBrush);
|
||||
|
||||
painter->drawRect(rect());
|
||||
|
||||
painter->setPen(app_pal.color(QPalette::Text));
|
||||
|
||||
for (int i=0;i<node_inputs_.size();i++) {
|
||||
QRectF input_rect = GetInputRect(i);
|
||||
|
||||
if (highlighted_index_ == i) {
|
||||
QColor highlight_col = app_pal.color(QPalette::Text);
|
||||
highlight_col.setAlpha(64);
|
||||
painter->fillRect(input_rect, highlight_col);
|
||||
}
|
||||
|
||||
painter->drawText(input_rect, Qt::AlignCenter, node_->GetInputName(node_inputs_.at(i)));
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the titlebar
|
||||
if (!hide_titlebar_ && node_) {
|
||||
|
||||
painter->setPen(Qt::black);
|
||||
painter->setBrush(node_->brush(title_bar_rect_.top(), title_bar_rect_.bottom()));
|
||||
|
||||
painter->drawRect(title_bar_rect_);
|
||||
|
||||
painter->setPen(app_pal.color(QPalette::Text));
|
||||
|
||||
QString node_label = node_->GetLabel();
|
||||
QString node_shortname = node_->ShortName();
|
||||
|
||||
int icon_size = painter->fontMetrics().height()/2;
|
||||
|
||||
if (node_label.isEmpty()) {
|
||||
// Draw shortname only
|
||||
DrawNodeTitle(painter, node_shortname, title_bar_rect_, Qt::AlignVCenter, icon_size, true);
|
||||
} else {
|
||||
int text_pad = DefaultTextPadding()/2;
|
||||
QRectF safe_label_bounds = title_bar_rect_.adjusted(text_pad, text_pad, -text_pad, -text_pad);
|
||||
QFont f;
|
||||
qreal font_sz = f.pointSizeF();
|
||||
f.setPointSizeF(font_sz * 0.8);
|
||||
painter->setFont(f);
|
||||
DrawNodeTitle(painter, node_label, safe_label_bounds, Qt::AlignTop, icon_size, true);
|
||||
f.setPointSizeF(font_sz * 0.6);
|
||||
painter->setFont(f);
|
||||
DrawNodeTitle(painter, node_shortname, safe_label_bounds, Qt::AlignBottom, icon_size, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Draw final border
|
||||
QPen border_pen;
|
||||
border_pen.setWidth(node_border_width_);
|
||||
|
||||
if (option->state & QStyle::State_Selected) {
|
||||
border_pen.setColor(app_pal.color(QPalette::Highlight));
|
||||
} else {
|
||||
border_pen.setColor(Qt::black);
|
||||
}
|
||||
|
||||
painter->setPen(border_pen);
|
||||
painter->setBrush(Qt::NoBrush);
|
||||
|
||||
painter->drawRect(rect());
|
||||
|
||||
// Draw output triangle
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setBrush(app_pal.color(QPalette::Text));
|
||||
int triangle_sz = title_bar_rect_.height() / 2;
|
||||
int triangle_sz_half = triangle_sz / 2;
|
||||
|
||||
switch (flow_dir_) {
|
||||
case NodeViewCommon::kLeftToRight:
|
||||
// Triangle pointing right
|
||||
output_triangle_[0] = QPointF(rect().right(), rect().center().y() - triangle_sz_half);
|
||||
output_triangle_[1] = QPointF(rect().right() + triangle_sz_half, rect().center().y());
|
||||
output_triangle_[2] = QPointF(rect().right(), rect().center().y() + triangle_sz_half);
|
||||
break;
|
||||
case NodeViewCommon::kTopToBottom:
|
||||
// Triangle pointing down
|
||||
output_triangle_[0] = QPointF(rect().center().x() - triangle_sz_half, rect().bottom());
|
||||
output_triangle_[1] = QPointF(rect().center().x(), rect().bottom() + triangle_sz_half);
|
||||
output_triangle_[2] = QPointF(rect().center().x() + triangle_sz_half, rect().bottom());
|
||||
break;
|
||||
case NodeViewCommon::kBottomToTop:
|
||||
// Triangle pointing up
|
||||
output_triangle_[0] = QPointF(rect().center().x() - triangle_sz_half, rect().top());
|
||||
output_triangle_[1] = QPointF(rect().center().x(), rect().top() - triangle_sz_half);
|
||||
output_triangle_[2] = QPointF(rect().center().x() + triangle_sz_half, rect().top());
|
||||
break;
|
||||
case NodeViewCommon::kRightToLeft:
|
||||
// Triangle pointing left
|
||||
output_triangle_[0] = QPointF(rect().left(), rect().center().y() - triangle_sz_half);
|
||||
output_triangle_[1] = QPointF(rect().left() - triangle_sz_half, rect().center().y());
|
||||
output_triangle_[2] = QPointF(rect().left(), rect().center().y() + triangle_sz_half);
|
||||
break;
|
||||
}
|
||||
|
||||
painter->drawPolygon(output_triangle_);
|
||||
}
|
||||
|
||||
void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (last_arrow_rect_.contains(event->pos().toPoint())) {
|
||||
arrow_click_ = true;
|
||||
ToggleExpanded();
|
||||
return;
|
||||
}
|
||||
|
||||
event->setModifiers(FlipControlAndShiftModifiers(event->modifiers()));
|
||||
|
||||
QGraphicsRectItem::mousePressEvent(event);
|
||||
@@ -381,6 +441,10 @@ void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
|
||||
void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (arrow_click_) {
|
||||
return;
|
||||
}
|
||||
|
||||
event->setModifiers(FlipControlAndShiftModifiers(event->modifiers()));
|
||||
|
||||
QGraphicsRectItem::mouseMoveEvent(event);
|
||||
@@ -388,24 +452,22 @@ void NodeViewItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
||||
|
||||
void NodeViewItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (arrow_click_) {
|
||||
arrow_click_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
event->setModifiers(FlipControlAndShiftModifiers(event->modifiers()));
|
||||
|
||||
QGraphicsRectItem::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
void NodeViewItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
QGraphicsRectItem::mouseDoubleClickEvent(event);
|
||||
|
||||
if (!(event->modifiers() & Qt::ControlModifier)) {
|
||||
SetExpanded(!IsExpanded());
|
||||
}
|
||||
}
|
||||
|
||||
QVariant NodeViewItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
|
||||
{
|
||||
if (change == ItemPositionHasChanged && node_) {
|
||||
ReadjustAllEdges();
|
||||
|
||||
UpdateContextRect();
|
||||
}
|
||||
|
||||
return QGraphicsItem::itemChange(change, value);
|
||||
@@ -414,30 +476,37 @@ QVariant NodeViewItem::itemChange(QGraphicsItem::GraphicsItemChange change, cons
|
||||
void NodeViewItem::ReadjustAllEdges()
|
||||
{
|
||||
foreach (NodeViewEdge* edge, edges_) {
|
||||
if (NodeViewItem *to_item = edge->to_item()) {
|
||||
static_cast<NodeViewItem*>(to_item->parentItem())->UpdateFlowDirectionOfInputItem(to_item);
|
||||
}
|
||||
|
||||
edge->Adjust();
|
||||
}
|
||||
foreach (NodeViewItem *child, children_) {
|
||||
child->ReadjustAllEdges();
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF& rect, Qt::Alignment vertical_align, int icon_size, bool draw_arrow)
|
||||
void NodeViewItem::UpdateContextRect()
|
||||
{
|
||||
QGraphicsItem *item = parentItem();
|
||||
|
||||
while (item) {
|
||||
if (NodeViewContext *ctx = dynamic_cast<NodeViewContext*>(item)) {
|
||||
ctx->UpdateRect();
|
||||
break;
|
||||
}
|
||||
|
||||
item = item->parentItem();
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF& rect, Qt::Alignment vertical_align, int icon_full_size)
|
||||
{
|
||||
QFontMetrics fm = painter->fontMetrics();
|
||||
|
||||
painter->setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
|
||||
// Draw right or down arrow based on expanded state
|
||||
int icon_padding = title_bar_rect_.height() / 2 - icon_size / 2;
|
||||
int icon_full_size = icon_size + icon_padding * 2;
|
||||
if (draw_arrow) {
|
||||
const QIcon& expand_icon = IsExpanded() ? icon::TriDown : icon::TriRight;
|
||||
int icon_size_scaled = icon_size * painter->transform().m11();
|
||||
painter->drawPixmap(QRect(title_bar_rect_.x() + icon_padding,
|
||||
title_bar_rect_.y() + icon_padding,
|
||||
icon_size,
|
||||
icon_size), expand_icon.pixmap(QSize(icon_size_scaled, icon_size_scaled)));
|
||||
}
|
||||
|
||||
// Calculate how much space we have for text
|
||||
int item_width = title_bar_rect_.width();
|
||||
int item_width = this->rect().width();
|
||||
int max_text_width = item_width - DefaultTextPadding() * 2 - icon_full_size;
|
||||
int label_width = QtUtils::QFontMetricsWidth(fm, text);
|
||||
|
||||
@@ -454,9 +523,6 @@ void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF&
|
||||
text = concatenated;
|
||||
}
|
||||
|
||||
// Determine the text color (automatically calculate from node background color)
|
||||
painter->setPen(ColorCoding::GetUISelectorColor(node_->color()));
|
||||
|
||||
// Determine X position (favors horizontal centering unless it'll overrun the arrow)
|
||||
QRectF text_rect = rect;
|
||||
Qt::Alignment text_align = Qt::AlignHCenter | vertical_align;
|
||||
@@ -472,78 +538,80 @@ void NodeViewItem::DrawNodeTitle(QPainter* painter, QString text, const QRectF&
|
||||
text);
|
||||
}
|
||||
|
||||
void NodeViewItem::SetHighlightedIndex(int index)
|
||||
int NodeViewItem::DrawExpandArrow(QPainter *painter)
|
||||
{
|
||||
if (highlighted_index_ == index) {
|
||||
return;
|
||||
}
|
||||
// Draw right or down arrow based on expanded state
|
||||
int icon_size = painter->fontMetrics().height()/2;
|
||||
int icon_padding = DefaultItemHeight() / 2 - icon_size / 2;
|
||||
int icon_full_size = icon_size + icon_padding * 2;
|
||||
|
||||
highlighted_index_ = index;
|
||||
painter->setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
|
||||
const QIcon& expand_icon = IsExpanded() ? icon::TriDown : icon::TriRight;
|
||||
int icon_size_scaled = icon_size * painter->transform().m11();
|
||||
|
||||
last_arrow_rect_ = QRect(this->rect().x() + icon_padding,
|
||||
this->rect().y() + icon_padding,
|
||||
icon_size,
|
||||
icon_size);
|
||||
|
||||
painter->drawPixmap(last_arrow_rect_, expand_icon.pixmap(QSize(icon_size_scaled, icon_size_scaled)));
|
||||
|
||||
return icon_full_size;
|
||||
}
|
||||
|
||||
void NodeViewItem::SetLabelAsOutput(bool e)
|
||||
{
|
||||
label_as_output_ = e;
|
||||
output_connector_->setVisible(!e);
|
||||
update();
|
||||
}
|
||||
|
||||
QRectF NodeViewItem::GetInputRect(int index) const
|
||||
QPointF NodeViewItem::GetInputPoint() const
|
||||
{
|
||||
QRectF r = title_bar_rect_;
|
||||
|
||||
if (!hide_titlebar_) {
|
||||
index++;
|
||||
}
|
||||
|
||||
if (IsExpanded()) {
|
||||
r.translate(0, r.height() * index);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
QPointF NodeViewItem::GetInputPoint(const QString &input, int element, const QPointF& source_pos) const
|
||||
{
|
||||
return pos() + GetInputPointInternal(node_inputs_.indexOf(input), source_pos);
|
||||
return input_connector_->scenePos();
|
||||
}
|
||||
|
||||
QPointF NodeViewItem::GetOutputPoint() const
|
||||
{
|
||||
QPointF p = output_connector_->scenePos();
|
||||
QRectF r = output_connector_->boundingRect();
|
||||
|
||||
switch (flow_dir_) {
|
||||
case NodeViewCommon::kLeftToRight:
|
||||
default:
|
||||
return pos() + QPointF(rect().right(), rect().center().y());
|
||||
p.setX(p.x() + r.width());
|
||||
break;
|
||||
case NodeViewCommon::kRightToLeft:
|
||||
return pos() + QPointF(rect().left(), rect().center().y());
|
||||
p.setX(p.x() - r.width());
|
||||
break;
|
||||
case NodeViewCommon::kTopToBottom:
|
||||
return pos() + QPointF(rect().center().x(), rect().bottom());
|
||||
p.setY(p.y() + r.height());
|
||||
break;
|
||||
case NodeViewCommon::kBottomToTop:
|
||||
return pos() + QPointF(rect().center().x(), rect().top());
|
||||
p.setY(p.y() - r.height());
|
||||
break;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void NodeViewItem::SetFlowDirection(NodeViewCommon::FlowDirection dir)
|
||||
{
|
||||
flow_dir_ = dir;
|
||||
if (flow_dir_ != dir) {
|
||||
flow_dir_ = dir;
|
||||
|
||||
UpdateNodePosition();
|
||||
}
|
||||
input_connector_->SetFlowDirection(dir);
|
||||
output_connector_->SetFlowDirection(dir);
|
||||
|
||||
QPointF NodeViewItem::GetInputPointInternal(int index, const QPointF& source_pos) const
|
||||
{
|
||||
QRectF input_rect = GetInputRect(index);
|
||||
UpdateInputConnectorPosition();
|
||||
UpdateOutputConnectorPosition();
|
||||
|
||||
Qt::Orientation flow_orientation = NodeViewCommon::GetFlowOrientation(flow_dir_);
|
||||
|
||||
if (flow_orientation == Qt::Horizontal || IsExpanded()) {
|
||||
if (flow_dir_ == NodeViewCommon::kLeftToRight
|
||||
|| (flow_orientation == Qt::Vertical && source_pos.x() < pos().x())) {
|
||||
return QPointF(input_rect.left(), input_rect.center().y());
|
||||
} else {
|
||||
return QPointF(input_rect.right(), input_rect.center().y());
|
||||
}
|
||||
} else {
|
||||
if (flow_dir_ == NodeViewCommon::kTopToBottom) {
|
||||
return QPointF(input_rect.center().x(), input_rect.top());
|
||||
} else {
|
||||
return QPointF(input_rect.center().x(), input_rect.bottom());
|
||||
if (IsOutputItem()) {
|
||||
UpdateNodePosition();
|
||||
}
|
||||
|
||||
ReadjustAllEdges();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,4 +620,196 @@ void NodeViewItem::UpdateNodePosition()
|
||||
setPos(NodeToScreenPoint(cached_node_pos_, flow_dir_));
|
||||
}
|
||||
|
||||
void NodeViewItem::UpdateInputConnectorPosition()
|
||||
{
|
||||
QRectF output_rect = input_connector_->boundingRect();
|
||||
|
||||
NodeViewCommon::FlowDirection using_flow_dir = flow_dir_;
|
||||
|
||||
if (IsExpanded() && !NodeViewCommon::IsFlowHorizontal(flow_dir_)) {
|
||||
if (edges_.isEmpty() || edges_.first()->from_item()->x() < this->x()) {
|
||||
using_flow_dir = NodeViewCommon::kLeftToRight;
|
||||
} else {
|
||||
using_flow_dir = NodeViewCommon::kRightToLeft;
|
||||
}
|
||||
}
|
||||
|
||||
// Input connector flow directions change conditionally
|
||||
switch (using_flow_dir) {
|
||||
case NodeViewCommon::kLeftToRight:
|
||||
input_connector_->setPos(rect().left() - output_rect.width(), 0);
|
||||
break;
|
||||
case NodeViewCommon::kRightToLeft:
|
||||
input_connector_->setPos(rect().right() + output_rect.width(), 0);
|
||||
break;
|
||||
case NodeViewCommon::kTopToBottom:
|
||||
input_connector_->setPos(rect().center().x(), rect().top() - output_rect.height());
|
||||
break;
|
||||
case NodeViewCommon::kBottomToTop:
|
||||
input_connector_->setPos(rect().center().x(), rect().bottom() + output_rect.height());
|
||||
break;
|
||||
case NodeViewCommon::kInvalidDirection:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewItem::UpdateOutputConnectorPosition()
|
||||
{
|
||||
switch (flow_dir_) {
|
||||
case NodeViewCommon::kLeftToRight:
|
||||
output_connector_->setPos(rect().right(), 0);
|
||||
break;
|
||||
case NodeViewCommon::kRightToLeft:
|
||||
output_connector_->setPos(rect().left(), 0);
|
||||
break;
|
||||
case NodeViewCommon::kTopToBottom:
|
||||
output_connector_->setPos(rect().center().x(), rect().bottom());
|
||||
break;
|
||||
case NodeViewCommon::kBottomToTop:
|
||||
output_connector_->setPos(rect().center().x(), rect().top());
|
||||
break;
|
||||
case NodeViewCommon::kInvalidDirection:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool NodeViewItem::IsInputValid(const QString &input)
|
||||
{
|
||||
return node_->IsInputConnectable(input) && !node_->IsInputHidden(input);
|
||||
}
|
||||
|
||||
void NodeViewItem::SetRectSize(int height_units)
|
||||
{
|
||||
// Set rect
|
||||
int widget_width = DefaultItemWidth();
|
||||
int widget_height = DefaultItemHeight();
|
||||
|
||||
setRect(QRectF(-widget_width/2, -widget_height/2, widget_width, widget_height * height_units));
|
||||
}
|
||||
|
||||
bool NodeViewItem::CanBeExpanded() const
|
||||
{
|
||||
if (IsOutputItem()) {
|
||||
return has_connectable_inputs_;
|
||||
} else {
|
||||
return node_->GetInputFlags(input_) & kInputFlagArray && element_ == -1 && !node_->IsInputConnected(input_);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewItem::UpdateChildrenPositions()
|
||||
{
|
||||
int y = 1;
|
||||
int h = DefaultItemHeight();
|
||||
|
||||
foreach (NodeViewItem *c, children_) {
|
||||
c->setPos(QPointF(0, y * h));
|
||||
|
||||
y += c->GetLogicalHeightWithChildren();
|
||||
}
|
||||
|
||||
SetRectSize(y);
|
||||
|
||||
if (NodeViewItem *p = dynamic_cast<NodeViewItem*>(parentItem())) {
|
||||
p->UpdateChildrenPositions();
|
||||
}
|
||||
}
|
||||
|
||||
int NodeViewItem::GetLogicalHeightWithChildren() const
|
||||
{
|
||||
int h = 1;
|
||||
|
||||
foreach (NodeViewItem *c, children_) {
|
||||
h += c->GetLogicalHeightWithChildren();
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
void NodeViewItem::UpdateFlowDirectionOfInputItem(NodeViewItem *child)
|
||||
{
|
||||
if (!child->IsOutputItem()) {
|
||||
if (NodeViewCommon::IsFlowVertical(flow_dir_)) {
|
||||
if (!child->edges().isEmpty() && child->edges().first()->from_item()->scenePos().x() > child->scenePos().x()) {
|
||||
child->SetFlowDirection(NodeViewCommon::kRightToLeft);
|
||||
} else {
|
||||
child->SetFlowDirection(NodeViewCommon::kLeftToRight);
|
||||
}
|
||||
} else {
|
||||
child->SetFlowDirection(flow_dir_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewItem::RepopulateInputs()
|
||||
{
|
||||
if (IsOutputItem()) {
|
||||
has_connectable_inputs_ = false;
|
||||
|
||||
foreach (const QString& input, node_->inputs()) {
|
||||
if (IsInputValid(input)) {
|
||||
has_connectable_inputs_ = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
input_connector_->setVisible(has_connectable_inputs_);
|
||||
}
|
||||
|
||||
if (IsExpanded() && (IsOutputItem() || element_ == -1)) {
|
||||
// Create or remove inputs when necessary
|
||||
// NOTE: This is not the most efficient thing in the world, but it does work
|
||||
SetExpanded(false);
|
||||
SetExpanded(true);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewItem::InputArraySizeChanged(const QString &input)
|
||||
{
|
||||
if (input == input_) {
|
||||
RepopulateInputs();
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewItem::NodeAppearanceChanged()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
void NodeViewItem::SetHighlighted(bool e)
|
||||
{
|
||||
highlighted_ = e;
|
||||
update();
|
||||
}
|
||||
|
||||
NodeViewItem *NodeViewItem::GetItemForInput(NodeInput input)
|
||||
{
|
||||
if (NodeGroup *group = dynamic_cast<NodeGroup*>(node_)) {
|
||||
if (input.node() != group) {
|
||||
// Translate input to group input
|
||||
QString id = NodeGroup::GetGroupInputIDFromInput(input);
|
||||
input.set_node(group);
|
||||
input.set_input(id);
|
||||
}
|
||||
}
|
||||
|
||||
if (IsExpanded()) {
|
||||
if (input_.isEmpty()) {
|
||||
// Look for the input in our children
|
||||
foreach (NodeViewItem *i, children_) {
|
||||
if (i->input_ == input.input()) {
|
||||
return i->GetItemForInput(input);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Look for element in our children
|
||||
if (input.element() >= 0 && input.element() < children_.size()) {
|
||||
return children_.at(input.element())->GetItemForInput(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to this object
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,9 +28,11 @@
|
||||
|
||||
#include "node/node.h"
|
||||
#include "nodeviewcommon.h"
|
||||
#include "nodeviewitemconnector.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class NodeViewItem;
|
||||
class NodeViewEdge;
|
||||
|
||||
/**
|
||||
@@ -40,18 +42,24 @@ class NodeViewEdge;
|
||||
*
|
||||
* To retrieve the NodeViewItem for a certain Node, use NodeView::NodeToUIObject().
|
||||
*/
|
||||
class NodeViewItem : public QGraphicsRectItem
|
||||
class NodeViewItem : public QObject, public QGraphicsRectItem
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
NodeViewItem(QGraphicsItem* parent = nullptr);
|
||||
NodeViewItem(Node *node, const QString &input, int element, Node *context, QGraphicsItem* parent = nullptr);
|
||||
NodeViewItem(Node *node, Node *context, QGraphicsItem* parent = nullptr) :
|
||||
NodeViewItem(node, QString(), -1, context, parent)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~NodeViewItem() override;
|
||||
|
||||
Node::Position GetNodePositionData() const;
|
||||
QPointF GetNodePosition() const;
|
||||
void SetNodePosition(const QPointF& pos);
|
||||
void SetNodePosition(const Node::Position& pos);
|
||||
|
||||
/**
|
||||
* @brief Set the Node to correspond to this widget
|
||||
*/
|
||||
void SetNode(Node* n);
|
||||
QVector<NodeViewEdge*> GetAllEdgesRecursively() const;
|
||||
|
||||
/**
|
||||
* @brief Get currently attached node
|
||||
@@ -61,6 +69,16 @@ public:
|
||||
return node_;
|
||||
}
|
||||
|
||||
NodeInput GetInput() const
|
||||
{
|
||||
return NodeInput(node_, input_, element_);
|
||||
}
|
||||
|
||||
Node *GetContext() const
|
||||
{
|
||||
return context_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get expanded state
|
||||
*/
|
||||
@@ -69,17 +87,18 @@ public:
|
||||
return expanded_;
|
||||
}
|
||||
|
||||
const QVector<NodeViewEdge*> &edges() const
|
||||
{
|
||||
return edges_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set expanded state
|
||||
*/
|
||||
void SetExpanded(bool e, bool hide_titlebar = false);
|
||||
void ToggleExpanded();
|
||||
|
||||
/**
|
||||
* @brief Returns GLOBAL point that edges should connect to for any NodeParam member of this object
|
||||
*/
|
||||
QPointF GetInputPoint(const QString& input, int element, const QPointF &source_pos) const;
|
||||
|
||||
QPointF GetInputPoint() const;
|
||||
QPointF GetOutputPoint() const;
|
||||
|
||||
/**
|
||||
@@ -87,6 +106,11 @@ public:
|
||||
*/
|
||||
void SetFlowDirection(NodeViewCommon::FlowDirection dir);
|
||||
|
||||
NodeViewCommon::FlowDirection GetFlowDirection() const
|
||||
{
|
||||
return flow_dir_;
|
||||
}
|
||||
|
||||
static int DefaultTextPadding();
|
||||
|
||||
static int DefaultItemHeight();
|
||||
@@ -106,29 +130,27 @@ public:
|
||||
void AddEdge(NodeViewEdge* edge);
|
||||
void RemoveEdge(NodeViewEdge* edge);
|
||||
|
||||
int GetIndexAt(QPointF pt) const;
|
||||
|
||||
NodeInput GetInputAtIndex(int index) const
|
||||
bool IsLabelledAsOutputOfContext() const
|
||||
{
|
||||
return NodeInput(node_, node_inputs_.at(index));
|
||||
return label_as_output_;
|
||||
}
|
||||
|
||||
void SetHighlightedIndex(int index);
|
||||
void SetLabelAsOutput(bool e);
|
||||
|
||||
void SetPreventRemoving(bool e)
|
||||
void SetHighlighted(bool e);
|
||||
|
||||
NodeViewItem *GetItemForInput(NodeInput input);
|
||||
|
||||
bool IsOutputItem() const
|
||||
{
|
||||
prevent_removing_ = e;
|
||||
return input_.isEmpty();
|
||||
}
|
||||
|
||||
bool GetPreventRemoving() const
|
||||
{
|
||||
return prevent_removing_;
|
||||
}
|
||||
void ReadjustAllEdges();
|
||||
|
||||
const QPolygonF &GetOutputTriangle() const
|
||||
{
|
||||
return output_triangle_;
|
||||
}
|
||||
void UpdateFlowDirectionOfInputItem(NodeViewItem *child);
|
||||
|
||||
bool CanBeExpanded() const;
|
||||
|
||||
protected:
|
||||
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
|
||||
@@ -136,44 +158,45 @@ protected:
|
||||
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
|
||||
virtual QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
|
||||
|
||||
private:
|
||||
void ReadjustAllEdges();
|
||||
void UpdateContextRect();
|
||||
|
||||
void DrawNodeTitle(QPainter *painter, QString text, const QRectF &rect, Qt::Alignment vertical_align, int icon_size, bool draw_arrow);
|
||||
void DrawNodeTitle(QPainter *painter, QString text, const QRectF &rect, Qt::Alignment vertical_align, int icon_full_size);
|
||||
|
||||
/**
|
||||
* @brief Returns local rect of a NodeInput in array node_inputs_[index]
|
||||
*/
|
||||
QRectF GetInputRect(int index) const;
|
||||
|
||||
/**
|
||||
* @brief Returns local point that edges should connect to for a NodeInput in array node_inputs_[index]
|
||||
*/
|
||||
QPointF GetInputPointInternal(int index, const QPointF &source_pos) const;
|
||||
int DrawExpandArrow(QPainter *painter);
|
||||
|
||||
/**
|
||||
* @brief Internal update function when logical position changes
|
||||
*/
|
||||
void UpdateNodePosition();
|
||||
|
||||
void UpdateInputConnectorPosition();
|
||||
void UpdateOutputConnectorPosition();
|
||||
|
||||
bool IsInputValid(const QString &input);
|
||||
|
||||
void SetRectSize(int height_units = 1);
|
||||
|
||||
void UpdateChildrenPositions();
|
||||
|
||||
int GetLogicalHeightWithChildren() const;
|
||||
|
||||
/**
|
||||
* @brief Reference to attached Node
|
||||
*/
|
||||
Node* node_;
|
||||
Node *node_;
|
||||
QString input_;
|
||||
int element_;
|
||||
|
||||
Node *context_;
|
||||
|
||||
/**
|
||||
* @brief Cached list of node inputs
|
||||
*/
|
||||
QVector<QString> node_inputs_;
|
||||
|
||||
/**
|
||||
* @brief Rectangle of the Node's title bar (equal to rect() when collapsed)
|
||||
*/
|
||||
QRectF title_bar_rect_;
|
||||
QVector<NodeViewItem*> children_;
|
||||
|
||||
/// Sizing variables to use when drawing
|
||||
int node_border_width_;
|
||||
@@ -183,9 +206,7 @@ private:
|
||||
*/
|
||||
bool expanded_;
|
||||
|
||||
bool hide_titlebar_;
|
||||
|
||||
int highlighted_index_;
|
||||
bool highlighted_;
|
||||
|
||||
NodeViewCommon::FlowDirection flow_dir_;
|
||||
|
||||
@@ -193,9 +214,22 @@ private:
|
||||
|
||||
QPointF cached_node_pos_;
|
||||
|
||||
bool prevent_removing_;
|
||||
QRect last_arrow_rect_;
|
||||
bool arrow_click_;
|
||||
|
||||
QPolygonF output_triangle_;
|
||||
NodeViewItemConnector *input_connector_;
|
||||
NodeViewItemConnector *output_connector_;
|
||||
|
||||
bool has_connectable_inputs_;
|
||||
|
||||
bool label_as_output_;
|
||||
|
||||
private slots:
|
||||
void NodeAppearanceChanged();
|
||||
|
||||
void RepopulateInputs();
|
||||
|
||||
void InputArraySizeChanged(const QString &input);
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "nodeviewitemconnector.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFontMetrics>
|
||||
#include <QPalette>
|
||||
#include <QPen>
|
||||
|
||||
#include "nodeviewitem.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
NodeViewItemConnector::NodeViewItemConnector(bool is_output, QGraphicsItem *parent) :
|
||||
QGraphicsPolygonItem(parent),
|
||||
output_(is_output)
|
||||
{
|
||||
QColor c = qApp->palette().text().color();
|
||||
setPen(QPen(c, NodeViewItem::DefaultItemBorder()));
|
||||
setBrush(c);
|
||||
}
|
||||
|
||||
void NodeViewItemConnector::SetFlowDirection(NodeViewCommon::FlowDirection dir)
|
||||
{
|
||||
QFont f;
|
||||
QFontMetricsF fm(f);
|
||||
|
||||
int triangle_sz = fm.height()/2;
|
||||
int triangle_sz_half = triangle_sz / 2;
|
||||
|
||||
QPolygonF p;
|
||||
p.resize(3);
|
||||
|
||||
switch (dir) {
|
||||
case NodeViewCommon::kLeftToRight:
|
||||
// Triangle pointing right
|
||||
p[0] = QPointF(0, -triangle_sz_half);
|
||||
p[1] = QPointF(triangle_sz_half, 0);
|
||||
p[2] = QPointF(0, triangle_sz_half);
|
||||
break;
|
||||
case NodeViewCommon::kTopToBottom:
|
||||
// Triangle pointing down
|
||||
p[0] = QPointF(-triangle_sz_half, 0);
|
||||
p[1] = QPointF(0, triangle_sz_half);
|
||||
p[2] = QPointF(triangle_sz_half, 0);
|
||||
break;
|
||||
case NodeViewCommon::kBottomToTop:
|
||||
// Triangle pointing up
|
||||
p[0] = QPointF(-triangle_sz_half, 0);
|
||||
p[1] = QPointF(0, -triangle_sz_half);
|
||||
p[2] = QPointF(triangle_sz_half, 0);
|
||||
break;
|
||||
case NodeViewCommon::kRightToLeft:
|
||||
// Triangle pointing left
|
||||
p[0] = QPointF(0, -triangle_sz_half);
|
||||
p[1] = QPointF(-triangle_sz_half, 0);
|
||||
p[2] = QPointF(0, triangle_sz_half);
|
||||
break;
|
||||
case NodeViewCommon::kInvalidDirection:
|
||||
break;
|
||||
}
|
||||
|
||||
setPolygon(p);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef NODEVIEWITEMCONNECTOR_H
|
||||
#define NODEVIEWITEMCONNECTOR_H
|
||||
|
||||
#include <QGraphicsPolygonItem>
|
||||
|
||||
#include "nodeviewcommon.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class NodeViewItemConnector : public QGraphicsPolygonItem
|
||||
{
|
||||
public:
|
||||
NodeViewItemConnector(bool is_output, QGraphicsItem *parent = nullptr);
|
||||
|
||||
void SetFlowDirection(NodeViewCommon::FlowDirection dir);
|
||||
|
||||
bool IsOutput() const
|
||||
{
|
||||
return output_;
|
||||
}
|
||||
|
||||
private:
|
||||
bool output_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // NODEVIEWITEMCONNECTOR_H
|
||||
@@ -38,6 +38,7 @@ NodeViewMiniMap::NodeViewMiniMap(NodeViewScene *scene, QWidget *parent) :
|
||||
setViewportUpdateMode(FullViewportUpdate);
|
||||
setFrameShape(QFrame::Panel);
|
||||
setFrameShadow(QFrame::Plain);
|
||||
setMouseTracking(true);
|
||||
|
||||
QMetaObject::invokeMethod(this, &NodeViewMiniMap::SetDefaultSize, Qt::QueuedConnection);
|
||||
|
||||
@@ -87,7 +88,7 @@ void NodeViewMiniMap::resizeEvent(QResizeEvent *event)
|
||||
void NodeViewMiniMap::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
if (event->pos().x() <= resize_triangle_sz_ && event->pos().y() <= resize_triangle_sz_) {
|
||||
if (MouseInsideResizeTriangle(event)) {
|
||||
// Resizing!
|
||||
resizing_ = true;
|
||||
resize_anchor_ = QCursor::pos();
|
||||
@@ -107,6 +108,8 @@ void NodeViewMiniMap::mouseMoveEvent(QMouseEvent *event)
|
||||
} else {
|
||||
EmitMoveSignal(event);
|
||||
}
|
||||
} else {
|
||||
setCursor(MouseInsideResizeTriangle(event) ? Qt::SizeFDiagCursor : Qt::ArrowCursor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +138,11 @@ void NodeViewMiniMap::SetDefaultSize()
|
||||
}
|
||||
}
|
||||
|
||||
bool NodeViewMiniMap::MouseInsideResizeTriangle(QMouseEvent *event)
|
||||
{
|
||||
return event->pos().x() <= resize_triangle_sz_ && event->pos().y() <= resize_triangle_sz_;
|
||||
}
|
||||
|
||||
void NodeViewMiniMap::EmitMoveSignal(QMouseEvent *event)
|
||||
{
|
||||
emit MoveToScenePoint(mapToScene(event->pos()));
|
||||
|
||||
@@ -57,6 +57,8 @@ private slots:
|
||||
void SetDefaultSize();
|
||||
|
||||
private:
|
||||
bool MouseInsideResizeTriangle(QMouseEvent *event);
|
||||
|
||||
void EmitMoveSignal(QMouseEvent *event);
|
||||
|
||||
int resize_triangle_sz_;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "nodeviewscene.h"
|
||||
|
||||
#include "common/functiontimer.h"
|
||||
#include "core.h"
|
||||
#include "node/project/sequence/sequence.h"
|
||||
#include "nodeviewedge.h"
|
||||
#include "nodeviewitem.h"
|
||||
@@ -38,203 +39,65 @@ void NodeViewScene::SetFlowDirection(NodeViewCommon::FlowDirection direction)
|
||||
{
|
||||
direction_ = direction;
|
||||
|
||||
{
|
||||
// Iterate over node items setting direction
|
||||
QHash<Node*, NodeViewItem*>::const_iterator i;
|
||||
for (i=item_map_.constBegin(); i!=item_map_.constEnd(); i++) {
|
||||
i.value()->SetFlowDirection(direction_);
|
||||
}
|
||||
foreach (NodeViewContext *ctx, context_map_) {
|
||||
ctx->SetFlowDirection(direction_);
|
||||
}
|
||||
|
||||
{
|
||||
// Iterate over edge items setting direction
|
||||
foreach (NodeViewEdge* edge, edges_) {
|
||||
edge->SetFlowDirection(direction_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewScene::clear()
|
||||
{
|
||||
// Deselect everything (prevents signals that a selection has changed after deleting an object)
|
||||
DeselectAll();
|
||||
|
||||
// HACK: QGraphicsScene contains some sort of internal caching of the selected items which doesn't update unless
|
||||
// we call a function like this. That means even though we deselect all items above, QGraphicsScene will
|
||||
// continue to incorrectly signal selectionChanged() when items that were selected (but are now not) get
|
||||
// deleted. Calling this function appears to update the internal cache and prevent this.
|
||||
selectedItems();
|
||||
|
||||
for (auto it=item_map_.cbegin(); it!=item_map_.cend(); it++) {
|
||||
DisconnectNode(it.key());
|
||||
delete it.value();
|
||||
}
|
||||
item_map_.clear();
|
||||
|
||||
qDeleteAll(edges_);
|
||||
edges_.clear();
|
||||
}
|
||||
|
||||
void NodeViewScene::SelectAll()
|
||||
{
|
||||
QList<QGraphicsItem *> all_items = this->items();
|
||||
|
||||
foreach (QGraphicsItem* i, all_items) {
|
||||
foreach (QGraphicsItem* i, items()) {
|
||||
i->setSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewScene::DeselectAll()
|
||||
{
|
||||
QList<QGraphicsItem *> selected_items = this->selectedItems();
|
||||
|
||||
foreach (QGraphicsItem* i, selected_items) {
|
||||
foreach (QGraphicsItem* i, items()) {
|
||||
i->setSelected(false);
|
||||
}
|
||||
}
|
||||
|
||||
NodeViewItem *NodeViewScene::NodeToUIObject(Node *n)
|
||||
{
|
||||
return item_map_.value(n);
|
||||
}
|
||||
|
||||
NodeViewEdge *NodeViewScene::EdgeToUIObject(Node *output, const NodeInput& input)
|
||||
{
|
||||
foreach (NodeViewEdge* edge, edges_) {
|
||||
if (edge->output() == output && edge->input() == input) {
|
||||
return edge;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QVector<Node *> NodeViewScene::GetSelectedNodes() const
|
||||
{
|
||||
QHash<Node*, NodeViewItem*>::const_iterator iterator;
|
||||
QVector<Node *> selected;
|
||||
|
||||
for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) {
|
||||
if (iterator.value()->isSelected()) {
|
||||
selected.append(iterator.key());
|
||||
}
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
QVector<NodeViewItem *> NodeViewScene::GetSelectedItems() const
|
||||
{
|
||||
QHash<Node*, NodeViewItem*>::const_iterator iterator;
|
||||
QVector<NodeViewItem *> selected;
|
||||
QVector<NodeViewItem *> items;
|
||||
|
||||
for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) {
|
||||
if (iterator.value()->isSelected()) {
|
||||
selected.append(iterator.value());
|
||||
foreach (NodeViewContext *ctx, context_map_) {
|
||||
items.append(ctx->GetSelectedItems());
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
NodeViewContext *NodeViewScene::AddContext(Node *node)
|
||||
{
|
||||
NodeViewContext *context_item = context_map_.value(node);
|
||||
|
||||
if (!context_item) {
|
||||
context_item = new NodeViewContext(node);
|
||||
|
||||
context_item->SetFlowDirection(GetFlowDirection());
|
||||
context_item->SetCurvedEdges(GetEdgesAreCurved());
|
||||
|
||||
QPointF pos(0, 0);
|
||||
QRectF item_rect = context_item->rect();
|
||||
while (!items(item_rect).isEmpty()) {
|
||||
pos.setY(pos.y() + item_rect.height());
|
||||
item_rect = context_item->rect().translated(pos);
|
||||
}
|
||||
context_item->setPos(pos);
|
||||
|
||||
addItem(context_item);
|
||||
|
||||
context_map_.insert(node, context_item);
|
||||
}
|
||||
|
||||
return selected;
|
||||
return context_item;
|
||||
}
|
||||
|
||||
QVector<NodeViewEdge *> NodeViewScene::GetSelectedEdges() const
|
||||
void NodeViewScene::RemoveContext(Node *node)
|
||||
{
|
||||
QVector<NodeViewEdge*> edges;
|
||||
|
||||
foreach (NodeViewEdge* e, edges_) {
|
||||
if (e->isSelected()) {
|
||||
edges.append(e);
|
||||
}
|
||||
}
|
||||
|
||||
return edges;
|
||||
}
|
||||
|
||||
NodeViewItem* NodeViewScene::AddNode(Node* node)
|
||||
{
|
||||
NodeViewItem* item = new NodeViewItem();
|
||||
|
||||
item->SetFlowDirection(direction_);
|
||||
item->SetNode(node);
|
||||
|
||||
addItem(item);
|
||||
item_map_.insert(node, item);
|
||||
|
||||
ConnectNode(node);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void NodeViewScene::RemoveNode(Node *node)
|
||||
{
|
||||
DisconnectNode(node);
|
||||
|
||||
delete item_map_.take(node);
|
||||
}
|
||||
|
||||
NodeViewEdge* NodeViewScene::AddEdge(Node *output, const NodeInput &input)
|
||||
{
|
||||
NodeViewEdge *edge = EdgeToUIObject(output, input);
|
||||
|
||||
if (!edge) {
|
||||
edge = AddEdgeInternal(output, input, NodeToUIObject(output), NodeToUIObject(input.node()));
|
||||
}
|
||||
|
||||
return edge;
|
||||
}
|
||||
|
||||
void NodeViewScene::RemoveEdge(Node *output, const NodeInput &input)
|
||||
{
|
||||
NodeViewEdge* edge = EdgeToUIObject(output, input);
|
||||
if (edge) {
|
||||
edge->from_item()->RemoveEdge(edge);
|
||||
edge->to_item()->RemoveEdge(edge);
|
||||
edges_.removeOne(edge);
|
||||
delete edge;
|
||||
}
|
||||
}
|
||||
|
||||
int NodeViewScene::DetermineWeight(Node *n)
|
||||
{
|
||||
QVector<Node*> inputs = n->GetImmediateDependencies();
|
||||
|
||||
int weight = 0;
|
||||
|
||||
foreach (Node* i, inputs) {
|
||||
if (i->GetNumberOfRoutesTo(n) == 1) {
|
||||
weight += DetermineWeight(i);
|
||||
}
|
||||
}
|
||||
|
||||
return qMax(1, weight);
|
||||
}
|
||||
|
||||
NodeViewEdge* NodeViewScene::AddEdgeInternal(Node *output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to)
|
||||
{
|
||||
NodeViewEdge* edge_ui = new NodeViewEdge(output, input, from, to);
|
||||
|
||||
edge_ui->SetFlowDirection(direction_);
|
||||
edge_ui->SetCurved(curved_edges_);
|
||||
|
||||
from->AddEdge(edge_ui);
|
||||
to->AddEdge(edge_ui);
|
||||
|
||||
addItem(edge_ui);
|
||||
edges_.append(edge_ui);
|
||||
|
||||
return edge_ui;
|
||||
}
|
||||
|
||||
void NodeViewScene::ConnectNode(Node *n)
|
||||
{
|
||||
connect(n, &Node::LabelChanged, this, &NodeViewScene::NodeAppearanceChanged);
|
||||
connect(n, &Node::ColorChanged, this, &NodeViewScene::NodeAppearanceChanged);
|
||||
}
|
||||
|
||||
void NodeViewScene::DisconnectNode(Node *n)
|
||||
{
|
||||
disconnect(n, &Node::ColorChanged, this, &NodeViewScene::NodeAppearanceChanged);
|
||||
disconnect(n, &Node::LabelChanged, this, &NodeViewScene::NodeAppearanceChanged);
|
||||
delete context_map_.take(node);
|
||||
}
|
||||
|
||||
Qt::Orientation NodeViewScene::GetFlowOrientation() const
|
||||
@@ -242,26 +105,15 @@ Qt::Orientation NodeViewScene::GetFlowOrientation() const
|
||||
return NodeViewCommon::GetFlowOrientation(direction_);
|
||||
}
|
||||
|
||||
NodeViewCommon::FlowDirection NodeViewScene::GetFlowDirection() const
|
||||
{
|
||||
return direction_;
|
||||
}
|
||||
|
||||
void NodeViewScene::SetEdgesAreCurved(bool curved)
|
||||
{
|
||||
if (curved_edges_ != curved) {
|
||||
curved_edges_ = curved;
|
||||
|
||||
foreach (NodeViewEdge* e, edges_) {
|
||||
e->SetCurved(curved_edges_);
|
||||
foreach (NodeViewContext *ctx, context_map_) {
|
||||
ctx->SetCurvedEdges(curved_edges_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewScene::NodeAppearanceChanged()
|
||||
{
|
||||
// Force item to update
|
||||
item_map_.value(static_cast<Node*>(sender()))->update();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,8 +25,10 @@
|
||||
#include <QTimer>
|
||||
|
||||
#include "node/graph.h"
|
||||
#include "nodeviewcontext.h"
|
||||
#include "nodeviewedge.h"
|
||||
#include "nodeviewitem.h"
|
||||
#include "undo/undostack.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -36,42 +38,23 @@ class NodeViewScene : public QGraphicsScene
|
||||
public:
|
||||
NodeViewScene(QObject *parent = nullptr);
|
||||
|
||||
void clear();
|
||||
|
||||
void SelectAll();
|
||||
void DeselectAll();
|
||||
|
||||
/**
|
||||
* @brief Retrieve the graphical widget corresponding to a specific Node
|
||||
*
|
||||
* In situations where you know what Node you're working with but need the UI object (e.g. for positioning), this
|
||||
* static function will retrieve the NodeViewItem (Node UI representation) connected to this Node in a certain
|
||||
* QGraphicsScene. This can be called from any other UI object, since it'll have a reference to the QGraphicsScene
|
||||
* through QGraphicsItem::scene().
|
||||
*
|
||||
* If the scene does not contain a widget for this node (usually meaning the node's graph is not the active graph
|
||||
* in this view/scene), this function returns nullptr.
|
||||
*/
|
||||
NodeViewItem* NodeToUIObject(Node* n);
|
||||
NodeViewEdge *EdgeToUIObject(Node *output, const NodeInput &input);
|
||||
|
||||
QVector<Node *> GetSelectedNodes() const;
|
||||
QVector<NodeViewItem*> GetSelectedItems() const;
|
||||
QVector<NodeViewEdge*> GetSelectedEdges() const;
|
||||
|
||||
const QHash<Node*, NodeViewItem*>& item_map() const
|
||||
const QHash<Node*, NodeViewContext*> &context_map() const
|
||||
{
|
||||
return item_map_;
|
||||
}
|
||||
|
||||
const QVector<NodeViewEdge*>& edges() const
|
||||
{
|
||||
return edges_;
|
||||
return context_map_;
|
||||
}
|
||||
|
||||
Qt::Orientation GetFlowOrientation() const;
|
||||
|
||||
NodeViewCommon::FlowDirection GetFlowDirection() const;
|
||||
NodeViewCommon::FlowDirection GetFlowDirection() const
|
||||
{
|
||||
return direction_;
|
||||
}
|
||||
|
||||
void SetFlowDirection(NodeViewCommon::FlowDirection direction);
|
||||
|
||||
bool GetEdgesAreCurved() const
|
||||
@@ -80,24 +63,8 @@ public:
|
||||
}
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief Slot when a Node is added to a graph (SetGraph() connects this)
|
||||
*
|
||||
* This should NEVER be called directly, only connected to a NodeGraph. To add a Node to the NodeGraph
|
||||
* use NodeGraph::AddNode().
|
||||
*/
|
||||
NodeViewItem *AddNode(Node* node);
|
||||
|
||||
/**
|
||||
* @brief Slot when a Node is removed from a graph (SetGraph() connects this)
|
||||
*
|
||||
* This should NEVER be called directly, only connected to a NodeGraph. To remove a Node from the NodeGraph
|
||||
* use NodeGraph::RemoveNode().
|
||||
*/
|
||||
void RemoveNode(Node* node);
|
||||
|
||||
NodeViewEdge *AddEdge(Node *output, const NodeInput& input);
|
||||
void RemoveEdge(Node *output, const NodeInput& input);
|
||||
NodeViewContext *AddContext(Node *node);
|
||||
void RemoveContext(Node *node);
|
||||
|
||||
/**
|
||||
* @brief Set whether edges in this scene should be curved or not
|
||||
@@ -105,17 +72,7 @@ public slots:
|
||||
void SetEdgesAreCurved(bool curved);
|
||||
|
||||
private:
|
||||
static int DetermineWeight(Node* n);
|
||||
|
||||
NodeViewEdge* AddEdgeInternal(Node *output, const NodeInput &input, NodeViewItem* from, NodeViewItem* to);
|
||||
|
||||
void ConnectNode(Node *n);
|
||||
|
||||
void DisconnectNode(Node *n);
|
||||
|
||||
QHash<Node*, NodeViewItem*> item_map_;
|
||||
|
||||
QVector<NodeViewEdge*> edges_;
|
||||
QHash<Node*, NodeViewContext*> context_map_;
|
||||
|
||||
NodeGraph* graph_;
|
||||
|
||||
@@ -123,12 +80,6 @@ private:
|
||||
|
||||
bool curved_edges_;
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* @brief Receiver for when a node's label has changed
|
||||
*/
|
||||
void NodeAppearanceChanged();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -193,4 +193,100 @@ void NodeOverrideColorCommand::undo()
|
||||
node_->SetOverrideColor(old_index_);
|
||||
}
|
||||
|
||||
NodeViewDeleteCommand::NodeViewDeleteCommand()
|
||||
{
|
||||
}
|
||||
|
||||
void NodeViewDeleteCommand::AddNode(Node *node, Node *context)
|
||||
{
|
||||
foreach (const NodePair &pair, nodes_) {
|
||||
if (pair.first == node && pair.second == context) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
nodes_.append(NodePair({node, context}));
|
||||
|
||||
for (auto it=node->input_connections().cbegin(); it!=node->input_connections().cend(); it++) {
|
||||
if (context->ContextContainsNode(it->second)) {
|
||||
AddEdge(it->second, it->first);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it=node->output_connections().cbegin(); it!=node->output_connections().cend(); it++) {
|
||||
if (context->ContextContainsNode(it->second.node())) {
|
||||
AddEdge(it->first, it->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewDeleteCommand::AddEdge(Node *output, const NodeInput &input)
|
||||
{
|
||||
foreach (const Node::OutputConnection &edge, edges_) {
|
||||
if (edge.first == output && edge.second == input) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
edges_.append({output, input});
|
||||
}
|
||||
|
||||
Project *NodeViewDeleteCommand::GetRelevantProject() const
|
||||
{
|
||||
if (!nodes_.isEmpty()) {
|
||||
return nodes_.first().first->project();
|
||||
}
|
||||
|
||||
if (!edges_.isEmpty()) {
|
||||
return edges_.first().first->project();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void NodeViewDeleteCommand::redo()
|
||||
{
|
||||
foreach (const Node::OutputConnection &edge, edges_) {
|
||||
Node::DisconnectEdge(edge.first, edge.second);
|
||||
}
|
||||
|
||||
foreach (const NodePair &pair, nodes_) {
|
||||
RemovedNode rn;
|
||||
|
||||
rn.node = pair.first;
|
||||
rn.context = pair.second;
|
||||
rn.pos = rn.context->GetNodePositionInContext(rn.node);
|
||||
|
||||
rn.context->RemoveNodeFromContext(rn.node);
|
||||
|
||||
// If node is no longer in any contexts and is not connected to anything, remove it
|
||||
if (rn.node->parent()->GetNumberOfContextsNodeIsIn(rn.node, true) == 0
|
||||
&& rn.node->input_connections().empty()
|
||||
&& rn.node->output_connections().empty()) {
|
||||
rn.removed_from_graph = rn.node->parent();
|
||||
rn.node->setParent(&memory_manager_);
|
||||
} else {
|
||||
rn.removed_from_graph = nullptr;
|
||||
}
|
||||
|
||||
removed_nodes_.append(rn);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewDeleteCommand::undo()
|
||||
{
|
||||
for (auto rn=removed_nodes_.crbegin(); rn!=removed_nodes_.crend(); rn++) {
|
||||
if (rn->removed_from_graph) {
|
||||
rn->node->setParent(rn->removed_from_graph);
|
||||
}
|
||||
|
||||
rn->context->SetNodePositionInContext(rn->node, rn->pos);
|
||||
}
|
||||
removed_nodes_.clear();
|
||||
|
||||
for (auto edge=edges_.crbegin(); edge!=edges_.crend(); edge++) {
|
||||
Node::ConnectEdge(edge->first, edge->second);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -363,6 +363,42 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class NodeViewDeleteCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
NodeViewDeleteCommand();
|
||||
|
||||
void AddNode(Node *node, Node *context);
|
||||
|
||||
void AddEdge(Node *output, const NodeInput &input);
|
||||
|
||||
virtual Project * GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
using NodePair = QPair<Node *, Node*>;
|
||||
|
||||
QVector<NodePair> nodes_;
|
||||
|
||||
QVector<Node::OutputConnection> edges_;
|
||||
|
||||
struct RemovedNode {
|
||||
Node *node;
|
||||
Node *context;
|
||||
QPointF pos;
|
||||
NodeGraph *removed_from_graph;
|
||||
};
|
||||
|
||||
QVector<RemovedNode> removed_nodes_;
|
||||
|
||||
QObject memory_manager_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // NODEVIEWUNDO_H
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "nodewidget.h"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace olive {
|
||||
|
||||
NodeWidget::NodeWidget(QWidget *parent) :
|
||||
QWidget(parent)
|
||||
{
|
||||
QVBoxLayout *outer_layout = new QVBoxLayout(this);
|
||||
outer_layout->setMargin(0);
|
||||
|
||||
toolbar_ = new NodeViewToolBar();
|
||||
outer_layout->addWidget(toolbar_);
|
||||
|
||||
// Create NodeView widget
|
||||
node_view_ = new NodeView(this);
|
||||
outer_layout->addWidget(node_view_);
|
||||
|
||||
// Connect toolbar to NodeView
|
||||
connect(toolbar_, &NodeViewToolBar::MiniMapEnabledToggled, node_view_, &NodeView::SetMiniMapEnabled);
|
||||
connect(toolbar_, &NodeViewToolBar::AddNodeClicked, node_view_, &NodeView::ShowAddMenu);
|
||||
|
||||
// Set defaults
|
||||
toolbar_->SetMiniMapEnabled(true);
|
||||
node_view_->SetMiniMapEnabled(true);
|
||||
|
||||
setSizePolicy(node_view_->sizePolicy());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef NODEWIDGET_H
|
||||
#define NODEWIDGET_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "nodeview.h"
|
||||
#include "nodeviewtoolbar.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class NodeWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
NodeWidget(QWidget *parent = nullptr);
|
||||
|
||||
NodeView *view() const
|
||||
{
|
||||
return node_view_;
|
||||
}
|
||||
|
||||
void SetContexts(const QVector<Node*> &nodes)
|
||||
{
|
||||
node_view_->SetContexts(nodes);
|
||||
toolbar_->setEnabled(!nodes.isEmpty());
|
||||
}
|
||||
|
||||
private:
|
||||
NodeView *node_view_;
|
||||
|
||||
NodeViewToolBar *toolbar_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // NODEWIDGET_H
|
||||
@@ -28,6 +28,8 @@
|
||||
#include <QStyleOption>
|
||||
#include <QVariant>
|
||||
|
||||
#include "panel/panelmanager.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
PanelWidget::PanelWidget(const QString &object_name, QWidget *parent) :
|
||||
@@ -39,6 +41,13 @@ PanelWidget::PanelWidget(const QString &object_name, QWidget *parent) :
|
||||
setFocusPolicy(Qt::ClickFocus);
|
||||
|
||||
connect(this, &PanelWidget::visibilityChanged, this, &PanelWidget::PanelVisibilityChanged);
|
||||
|
||||
PanelManager::instance()->RegisterPanel(this);
|
||||
}
|
||||
|
||||
PanelWidget::~PanelWidget()
|
||||
{
|
||||
PanelManager::instance()->UnregisterPanel(this);
|
||||
}
|
||||
|
||||
void PanelWidget::SetMovementLocked(bool locked)
|
||||
|
||||
@@ -45,6 +45,8 @@ public:
|
||||
*/
|
||||
PanelWidget(const QString& object_name, QWidget* parent);
|
||||
|
||||
virtual ~PanelWidget() override;
|
||||
|
||||
/**
|
||||
* @brief Set whether panel movement is locked or not
|
||||
*/
|
||||
|
||||
@@ -39,7 +39,7 @@ PlaybackControls::PlaybackControls(QWidget *parent) :
|
||||
lower_control_layout->setSpacing(0);
|
||||
lower_control_layout->setMargin(0);
|
||||
|
||||
QSizePolicy lower_container_size_policy(QSizePolicy::Minimum, QSizePolicy::Expanding);
|
||||
QSizePolicy lower_container_size_policy(QSizePolicy::Maximum, QSizePolicy::Expanding);
|
||||
lower_container_size_policy.setHorizontalStretch(1);
|
||||
|
||||
// In the lower-left, we create a current timecode label wrapped in a QWidget for fixed sizing
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
#include "common/define.h"
|
||||
#include "core.h"
|
||||
#include "dialog/nodeproperties/nodepropertiesdialog.h"
|
||||
#include "dialog/footageproperties/footageproperties.h"
|
||||
#include "dialog/sequence/sequence.h"
|
||||
#include "projectexplorerundo.h"
|
||||
#include "task/precache/precachetask.h"
|
||||
@@ -446,8 +446,8 @@ void ProjectExplorer::ShowItemPropertiesDialog()
|
||||
// FIXME: Support for multiple items
|
||||
if (dynamic_cast<Footage*>(sel)) {
|
||||
|
||||
NodePropertiesDialog npd(sel, static_cast<Footage*>(sel)->GetVideoParams().time_base(), this);
|
||||
npd.exec();
|
||||
FootagePropertiesDialog fpd(this, static_cast<Footage*>(sel));
|
||||
fpd.exec();
|
||||
|
||||
} else if (dynamic_cast<Folder*>(sel)) {
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <QApplication>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QScreen>
|
||||
#include <QtMath>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
@@ -78,6 +79,7 @@ SliderLadder::SliderLadder(double drag_multiplier, int nb_outer_values, QString
|
||||
|
||||
if (UsingLadders()) {
|
||||
drag_start_x_ = -1;
|
||||
wrap_count_ = 0;
|
||||
} else {
|
||||
#if defined(Q_OS_MAC)
|
||||
CGAssociateMouseAndMouseCursorPosition(false);
|
||||
@@ -144,7 +146,7 @@ void SliderLadder::TimerUpdate()
|
||||
|
||||
if (UsingLadders()) {
|
||||
|
||||
bool is_under_mouse = (now_pos >= ladder_left && now_pos <= ladder_right);
|
||||
bool is_under_mouse = (now_pos >= ladder_left && now_pos <= ladder_right && wrap_count_ == 0);
|
||||
|
||||
if (drag_start_x_ != -1 && (is_under_mouse
|
||||
|| (drag_start_x_ < ladder_left && now_pos > ladder_right)
|
||||
@@ -191,6 +193,25 @@ void SliderLadder::TimerUpdate()
|
||||
}
|
||||
|
||||
emit DraggedByValue(now_pos - drag_start_x_, elements_.at(active_element_)->GetMultiplier());
|
||||
|
||||
// Determine if cursor is at desktop edge, if so wrap around to other side
|
||||
int left = 0;
|
||||
int right = 0;
|
||||
foreach (QScreen *screen, qApp->screens()) {
|
||||
left = qMin(left, screen->geometry().left());
|
||||
right = qMax(right, screen->geometry().right());
|
||||
}
|
||||
if (now_pos == left || now_pos == right) {
|
||||
if (now_pos == left) {
|
||||
wrap_count_--;
|
||||
now_pos = right-1;
|
||||
} else {
|
||||
wrap_count_++;
|
||||
now_pos = left+1;
|
||||
}
|
||||
QCursor::setPos(now_pos, QCursor::pos().y());
|
||||
}
|
||||
|
||||
drag_start_x_ = now_pos;
|
||||
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ private:
|
||||
|
||||
int drag_start_x_;
|
||||
int drag_start_y_;
|
||||
int wrap_count_;
|
||||
|
||||
QList<SliderLadderElement*> elements_;
|
||||
|
||||
|
||||
@@ -78,29 +78,46 @@ void FloatSlider::SetDisplayType(const FloatSlider::DisplayType &type)
|
||||
}
|
||||
}
|
||||
|
||||
QString FloatSlider::ValueToString(double val, FloatSlider::DisplayType display, int decimal_places, bool autotrim_decimal_places)
|
||||
double FloatSlider::TransformValueToDisplay(double val, DisplayType display)
|
||||
{
|
||||
switch (display) {
|
||||
case kNormal:
|
||||
// Do nothing, skip to the return string at the end
|
||||
break;
|
||||
case kDecibel:
|
||||
// Convert to decibels and return dB formatted string
|
||||
|
||||
// Return negative infinity for zero volume
|
||||
if (qIsNull(val)) {
|
||||
return tr("\xE2\x88\x9E");
|
||||
}
|
||||
|
||||
val = Decibel::fromLinear(val);
|
||||
break;
|
||||
case kPercentage:
|
||||
// Multiply value by 100 for user-friendly percentage
|
||||
val *= 100.0;
|
||||
break;
|
||||
}
|
||||
|
||||
return FloatToString(val, decimal_places, autotrim_decimal_places);
|
||||
return val;
|
||||
}
|
||||
|
||||
double FloatSlider::TransformDisplayToValue(double val, DisplayType display)
|
||||
{
|
||||
switch (display) {
|
||||
case kNormal:
|
||||
break;
|
||||
case kDecibel:
|
||||
val = Decibel::toLinear(val);
|
||||
break;
|
||||
case kPercentage:
|
||||
val *= 0.01;
|
||||
break;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
QString FloatSlider::ValueToString(double val, FloatSlider::DisplayType display, int decimal_places, bool autotrim_decimal_places)
|
||||
{
|
||||
// Return negative infinity for zero volume
|
||||
if (display == kDecibel && qIsNull(val)) {
|
||||
return tr("\xE2\x88\x9E");
|
||||
}
|
||||
|
||||
return FloatToString(TransformValueToDisplay(val, display), decimal_places, autotrim_decimal_places);
|
||||
}
|
||||
|
||||
QString FloatSlider::ValueToString(const QVariant &v) const
|
||||
@@ -110,46 +127,21 @@ QString FloatSlider::ValueToString(const QVariant &v) const
|
||||
|
||||
QVariant FloatSlider::StringToValue(const QString &s, bool *ok) const
|
||||
{
|
||||
switch (display_type_) {
|
||||
case kNormal:
|
||||
// Do nothing, skip to the return string at the end
|
||||
break;
|
||||
case kDecibel:
|
||||
{
|
||||
bool valid;
|
||||
bool valid;
|
||||
double val = s.toDouble(&valid);
|
||||
|
||||
// See if we can get a decimal number out of this
|
||||
qreal decibels = s.toDouble(&valid);
|
||||
|
||||
if (ok) *ok = valid;
|
||||
|
||||
if (valid) {
|
||||
// Convert from decibel scale to linear decimal
|
||||
return Decibel::toLinear(decibels);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case kPercentage:
|
||||
{
|
||||
bool valid;
|
||||
|
||||
// Try to get double value
|
||||
double val = s.toDouble(&valid);
|
||||
|
||||
if (ok) *ok = valid;
|
||||
|
||||
// If we could get it, convert back to a 0.0 - 1.0 value and return
|
||||
if (valid) {
|
||||
return val * 0.01;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
// If we were given an `ok` pointer, set it to `valid`
|
||||
if (ok) {
|
||||
*ok = valid;
|
||||
}
|
||||
|
||||
// Just try to convert the string to a double
|
||||
return s.toDouble(ok) - GetOffset().toDouble();
|
||||
// If valid, transform it from display
|
||||
if (valid) {
|
||||
val = TransformDisplayToValue(val, display_type_);
|
||||
}
|
||||
|
||||
// Return un-offset value
|
||||
return val - GetOffset().toDouble();
|
||||
}
|
||||
|
||||
QVariant FloatSlider::AdjustDragDistanceInternal(const QVariant &start, const double &drag) const
|
||||
|
||||
@@ -49,6 +49,10 @@ public:
|
||||
|
||||
void SetDisplayType(const DisplayType& type);
|
||||
|
||||
static double TransformValueToDisplay(double val, DisplayType display);
|
||||
|
||||
static double TransformDisplayToValue(double val, DisplayType display);
|
||||
|
||||
static QString ValueToString(double val, DisplayType display, int decimal_places, bool autotrim_decimal_places);
|
||||
|
||||
protected:
|
||||
|
||||
@@ -18,6 +18,8 @@ set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
widget/timebased/timebasedview.cpp
|
||||
widget/timebased/timebasedview.h
|
||||
widget/timebased/timebasedviewselectionmanager.cpp
|
||||
widget/timebased/timebasedviewselectionmanager.h
|
||||
widget/timebased/timebasedwidget.cpp
|
||||
widget/timebased/timebasedwidget.h
|
||||
widget/timebased/timescaledobject.cpp
|
||||
|
||||
@@ -53,11 +53,20 @@ public:
|
||||
return dragging_playhead_;
|
||||
}
|
||||
|
||||
// To be called only by selection managers
|
||||
virtual void SelectionManagerSelectEvent(void *obj){}
|
||||
virtual void SelectionManagerDeselectEvent(void *obj){}
|
||||
|
||||
public slots:
|
||||
void SetTime(const rational &time);
|
||||
|
||||
void SetEndTime(const rational& length);
|
||||
|
||||
/**
|
||||
* @brief Slot called whenever the view resizes or the scene contents change to enforce minimum scene sizes
|
||||
*/
|
||||
void UpdateSceneRect();
|
||||
|
||||
signals:
|
||||
void TimeChanged(const rational& time);
|
||||
|
||||
@@ -97,12 +106,6 @@ protected:
|
||||
y_axis_enabled_ = e;
|
||||
}
|
||||
|
||||
protected slots:
|
||||
/**
|
||||
* @brief Slot called whenever the view resizes or the scene contents change to enforce minimum scene sizes
|
||||
*/
|
||||
void UpdateSceneRect();
|
||||
|
||||
private:
|
||||
qreal GetPlayheadX();
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "timebasedviewselectionmanager.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef TIMEBASEDVIEWSELECTIONMANAGER_H
|
||||
#define TIMEBASEDVIEWSELECTIONMANAGER_H
|
||||
|
||||
#include <QGraphicsView>
|
||||
#include <QMouseEvent>
|
||||
#include <QRubberBand>
|
||||
#include <QToolTip>
|
||||
|
||||
#include "common/rational.h"
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "timebasedview.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
template <typename T>
|
||||
class TimeBasedViewSelectionManager
|
||||
{
|
||||
public:
|
||||
TimeBasedViewSelectionManager(TimeBasedView *view) :
|
||||
view_(view),
|
||||
rubberband_(nullptr)
|
||||
{}
|
||||
|
||||
void ClearDrawnObjects()
|
||||
{
|
||||
drawn_objects_.clear();
|
||||
}
|
||||
|
||||
void DeclareDrawnObject(T *object, const QRectF &pos)
|
||||
{
|
||||
drawn_objects_.append({object, pos});
|
||||
}
|
||||
|
||||
bool Select(T *key)
|
||||
{
|
||||
Q_ASSERT(key);
|
||||
|
||||
if (!IsSelected(key)) {
|
||||
selected_.append(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Deselect(T *key)
|
||||
{
|
||||
Q_ASSERT(key);
|
||||
|
||||
return selected_.removeOne(key);
|
||||
}
|
||||
|
||||
void ClearSelection()
|
||||
{
|
||||
selected_.clear();
|
||||
}
|
||||
|
||||
bool IsSelected(T *key) const
|
||||
{
|
||||
return selected_.contains(key);
|
||||
}
|
||||
|
||||
const QVector<T*> &GetSelectedObjects() const
|
||||
{
|
||||
return selected_;
|
||||
}
|
||||
|
||||
void SetTimebase(const rational &tb)
|
||||
{
|
||||
timebase_ = tb;
|
||||
}
|
||||
|
||||
T *GetObjectAtPoint(const QPointF &scene_pt)
|
||||
{
|
||||
// Iterate in reverse order because the objects drawn later will appear on top to the user
|
||||
for (auto it=drawn_objects_.crbegin(); it!=drawn_objects_.crend(); it++) {
|
||||
const DrawnObject &kp = *it;
|
||||
if (kp.second.contains(scene_pt)) {
|
||||
return kp.first;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
T *GetObjectAtPoint(const QPoint &pt)
|
||||
{
|
||||
return GetObjectAtPoint(view_->mapToScene(pt));
|
||||
}
|
||||
|
||||
T *MousePress(QMouseEvent *event)
|
||||
{
|
||||
T *key_under_cursor = nullptr;
|
||||
|
||||
if (event->button() == Qt::LeftButton || event->button() == Qt::RightButton) {
|
||||
// See if there's a keyframe in this position
|
||||
key_under_cursor = GetObjectAtPoint(event->pos());
|
||||
|
||||
bool holding_shift = event->modifiers() & Qt::ShiftModifier;
|
||||
|
||||
if (!key_under_cursor || !IsSelected(key_under_cursor)) {
|
||||
if (!holding_shift) {
|
||||
// If not already selecting and not holding shift, clear the current selection
|
||||
ClearSelection();
|
||||
}
|
||||
|
||||
// Add item to selection, either nothing if shift wasn't held, or the existing selection
|
||||
if (key_under_cursor) {
|
||||
Select(key_under_cursor);
|
||||
view_->SelectionManagerSelectEvent(key_under_cursor);
|
||||
}
|
||||
} else if (holding_shift) {
|
||||
// If selected and holding shift, de-select this item but do nothing else
|
||||
Deselect(key_under_cursor);
|
||||
view_->SelectionManagerDeselectEvent(key_under_cursor);
|
||||
key_under_cursor = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return key_under_cursor;
|
||||
}
|
||||
|
||||
bool IsDragging() const
|
||||
{
|
||||
return !dragging_.isEmpty();
|
||||
}
|
||||
|
||||
void DragStart(T *initial_item, QMouseEvent *event)
|
||||
{
|
||||
initial_drag_item_ = initial_item;
|
||||
|
||||
dragging_.resize(selected_.size());
|
||||
for (int i=0; i<selected_.size(); i++) {
|
||||
T *obj = selected_.at(i);
|
||||
|
||||
dragging_[i] = {obj->time(), view_->TimeToScene(obj->time())};
|
||||
}
|
||||
|
||||
drag_mouse_start_ = view_->mapToScene(event->pos());
|
||||
}
|
||||
|
||||
void DragMove(QMouseEvent *event, const QString &tip_format)
|
||||
{
|
||||
QPointF diff = view_->mapToScene(event->pos()) - drag_mouse_start_;
|
||||
|
||||
for (int i=0; i<selected_.size(); i++) {
|
||||
rational proposed_time = view_->SceneToTimeNoGrid(dragging_.at(i).x + diff.x());
|
||||
T *sel = selected_.at(i);
|
||||
|
||||
// Magic number: use interval of 1ms to avoid collisions
|
||||
rational adj(1, 1000);
|
||||
if (dragging_.at(i).time < proposed_time) {
|
||||
adj = -adj;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
NodeKeyframe *key_at_time = sel->parent()->GetKeyframeAtTimeOnTrack(sel->input(), proposed_time, sel->track(), sel->element());
|
||||
if (!key_at_time || key_at_time == sel) {
|
||||
break;
|
||||
}
|
||||
|
||||
proposed_time += adj;
|
||||
}
|
||||
|
||||
sel->set_time(proposed_time);
|
||||
}
|
||||
|
||||
// Show information about this keyframe
|
||||
QString tip = Timecode::time_to_timecode(initial_drag_item_->time(), timebase_,
|
||||
Core::instance()->GetTimecodeDisplay(), false);
|
||||
|
||||
if (!tip_format.isEmpty()) {
|
||||
tip = tip_format.arg(tip);
|
||||
}
|
||||
|
||||
QToolTip::hideText();
|
||||
QToolTip::showText(QCursor::pos(), tip);
|
||||
}
|
||||
|
||||
void DragStop(MultiUndoCommand *command)
|
||||
{
|
||||
QToolTip::hideText();
|
||||
|
||||
for (int i=0; i<selected_.size(); i++) {
|
||||
command->add_child(new SetTimeCommand(selected_.at(i), selected_.at(i)->time(), dragging_.at(i).time));
|
||||
}
|
||||
|
||||
dragging_.clear();
|
||||
}
|
||||
|
||||
void RubberBandStart(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton || event->button() == Qt::RightButton) {
|
||||
rubberband_start_ = event->pos();
|
||||
|
||||
rubberband_ = new QRubberBand(QRubberBand::Rectangle, view_);
|
||||
rubberband_->setGeometry(QRect(rubberband_start_.x(), rubberband_start_.y(), 0, 0));
|
||||
rubberband_->show();
|
||||
|
||||
rubberband_preselected_ = selected_;
|
||||
}
|
||||
}
|
||||
|
||||
void RubberBandMove(QMouseEvent *event)
|
||||
{
|
||||
if (IsRubberBanding()) {
|
||||
QRect band_rect = QRect(rubberband_start_, event->pos()).normalized();
|
||||
rubberband_->setGeometry(band_rect);
|
||||
|
||||
QRectF scene_rect = view_->mapToScene(band_rect).boundingRect();
|
||||
|
||||
selected_ = rubberband_preselected_;
|
||||
foreach (const DrawnObject &kp, drawn_objects_) {
|
||||
if (scene_rect.intersects(kp.second)) {
|
||||
Select(kp.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RubberBandStop()
|
||||
{
|
||||
if (IsRubberBanding()) {
|
||||
delete rubberband_;
|
||||
rubberband_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsRubberBanding() const
|
||||
{
|
||||
return rubberband_;
|
||||
}
|
||||
|
||||
private:
|
||||
class SetTimeCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
SetTimeCommand(T* key, const rational& time)
|
||||
{
|
||||
key_ = key;
|
||||
new_time_ = time;
|
||||
old_time_ = key_->time();
|
||||
}
|
||||
|
||||
SetTimeCommand(T* key, const rational& new_time, const rational& old_time)
|
||||
{
|
||||
key_ = key;
|
||||
new_time_ = new_time;
|
||||
old_time_ = old_time;
|
||||
}
|
||||
|
||||
virtual Project* GetRelevantProject() const override
|
||||
{
|
||||
return key_->parent()->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override
|
||||
{
|
||||
key_->set_time(new_time_);
|
||||
}
|
||||
|
||||
virtual void undo() override
|
||||
{
|
||||
key_->set_time(old_time_);
|
||||
}
|
||||
|
||||
private:
|
||||
T* key_;
|
||||
|
||||
rational old_time_;
|
||||
rational new_time_;
|
||||
|
||||
};
|
||||
|
||||
TimeBasedView *view_;
|
||||
|
||||
using DrawnObject = QPair<T*, QRectF>;
|
||||
QVector<DrawnObject> drawn_objects_;
|
||||
|
||||
QVector<T*> selected_;
|
||||
|
||||
struct DragObject
|
||||
{
|
||||
rational time;
|
||||
double x;
|
||||
};
|
||||
|
||||
QVector<DragObject> dragging_;
|
||||
|
||||
T *initial_drag_item_;
|
||||
|
||||
QPointF drag_mouse_start_;
|
||||
|
||||
rational timebase_;
|
||||
|
||||
QRubberBand *rubberband_;
|
||||
QPoint rubberband_start_;
|
||||
QVector<T*> rubberband_preselected_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // TIMEBASEDVIEWSELECTIONMANAGER_H
|
||||
@@ -99,8 +99,6 @@ void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node)
|
||||
scrollbar_->ConnectTimelinePoints(nullptr);
|
||||
}
|
||||
|
||||
emit ConnectedNodeChanged(old, node);
|
||||
|
||||
// Call derivatives
|
||||
ConnectedNodeChangeEvent(viewer_node_);
|
||||
|
||||
@@ -125,6 +123,8 @@ void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node)
|
||||
}
|
||||
|
||||
UpdateMaximumScroll();
|
||||
|
||||
emit ConnectedNodeChanged(old, node);
|
||||
}
|
||||
|
||||
void TimeBasedWidget::UpdateMaximumScroll()
|
||||
|
||||
@@ -72,6 +72,13 @@ rational TimeScaledObject::SceneToTime(const double &x, const double &x_scale, c
|
||||
return rational(rounded_x_mvmt * timebase.numerator(), timebase.denominator());
|
||||
}
|
||||
|
||||
rational TimeScaledObject::SceneToTimeNoGrid(const double &x, const double &x_scale)
|
||||
{
|
||||
double unscaled_time = x / x_scale;
|
||||
|
||||
return rational::fromDouble(unscaled_time);
|
||||
}
|
||||
|
||||
double TimeScaledObject::TimeToScene(const rational &time) const
|
||||
{
|
||||
return time.toDouble() * scale_;
|
||||
@@ -82,6 +89,11 @@ rational TimeScaledObject::SceneToTime(const double &x, bool round) const
|
||||
return SceneToTime(x, scale_, timebase_, round);
|
||||
}
|
||||
|
||||
rational TimeScaledObject::SceneToTimeNoGrid(const double &x) const
|
||||
{
|
||||
return SceneToTimeNoGrid(x, scale_);
|
||||
}
|
||||
|
||||
void TimeScaledObject::SetMaximumScale(const double &max)
|
||||
{
|
||||
max_scale_ = max;
|
||||
|
||||
@@ -42,6 +42,7 @@ public:
|
||||
const double& timebase_dbl() const;
|
||||
|
||||
static rational SceneToTime(const double &x, const double& x_scale, const rational& timebase, bool round = false);
|
||||
static rational SceneToTimeNoGrid(const double &x, const double& x_scale);
|
||||
|
||||
const double& GetScale() const;
|
||||
const double &GetMaximumScale() const { return max_scale_; }
|
||||
@@ -54,6 +55,7 @@ public:
|
||||
|
||||
double TimeToScene(const rational& time) const;
|
||||
rational SceneToTime(const double &x, bool round = false) const;
|
||||
rational SceneToTimeNoGrid(const double &x) const;
|
||||
|
||||
protected:
|
||||
virtual void TimebaseChangedEvent(const rational&){}
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#include "core.h"
|
||||
#include "common/range.h"
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "dialog/nodeproperties/nodepropertiesdialog.h"
|
||||
#include "dialog/sequence/sequence.h"
|
||||
#include "dialog/speedduration/speeddurationdialog.h"
|
||||
#include "node/block/transition/transition.h"
|
||||
@@ -301,55 +300,64 @@ void TimelineWidget::DisconnectNodeEvent(ViewerOutput *n)
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineWidget::CopyNodesToClipboardInternal(QXmlStreamWriter *writer, const QVector<Node *> &nodes, void* userdata)
|
||||
void TimelineWidget::CopyNodesToClipboardCallback(const QVector<Node *> &nodes, ProjectSerializer::SaveData *sdata, void *userdata)
|
||||
{
|
||||
// Cache the earliest in point so all copied clips have a "relative" in point that can be pasted anywhere
|
||||
QVector<Block*>& selected = *static_cast<QVector<Block*>*>(userdata);
|
||||
rational earliest_in = RATIONAL_MAX;
|
||||
ProjectSerializer::SerializedProperties properties;
|
||||
|
||||
foreach (Block* block, selected) {
|
||||
earliest_in = qMin(earliest_in, block->in());
|
||||
}
|
||||
|
||||
foreach (Block* block, selected) {
|
||||
writer->writeStartElement(QStringLiteral("block"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(block)));
|
||||
writer->writeAttribute(QStringLiteral("in"), (block->in() - earliest_in).toString());
|
||||
|
||||
Track* track = block->track();
|
||||
writer->writeAttribute(QStringLiteral("tracktype"), QString::number(track->type()));
|
||||
writer->writeAttribute(QStringLiteral("trackindex"), QString::number(track->Index()));
|
||||
|
||||
writer->writeEndElement();
|
||||
properties[block][QStringLiteral("in")] = (block->in() - earliest_in).toString();
|
||||
properties[block][QStringLiteral("track")] = block->track()->ToReference().ToString();
|
||||
}
|
||||
|
||||
sdata->SetProperties(properties);
|
||||
}
|
||||
|
||||
void TimelineWidget::PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNodeData& xml_node_data, void *userdata)
|
||||
void TimelineWidget::PasteNodesToClipboardCallback(const QVector<Node *> &nodes, const ProjectSerializer::LoadData &load_data, void *userdata)
|
||||
{
|
||||
QVector<BlockPasteData>& paste_data = *static_cast<QVector<BlockPasteData>*>(userdata);
|
||||
bool insert = *(bool*)userdata;
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("block")) {
|
||||
BlockPasteData bpd;
|
||||
MultiUndoCommand *command = new MultiUndoCommand();
|
||||
|
||||
foreach (QXmlStreamAttribute attr, reader->attributes()) {
|
||||
if (attr.name() == QStringLiteral("ptr")) {
|
||||
bpd.block = static_cast<Block*>(xml_node_data.node_ptrs.value(attr.value().toULongLong()));
|
||||
} else if (attr.name() == QStringLiteral("in")) {
|
||||
bpd.in = rational::fromString(attr.value().toString());
|
||||
} else if (attr.name() == QStringLiteral("tracktype")) {
|
||||
bpd.track_type = static_cast<Track::Type>(attr.value().toInt());
|
||||
} else if (attr.name() == QStringLiteral("trackindex")) {
|
||||
bpd.track_index = attr.value().toInt();
|
||||
}
|
||||
}
|
||||
foreach (Node *n, nodes) {
|
||||
command->add_child(new NodeAddCommand(GetConnectedNode()->project(), n));
|
||||
}
|
||||
|
||||
paste_data.append(bpd);
|
||||
rational paste_start = GetTime();
|
||||
|
||||
reader->skipCurrentElement();
|
||||
if (insert) {
|
||||
rational paste_end = GetTime();
|
||||
|
||||
for (auto it=load_data.properties.cbegin(); it!=load_data.properties.cend(); it++) {
|
||||
rational length = static_cast<Block*>(it.key())->length();
|
||||
rational in = rational::fromString(it.value()[QStringLiteral("in")]);
|
||||
|
||||
paste_end = qMax(paste_end, paste_start + in + length);
|
||||
}
|
||||
|
||||
if (paste_end != paste_start) {
|
||||
InsertGapsAt(paste_start, paste_end - paste_start, command);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it=load_data.properties.cbegin(); it!=load_data.properties.cend(); it++) {
|
||||
Block *block = static_cast<Block*>(it.key());
|
||||
rational in = rational::fromString(it.value()[QStringLiteral("in")]);
|
||||
Track::Reference track = Track::Reference::FromString(it.value()[QStringLiteral("track")]);
|
||||
|
||||
command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(track.type()),
|
||||
track.index(),
|
||||
block,
|
||||
paste_start + in));
|
||||
}
|
||||
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
}
|
||||
|
||||
void TimelineWidget::SelectAll()
|
||||
@@ -500,8 +508,6 @@ void TimelineWidget::DeleteSelected(bool ripple)
|
||||
return;
|
||||
}
|
||||
|
||||
MultiUndoCommand* command = new MultiUndoCommand();
|
||||
|
||||
QVector<Block*> clips_to_delete;
|
||||
QVector<TransitionBlock*> transitions_to_delete;
|
||||
|
||||
@@ -513,9 +519,16 @@ void TimelineWidget::DeleteSelected(bool ripple)
|
||||
}
|
||||
}
|
||||
|
||||
MultiUndoCommand* command = new MultiUndoCommand();
|
||||
|
||||
// For transitions, remove them but extend their attached blocks to fill their place
|
||||
foreach (TransitionBlock* transition, transitions_to_delete) {
|
||||
command->add_child(new TransitionRemoveCommand(transition, true));
|
||||
TransitionRemoveCommand *trc = new TransitionRemoveCommand(transition, true);
|
||||
|
||||
// Perform the transition removal now so that replacing blocks with gaps below won't get confused
|
||||
trc->redo_now();
|
||||
|
||||
command->add_child(trc);
|
||||
}
|
||||
|
||||
// Replace clips with gaps (effectively deleting them)
|
||||
@@ -525,20 +538,28 @@ void TimelineWidget::DeleteSelected(bool ripple)
|
||||
command->add_child(new SetSelectionsCommand(this, TimelineWidgetSelections(), GetSelections(), false));
|
||||
|
||||
// Insert ripple command now that it's all cleaned up gaps
|
||||
TimelineRippleDeleteGapsAtRegionsCommand *ripple_command = nullptr;
|
||||
rational new_playhead = RATIONAL_MAX;
|
||||
if (ripple) {
|
||||
TimeRangeList range_list;
|
||||
QVector<QPair<Track*, TimeRange> > range_list;
|
||||
|
||||
foreach (Block* b, blocks_to_delete) {
|
||||
range_list.insert(TimeRange(b->in(), b->out()));
|
||||
range_list.append({b->track(), b->range()});
|
||||
new_playhead = qMin(new_playhead, b->in());
|
||||
}
|
||||
|
||||
command->add_child(new TimelineRippleDeleteGapsAtRegionsCommand(sequence(), range_list));
|
||||
ripple_command = new TimelineRippleDeleteGapsAtRegionsCommand(sequence(), range_list);
|
||||
command->add_child(ripple_command);
|
||||
}
|
||||
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
|
||||
// Ensures any current drag operations are cancelled
|
||||
ClearGhosts();
|
||||
|
||||
if (ripple_command && ripple_command->HasCommands() && new_playhead != RATIONAL_MAX) {
|
||||
SetTimeAndSignal(new_playhead);
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineWidget::IncreaseTrackHeight()
|
||||
@@ -651,34 +672,7 @@ void TimelineWidget::Paste(bool insert)
|
||||
return;
|
||||
}
|
||||
|
||||
MultiUndoCommand* command = new MultiUndoCommand();
|
||||
|
||||
QVector<BlockPasteData> paste_data;
|
||||
QVector<Node*> pasted = PasteNodesFromClipboard(GetConnectedNode()->parent(), command, &paste_data);
|
||||
|
||||
rational paste_start = GetTime();
|
||||
|
||||
if (insert) {
|
||||
rational paste_end = GetTime();
|
||||
|
||||
foreach (const BlockPasteData& bpd, paste_data) {
|
||||
paste_end = qMax(paste_end, paste_start + bpd.in + bpd.block->length());
|
||||
}
|
||||
|
||||
if (paste_end != paste_start) {
|
||||
InsertGapsAt(paste_start, paste_end - paste_start, command);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const BlockPasteData& bpd, paste_data) {
|
||||
qDebug() << "Placing" << bpd.block;
|
||||
command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(bpd.track_type),
|
||||
bpd.track_index,
|
||||
bpd.block,
|
||||
paste_start + bpd.in));
|
||||
}
|
||||
|
||||
Core::instance()->undo_stack()->pushIfHasChildren(command);
|
||||
PasteNodesFromClipboard(&insert);
|
||||
}
|
||||
|
||||
void TimelineWidget::DeleteInToOut(bool ripple)
|
||||
@@ -944,6 +938,10 @@ void TimelineWidget::AddBlock(Block *block)
|
||||
connect(block, &Block::PreviewChanged, this, &TimelineWidget::BlockUpdated);
|
||||
|
||||
added_blocks_.append(block);
|
||||
|
||||
if (selections_[block->track()->ToReference()].contains(block->range()) && !selected_blocks_.contains(block)) {
|
||||
selected_blocks_.append(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1047,17 +1045,7 @@ void TimelineWidget::ShowContextMenu()
|
||||
menu.addSeparator();
|
||||
|
||||
QAction* properties_action = menu.addAction(tr("Properties"));
|
||||
connect(properties_action, &QAction::triggered, this, [this](){
|
||||
QVector<Block*> block_items = GetSelectedBlocks();
|
||||
QVector<Node*> nodes;
|
||||
|
||||
foreach (Block* i, block_items) {
|
||||
nodes.append(i);
|
||||
}
|
||||
|
||||
NodePropertiesDialog npd(nodes, timebase(), this);
|
||||
npd.exec();
|
||||
});
|
||||
connect(properties_action, &QAction::triggered, this, &TimelineWidget::ShowSpeedDurationDialogForSelectedClips);
|
||||
}
|
||||
|
||||
if (selected.isEmpty()) {
|
||||
@@ -1219,9 +1207,20 @@ void TimelineWidget::UpdateViewTimebases()
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineWidget::NudgeInternal(const rational &amount)
|
||||
void TimelineWidget::NudgeInternal(rational amount)
|
||||
{
|
||||
if (!selected_blocks_.isEmpty()) {
|
||||
// Validate
|
||||
foreach (Block* b, selected_blocks_) {
|
||||
if (b->in() + amount < 0) {
|
||||
amount = -b->in();
|
||||
}
|
||||
}
|
||||
|
||||
if (amount.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MultiUndoCommand *command = new MultiUndoCommand();
|
||||
|
||||
foreach (Block* b, selected_blocks_) {
|
||||
@@ -1392,7 +1391,7 @@ QVector<Timeline::EditToInfo> TimelineWidget::GetEditToInfo(const rational& play
|
||||
QVector<Timeline::EditToInfo> info_list(tracks.size());
|
||||
|
||||
for (int i=0;i<tracks.size();i++) {
|
||||
Timeline::EditToInfo info;
|
||||
Timeline::EditToInfo &info = info_list[i];
|
||||
|
||||
Track* track = tracks.at(i);
|
||||
info.track = track;
|
||||
@@ -1421,8 +1420,6 @@ QVector<Timeline::EditToInfo> TimelineWidget::GetEditToInfo(const rational& play
|
||||
}
|
||||
|
||||
info.nearest_block = b;
|
||||
|
||||
info_list[i] = info;
|
||||
}
|
||||
|
||||
return info_list;
|
||||
@@ -1439,7 +1436,7 @@ void TimelineWidget::RippleTo(Timeline::MovementMode mode)
|
||||
}
|
||||
|
||||
// Find each track's nearest point and determine the overall timeline's nearest point
|
||||
rational closest_point_to_playhead = (mode == Timeline::kTrimIn) ? 0 : RATIONAL_MAX;
|
||||
rational closest_point_to_playhead = (mode == Timeline::kTrimIn) ? RATIONAL_MIN : RATIONAL_MAX;
|
||||
|
||||
foreach (const Timeline::EditToInfo& info, tracks) {
|
||||
if (info.nearest_block) {
|
||||
@@ -1451,6 +1448,11 @@ void TimelineWidget::RippleTo(Timeline::MovementMode mode)
|
||||
}
|
||||
}
|
||||
|
||||
if (closest_point_to_playhead == RATIONAL_MIN || closest_point_to_playhead == RATIONAL_MAX) {
|
||||
// Assume no blocks will be acted upon
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're not inserting gaps and the edit point is right on the nearest in point, we enter a
|
||||
// single-frame mode where we remove one frame only
|
||||
if (closest_point_to_playhead == playhead_time) {
|
||||
@@ -1573,31 +1575,6 @@ QVector<Block *> TimelineWidget::GetBlocksInGlobalRect(const QPoint &p1, const Q
|
||||
return blocks_in_rect;
|
||||
}
|
||||
|
||||
QVector<Block *> TimelineWidget::GetBlocksInSelection(const TimelineWidgetSelections &sel)
|
||||
{
|
||||
QVector<Block *> blocks;
|
||||
|
||||
for (auto it=sel.cbegin(); it!=sel.cend(); it++) {
|
||||
const Track::Reference &ref = it.key();
|
||||
const TimeRangeList &list = it.value();
|
||||
|
||||
for (const TimeRange &r : list) {
|
||||
Track *track = GetTrackFromReference(ref);
|
||||
if (track) {
|
||||
for (Block *b : track->Blocks()) {
|
||||
if (r.Contains(b->range())) {
|
||||
blocks.append(b);
|
||||
} else if (b->in() >= r.out()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return blocks;
|
||||
}
|
||||
|
||||
void TimelineWidget::HideSnaps()
|
||||
{
|
||||
foreach (TimelineAndTrackView* tview, views_) {
|
||||
@@ -1722,8 +1699,31 @@ void TimelineWidget::SetSelections(const TimelineWidgetSelections &s, bool proce
|
||||
}
|
||||
|
||||
if (process_block_changes) {
|
||||
SignalDeselectedBlocks(GetBlocksInSelection(selections_.Subtracted(s)));
|
||||
SignalSelectedBlocks(GetBlocksInSelection(s.Subtracted(selections_)));
|
||||
QVector<Block*> deselected;
|
||||
QVector<Block*> selected;
|
||||
|
||||
foreach (Block *b, selected_blocks_) {
|
||||
if (!s[b->track()->ToReference()].contains(b->range())) {
|
||||
deselected.append(b);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: This loop could do with some optimization
|
||||
for (auto it=s.cbegin(); it!=s.cend(); it++) {
|
||||
Track *track = GetTrackFromReference(it.key());
|
||||
if (track) {
|
||||
const TimeRangeList &ranges = it.value();
|
||||
|
||||
foreach (Block *b, track->Blocks()) {
|
||||
if (!selected_blocks_.contains(b) && ranges.contains(b->range())) {
|
||||
selected.append(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SignalDeselectedBlocks(deselected);
|
||||
SignalSelectedBlocks(selected);
|
||||
}
|
||||
|
||||
selections_ = s;
|
||||
|
||||
@@ -279,15 +279,8 @@ protected:
|
||||
virtual void ConnectNodeEvent(ViewerOutput* n) override;
|
||||
virtual void DisconnectNodeEvent(ViewerOutput* n) override;
|
||||
|
||||
virtual void CopyNodesToClipboardInternal(QXmlStreamWriter *writer, const QVector<Node*> &nodes, void* userdata) override;
|
||||
virtual void PasteNodesFromClipboardInternal(QXmlStreamReader *reader, XMLNodeData &xml_node_data, void* userdata) override;
|
||||
|
||||
struct BlockPasteData {
|
||||
Block* block;
|
||||
rational in;
|
||||
Track::Type track_type;
|
||||
int track_index;
|
||||
};
|
||||
virtual void CopyNodesToClipboardCallback(const QVector<Node*> &nodes, ProjectSerializer::SaveData *data, void *userdata) override;
|
||||
virtual void PasteNodesToClipboardCallback(const QVector<Node*> &nodes, const ProjectSerializer::LoadData &load_data, void *userdata) override;
|
||||
|
||||
private:
|
||||
QVector<Timeline::EditToInfo> GetEditToInfo(const rational &playhead_time, Timeline::MovementMode mode);
|
||||
@@ -302,8 +295,6 @@ private:
|
||||
|
||||
QVector<Block*> GetBlocksInGlobalRect(const QPoint &p1, const QPoint &p2);
|
||||
|
||||
QVector<Block*> GetBlocksInSelection(const TimelineWidgetSelections &sel);
|
||||
|
||||
QPoint drag_origin_;
|
||||
|
||||
QRubberBand rubberband_;
|
||||
@@ -366,7 +357,7 @@ private:
|
||||
|
||||
void UpdateViewTimebases();
|
||||
|
||||
void NudgeInternal(const rational &amount);
|
||||
void NudgeInternal(rational amount);
|
||||
|
||||
void MoveToPlayheadInternal(bool out);
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ void AddTool::MouseRelease(TimelineViewMouseEvent *event)
|
||||
NodeGraph* graph = static_cast<NodeGraph*>(parent()->GetConnectedNode()->parent());
|
||||
|
||||
command->add_child(new NodeAddCommand(graph, clip));
|
||||
command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0), false));
|
||||
command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0)));
|
||||
command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(track.type()),
|
||||
track.index(),
|
||||
clip,
|
||||
@@ -155,10 +155,10 @@ void AddTool::MouseRelease(TimelineViewMouseEvent *event)
|
||||
}
|
||||
|
||||
if (node_to_add) {
|
||||
QPointF extra_node_offset(-1, 0);
|
||||
QPointF extra_node_offset(kDefaultDistanceFromOutput, 0);
|
||||
command->add_child(new NodeAddCommand(graph, node_to_add));
|
||||
command->add_child(new NodeEdgeAddCommand(node_to_add, NodeInput(clip, ClipBlock::kBufferIn)));
|
||||
command->add_child(new NodeSetPositionCommand(node_to_add, clip, extra_node_offset, false));
|
||||
command->add_child(new NodeSetPositionCommand(node_to_add, clip, extra_node_offset));
|
||||
}
|
||||
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
|
||||
@@ -355,7 +355,7 @@ void ImportTool::DropGhosts(bool insert)
|
||||
|
||||
command->add_child(new NodeAddCommand(dst_graph, new_sequence));
|
||||
command->add_child(new FolderAddChild(Core::instance()->GetSelectedFolderInActiveProject(), new_sequence));
|
||||
command->add_child(new NodeSetPositionCommand(new_sequence, new_sequence, QPointF(0, 0), false));
|
||||
command->add_child(new NodeSetPositionCommand(new_sequence, new_sequence, QPointF(0, 0)));
|
||||
new_sequence->add_default_nodes(command);
|
||||
|
||||
FootageToGhosts(0, dragged_footage_, new_sequence->GetVideoParams().time_base(), 0);
|
||||
@@ -394,10 +394,14 @@ void ImportTool::DropGhosts(bool insert)
|
||||
command->add_child(new NodeAddCommand(dst_graph, clip));
|
||||
|
||||
// Position clip in its own context
|
||||
command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0), false));
|
||||
command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0)));
|
||||
|
||||
int dep_pos = kDefaultDistanceFromOutput;
|
||||
|
||||
// Position footage in its context
|
||||
command->add_child(new NodeSetPositionCommand(footage_stream.footage, clip, QPointF(-2, 0), false));
|
||||
command->add_child(new NodeSetPositionCommand(footage_stream.footage, clip, QPointF(dep_pos, 0)));
|
||||
|
||||
dep_pos++;
|
||||
|
||||
switch (Track::Reference::TypeFromString(footage_stream.output)) {
|
||||
case Track::kVideo:
|
||||
@@ -409,7 +413,7 @@ void ImportTool::DropGhosts(bool insert)
|
||||
|
||||
command->add_child(new NodeEdgeAddCommand(footage_stream.footage, NodeInput(transform, TransformDistortNode::kTextureInput)));
|
||||
command->add_child(new NodeEdgeAddCommand(transform, NodeInput(clip, ClipBlock::kBufferIn)));
|
||||
command->add_child(new NodeSetPositionCommand(transform, clip, QPointF(-1, 0), false));
|
||||
command->add_child(new NodeSetPositionCommand(transform, clip, QPointF(dep_pos, 0)));
|
||||
break;
|
||||
}
|
||||
case Track::kAudio:
|
||||
@@ -421,7 +425,7 @@ void ImportTool::DropGhosts(bool insert)
|
||||
|
||||
command->add_child(new NodeEdgeAddCommand(footage_stream.footage, NodeInput(volume_node, VolumeNode::kSamplesInput)));
|
||||
command->add_child(new NodeEdgeAddCommand(volume_node, NodeInput(clip, ClipBlock::kBufferIn)));
|
||||
command->add_child(new NodeSetPositionCommand(volume_node, clip, QPointF(-1, 0), false));
|
||||
command->add_child(new NodeSetPositionCommand(volume_node, clip, QPointF(dep_pos, 0)));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -575,7 +575,7 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event)
|
||||
} else {
|
||||
new_sel.TrimOut(reference_ghost->GetOutAdjustment());
|
||||
}
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections(), false));
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections(), true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,7 +624,7 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event)
|
||||
TimelineWidgetSelections new_sel = parent()->GetSelections();
|
||||
new_sel.ShiftTime(blocks_moving.first().ghost->GetInAdjustment());
|
||||
new_sel.ShiftTracks(drag_track_type_, blocks_moving.first().ghost->GetTrackAdjustment());
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections(), false));
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections(), true));
|
||||
}
|
||||
|
||||
if (!blocks_sliding.isEmpty()) {
|
||||
@@ -685,7 +685,7 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event)
|
||||
// Adjust selections
|
||||
TimelineWidgetSelections new_sel = parent()->GetSelections();
|
||||
new_sel.ShiftTime(movement);
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections(), false));
|
||||
command->add_child(new TimelineWidget::SetSelectionsCommand(parent(), new_sel, parent()->GetSelections(), true));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
const int TimelineTool::kDefaultDistanceFromOutput = -4;
|
||||
|
||||
TimelineTool::TimelineTool(TimelineWidget *parent) :
|
||||
dragging_(false),
|
||||
parent_(parent)
|
||||
|
||||
@@ -84,6 +84,8 @@ protected:
|
||||
|
||||
TimelineCoordinate drag_start_;
|
||||
|
||||
static const int kDefaultDistanceFromOutput;
|
||||
|
||||
private:
|
||||
TimelineWidget* parent_;
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ void TransitionTool::MouseRelease(TimelineViewMouseEvent *event)
|
||||
command->add_child(new NodeAddCommand(static_cast<NodeGraph*>(parent()->GetConnectedNode()->parent()),
|
||||
transition));
|
||||
|
||||
command->add_child(new NodeSetPositionCommand(transition, transition, QPointF(0, 0), false));
|
||||
command->add_child(new NodeSetPositionCommand(transition, transition, QPointF(0, 0)));
|
||||
|
||||
command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(track.type()),
|
||||
track.index(),
|
||||
@@ -138,8 +138,8 @@ void TransitionTool::MouseRelease(TimelineViewMouseEvent *event)
|
||||
command->add_child(new NodeEdgeAddCommand(in_block,
|
||||
NodeInput(transition, TransitionBlock::kInBlockInput)));
|
||||
|
||||
command->add_child(new NodeSetPositionCommand(out_block, transition, QPointF(-1, -0.5), false));
|
||||
command->add_child(new NodeSetPositionCommand(in_block, transition, QPointF(-1, 0.5), false));
|
||||
command->add_child(new NodeSetPositionCommand(out_block, transition, QPointF(-1, -0.5)));
|
||||
command->add_child(new NodeSetPositionCommand(in_block, transition, QPointF(-1, 0.5)));
|
||||
} else {
|
||||
Block* block_to_transition = Node::ValueToPtr<Block>(ghost_->GetData(TimelineViewGhostItem::kAttachedBlock));
|
||||
QString transition_input_to_connect;
|
||||
@@ -154,7 +154,7 @@ void TransitionTool::MouseRelease(TimelineViewMouseEvent *event)
|
||||
command->add_child(new NodeEdgeAddCommand(block_to_transition,
|
||||
NodeInput(transition, transition_input_to_connect)));
|
||||
|
||||
command->add_child(new NodeSetPositionCommand(block_to_transition, transition, QPointF(-1, 0), false));
|
||||
command->add_child(new NodeSetPositionCommand(block_to_transition, transition, QPointF(-1, 0)));
|
||||
}
|
||||
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
|
||||
@@ -22,10 +22,15 @@
|
||||
|
||||
#include <QDebug>
|
||||
#include <QHBoxLayout>
|
||||
#include <QMessageBox>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QtMath>
|
||||
|
||||
#include "core.h"
|
||||
#include "widget/menu/menu.h"
|
||||
#include "widget/timelinewidget/undo/timelineundogeneral.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
TrackViewItem::TrackViewItem(Track* track, QWidget *parent) :
|
||||
@@ -63,8 +68,10 @@ TrackViewItem::TrackViewItem(Track* track, QWidget *parent) :
|
||||
layout->addWidget(lock_button_);
|
||||
|
||||
setMinimumHeight(mute_button_->height());
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
connect(track, &Track::MutedChanged, mute_button_, &QPushButton::setChecked);
|
||||
connect(this, &QWidget::customContextMenuRequested, this, &TrackViewItem::ShowContextMenu);
|
||||
}
|
||||
|
||||
QPushButton *TrackViewItem::CreateMSLButton(const QString& text, const QColor& checked_color) const
|
||||
@@ -114,4 +121,52 @@ void TrackViewItem::UpdateLabel()
|
||||
label_->setText(track_->GetLabelOrName());
|
||||
}
|
||||
|
||||
void TrackViewItem::ShowContextMenu(const QPoint &p)
|
||||
{
|
||||
Menu m(this);
|
||||
|
||||
QAction *delete_action = m.addAction(tr("&Delete"));
|
||||
connect(delete_action, &QAction::triggered, this, &TrackViewItem::DeleteTrack, Qt::QueuedConnection);
|
||||
|
||||
m.addSeparator();
|
||||
|
||||
QAction *delete_unused_action = m.addAction(tr("Delete All &Empty"));
|
||||
connect(delete_unused_action, &QAction::triggered, this, &TrackViewItem::DeleteAllEmptyTracks, Qt::QueuedConnection);
|
||||
|
||||
m.exec(mapToGlobal(p));
|
||||
}
|
||||
|
||||
void TrackViewItem::DeleteTrack()
|
||||
{
|
||||
Core::instance()->undo_stack()->push(new TimelineRemoveTrackCommand(track_));
|
||||
}
|
||||
|
||||
void TrackViewItem::DeleteAllEmptyTracks()
|
||||
{
|
||||
Sequence *sequence = track_->sequence();
|
||||
QVector<Track*> tracks_to_remove;
|
||||
QStringList track_names_to_remove;
|
||||
|
||||
foreach (Track *t, sequence->GetTracks()) {
|
||||
if (t->Blocks().isEmpty()) {
|
||||
tracks_to_remove.append(t);
|
||||
track_names_to_remove.append(t->GetLabelOrName());
|
||||
}
|
||||
}
|
||||
|
||||
if (tracks_to_remove.isEmpty()) {
|
||||
QMessageBox::information(this, tr("Delete All Empty"), tr("No tracks are currently empty"));
|
||||
} else {
|
||||
if (QMessageBox::question(this, tr("Delete All Empty"),
|
||||
tr("This will delete the following tracks:\n\n%1\n\nDo you wish to continue?").arg(track_names_to_remove.join('\n')),
|
||||
QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
|
||||
MultiUndoCommand *command = new MultiUndoCommand();
|
||||
foreach (Track *track, tracks_to_remove) {
|
||||
command->add_child(new TimelineRemoveTrackCommand(track));
|
||||
}
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -61,6 +61,12 @@ private slots:
|
||||
|
||||
void UpdateLabel();
|
||||
|
||||
void ShowContextMenu(const QPoint &p);
|
||||
|
||||
void DeleteTrack();
|
||||
|
||||
void DeleteAllEmptyTracks();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ void TrackViewSplitter::SetHeightWithSizes(QList<int> sizes)
|
||||
if (alignment_ == Qt::AlignBottom) {
|
||||
sizes.replace(0, spacer_height_);
|
||||
} else {
|
||||
sizes.replace(count() - 1, spacer_height_);
|
||||
sizes.replace(sizes.size() - 1, spacer_height_);
|
||||
}
|
||||
|
||||
foreach (int s, sizes) {
|
||||
|
||||
@@ -73,131 +73,121 @@ void BlockSetMediaInCommand::undo()
|
||||
//
|
||||
// TimelineAddTrackCommand
|
||||
//
|
||||
TimelineAddTrackCommand::TimelineAddTrackCommand(TrackList *timeline, bool automerge_tracks)
|
||||
TimelineAddTrackCommand::TimelineAddTrackCommand(TrackList *timeline, bool automerge_tracks) :
|
||||
timeline_(timeline),
|
||||
merge_(nullptr),
|
||||
position_command_(nullptr)
|
||||
{
|
||||
timeline_ = timeline;
|
||||
position_command_ = nullptr;
|
||||
|
||||
// Create new track
|
||||
track_ = new Track();
|
||||
track_->setParent(&memory_manager_);
|
||||
|
||||
if (timeline->GetTrackCount() > 0 && automerge_tracks) {
|
||||
if (timeline_->type() == Track::kVideo) {
|
||||
merge_ = new MergeNode();
|
||||
base_ = NodeInput(merge_, MergeNode::kBaseIn);
|
||||
blend_ = NodeInput(merge_, MergeNode::kBlendIn);
|
||||
} else if (timeline_->type() == Track::kAudio) {
|
||||
merge_ = new MathNode();
|
||||
base_ = NodeInput(merge_, MathNode::kParamAIn);
|
||||
blend_ = NodeInput(merge_, MathNode::kParamBIn);
|
||||
} else {
|
||||
merge_ = nullptr;
|
||||
}
|
||||
} else {
|
||||
merge_ = nullptr;
|
||||
// Determine what input to connect it to
|
||||
QString relevant_input;
|
||||
|
||||
if (timeline_->type() == Track::kVideo) {
|
||||
relevant_input = Sequence::kTextureInput;
|
||||
} else if (timeline_->type() == Track::kAudio) {
|
||||
relevant_input = Sequence::kSamplesInput;
|
||||
}
|
||||
|
||||
if (merge_) {
|
||||
merge_->setParent(&memory_manager_);
|
||||
// If we have an input to connect to, set it as our `direct` connection
|
||||
if (!relevant_input.isEmpty()) {
|
||||
direct_ = NodeInput(timeline_->parent(), relevant_input);
|
||||
|
||||
// If we're automerging and something is already connected, determine if/how to merge it
|
||||
if (automerge_tracks && direct_.IsConnected()) {
|
||||
if (timeline_->type() == Track::kVideo) {
|
||||
// Use merge for video
|
||||
merge_ = new MergeNode();
|
||||
base_ = NodeInput(merge_, MergeNode::kBaseIn);
|
||||
blend_ = NodeInput(merge_, MergeNode::kBlendIn);
|
||||
} else if (timeline_->type() == Track::kAudio) {
|
||||
// Use math (add) for audio
|
||||
merge_ = new MathNode();
|
||||
base_ = NodeInput(merge_, MathNode::kParamAIn);
|
||||
blend_ = NodeInput(merge_, MathNode::kParamBIn);
|
||||
}
|
||||
|
||||
if (merge_) {
|
||||
// If we got created a merge node, ensure it's parented
|
||||
merge_->setParent(&memory_manager_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineAddTrackCommand::redo()
|
||||
{
|
||||
// Add track
|
||||
// Get sequence
|
||||
Sequence* sequence = timeline_->parent();
|
||||
|
||||
// Add track to sequence
|
||||
track_->setParent(timeline_->GetParentGraph());
|
||||
timeline_->ArrayAppend();
|
||||
Node::ConnectEdge(track_, timeline_->track_input(timeline_->ArraySize() - 1));
|
||||
|
||||
qreal position_factor = 0.5;
|
||||
if (timeline_->type() == Track::kVideo) {
|
||||
position_factor = -position_factor;
|
||||
}
|
||||
bool create_pos_command = (!position_command_ && (timeline_->type() == Track::kVideo || timeline_->type() == Track::kAudio));
|
||||
if (create_pos_command) {
|
||||
position_command_ = new MultiUndoCommand();
|
||||
}
|
||||
|
||||
// Add merge if applicable
|
||||
Track* last_track = nullptr;
|
||||
if (merge_) {
|
||||
// Determine what was previously connected
|
||||
Node *previous_connection = direct_.GetConnectedOutput();
|
||||
|
||||
// Add merge to graph
|
||||
merge_->setParent(timeline_->GetParentGraph());
|
||||
|
||||
last_track = timeline_->GetTrackAt(timeline_->GetTrackCount()-2);
|
||||
|
||||
// Whatever this track used to be connected to, connect the merge instead
|
||||
const Node::OutputConnections edges = last_track->output_connections();
|
||||
for (const Node::OutputConnection& ic : edges) {
|
||||
const NodeInput& i = ic.second;
|
||||
|
||||
// Ignore the track input, but funnel everything else through our merge
|
||||
if (i.node() != timeline_->parent() || i.input() != timeline_->track_input()) {
|
||||
Node::DisconnectEdge(last_track, i);
|
||||
Node::ConnectEdge(merge_, i);
|
||||
}
|
||||
}
|
||||
|
||||
// Connect this as the "blend" track
|
||||
// Connect merge between what used to be here
|
||||
Node::DisconnectEdge(previous_connection, direct_);
|
||||
Node::ConnectEdge(merge_, direct_);
|
||||
Node::ConnectEdge(previous_connection, base_);
|
||||
Node::ConnectEdge(track_, blend_);
|
||||
Node::ConnectEdge(last_track, base_);
|
||||
} else if (timeline_->GetTrackCount() == 1) {
|
||||
// If this was the first track we added,
|
||||
QString relevant_input;
|
||||
|
||||
if (timeline_->type() == Track::kVideo) {
|
||||
relevant_input = ViewerOutput::kTextureInput;
|
||||
} else if (timeline_->type() == Track::kAudio) {
|
||||
relevant_input = ViewerOutput::kSamplesInput;
|
||||
if (create_pos_command) {
|
||||
position_command_->add_child(new NodeSetPositionCommand(track_, sequence, sequence->GetNodePositionInContext(sequence) + QPointF(-1, -position_factor)));
|
||||
position_command_->add_child(new NodeSetPositionCommand(merge_, sequence, sequence->GetNodePositionInContext(sequence)));
|
||||
position_command_->add_child(new NodeSetPositionAndDependenciesRecursivelyCommand(merge_, sequence, sequence->GetNodePositionInContext(sequence) + QPointF(-1, position_factor * timeline_->GetTrackCount())));
|
||||
}
|
||||
} else if (direct_.IsValid() && !direct_.IsConnected()) {
|
||||
// If no merge, we have a direct connection, and nothing else is connected, connect this
|
||||
Node::ConnectEdge(track_, direct_);
|
||||
|
||||
if (!relevant_input.isEmpty() && !timeline_->parent()->IsInputConnected(relevant_input)) {
|
||||
direct_ = NodeInput(timeline_->parent(), relevant_input);
|
||||
|
||||
Node::ConnectEdge(track_, direct_);
|
||||
} else {
|
||||
direct_ = NodeInput();
|
||||
if (create_pos_command) {
|
||||
// Just position directly next to the context node
|
||||
position_command_->add_child(new NodeSetPositionCommand(track_, sequence, sequence->GetNodePositionInContext(sequence) + QPointF(-1, position_factor)));
|
||||
}
|
||||
}
|
||||
|
||||
// Position track in context
|
||||
if (!position_command_) {
|
||||
int track_count = timeline_->parent()->GetTracks().size();
|
||||
position_command_ = new MultiUndoCommand();
|
||||
|
||||
// Position either the merge or the track as an "element"
|
||||
Node *node_to_position = merge_ ? merge_ : track_;
|
||||
double node_index = track_count - 1;
|
||||
if (merge_) {
|
||||
node_index -= 1;
|
||||
position_command_->add_child(new NodeRemovePositionFromContextCommand(last_track, timeline_->parent()));
|
||||
}
|
||||
|
||||
position_command_->add_child(new NodeSetPositionAsChildCommand(node_to_position, timeline_->parent(), timeline_->parent(), node_index, track_count, true));
|
||||
|
||||
// If we positioned a merge, position the tracks as children of the merge
|
||||
if (merge_) {
|
||||
// `last_track` should be non-null if `merge_` is non-null
|
||||
position_command_->add_child(new NodeSetPositionAsChildCommand(last_track, merge_, timeline_->parent(), 0, 2, true));
|
||||
position_command_->add_child(new NodeSetPositionAsChildCommand(track_, merge_, timeline_->parent(), 1, 2, true));
|
||||
}
|
||||
// Run position command if we created one
|
||||
if (position_command_) {
|
||||
position_command_->redo_now();
|
||||
}
|
||||
position_command_->redo_now();
|
||||
}
|
||||
|
||||
void TimelineAddTrackCommand::undo()
|
||||
{
|
||||
position_command_->undo_now();
|
||||
if (position_command_) {
|
||||
position_command_->undo_now();
|
||||
}
|
||||
|
||||
// Remove merge if applicable
|
||||
if (merge_) {
|
||||
// Assume whatever this merge is connected to USED to be connected to the last track
|
||||
Track* last_track = timeline_->GetTrackAt(timeline_->GetTrackCount()-2);
|
||||
Node *previous_connection = base_.GetConnectedOutput();
|
||||
|
||||
Node::DisconnectEdge(track_, blend_);
|
||||
Node::DisconnectEdge(last_track, base_);
|
||||
|
||||
// Make copy of edges since the node's internal array will change as we disconnect things
|
||||
const Node::OutputConnections edges = merge_->output_connections();
|
||||
for (const Node::OutputConnection& ic : edges) {
|
||||
const NodeInput& i = ic.second;
|
||||
|
||||
Node::DisconnectEdge(merge_, i);
|
||||
Node::ConnectEdge(last_track, i);
|
||||
}
|
||||
Node::DisconnectEdge(previous_connection, base_);
|
||||
Node::DisconnectEdge(merge_, direct_);
|
||||
Node::ConnectEdge(previous_connection, direct_);
|
||||
|
||||
merge_->setParent(&memory_manager_);
|
||||
} else if (direct_.IsValid()) {
|
||||
} else if (direct_.IsValid() && direct_.GetConnectedOutput() == track_) {
|
||||
Node::DisconnectEdge(track_, direct_);
|
||||
}
|
||||
|
||||
@@ -374,7 +364,7 @@ void TrackListInsertGaps::redo()
|
||||
}
|
||||
|
||||
if (split_command_) {
|
||||
split_command_->redo();
|
||||
split_command_->redo_now();
|
||||
}
|
||||
|
||||
foreach (auto add_gap, gaps_added_) {
|
||||
@@ -416,7 +406,7 @@ void TrackListInsertGaps::undo()
|
||||
|
||||
// Un-split blocks
|
||||
if (split_command_) {
|
||||
split_command_->undo();
|
||||
split_command_->undo_now();
|
||||
}
|
||||
|
||||
// Restore original length of gaps
|
||||
@@ -496,11 +486,6 @@ void TrackReplaceBlockWithGapCommand::redo()
|
||||
|
||||
our_gap_->setParent(track_->parent());
|
||||
track_->ReplaceBlock(block_, our_gap_);
|
||||
|
||||
if (!position_command_) {
|
||||
position_command_ = new NodeSetPositionAsChildCommand(our_gap_, track_, track_, our_gap_->index(), track_->Blocks().size(), true);
|
||||
}
|
||||
position_command_->redo_now();
|
||||
}
|
||||
|
||||
track_->EndOperation();
|
||||
@@ -533,8 +518,6 @@ void TrackReplaceBlockWithGapCommand::undo()
|
||||
track_->ReplaceBlock(our_gap_, block_);
|
||||
our_gap_->setParent(&memory_manager_);
|
||||
|
||||
position_command_->undo_now();
|
||||
|
||||
} else {
|
||||
|
||||
// If we're here, assume that we extended an existing gap
|
||||
@@ -608,4 +591,27 @@ void TrackReplaceBlockWithGapCommand::CreateRemoveTransitionCommandIfNecessary(b
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineRemoveTrackCommand::prepare()
|
||||
{
|
||||
list_ = track_->sequence()->track_list(track_->type());
|
||||
|
||||
index_ = list_->GetArrayIndexFromCacheIndex(track_->Index());
|
||||
|
||||
remove_command_ = new NodeRemoveWithExclusiveDependenciesAndDisconnect(track_);
|
||||
}
|
||||
|
||||
void TimelineRemoveTrackCommand::redo()
|
||||
{
|
||||
remove_command_->redo_now();
|
||||
|
||||
list_->parent()->InputArrayRemove(list_->track_input(), index_);
|
||||
}
|
||||
|
||||
void TimelineRemoveTrackCommand::undo()
|
||||
{
|
||||
list_->parent()->InputArrayInsert(list_->track_input(), index_);
|
||||
|
||||
remove_command_->undo_now();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -163,6 +163,42 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class TimelineRemoveTrackCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
TimelineRemoveTrackCommand(Track *track) :
|
||||
track_(track),
|
||||
remove_command_(nullptr)
|
||||
{}
|
||||
|
||||
virtual ~TimelineRemoveTrackCommand()
|
||||
{
|
||||
delete remove_command_;
|
||||
}
|
||||
|
||||
virtual Project* GetRelevantProject() const override
|
||||
{
|
||||
return track_->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void prepare() override;
|
||||
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
Track *track_;
|
||||
|
||||
TrackList *list_;
|
||||
|
||||
int index_;
|
||||
|
||||
UndoCommand *remove_command_;
|
||||
|
||||
};
|
||||
|
||||
class TransitionRemoveCommand : public UndoCommand {
|
||||
public:
|
||||
TransitionRemoveCommand(TransitionBlock* block, bool remove_from_graph) :
|
||||
@@ -203,16 +239,10 @@ public:
|
||||
existing_gap_(nullptr),
|
||||
existing_merged_gap_(nullptr),
|
||||
our_gap_(nullptr),
|
||||
handle_transitions_(handle_transitions),
|
||||
position_command_(nullptr)
|
||||
handle_transitions_(handle_transitions)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~TrackReplaceBlockWithGapCommand() override
|
||||
{
|
||||
delete position_command_;
|
||||
}
|
||||
|
||||
virtual Project* GetRelevantProject() const override
|
||||
{
|
||||
return block_->project();
|
||||
@@ -236,8 +266,6 @@ private:
|
||||
|
||||
bool handle_transitions_;
|
||||
|
||||
NodeSetPositionAsChildCommand* position_command_;
|
||||
|
||||
QObject memory_manager_;
|
||||
|
||||
QVector<TransitionRemoveCommand*> transition_remove_commands_;
|
||||
|
||||
@@ -324,7 +324,6 @@ TrackPlaceBlockCommand::~TrackPlaceBlockCommand()
|
||||
{
|
||||
delete ripple_remove_command_;
|
||||
qDeleteAll(add_track_commands_);
|
||||
qDeleteAll(position_commands_);
|
||||
}
|
||||
|
||||
void TrackPlaceBlockCommand::redo()
|
||||
@@ -367,14 +366,6 @@ void TrackPlaceBlockCommand::redo()
|
||||
}
|
||||
|
||||
track->AppendBlock(insert_);
|
||||
|
||||
if (position_commands_.isEmpty()) {
|
||||
// Create position commands for insert and gap if necessary
|
||||
if (gap_) {
|
||||
position_commands_.append(new NodeSetPositionAsChildCommand(gap_, track, track, gap_->index(), track->Blocks().size(), true));
|
||||
}
|
||||
position_commands_.append(new NodeSetPositionAsChildCommand(insert_, track, track, insert_->index(), track->Blocks().size(), true));
|
||||
}
|
||||
} else {
|
||||
// Place the Block at this point
|
||||
if (!ripple_remove_command_) {
|
||||
@@ -384,10 +375,6 @@ void TrackPlaceBlockCommand::redo()
|
||||
|
||||
ripple_remove_command_->redo_now();
|
||||
track->InsertBlockAfter(insert_, ripple_remove_command_->GetInsertionIndex());
|
||||
|
||||
if (position_commands_.isEmpty()) {
|
||||
position_commands_.append(new NodeSetPositionAsChildCommand(insert_, track, track, insert_->index(), track->Blocks().size(), true));
|
||||
}
|
||||
}
|
||||
|
||||
track->EndOperation();
|
||||
@@ -397,18 +384,10 @@ void TrackPlaceBlockCommand::redo()
|
||||
foreach (const TimeRange &r, ranges_to_invalidate) {
|
||||
track->Node::InvalidateCache(r, Track::kBlockInput);
|
||||
}
|
||||
|
||||
for (int i=0; i<position_commands_.size(); i++) {
|
||||
position_commands_.at(i)->redo_now();
|
||||
}
|
||||
}
|
||||
|
||||
void TrackPlaceBlockCommand::undo()
|
||||
{
|
||||
for (int i=position_commands_.size()-1; i>=0; i--) {
|
||||
position_commands_.at(i)->undo_now();
|
||||
}
|
||||
|
||||
Track* t = timeline_->GetTrackAt(track_index_);
|
||||
|
||||
TimeRange insert_range(insert_->in(), insert_->out());
|
||||
|
||||
@@ -198,7 +198,6 @@ private:
|
||||
QVector<TimelineAddTrackCommand*> add_track_commands_;
|
||||
QObject memory_manager_;
|
||||
TrackRippleRemoveAreaCommand* ripple_remove_command_;
|
||||
QVector<NodeSetPositionAsChildCommand*> position_commands_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ void TrackRippleRemoveAreaCommand::redo()
|
||||
|
||||
if (splice_split_command_) {
|
||||
// We're just splicing
|
||||
splice_split_command_->redo();
|
||||
splice_split_command_->redo_now();
|
||||
|
||||
// Trim the in of the split
|
||||
Block* split = splice_split_command_->new_block();
|
||||
@@ -157,7 +157,7 @@ void TrackRippleRemoveAreaCommand::undo()
|
||||
track_->BeginOperation();
|
||||
|
||||
if (splice_split_command_) {
|
||||
splice_split_command_->undo();
|
||||
splice_split_command_->undo_now();
|
||||
} else {
|
||||
if (trim_out_.block) {
|
||||
trim_out_.block->set_length_and_media_out(trim_out_.old_length);
|
||||
@@ -457,46 +457,128 @@ void TrackListRippleToolCommand::ripple(bool redo)
|
||||
//
|
||||
// TimelineRippleDeleteGapsAtRegionsCommand
|
||||
//
|
||||
void TimelineRippleDeleteGapsAtRegionsCommand::redo()
|
||||
void TimelineRippleDeleteGapsAtRegionsCommand::prepare()
|
||||
{
|
||||
if (commands_.isEmpty()) {
|
||||
foreach (const TimeRange& range, regions_) {
|
||||
rational max_ripple_length = range.length();
|
||||
int max_gaps = 0;
|
||||
QHash<Track*, QVector<RemovalRequest> > requested_gaps;
|
||||
|
||||
QVector<Block*> blocks_around_range;
|
||||
// Convert regions to gaps
|
||||
for (const QPair<Track*, TimeRange> ®ion : qAsConst(regions_)) {
|
||||
Track *track = region.first;
|
||||
const TimeRange &range = region.second;
|
||||
|
||||
foreach (Track* track, timeline_->GetTracks()) {
|
||||
// Get the block from every other track that is either at or just before our block's in point
|
||||
Block* block_at_time = track->NearestBlockBeforeOrAt(range.in());
|
||||
GapBlock *gap = dynamic_cast<GapBlock*>(track->NearestBlockBeforeOrAt(range.in()));
|
||||
|
||||
if (block_at_time) {
|
||||
if (dynamic_cast<GapBlock*>(block_at_time)) {
|
||||
max_ripple_length = qMin(block_at_time->length(), max_ripple_length);
|
||||
} else {
|
||||
max_ripple_length = 0;
|
||||
break;
|
||||
if (gap) {
|
||||
QVector<RemovalRequest> &gaps_on_track = requested_gaps[track];
|
||||
|
||||
RemovalRequest this_req = {gap, range};
|
||||
|
||||
// Insertion sort
|
||||
bool inserted = false;
|
||||
for (int i=0; i<gaps_on_track.size(); i++) {
|
||||
if (gaps_on_track.at(i).range.in() < range.in()) {
|
||||
gaps_on_track.insert(i, this_req);
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!inserted) {
|
||||
gaps_on_track.append(this_req);
|
||||
}
|
||||
|
||||
max_gaps = qMax(max_gaps, gaps_on_track.size());
|
||||
}
|
||||
}
|
||||
|
||||
for (int gap_index=0; gap_index<max_gaps; gap_index++) {
|
||||
rational earliest_point = RATIONAL_MAX;
|
||||
rational ripple_length = RATIONAL_MAX;
|
||||
|
||||
foreach (const QVector<RemovalRequest> &gaps_on_track, requested_gaps) {
|
||||
if (gap_index < gaps_on_track.size()) {
|
||||
const RemovalRequest &gap = gaps_on_track.at(gap_index);
|
||||
earliest_point = qMin(earliest_point, gap.range.in());
|
||||
ripple_length = qMin(ripple_length, gap.range.length());
|
||||
}
|
||||
}
|
||||
|
||||
// Determine which gaps will be involved in this operation
|
||||
QVector<GapBlock*> gaps;
|
||||
|
||||
foreach (Track* track, timeline_->GetTracks()) {
|
||||
if (track->IsLocked()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const QVector<RemovalRequest> &requested_gaps_on_track = requested_gaps.value(track);
|
||||
GapBlock *gap = nullptr;
|
||||
if (gap_index < requested_gaps_on_track.size()) {
|
||||
// A requested gap was at this index, use it
|
||||
gap = requested_gaps_on_track.at(gap_index).gap;
|
||||
} else {
|
||||
// No requested gap was at this index, find one
|
||||
Block *block = track->NearestBlockAfterOrAt(earliest_point);
|
||||
|
||||
if (block) {
|
||||
// Found a block, test if it's a gap
|
||||
gap = dynamic_cast<GapBlock*>(block);
|
||||
|
||||
if (!gap) {
|
||||
if (block->in() == earliest_point) {
|
||||
if (block->next()) {
|
||||
gap = dynamic_cast<GapBlock*>(block->next());
|
||||
|
||||
if (!gap) {
|
||||
ripple_length = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
gap = dynamic_cast<GapBlock*>(block->previous());
|
||||
|
||||
if (!gap) {
|
||||
ripple_length = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blocks_around_range.append(block_at_time);
|
||||
} else {
|
||||
// Assume track finishes here and track won't be affected by this operation
|
||||
}
|
||||
}
|
||||
|
||||
if (max_ripple_length > 0) {
|
||||
foreach (Block* resize, blocks_around_range) {
|
||||
if (resize->length() == max_ripple_length) {
|
||||
// Remove block entirely
|
||||
commands_.append(new TrackRippleRemoveBlockCommand(resize->track(), resize));
|
||||
} else {
|
||||
// Resize block
|
||||
commands_.append(new BlockResizeCommand(resize, resize->length() - max_ripple_length));
|
||||
}
|
||||
if (gap) {
|
||||
gaps.append(gap);
|
||||
ripple_length = qMin(ripple_length, gap->length());
|
||||
}
|
||||
|
||||
if (ripple_length == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ripple_length > 0) {
|
||||
foreach (GapBlock *gap, gaps) {
|
||||
if (gap->length() == ripple_length) {
|
||||
commands_.append(new TrackRippleRemoveBlockCommand(gap->track(), gap));
|
||||
} else {
|
||||
commands_.append(new BlockResizeCommand(gap, gap->length() - ripple_length));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (UndoCommand* c, commands_) {
|
||||
c->redo_now();
|
||||
void TimelineRippleDeleteGapsAtRegionsCommand::redo()
|
||||
{
|
||||
for (auto it=commands_.cbegin(); it!=commands_.cend(); it++) {
|
||||
(*it)->redo_now();
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineRippleDeleteGapsAtRegionsCommand::undo()
|
||||
{
|
||||
for (auto it=commands_.crbegin(); it!=commands_.crend(); it++) {
|
||||
(*it)->undo_now();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -202,7 +202,7 @@ private:
|
||||
|
||||
class TimelineRippleDeleteGapsAtRegionsCommand : public UndoCommand {
|
||||
public:
|
||||
TimelineRippleDeleteGapsAtRegionsCommand(Sequence* vo, const TimeRangeList& regions) :
|
||||
TimelineRippleDeleteGapsAtRegionsCommand(Sequence* vo, const QVector<QPair<Track*, TimeRange> >& regions) :
|
||||
timeline_(vo),
|
||||
regions_(regions)
|
||||
{
|
||||
@@ -218,22 +218,29 @@ public:
|
||||
return timeline_->project();
|
||||
}
|
||||
|
||||
bool HasCommands() const
|
||||
{
|
||||
return !commands_.isEmpty();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void prepare() override;
|
||||
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override
|
||||
{
|
||||
for (int i=commands_.size()-1;i>=0;i--) {
|
||||
commands_.at(i)->undo_now();
|
||||
}
|
||||
}
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
Sequence* timeline_;
|
||||
TimeRangeList regions_;
|
||||
QVector<QPair<Track*, TimeRange> > regions_;
|
||||
|
||||
QVector<UndoCommand*> commands_;
|
||||
|
||||
struct RemovalRequest {
|
||||
GapBlock *gap;
|
||||
TimeRange range;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -61,12 +61,6 @@ void BlockSplitCommand::redo()
|
||||
// Insert new block
|
||||
track->InsertBlockAfter(new_block(), block_);
|
||||
|
||||
// Position the block
|
||||
if (!position_command_) {
|
||||
position_command_ = new NodeSetPositionAsChildCommand(new_block(), track, track, new_block()->index(), track->Blocks().size(), true);
|
||||
}
|
||||
position_command_->redo_now();
|
||||
|
||||
// If the block had an out transition, we move it to the new block
|
||||
moved_transition_ = NodeInput();
|
||||
|
||||
@@ -96,8 +90,6 @@ void BlockSplitCommand::undo()
|
||||
Node::ConnectEdge(block_, moved_transition_);
|
||||
}
|
||||
|
||||
position_command_->undo_now();
|
||||
|
||||
block_->set_length_and_media_out(old_length_);
|
||||
track->RippleRemoveBlock(new_block());
|
||||
|
||||
@@ -130,7 +122,7 @@ void BlockSplitPreservingLinksCommand::redo()
|
||||
|
||||
if (b->in() < time && b->out() > time) {
|
||||
BlockSplitCommand* split_command = new BlockSplitCommand(b, time);
|
||||
split_command->redo();
|
||||
split_command->redo_now();
|
||||
splits.replace(j, split_command->new_block());
|
||||
commands_.append(split_command);
|
||||
} else {
|
||||
|
||||
@@ -31,15 +31,13 @@ public:
|
||||
block_(block),
|
||||
new_block_(nullptr),
|
||||
point_(point),
|
||||
reconnect_tree_command_(nullptr),
|
||||
position_command_(nullptr)
|
||||
reconnect_tree_command_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~BlockSplitCommand() override
|
||||
{
|
||||
delete reconnect_tree_command_;
|
||||
delete position_command_;
|
||||
}
|
||||
|
||||
virtual Project* GetRelevantProject() const override
|
||||
@@ -55,6 +53,7 @@ public:
|
||||
return new_block_;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override;
|
||||
@@ -70,8 +69,6 @@ private:
|
||||
|
||||
NodeInput moved_transition_;
|
||||
|
||||
NodeSetPositionAsChildCommand* position_command_;
|
||||
|
||||
};
|
||||
|
||||
class BlockSplitPreservingLinksCommand : public UndoCommand {
|
||||
@@ -92,6 +89,7 @@ public:
|
||||
return blocks_.first()->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override
|
||||
@@ -129,6 +127,7 @@ public:
|
||||
return track_->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void prepare() override;
|
||||
|
||||
virtual void redo() override
|
||||
|
||||
@@ -1,391 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "videoparamedit.h"
|
||||
|
||||
#include <QGridLayout>
|
||||
|
||||
namespace olive {
|
||||
|
||||
VideoParamEdit::VideoParamEdit(QWidget* parent) :
|
||||
QWidget(parent),
|
||||
color_manager_(nullptr),
|
||||
mask_(0)
|
||||
{
|
||||
QGridLayout* layout = new QGridLayout(this);
|
||||
|
||||
layout->setMargin(0);
|
||||
|
||||
int row = 0;
|
||||
|
||||
// Enabled
|
||||
enabled_lbl_ = new QLabel(tr("Enabled:"));
|
||||
layout->addWidget(enabled_lbl_, row, 0);
|
||||
enabled_box_ = new QCheckBox();
|
||||
connect(enabled_box_, &QCheckBox::clicked, this, &VideoParamEdit::Changed);
|
||||
layout->addWidget(enabled_box_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
// Width
|
||||
width_lbl_ = new QLabel(tr("Width:"));
|
||||
layout->addWidget(width_lbl_, row, 0);
|
||||
|
||||
width_slider_ = new IntegerSlider();
|
||||
width_slider_->SetMinimum(1);
|
||||
width_slider_->SetMaximum(32768);
|
||||
connect(width_slider_, &IntegerSlider::ValueChanged, this, &VideoParamEdit::Changed);
|
||||
layout->addWidget(width_slider_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
// Height
|
||||
height_lbl_ = new QLabel(tr("Height:"));
|
||||
layout->addWidget(height_lbl_, row, 0);
|
||||
|
||||
height_slider_ = new IntegerSlider();
|
||||
height_slider_->SetMinimum(1);
|
||||
height_slider_->SetMaximum(32768);
|
||||
connect(height_slider_, &IntegerSlider::ValueChanged, this, &VideoParamEdit::Changed);
|
||||
layout->addWidget(height_slider_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
// Depth
|
||||
depth_lbl_ = new QLabel(tr("Depth:"));
|
||||
layout->addWidget(depth_lbl_, row, 0);
|
||||
|
||||
depth_slider_ = new IntegerSlider();
|
||||
depth_slider_->SetMinimum(1);
|
||||
depth_slider_->SetMaximum(32768);
|
||||
connect(depth_slider_, &IntegerSlider::ValueChanged, this, &VideoParamEdit::Changed);
|
||||
layout->addWidget(depth_slider_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
// Pixel Format
|
||||
format_lbl_ = new QLabel(tr("Format:"));
|
||||
layout->addWidget(format_lbl_, row, 0);
|
||||
format_combobox_ = new PixelFormatComboBox(true);
|
||||
connect(format_combobox_, static_cast<void (PixelFormatComboBox::*)(int)>(&PixelFormatComboBox::currentIndexChanged), this, &VideoParamEdit::Changed);
|
||||
layout->addWidget(format_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
// Frame Rate
|
||||
frame_rate_lbl_ = new QLabel(tr("Frame Rate:"));
|
||||
layout->addWidget(frame_rate_lbl_, row, 0);
|
||||
|
||||
frame_rate_combobox_ = new FrameRateComboBox();
|
||||
connect(frame_rate_combobox_, &FrameRateComboBox::FrameRateChanged, this, &VideoParamEdit::Changed);
|
||||
layout->addWidget(frame_rate_combobox_, row, 1);
|
||||
|
||||
frame_rate_slider_ = new RationalSlider();
|
||||
frame_rate_slider_->SetMinimum(0);
|
||||
frame_rate_slider_->SetDecimalPlaces(3);
|
||||
frame_rate_slider_->SetAutoTrimDecimalPlaces(true);
|
||||
frame_rate_slider_->SetTimebase(rational(1, 1000)); // Drag interval
|
||||
frame_rate_slider_->DisableDisplayType(RationalSlider::kTime);
|
||||
connect(frame_rate_slider_, &RationalSlider::ValueChanged, this, &VideoParamEdit::Changed);
|
||||
layout->addWidget(frame_rate_slider_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
// Pixel Aspect Ratio
|
||||
pixel_aspect_lbl_ = new QLabel(tr("Pixel Aspect Ratio:"));
|
||||
layout->addWidget(pixel_aspect_lbl_, row, 0);
|
||||
|
||||
pixel_aspect_combobox_ = new PixelAspectRatioComboBox();
|
||||
connect(pixel_aspect_combobox_, static_cast<void (PixelAspectRatioComboBox::*)(int)>(&PixelAspectRatioComboBox::currentIndexChanged), this, &VideoParamEdit::Changed);
|
||||
layout->addWidget(pixel_aspect_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
// Interlacing
|
||||
interlaced_lbl_ = new QLabel(tr("Interlacing:"));
|
||||
layout->addWidget(interlaced_lbl_, row, 0);
|
||||
|
||||
interlaced_combobox_ = new InterlacedComboBox();
|
||||
connect(interlaced_combobox_, static_cast<void (InterlacedComboBox::*)(int)>(&InterlacedComboBox::currentIndexChanged), this, &VideoParamEdit::Changed);
|
||||
layout->addWidget(interlaced_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
// Channel Count
|
||||
channel_count_lbl_ = new QLabel(tr("Channel Count:"));
|
||||
layout->addWidget(channel_count_lbl_, row, 0);
|
||||
|
||||
channel_count_combobox_ = new QComboBox();
|
||||
channel_count_combobox_->addItem(tr("RGB"), VideoParams::kRGBChannelCount);
|
||||
channel_count_combobox_->addItem(tr("RGBA"), VideoParams::kRGBAChannelCount);
|
||||
connect(channel_count_combobox_, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &VideoParamEdit::Changed);
|
||||
layout->addWidget(channel_count_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
// Divider
|
||||
divider_lbl_ = new QLabel(tr("Divider:"));
|
||||
layout->addWidget(divider_lbl_, row, 0);
|
||||
|
||||
divider_combobox_ = new VideoDividerComboBox();
|
||||
connect(divider_combobox_, static_cast<void (VideoDividerComboBox::*)(int)>(&VideoDividerComboBox::currentIndexChanged), this, &VideoParamEdit::Changed);
|
||||
layout->addWidget(divider_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
// Stream Index
|
||||
stream_index_lbl_ = new QLabel(tr("Stream Index:"));
|
||||
layout->addWidget(stream_index_lbl_, row, 0);
|
||||
stream_index_slider_ = new IntegerSlider();
|
||||
stream_index_slider_->SetMinimum(0);
|
||||
connect(stream_index_slider_, &IntegerSlider::ValueChanged, this, &VideoParamEdit::Changed);
|
||||
layout->addWidget(stream_index_slider_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
// Video type
|
||||
video_type_lbl_ = new QLabel(tr("Video Type:"));
|
||||
layout->addWidget(video_type_lbl_, row, 0);
|
||||
video_type_combobox_ = new QComboBox();
|
||||
video_type_combobox_->addItem(tr("Video"), VideoParams::kVideoTypeVideo);
|
||||
video_type_combobox_->addItem(tr("Still"), VideoParams::kVideoTypeStill);
|
||||
video_type_combobox_->addItem(tr("Image Sequence"), VideoParams::kVideoTypeImageSequence);
|
||||
connect(video_type_combobox_, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &VideoParamEdit::Changed);
|
||||
layout->addWidget(video_type_combobox_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
// Start time (for image sequences)
|
||||
start_time_lbl_ = new QLabel(tr("Start Time"));
|
||||
layout->addWidget(start_time_lbl_, row, 0);
|
||||
|
||||
start_time_slider_ = new IntegerSlider();
|
||||
start_time_slider_->SetMinimum(0);
|
||||
connect(start_time_slider_, &IntegerSlider::ValueChanged, this, &VideoParamEdit::Changed);
|
||||
layout->addWidget(start_time_slider_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
// End time (for image sequences)
|
||||
end_time_lbl_ = new QLabel(tr("End Time"));
|
||||
layout->addWidget(end_time_lbl_, row, 0);
|
||||
|
||||
end_time_slider_ = new IntegerSlider();
|
||||
end_time_slider_->SetMinimum(0);
|
||||
connect(end_time_slider_, &IntegerSlider::ValueChanged, this, &VideoParamEdit::Changed);
|
||||
layout->addWidget(end_time_slider_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
// Premultiplied alpha
|
||||
premultiplied_alpha_lbl_ = new QLabel(tr("Premultiplied Alpha"));
|
||||
layout->addWidget(premultiplied_alpha_lbl_, row, 0);
|
||||
|
||||
premultiplied_alpha_box_ = new QCheckBox();
|
||||
connect(premultiplied_alpha_box_, &QCheckBox::clicked, this, &VideoParamEdit::Changed);
|
||||
layout->addWidget(premultiplied_alpha_box_, row, 1);
|
||||
|
||||
row++;
|
||||
|
||||
// Colorspace
|
||||
colorspace_lbl_ = new QLabel(tr("Colorspace"));
|
||||
layout->addWidget(colorspace_lbl_, row, 0);
|
||||
|
||||
colorspace_combobox_ = new QComboBox();
|
||||
connect(colorspace_combobox_, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &VideoParamEdit::Changed);
|
||||
layout->addWidget(colorspace_combobox_, row, 1);
|
||||
}
|
||||
|
||||
void VideoParamEdit::SetParameterMask(uint64_t mask)
|
||||
{
|
||||
mask_ = mask;
|
||||
|
||||
width_lbl_->setVisible(mask & kWidthHeight);
|
||||
width_slider_->setVisible(mask & kWidthHeight);
|
||||
height_lbl_->setVisible(mask & kWidthHeight);
|
||||
height_slider_->setVisible(mask & kWidthHeight);
|
||||
|
||||
depth_lbl_->setVisible(mask & kDepth);
|
||||
depth_slider_->setVisible(mask & kDepth);
|
||||
|
||||
frame_rate_lbl_->setVisible(mask & kFrameRate);
|
||||
frame_rate_combobox_->setVisible((mask & kFrameRate) && !(mask & kFrameRateIsArbitrary));
|
||||
frame_rate_slider_->setVisible((mask & kFrameRate) && (mask & kFrameRateIsArbitrary));
|
||||
|
||||
pixel_aspect_lbl_->setVisible(mask & kPixelAspect);
|
||||
pixel_aspect_combobox_->setVisible(mask & kPixelAspect);
|
||||
|
||||
interlaced_lbl_->setVisible(mask & kInterlacing);
|
||||
interlaced_combobox_->setVisible(mask & kInterlacing);
|
||||
|
||||
enabled_lbl_->setVisible(mask & kEnabled);
|
||||
enabled_box_->setVisible(mask & kEnabled);
|
||||
|
||||
format_lbl_->setVisible(mask & kFormat);
|
||||
format_combobox_->setVisible(mask & kFormat);
|
||||
|
||||
channel_count_lbl_->setVisible(mask & kChannelCount);
|
||||
channel_count_combobox_->setVisible(mask & kChannelCount);
|
||||
|
||||
divider_lbl_->setVisible(mask & kDivider);
|
||||
divider_combobox_->setVisible(mask & kDivider);
|
||||
|
||||
stream_index_lbl_->setVisible(mask & kStreamIndex);
|
||||
stream_index_slider_->setVisible(mask & kStreamIndex);
|
||||
|
||||
video_type_lbl_->setVisible(mask & kIsImageSequence);
|
||||
video_type_combobox_->setVisible(mask & kIsImageSequence);
|
||||
|
||||
start_time_lbl_->setVisible(mask & kStartTime);
|
||||
start_time_slider_->setVisible(mask & kStartTime);
|
||||
|
||||
end_time_lbl_->setVisible(mask & kEndTime);
|
||||
end_time_slider_->setVisible(mask & kEndTime);
|
||||
|
||||
premultiplied_alpha_lbl_->setVisible(mask & kPremultipliedAlpha);
|
||||
premultiplied_alpha_box_->setVisible(mask & kPremultipliedAlpha);
|
||||
|
||||
colorspace_lbl_->setVisible(mask & kColorspace);
|
||||
colorspace_combobox_->setVisible(mask & kColorspace);
|
||||
}
|
||||
|
||||
VideoParams VideoParamEdit::GetVideoParams() const
|
||||
{
|
||||
VideoParams p;
|
||||
|
||||
p.set_enabled(enabled_box_->isChecked());
|
||||
p.set_width(width_slider_->GetValue());
|
||||
p.set_height(height_slider_->GetValue());
|
||||
p.set_depth(depth_slider_->GetValue());
|
||||
|
||||
{
|
||||
rational using_frame_rate;
|
||||
|
||||
if (mask_ & kFrameRateIsArbitrary) {
|
||||
using_frame_rate = frame_rate_slider_->GetValue();
|
||||
} else {
|
||||
using_frame_rate = frame_rate_combobox_->GetFrameRate();
|
||||
}
|
||||
|
||||
p.set_frame_rate(using_frame_rate);
|
||||
if (mask_ & kFrameRateIsNotTimebase) {
|
||||
// Frame rate editor will only edit the frame rate
|
||||
p.set_time_base(timebase_temp_);
|
||||
} else {
|
||||
p.set_time_base(using_frame_rate.flipped());
|
||||
}
|
||||
}
|
||||
|
||||
p.set_pixel_aspect_ratio(pixel_aspect_combobox_->GetPixelAspectRatio());
|
||||
p.set_interlacing(interlaced_combobox_->GetInterlaceMode());
|
||||
p.set_format(format_combobox_->GetPixelFormat());
|
||||
p.set_channel_count(channel_count_combobox_->currentData().toInt());
|
||||
p.set_divider(divider_combobox_->GetDivider());
|
||||
p.set_stream_index(stream_index_slider_->GetValue());
|
||||
p.set_video_type(static_cast<VideoParams::Type>(video_type_combobox_->currentData().toInt()));
|
||||
p.set_start_time(start_time_slider_->GetValue());
|
||||
p.set_duration(end_time_slider_->GetValue() - start_time_slider_->GetValue() + 1);
|
||||
p.set_premultiplied_alpha(premultiplied_alpha_box_->isChecked());
|
||||
p.set_colorspace(colorspace_combobox_->currentData().toString());
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void VideoParamEdit::SetVideoParams(const VideoParams &p)
|
||||
{
|
||||
blockSignals(true);
|
||||
|
||||
enabled_box_->setChecked(p.enabled());
|
||||
width_slider_->SetValue(p.width());
|
||||
height_slider_->SetValue(p.height());
|
||||
depth_slider_->SetValue(p.depth());
|
||||
|
||||
frame_rate_combobox_->SetFrameRate(p.frame_rate());
|
||||
frame_rate_slider_->SetValue(p.frame_rate());
|
||||
timebase_temp_ = p.time_base();
|
||||
|
||||
pixel_aspect_combobox_->SetPixelAspectRatio(p.pixel_aspect_ratio());
|
||||
interlaced_combobox_->SetInterlaceMode(p.interlacing());
|
||||
format_combobox_->SetPixelFormat(p.format());
|
||||
SetChannelCount(p.channel_count());
|
||||
divider_combobox_->SetDivider(p.divider());
|
||||
stream_index_slider_->SetValue(p.stream_index());
|
||||
SetVideoTypeComboBox(p.video_type());
|
||||
start_time_slider_->SetValue(p.start_time());
|
||||
end_time_slider_->SetValue(p.start_time() + p.duration() - 1);
|
||||
premultiplied_alpha_box_->setChecked(p.premultiplied_alpha());
|
||||
|
||||
if (color_manager_) {
|
||||
// Assume colorspace box has been populated correctly
|
||||
for (int i=0; i<colorspace_combobox_->count(); i++) {
|
||||
if (colorspace_combobox_->itemData(i).toString() == p.colorspace()) {
|
||||
colorspace_combobox_->setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Box is empty, fill with single option so that it gets preserved in GetVideoParams()
|
||||
colorspace_combobox_->clear();
|
||||
colorspace_combobox_->addItem(p.colorspace(), p.colorspace());
|
||||
}
|
||||
|
||||
blockSignals(false);
|
||||
}
|
||||
|
||||
void VideoParamEdit::SetColorManager(ColorManager *cm)
|
||||
{
|
||||
color_manager_ = cm;
|
||||
|
||||
// Re-populate colorspace combobox
|
||||
colorspace_combobox_->clear();
|
||||
|
||||
if (color_manager_) {
|
||||
// Add default colorspace
|
||||
colorspace_combobox_->addItem(tr("Default (%1)").arg(color_manager_->GetDefaultInputColorSpace()), QString());
|
||||
|
||||
// Add remaining
|
||||
QStringList spaces = color_manager_->ListAvailableColorspaces();
|
||||
foreach (const QString& s, spaces) {
|
||||
colorspace_combobox_->addItem(s, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VideoParamEdit::SetChannelCount(int count)
|
||||
{
|
||||
for (int i=0; i<channel_count_combobox_->count(); i++) {
|
||||
if (channel_count_combobox_->itemData(i).toInt() == count) {
|
||||
channel_count_combobox_->setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VideoParamEdit::SetVideoTypeComboBox(VideoParams::Type type)
|
||||
{
|
||||
for (int i=0; i<video_type_combobox_->count(); i++) {
|
||||
if (video_type_combobox_->itemData(i).toInt() == type) {
|
||||
video_type_combobox_->setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef VIDEOPARAMEDIT_H
|
||||
#define VIDEOPARAMEDIT_H
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QLabel>
|
||||
#include <QWidget>
|
||||
|
||||
#include "node/color/colormanager/colormanager.h"
|
||||
#include "render/videoparams.h"
|
||||
#include "widget/slider/integerslider.h"
|
||||
#include "widget/slider/rationalslider.h"
|
||||
#include "widget/standardcombos/frameratecombobox.h"
|
||||
#include "widget/standardcombos/interlacedcombobox.h"
|
||||
#include "widget/standardcombos/pixelaspectratiocombobox.h"
|
||||
#include "widget/standardcombos/pixelformatcombobox.h"
|
||||
#include "widget/standardcombos/videodividercombobox.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class VideoParamEdit : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VideoParamEdit(QWidget* parent = nullptr);
|
||||
|
||||
enum ParamMask {
|
||||
kNone = 0x0,
|
||||
kEnabled = 0x1,
|
||||
kWidthHeight = 0x2,
|
||||
kDepth = 0x4,
|
||||
kFrameRate = 0x8,
|
||||
kFormat = 0x10,
|
||||
kChannelCount = 0x20,
|
||||
kPixelAspect = 0x40,
|
||||
kInterlacing = 0x80,
|
||||
kDivider = 0x100,
|
||||
kStreamIndex = 0x200,
|
||||
kIsImageSequence = 0x400,
|
||||
kStartTime = 0x800,
|
||||
kEndTime = 0x1000,
|
||||
kPremultipliedAlpha = 0x2000,
|
||||
kColorspace = 0x4000,
|
||||
kFrameRateIsNotTimebase = 0x8000,
|
||||
kFrameRateIsArbitrary = 0x10000
|
||||
};
|
||||
|
||||
void SetParameterMask(uint64_t mask);
|
||||
|
||||
VideoParams GetVideoParams() const;
|
||||
void SetVideoParams(const VideoParams& p);
|
||||
|
||||
/**
|
||||
* @brief Set pointer to ColorManager
|
||||
*
|
||||
* Call this before calling SetVideoParams because it'll populate the colorspace list so it
|
||||
* can correctly be chosen from in the UI.
|
||||
*/
|
||||
void SetColorManager(ColorManager* cm);
|
||||
|
||||
int GetWidth() const
|
||||
{
|
||||
return width_slider_->GetValue();
|
||||
}
|
||||
|
||||
void SetWidth(int w)
|
||||
{
|
||||
width_slider_->SetValue(w);
|
||||
}
|
||||
|
||||
int GetHeight() const
|
||||
{
|
||||
return height_slider_->GetValue();
|
||||
}
|
||||
|
||||
void SetHeight(int h)
|
||||
{
|
||||
height_slider_->SetValue(h);
|
||||
}
|
||||
|
||||
rational GetFrameRate() const
|
||||
{
|
||||
return frame_rate_combobox_->GetFrameRate();
|
||||
}
|
||||
|
||||
void SetFrameRate(const rational& r)
|
||||
{
|
||||
frame_rate_combobox_->SetFrameRate(r);
|
||||
}
|
||||
|
||||
rational GetPixelAspectRatio() const
|
||||
{
|
||||
return pixel_aspect_combobox_->GetPixelAspectRatio();
|
||||
}
|
||||
|
||||
void SetPixelAspectRatio(const rational& r)
|
||||
{
|
||||
pixel_aspect_combobox_->SetPixelAspectRatio(r);
|
||||
}
|
||||
|
||||
VideoParams::Interlacing GetInterlaceMode() const
|
||||
{
|
||||
return interlaced_combobox_->GetInterlaceMode();
|
||||
}
|
||||
|
||||
void SetInterlaceMode(VideoParams::Interlacing i)
|
||||
{
|
||||
interlaced_combobox_->SetInterlaceMode(i);
|
||||
}
|
||||
|
||||
signals:
|
||||
void Changed();
|
||||
|
||||
private:
|
||||
void SetChannelCount(int count);
|
||||
|
||||
void SetVideoTypeComboBox(VideoParams::Type type);
|
||||
|
||||
QLabel* enabled_lbl_;
|
||||
QCheckBox* enabled_box_;
|
||||
QLabel* width_lbl_;
|
||||
IntegerSlider* width_slider_;
|
||||
QLabel* height_lbl_;
|
||||
IntegerSlider* height_slider_;
|
||||
QLabel* depth_lbl_;
|
||||
IntegerSlider* depth_slider_;
|
||||
QLabel* frame_rate_lbl_;
|
||||
FrameRateComboBox* frame_rate_combobox_;
|
||||
RationalSlider* frame_rate_slider_;
|
||||
QLabel* pixel_aspect_lbl_;
|
||||
PixelAspectRatioComboBox* pixel_aspect_combobox_;
|
||||
QLabel* interlaced_lbl_;
|
||||
InterlacedComboBox* interlaced_combobox_;
|
||||
QLabel* format_lbl_;
|
||||
PixelFormatComboBox* format_combobox_;
|
||||
QLabel* channel_count_lbl_;
|
||||
QComboBox* channel_count_combobox_;
|
||||
QLabel* divider_lbl_;
|
||||
VideoDividerComboBox* divider_combobox_;
|
||||
QLabel* stream_index_lbl_;
|
||||
IntegerSlider* stream_index_slider_;
|
||||
QLabel* video_type_lbl_;
|
||||
QComboBox* video_type_combobox_;
|
||||
QLabel* start_time_lbl_;
|
||||
IntegerSlider* start_time_slider_;
|
||||
QLabel* end_time_lbl_;
|
||||
IntegerSlider* end_time_slider_;
|
||||
QLabel* premultiplied_alpha_lbl_;
|
||||
QCheckBox* premultiplied_alpha_box_;
|
||||
QLabel* colorspace_lbl_;
|
||||
QComboBox* colorspace_combobox_;
|
||||
|
||||
ColorManager* color_manager_;
|
||||
|
||||
rational timebase_temp_;
|
||||
|
||||
uint64_t mask_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // VIDEOPARAMEDIT_H
|
||||
@@ -26,6 +26,8 @@ set(OLIVE_SOURCES
|
||||
widget/viewer/viewerdisplay.h
|
||||
widget/viewer/viewerplaybacktimer.cpp
|
||||
widget/viewer/viewerplaybacktimer.h
|
||||
widget/viewer/viewerpreventsleep.cpp
|
||||
widget/viewer/viewerpreventsleep.h
|
||||
widget/viewer/viewerqueue.h
|
||||
widget/viewer/viewersafemargininfo.h
|
||||
widget/viewer/viewersizer.cpp
|
||||
|
||||
+95
-175
@@ -40,6 +40,7 @@
|
||||
#include "node/project/project.h"
|
||||
#include "render/rendermanager.h"
|
||||
#include "task/taskmanager.h"
|
||||
#include "viewerpreventsleep.h"
|
||||
#include "widget/menu/menu.h"
|
||||
#include "window/mainwindow/mainwindow.h"
|
||||
|
||||
@@ -80,14 +81,15 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
|
||||
display_widget_ = new ViewerDisplayWidget();
|
||||
display_widget_->setAcceptDrops(true);
|
||||
display_widget_->SetShowWidgetBackground(true);
|
||||
playback_devices_.append(display_widget_);
|
||||
connect(display_widget_, &ViewerDisplayWidget::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu);
|
||||
connect(display_widget_, &ViewerDisplayWidget::CursorColor, this, &ViewerWidget::CursorColor);
|
||||
connect(display_widget_, &ViewerDisplayWidget::ColorProcessorChanged, this, &ViewerWidget::ColorProcessorChanged);
|
||||
connect(display_widget_, &ViewerDisplayWidget::ColorManagerChanged, this, &ViewerWidget::ColorManagerChanged);
|
||||
connect(display_widget_, &ViewerDisplayWidget::DragEntered, this, &ViewerWidget::DragEntered);
|
||||
connect(display_widget_, &ViewerDisplayWidget::Dropped, this, &ViewerWidget::Dropped);
|
||||
connect(display_widget_, &ViewerDisplayWidget::VisibilityChanged, this, &ViewerWidget::Pause);
|
||||
connect(display_widget_, &ViewerDisplayWidget::TextureChanged, this, &ViewerWidget::TextureChanged);
|
||||
connect(display_widget_, &ViewerDisplayWidget::QueueStarved, this, &ViewerWidget::ForceRequeueFromCurrentTime);
|
||||
connect(sizer_, &ViewerSizer::RequestScale, display_widget_, &ViewerDisplayWidget::SetMatrixZoom);
|
||||
connect(sizer_, &ViewerSizer::RequestTranslate, display_widget_, &ViewerDisplayWidget::SetMatrixTranslate);
|
||||
connect(display_widget_, &ViewerDisplayWidget::HandDragMoved, sizer_, &ViewerSizer::HandDragMove);
|
||||
@@ -206,9 +208,8 @@ void ViewerWidget::ConnectNodeEvent(ViewerOutput *n)
|
||||
|
||||
ColorManager* color_manager = n->project()->color_manager();
|
||||
|
||||
display_widget_->ConnectColorManager(color_manager);
|
||||
foreach (ViewerWindow* window, windows_) {
|
||||
window->display_widget()->ConnectColorManager(color_manager);
|
||||
foreach (ViewerDisplayWidget *dw, playback_devices_) {
|
||||
dw->ConnectColorManager(color_manager);
|
||||
}
|
||||
|
||||
UpdateStack();
|
||||
@@ -246,9 +247,8 @@ void ViewerWidget::DisconnectNodeEvent(ViewerOutput *n)
|
||||
// Effectively disables the viewer and clears the state
|
||||
SetViewerResolution(0, 0);
|
||||
|
||||
display_widget_->DisconnectColorManager();
|
||||
foreach (ViewerWindow* window, windows_) {
|
||||
window->display_widget()->DisconnectColorManager();
|
||||
foreach (ViewerDisplayWidget *dw, playback_devices_) {
|
||||
dw->DisconnectColorManager();
|
||||
}
|
||||
|
||||
waveform_view_->SetViewer(nullptr);
|
||||
@@ -298,9 +298,8 @@ void ViewerWidget::SetColorMenuEnabled(bool enabled)
|
||||
|
||||
void ViewerWidget::SetMatrix(const QMatrix4x4 &mat)
|
||||
{
|
||||
display_widget_->SetMatrixCrop(mat);
|
||||
foreach (ViewerWindow* vw, windows_) {
|
||||
vw->display_widget()->SetMatrixCrop(mat);
|
||||
foreach (ViewerDisplayWidget *dw, playback_devices_) {
|
||||
dw->SetMatrixCrop(mat);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,9 +341,11 @@ void ViewerWidget::SetFullScreen(QScreen *screen)
|
||||
|
||||
vw->display_widget()->SetImage(QVariant::fromValue(display_widget()->GetCurrentTexture()));
|
||||
|
||||
(*vw->queue()) = playback_queue_;
|
||||
playback_devices_.append(vw->display_widget());
|
||||
|
||||
(*vw->display_widget()->queue()) = *playback_devices_.first()->queue();
|
||||
if (IsPlaying()) {
|
||||
vw->Play(GetTimestamp(), playback_speed_, timebase());
|
||||
vw->display_widget()->Play(GetTimestamp(), playback_speed_, timebase());
|
||||
}
|
||||
|
||||
windows_.insert(screen, vw);
|
||||
@@ -401,10 +402,8 @@ bool ViewerWidget::ShouldForceWaveform() const
|
||||
|
||||
void ViewerWidget::SetEmptyImage()
|
||||
{
|
||||
display_widget()->SetBlank();
|
||||
|
||||
foreach (ViewerWindow *vw, windows_) {
|
||||
vw->display_widget()->SetBlank();
|
||||
foreach (ViewerDisplayWidget *dw, playback_devices_) {
|
||||
dw->SetBlank();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,7 +431,7 @@ void ViewerWidget::QueueNextAudioBuffer()
|
||||
|
||||
// Clamp queue end by zero and the audio length
|
||||
queue_end = clamp(queue_end, rational(0), GetConnectedNode()->GetAudioLength());
|
||||
if (queue_end == audio_playback_queue_time_) {
|
||||
if (queue_end <= audio_playback_queue_time_) {
|
||||
// This will queue nothing, so stop the loop here
|
||||
if (prequeuing_audio_) {
|
||||
DecrementPrequeuedAudio();
|
||||
@@ -479,8 +478,6 @@ void ViewerWidget::ReceivedAudioBufferForPlayback()
|
||||
if (prequeuing_audio_) {
|
||||
// Add to prequeued audio buffer
|
||||
prequeued_audio_.append(pack);
|
||||
|
||||
DecrementPrequeuedAudio();
|
||||
} else {
|
||||
// Push directly to audio manager
|
||||
AudioManager::instance()->PushToOutput(GetConnectedNode()->GetAudioParams(), pack);
|
||||
@@ -489,6 +486,10 @@ void ViewerWidget::ReceivedAudioBufferForPlayback()
|
||||
}
|
||||
}
|
||||
|
||||
if (prequeuing_audio_) {
|
||||
DecrementPrequeuedAudio();
|
||||
}
|
||||
|
||||
delete watcher;
|
||||
}
|
||||
}
|
||||
@@ -502,18 +503,20 @@ void ViewerWidget::ReceivedAudioBufferForScrubbing()
|
||||
|
||||
if (watcher->HasResult()) {
|
||||
if (SampleBufferPtr samples = watcher->Get().value<SampleBufferPtr>()) {
|
||||
/* Fade code
|
||||
const int kFadeSz = qMin(200, samples->sample_count()/4);
|
||||
for (int i=0; i<kFadeSz; i++) {
|
||||
float amt = float(i)/float(kFadeSz);
|
||||
samples->transform_volume_for_sample(i, amt);
|
||||
samples->transform_volume_for_sample(samples->sample_count() - i - 1, amt);
|
||||
}*/
|
||||
if (samples->audio_params().channel_count() > 0) {
|
||||
/* Fade code
|
||||
const int kFadeSz = qMin(200, samples->sample_count()/4);
|
||||
for (int i=0; i<kFadeSz; i++) {
|
||||
float amt = float(i)/float(kFadeSz);
|
||||
samples->transform_volume_for_sample(i, amt);
|
||||
samples->transform_volume_for_sample(samples->sample_count() - i - 1, amt);
|
||||
}*/
|
||||
|
||||
QByteArray packed = packed_processor_.Convert(samples);
|
||||
AudioManager::instance()->ClearBufferedOutput();
|
||||
AudioManager::instance()->PushToOutput(samples->audio_params(), packed);
|
||||
AudioMonitor::PushBytesOnAll(packed);
|
||||
QByteArray packed = packed_processor_.Convert(samples);
|
||||
AudioManager::instance()->ClearBufferedOutput();
|
||||
AudioManager::instance()->PushToOutput(samples->audio_params(), packed);
|
||||
AudioMonitor::PushBytesOnAll(packed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -536,85 +539,30 @@ void ViewerWidget::UpdateTextureFromNode()
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsPlaying()) {
|
||||
qWarning() << "UpdateTextureFromNode called while playing";
|
||||
return;
|
||||
}
|
||||
|
||||
rational time = GetTime();
|
||||
bool frame_exists_at_time = FrameExistsAtTime(time);
|
||||
bool frame_might_be_still = ViewerMightBeAStill();
|
||||
|
||||
// Check playback queue for a frame
|
||||
if (IsPlaying()) {
|
||||
// We still run the playback queue even when FrameExistsAtTime returns false because we might be
|
||||
// playing backwards and about to start showing frames, so the queue should be prepared for
|
||||
// that.
|
||||
bool popped = false;
|
||||
|
||||
if (playback_queue_.empty()) {
|
||||
|
||||
ForceRequeueFromCurrentTime();
|
||||
|
||||
} else {
|
||||
while (!playback_queue_.empty()) {
|
||||
|
||||
ViewerPlaybackFrame pf = playback_queue_.front();
|
||||
|
||||
if (pf.timestamp == time) {
|
||||
|
||||
// Frame was in queue, no need to decode anything
|
||||
SetDisplayImage(pf.frame, true);
|
||||
return;
|
||||
|
||||
} else if (pf.timestamp > time) {
|
||||
|
||||
// The next frame in the queue is too new, so just do a regular update. Either the
|
||||
// frame we want will arrive in time, or we'll just have to skip it.
|
||||
break;
|
||||
|
||||
} else {
|
||||
|
||||
// Frame was too old, skip this frame
|
||||
PopOldestFrameFromPlaybackQueue();
|
||||
if (popped) {
|
||||
// We've already popped a frame in this loop, meaning a frame has been skipped
|
||||
display_widget_->IncrementSkippedFrames();
|
||||
} else {
|
||||
// Shown a frame and progressed to the next one
|
||||
display_widget_->IncrementFrameCount();
|
||||
popped = true;
|
||||
}
|
||||
|
||||
if (playback_queue_.empty()) {
|
||||
|
||||
ForceRequeueFromCurrentTime();
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (frame_exists_at_time || frame_might_be_still) {
|
||||
// Frame was not in queue, will require rendering or decoding from cache
|
||||
if (IsPlaying()) {
|
||||
// Is playing, yet the queue above failed to retrieve the frame. We effectively do a quick
|
||||
// reboot of the queue here, assuming the above loop has emptied it so far.
|
||||
display_widget_->update();
|
||||
} else {
|
||||
// Not playing, run a task to get the frame either from the cache or the renderer
|
||||
RenderTicketWatcher* watcher = new RenderTicketWatcher();
|
||||
watcher->setProperty("start", QDateTime::currentMSecsSinceEpoch());
|
||||
watcher->setProperty("time", QVariant::fromValue(time));
|
||||
connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::RendererGeneratedFrame);
|
||||
nonqueue_watchers_.append(watcher);
|
||||
// Not playing, run a task to get the frame either from the cache or the renderer
|
||||
RenderTicketWatcher* watcher = new RenderTicketWatcher();
|
||||
watcher->setProperty("start", QDateTime::currentMSecsSinceEpoch());
|
||||
watcher->setProperty("time", QVariant::fromValue(time));
|
||||
connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::RendererGeneratedFrame);
|
||||
nonqueue_watchers_.append(watcher);
|
||||
|
||||
// Clear queue because we want this frame more than any others
|
||||
if (!GetConnectedNode()->GetVideoAutoCacheEnabled() && !auto_cacher_.IsRenderingCustomRange()) {
|
||||
ClearVideoAutoCacherQueue();
|
||||
}
|
||||
|
||||
watcher->SetTicket(GetFrame(time, true));
|
||||
// Clear queue because we want this frame more than any others
|
||||
if (!GetConnectedNode()->GetVideoAutoCacheEnabled() && !auto_cacher_.IsRenderingCustomRange()) {
|
||||
ClearVideoAutoCacherQueue();
|
||||
}
|
||||
|
||||
watcher->SetTicket(GetFrame(time, true));
|
||||
} else {
|
||||
// There is definitely no frame here, we can immediately flip to showing nothing
|
||||
nonqueue_watchers_.clear();
|
||||
@@ -670,6 +618,7 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only)
|
||||
|
||||
if (prequeue_length_ > 0) {
|
||||
prequeuing_video_ = true;
|
||||
prequeue_count_ = 0;
|
||||
|
||||
// We "prioritize" the frames, which means they're pushed to the top of the render queue,
|
||||
// we queue in reverse so that they're still queued in order
|
||||
@@ -702,6 +651,8 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only)
|
||||
QueueNextAudioBuffer();
|
||||
}
|
||||
}
|
||||
// Force screen to stay awake
|
||||
PreventSleep(true);
|
||||
}
|
||||
|
||||
void ViewerWidget::PauseInternal()
|
||||
@@ -710,16 +661,13 @@ void ViewerWidget::PauseInternal()
|
||||
playback_speed_ = 0;
|
||||
controls_->ShowPlayButton();
|
||||
|
||||
disconnect(display_widget_, &ViewerDisplayWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate);
|
||||
|
||||
foreach (ViewerWindow* window, windows_) {
|
||||
window->Pause();
|
||||
foreach (ViewerDisplayWidget *dw, playback_devices_){
|
||||
dw->Pause();
|
||||
}
|
||||
|
||||
qDeleteAll(queue_watchers_);
|
||||
queue_watchers_.clear();
|
||||
|
||||
playback_queue_.clear();
|
||||
playback_backup_timer_.stop();
|
||||
|
||||
// Handle audio
|
||||
@@ -742,6 +690,9 @@ void ViewerWidget::PauseInternal()
|
||||
|
||||
prequeuing_video_ = false;
|
||||
prequeuing_audio_ = 0;
|
||||
|
||||
// Reset screen timeout timer
|
||||
PreventSleep(false);
|
||||
}
|
||||
|
||||
void ViewerWidget::PushScrubbedAudio()
|
||||
@@ -803,14 +754,10 @@ bool ViewerWidget::ViewerMightBeAStill()
|
||||
return GetConnectedNode() && GetConnectedNode()->GetConnectedTextureOutput() && GetConnectedNode()->GetVideoLength().isNull();
|
||||
}
|
||||
|
||||
void ViewerWidget::SetDisplayImage(QVariant frame, bool main_only)
|
||||
void ViewerWidget::SetDisplayImage(QVariant frame)
|
||||
{
|
||||
display_widget_->SetImage(frame);
|
||||
|
||||
if (!main_only) {
|
||||
foreach (ViewerWindow* vw, windows_) {
|
||||
vw->display_widget()->SetImage(frame);
|
||||
}
|
||||
foreach (ViewerDisplayWidget *dw, playback_devices_) {
|
||||
dw->SetImage(frame);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -868,25 +815,25 @@ void ViewerWidget::FinishPlayPreprocess()
|
||||
GetTime(), playback_speed_);
|
||||
}
|
||||
|
||||
playback_timer_.Start(playback_start_time, playback_speed_, timebase_dbl());
|
||||
display_widget_->ResetFPSTimer();
|
||||
|
||||
foreach (ViewerWindow* window, windows_) {
|
||||
window->Play(playback_start_time, playback_speed_, timebase());
|
||||
foreach (ViewerDisplayWidget *dw, playback_devices_) {
|
||||
dw->Play(playback_start_time, playback_speed_, timebase());
|
||||
}
|
||||
|
||||
if (display_widget_->isVisible()) {
|
||||
connect(display_widget_, &ViewerDisplayWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate);
|
||||
} else {
|
||||
playback_backup_timer_.setInterval(qFloor(timebase_dbl()));
|
||||
playback_backup_timer_.start();
|
||||
}
|
||||
// This is our timer for loading the queue and setting the time
|
||||
playback_backup_timer_.setInterval(qFloor(timebase_dbl()));
|
||||
playback_backup_timer_.start();
|
||||
|
||||
PlaybackTimerUpdate();
|
||||
}
|
||||
|
||||
int ViewerWidget::DeterminePlaybackQueueSize()
|
||||
{
|
||||
if (playback_speed_ == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t end_ts;
|
||||
|
||||
if (playback_speed_ > 0) {
|
||||
@@ -901,15 +848,6 @@ int ViewerWidget::DeterminePlaybackQueueSize()
|
||||
return qMin(kMaxPreQueueSize, remaining_frames);
|
||||
}
|
||||
|
||||
void ViewerWidget::PopOldestFrameFromPlaybackQueue()
|
||||
{
|
||||
playback_queue_.pop_front();
|
||||
|
||||
if (int(playback_queue_.size()) < DeterminePlaybackQueueSize()) {
|
||||
RequestNextFrameForQueue();
|
||||
}
|
||||
}
|
||||
|
||||
void ViewerWidget::UpdateStack()
|
||||
{
|
||||
rational new_tb;
|
||||
@@ -962,12 +900,9 @@ void ViewerWidget::ContextMenuSetCustomSafeMargins()
|
||||
|
||||
void ViewerWidget::WindowAboutToClose()
|
||||
{
|
||||
windows_.remove(windows_.key(static_cast<ViewerWindow*>(sender())));
|
||||
}
|
||||
|
||||
void ViewerWidget::ContextMenuScopeTriggered(QAction *action)
|
||||
{
|
||||
emit RequestScopePanel(static_cast<ScopePanel::Type>(action->data().toInt()));
|
||||
ViewerWindow *vw = static_cast<ViewerWindow*>(sender());
|
||||
windows_.remove(windows_.key(vw));
|
||||
playback_devices_.removeOne(vw->display_widget());
|
||||
}
|
||||
|
||||
void ViewerWidget::RendererGeneratedFrame()
|
||||
@@ -1014,13 +949,12 @@ void ViewerWidget::RendererGeneratedFrameForQueue()
|
||||
if (IsPlaying() || prequeuing_video_) {
|
||||
rational ts = watcher->property("time").value<rational>();
|
||||
|
||||
playback_queue_.AppendTimewise({ts, frame}, playback_speed_);
|
||||
|
||||
foreach (ViewerWindow* window, windows_) {
|
||||
window->queue()->AppendTimewise({ts, frame}, playback_speed_);
|
||||
foreach (ViewerDisplayWidget *dw, playback_devices_) {
|
||||
dw->queue()->AppendTimewise({ts, frame}, playback_speed_);
|
||||
}
|
||||
prequeue_count_++;
|
||||
|
||||
if (prequeuing_video_ && int(playback_queue_.size()) == prequeue_length_) {
|
||||
if (prequeuing_video_ && prequeue_count_ == prequeue_length_) {
|
||||
prequeuing_video_ = false;
|
||||
FinishPlayPreprocess();
|
||||
}
|
||||
@@ -1109,21 +1043,6 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos)
|
||||
|
||||
menu.addSeparator();
|
||||
|
||||
{
|
||||
// Scopes
|
||||
Menu* scopes_menu = new Menu(tr("Scopes"), &menu);
|
||||
menu.addMenu(scopes_menu);
|
||||
|
||||
for (int i=0;i<ScopePanel::kTypeCount;i++) {
|
||||
QAction* scope_action = scopes_menu->addAction(ScopePanel::TypeToName(static_cast<ScopePanel::Type>(i)));
|
||||
scope_action->setData(i);
|
||||
}
|
||||
|
||||
connect(scopes_menu, &Menu::triggered, this, &ViewerWidget::ContextMenuScopeTriggered);
|
||||
}
|
||||
|
||||
menu.addSeparator();
|
||||
|
||||
{
|
||||
Menu* cache_menu = new Menu(tr("Cache"), &menu);
|
||||
menu.addMenu(cache_menu);
|
||||
@@ -1263,10 +1182,8 @@ void ViewerWidget::SetColorTransform(const ColorTransform &transform)
|
||||
|
||||
void ViewerWidget::SetSignalCursorColorEnabled(bool e)
|
||||
{
|
||||
display_widget_->SetSignalCursorColorEnabled(e);
|
||||
|
||||
foreach (ViewerWindow* vw, windows_) {
|
||||
vw->display_widget()->SetSignalCursorColorEnabled(e);
|
||||
foreach (ViewerDisplayWidget *dw, playback_devices_) {
|
||||
dw->SetSignalCursorColorEnabled(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1282,7 +1199,9 @@ void ViewerWidget::TimebaseChangedEvent(const rational &timebase)
|
||||
|
||||
void ViewerWidget::PlaybackTimerUpdate()
|
||||
{
|
||||
rational current_time = Timecode::timestamp_to_time(playback_timer_.GetTimestampNow(), timebase());
|
||||
Q_ASSERT(playback_speed_ != 0);
|
||||
|
||||
rational current_time = Timecode::timestamp_to_time(display_widget_->timer()->GetTimestampNow(), timebase());
|
||||
|
||||
rational min_time, max_time;
|
||||
|
||||
@@ -1364,15 +1283,17 @@ void ViewerWidget::PlaybackTimerUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
if (display_widget_->isVisible()) {
|
||||
// Updating display widget
|
||||
UpdateTextureFromNode();
|
||||
} else if (!windows_.empty()) {
|
||||
// We still run the queue if windows are visible even if our own display widget isn't visible
|
||||
while (!playback_queue_.empty() && playback_queue_.front().timestamp != GetTime()) {
|
||||
PopOldestFrameFromPlaybackQueue();
|
||||
if (IsPlaying()) {
|
||||
int count = 0;
|
||||
for (int i=display_widget_->queue()->size(); i<DeterminePlaybackQueueSize(); i++) {
|
||||
RequestNextFrameForQueue();
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (ViewerDisplayWidget *dw, playback_devices_) {
|
||||
dw->queue()->PurgeBefore(current_time, playback_speed_);
|
||||
}
|
||||
}
|
||||
|
||||
void ViewerWidget::SetViewerResolution(int width, int height)
|
||||
@@ -1410,10 +1331,10 @@ void ViewerWidget::LengthChangedSlot(const rational &length)
|
||||
void ViewerWidget::InterlacingChangedSlot(VideoParams::Interlacing interlacing)
|
||||
{
|
||||
// Automatically set a "sane" deinterlacing option
|
||||
display_widget_->SetDeinterlacing(interlacing != VideoParams::kInterlaceNone);
|
||||
bool deint = interlacing != VideoParams::kInterlaceNone;
|
||||
|
||||
foreach (ViewerWindow* vw, windows_) {
|
||||
vw->display_widget()->SetDeinterlacing(interlacing != VideoParams::kInterlaceNone);
|
||||
foreach (ViewerDisplayWidget *dw, playback_devices_) {
|
||||
dw->SetDeinterlacing(deint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1421,9 +1342,8 @@ void ViewerWidget::UpdateRendererVideoParameters()
|
||||
{
|
||||
VideoParams vp = GetConnectedNode()->GetVideoParams();
|
||||
|
||||
display_widget_->SetVideoParams(vp);
|
||||
foreach (ViewerWindow* window, windows_) {
|
||||
window->display_widget()->SetVideoParams(vp);
|
||||
foreach (ViewerDisplayWidget *dw, playback_devices_) {
|
||||
dw->SetVideoParams(vp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user