Merge branch 'master' into marker

This commit is contained in:
ThomasWilshaw
2022-01-28 22:30:29 +00:00
committed by GitHub
307 changed files with 26841 additions and 11362 deletions
+1 -1
View File
@@ -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
+4
View File
@@ -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
)
+101
View File
@@ -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());
}
}
+74
View File
@@ -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_);
+1 -1
View File
@@ -50,7 +50,7 @@ private:
Color end_;
double val_;
float val_;
};
+1 -1
View File
@@ -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_);
}
}
+2 -2
View File
@@ -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();
+2 -4
View File
@@ -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
+371 -203
View File
@@ -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;
}
}
+41 -31
View File
@@ -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_;
};
+80 -127
View File
@@ -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)
+4 -14
View File
@@ -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);
+4 -6
View File
@@ -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
)
+510 -13
View File
@@ -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();
}
}
+111 -9
View File
@@ -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;
}
}
-136
View File
@@ -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
+15 -9
View File
@@ -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
)
+343 -162
View File
@@ -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);
}
}
+56 -41
View File
@@ -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);
};
}
+132 -203
View File
@@ -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)
{
+57 -73
View File
@@ -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);
};
}
+30 -11
View File
@@ -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)
+9
View File
@@ -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);
+6
View File
@@ -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
)
File diff suppressed because it is too large Load Diff
+57 -108
View File
@@ -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);
};
+11
View File
@@ -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)
+353
View File
@@ -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);
}
}
}
+95
View File
@@ -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
+63 -37
View File
@@ -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));
}
}
+6 -19
View File
@@ -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_;
};
+500 -240
View File
@@ -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;
}
}
+84 -50
View File
@@ -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
+9 -1
View File
@@ -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()));
+2
View File
@@ -57,6 +57,8 @@ private slots:
void SetDefaultSize();
private:
bool MouseInsideResizeTriangle(QMouseEvent *event);
void EmitMoveSignal(QMouseEvent *event);
int resize_triangle_sz_;
+38 -186
View File
@@ -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();
}
}
+12 -61
View File
@@ -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();
};
}
+96
View File
@@ -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);
}
}
}
+36
View File
@@ -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
+51
View File
@@ -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());
}
}
+57
View File
@@ -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
+9
View File
@@ -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)
+2
View File
@@ -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)) {
+22 -1
View File
@@ -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;
}
+1
View File
@@ -87,6 +87,7 @@ private:
int drag_start_x_;
int drag_start_y_;
int wrap_count_;
QList<SliderLadderElement*> elements_;
+40 -48
View File
@@ -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
+4
View File
@@ -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:
+2
View File
@@ -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
+9 -6
View File
@@ -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
+2 -2
View File
@@ -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()
+12
View File
@@ -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;
+2
View File
@@ -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&){}
+107 -107
View File
@@ -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;
+3 -12
View File
@@ -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);
+3 -3
View File
@@ -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);
+9 -5
View File
@@ -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:
+3 -3
View File
@@ -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));
}
}
+2
View File
@@ -24,6 +24,8 @@
namespace olive {
const int TimelineTool::kDefaultDistanceFromOutput = -4;
TimelineTool::TimelineTool(TimelineWidget *parent) :
dragging_(false),
parent_(parent)
+2
View File
@@ -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> &region : 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;
}
}
}
}
-182
View File
@@ -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
+2
View File
@@ -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
View File
@@ -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