Merge master into branch

This commit is contained in:
Thomas Wilshaw
2020-08-17 18:42:58 +01:00
316 changed files with 13821 additions and 4934 deletions
+3
View File
@@ -31,7 +31,9 @@ add_subdirectory(nodecombobox)
add_subdirectory(nodecopypaste)
add_subdirectory(nodeview)
add_subdirectory(nodeparamview)
add_subdirectory(nodetableview)
add_subdirectory(panel)
add_subdirectory(path)
add_subdirectory(pixelsampler)
add_subdirectory(playbackcontrols)
add_subdirectory(projectexplorer)
@@ -39,6 +41,7 @@ add_subdirectory(projecttoolbar)
add_subdirectory(resizablescrollbar)
add_subdirectory(scope)
add_subdirectory(slider)
add_subdirectory(standardcombos)
add_subdirectory(taskview)
add_subdirectory(timebased)
add_subdirectory(timelinewidget)
+15 -5
View File
@@ -90,11 +90,11 @@ void AudioMonitor::Stop()
}
}
void AudioMonitor::OutputPushed(const QByteArray &data)
void AudioMonitor::OutputPushed(const QByteArray &d)
{
QVector<double> v(params_.channel_count(), 0);
BytesToSampleSummary(data, v);
BytesToSampleSummary(d, v);
PushValue(v);
@@ -275,23 +275,33 @@ void AudioMonitor::UpdateValuesFromFile(QVector<double>& v)
// Determines how many milliseconds have passed since last update
qint64 current_time = QDateTime::currentMSecsSinceEpoch();
qint64 time_passed = current_time - last_time_;
int abs_speed = qAbs(playback_speed_);
// Multiply by speed if the speed is not 1
if (abs_speed != 1) {
time_passed *= abs_speed;
}
// Convert ms to float seconds and determine how many bytes that is
qint64 bytes_to_read = params_.time_to_bytes(static_cast<double>(time_passed) * 0.001);
if (playback_speed_ < 0) {
// If reversing, jump back by the amount of bytes we're going to read
bytes_to_read = qMin(bytes_to_read, file_.pos());
file_.seek(file_.pos() - bytes_to_read);
}
// Read bytes in from file
QByteArray b = file_.read(bytes_to_read);
if (playback_speed_ < 0) {
// If reversing, head back to where we were before the read so that the next read starts
// from where we left off
file_.seek(file_.pos() - bytes_to_read);
}
int abs_speed = qAbs(playback_speed_);
// If speed is not 1, transform it here
if (abs_speed != 1) {
int sample_sz = params_.samples_to_bytes(1);
int in_nb_samples = params_.bytes_to_samples(b.size());
@@ -299,8 +309,8 @@ void AudioMonitor::UpdateValuesFromFile(QVector<double>& v)
QByteArray speed_adjusted(out_nb_samples * sample_sz, Qt::Uninitialized);
for (int i=0;i<out_nb_samples;i++) {
memcpy(speed_adjusted.data() + params_.samples_to_bytes(i),
b.constData() + params_.samples_to_bytes(i * abs_speed),
memcpy(speed_adjusted.data() + i * sample_sz,
b.constData() + i * abs_speed * sample_sz,
sample_sz);
}
+1 -1
View File
@@ -45,7 +45,7 @@ public slots:
void Stop();
void OutputPushed(const QByteArray& data);
void OutputPushed(const QByteArray& d);
protected:
//virtual void paintEvent(QPaintEvent* event) override;
+8 -4
View File
@@ -20,6 +20,8 @@
#include "clickablelabel.h"
#include <QMouseEvent>
OLIVE_NAMESPACE_ENTER
ClickableLabel::ClickableLabel(const QString &text, QWidget *parent) :
@@ -32,16 +34,18 @@ ClickableLabel::ClickableLabel(QWidget *parent) :
{
}
void ClickableLabel::mouseReleaseEvent(QMouseEvent *)
void ClickableLabel::mouseReleaseEvent(QMouseEvent *event)
{
if (underMouse()) {
if (event->button() == Qt::LeftButton && underMouse()) {
emit MouseClicked();
}
}
void ClickableLabel::mouseDoubleClickEvent(QMouseEvent *)
void ClickableLabel::mouseDoubleClickEvent(QMouseEvent *event)
{
emit MouseDoubleClicked();
if (event->button() == Qt::LeftButton) {
emit MouseDoubleClicked();
}
}
OLIVE_NAMESPACE_EXIT
@@ -203,7 +203,6 @@ FloatSlider *ColorValuesTab::CreateColorSlider()
FloatSlider* fs = new FloatSlider();
fs->SetDragMultiplier(0.01);
fs->SetDecimalPlaces(5);
fs->SetLadderEnabled(true);
fs->SetLadderElementCount(1);
connect(fs, &FloatSlider::ValueChanged, this, &ColorValuesTab::SliderChanged);
return fs;
+73 -12
View File
@@ -20,8 +20,10 @@
#include "curveview.h"
#include <QScrollBar>
#include <QMouseEvent>
#include <QtMath>
#include <cfloat>
#include "common/qtutils.h"
@@ -61,6 +63,16 @@ void CurveView::Clear()
void CurveView::SetTrackCount(int count)
{
track_count_ = count;
track_visible_.resize(track_count_);
track_visible_.fill(true);
}
void CurveView::SetTrackVisible(int track, bool visible)
{
track_visible_[track] = visible;
SetKeyframeTrackVisible(track, visible);
}
void CurveView::drawBackground(QPainter *painter, const QRectF &rect)
@@ -116,6 +128,10 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect)
// Draw keyframe lines
for (int j=0;j<track_count_;j++) {
if (!track_visible_.at(j)) {
continue;
}
painter->setPen(QPen(GetKeyframeColor(j), qMax(1, fontMetrics().height() / 4)));
QList<NodeKeyframe*> keys = GetKeyframesSortedByTime(j);
@@ -237,21 +253,22 @@ void CurveView::VerticalScaleChangedEvent(double scale)
void CurveView::wheelEvent(QWheelEvent *event)
{
if (WheelEventIsAZoomEvent(event)) {
if (!event->angleDelta().isNull()) {
if (event->angleDelta().x() + event->angleDelta().y() > 0) {
emit ScaleChanged(GetScale() * 1.1);
SetYScale(GetYScale() * 1.1);
} else {
emit ScaleChanged(GetScale() * 0.9);
SetYScale(GetYScale() * 0.9);
}
}
} else {
if (!HandleZoomFromScroll(event)) {
KeyframeViewBase::wheelEvent(event);
}
}
void CurveView::ContextMenuEvent(Menu &m)
{
m.addSeparator();
// View settings
QAction* zoom_fit_action = m.addAction(tr("Zoom to Fit"));
connect(zoom_fit_action, &QAction::triggered, this, &CurveView::ZoomToFit);
//QAction* reset_zoom_action = m.addAction(tr("Reset Zoom"));
}
QList<NodeKeyframe *> CurveView::GetKeyframesSortedByTime(int track)
{
QList<NodeKeyframe *> sorted;
@@ -285,7 +302,12 @@ QList<NodeKeyframe *> CurveView::GetKeyframesSortedByTime(int track)
qreal CurveView::GetItemYFromKeyframeValue(NodeKeyframe *key)
{
return -key->value().toDouble() * GetYScale();
return GetItemYFromKeyframeValue(key->value().toDouble());
}
qreal CurveView::GetItemYFromKeyframeValue(double value)
{
return -value * GetYScale();
}
void CurveView::SetItemYFromKeyframeValue(NodeKeyframe *key, KeyframeViewItem *item)
@@ -367,6 +389,45 @@ void CurveView::BezierControlPointDestroyed()
bezier_control_points_.removeOne(item);
}
void CurveView::ZoomToFit()
{
if (item_map().isEmpty()) {
// Prevent scaling to DBL_MIN/DBL_MAX
return;
}
QMap<NodeKeyframe *, KeyframeViewItem *>::const_iterator i;
rational min_time = RATIONAL_MAX;
rational max_time = RATIONAL_MIN;
double min_val = DBL_MAX;
double max_val = DBL_MIN;
for (i=item_map().constBegin(); i!=item_map().constEnd(); i++) {
rational transformed_time = GetAdjustedTime(i.key()->parent()->parentNode(),
GetTimeTarget(),
i.key()->time(),
NodeParam::kOutput);
min_time = qMin(transformed_time, min_time);
max_time = qMax(transformed_time, max_time);
min_val = qMin(i.key()->value().toDouble(), min_val);
max_val = qMax(i.key()->value().toDouble(), max_val);
}
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);
emit ScaleChanged(new_x_scale);
SetYScale(new_y_scale);
horizontalScrollBar()->setValue(TimeToScene(min_time) - CalculatePaddingFromDimensionScale(this->width()));
verticalScrollBar()->setValue(GetItemYFromKeyframeValue(max_val) - CalculatePaddingFromDimensionScale(this->height()));
}
void CurveView::AddKeyframe(NodeKeyframePtr key)
{
KeyframeViewItem* item = AddKeyframeInternal(key);
+10
View File
@@ -30,6 +30,7 @@ OLIVE_NAMESPACE_ENTER
class CurveView : public KeyframeViewBase
{
Q_OBJECT
public:
CurveView(QWidget* parent = nullptr);
@@ -39,9 +40,13 @@ public:
void SetTrackCount(int count);
void SetTrackVisible(int track, bool visible);
public slots:
void AddKeyframe(NodeKeyframePtr key);
void ZoomToFit();
protected:
virtual void drawBackground(QPainter* painter, const QRectF& rect) override;
@@ -53,10 +58,13 @@ protected:
virtual void wheelEvent(QWheelEvent* event) override;
virtual void ContextMenuEvent(Menu &m) override;
private:
QList<NodeKeyframe*> GetKeyframesSortedByTime(int track);
qreal GetItemYFromKeyframeValue(NodeKeyframe* key);
qreal GetItemYFromKeyframeValue(double value);
void SetItemYFromKeyframeValue(NodeKeyframe* key, KeyframeViewItem* item);
@@ -76,6 +84,8 @@ private:
QList<BezierControlPointItem*> bezier_control_points_;
QVector<bool> track_visible_;
int track_count_;
private slots:
+22 -10
View File
@@ -117,12 +117,17 @@ void CurveWidget::SetInput(NodeInput *input)
{
if (bridge_) {
foreach (QWidget* bridge_widget, bridge_->widgets()) {
delete bridge_widget;
bridge_widget->deleteLater();
}
delete bridge_;
bridge_->deleteLater();
bridge_ = nullptr;
}
foreach (QCheckBox* box, checkboxes_) {
box->deleteLater();
}
checkboxes_.clear();
if (input_) {
disconnect(input_, &NodeInput::KeyframeAdded, view_, &CurveView::AddKeyframe);
disconnect(input_, &NodeInput::KeyframeRemoved, view_, &CurveView::RemoveKeyframe);
@@ -142,7 +147,15 @@ void CurveWidget::SetInput(NodeInput *input)
for (int i=0;i<bridge_->widgets().size();i++) {
// Insert between two stretches to center the widget
widget_bridge_layout_->insertWidget(2 + i, bridge_->widgets().at(i));
QCheckBox* checkbox = new QCheckBox();
checkbox->setChecked(true);
widget_bridge_layout_->insertWidget(2 + i*2, checkbox);
checkboxes_.append(checkbox);
connect(checkbox, &QCheckBox::clicked, this, [this](bool e){
view_->SetTrackVisible(checkboxes_.indexOf(static_cast<QCheckBox*>(sender())), e);
});
widget_bridge_layout_->insertWidget(2 + i*2 + 1, bridge_->widgets().at(i));
}
connect(input_, &NodeInput::KeyframeAdded, view_, &CurveView::AddKeyframe);
@@ -156,6 +169,8 @@ void CurveWidget::SetInput(NodeInput *input)
}
UpdateInputLabel();
QMetaObject::invokeMethod(view_, "ZoomToFit", Qt::QueuedConnection);
}
const double &CurveWidget::GetVerticalScale()
@@ -205,8 +220,6 @@ void CurveWidget::ScaleChangedEvent(const double &scale)
void CurveWidget::TimeTargetChangedEvent(Node *target)
{
ConnectViewerNode(nullptr);
key_control_->SetTimeTarget(target);
view_->SetTimeTarget(target);
@@ -214,12 +227,11 @@ void CurveWidget::TimeTargetChangedEvent(Node *target)
if (bridge_) {
bridge_->SetTimeTarget(target);
}
}
// FIXME: If a non-viewer node is ever set here, it will fail to update the length
ViewerOutput* viewer = dynamic_cast<ViewerOutput*>(target);
if (viewer) {
ConnectViewerNode(viewer);
}
void CurveWidget::ConnectedNodeChanged(ViewerOutput *n)
{
SetTimeTarget(n);
}
void CurveWidget::UpdateInputLabel()
+5
View File
@@ -21,6 +21,7 @@
#ifndef CURVEWIDGET_H
#define CURVEWIDGET_H
#include <QCheckBox>
#include <QHBoxLayout>
#include <QPushButton>
#include <QWidget>
@@ -58,6 +59,8 @@ protected:
virtual void TimeTargetChangedEvent(Node* target) override;
virtual void ConnectedNodeChanged(ViewerOutput* n) override;
private:
void UpdateInputLabel();
@@ -87,6 +90,8 @@ private:
NodeParamViewKeyframeControl* key_control_;
QList<QCheckBox*> checkboxes_;
private slots:
void SelectionChanged();
+8 -1
View File
@@ -23,7 +23,8 @@
OLIVE_NAMESPACE_ENTER
KeyframeView::KeyframeView(QWidget *parent) :
KeyframeViewBase(parent)
KeyframeViewBase(parent),
max_scroll_(0)
{
setAlignment(Qt::AlignLeft | Qt::AlignTop);
}
@@ -35,6 +36,12 @@ void KeyframeView::wheelEvent(QWheelEvent *event)
}
}
void KeyframeView::SceneRectUpdateEvent(QRectF &rect)
{
rect.setY(0);
rect.setHeight(max_scroll_);
}
void KeyframeView::AddKeyframe(NodeKeyframePtr key, int y)
{
QPoint global_pt(0, y);
+10
View File
@@ -31,12 +31,22 @@ class KeyframeView : public KeyframeViewBase
public:
KeyframeView(QWidget* parent = nullptr);
void SetMaxScroll(int i)
{
max_scroll_ = i;
}
protected:
virtual void wheelEvent(QWheelEvent* event) override;
virtual void SceneRectUpdateEvent(QRectF& rect) override;
public slots:
void AddKeyframe(NodeKeyframePtr key, int y);
private:
int max_scroll_;
};
OLIVE_NAMESPACE_EXIT
+58 -33
View File
@@ -35,8 +35,6 @@ OLIVE_NAMESPACE_ENTER
KeyframeViewBase::KeyframeViewBase(QWidget *parent) :
TimelineViewBase(parent),
dragging_bezier_point_(nullptr),
y_axis_enabled_(false),
y_scale_(1.0),
currently_autoselecting_(false)
{
SetDefaultDragMode(RubberBandDrag);
@@ -57,22 +55,6 @@ void KeyframeViewBase::Clear()
item_map_.clear();
}
const double &KeyframeViewBase::GetYScale() const
{
return y_scale_;
}
void KeyframeViewBase::SetYScale(const double &y_scale)
{
y_scale_ = y_scale;
if (y_axis_enabled_) {
VerticalScaleChangedEvent(y_scale_);
viewport()->update();
}
}
void KeyframeViewBase::DeleteSelected()
{
QUndoCommand* command = new QUndoCommand();
@@ -92,6 +74,19 @@ void KeyframeViewBase::DeleteSelected()
Core::instance()->undo_stack()->pushIfHasChildren(command);
}
void KeyframeViewBase::RemoveKeyframesOfNode(Node *n)
{
QList<NodeInput*> inputs = n->GetInputsIncludingArrays();
foreach (NodeInput* i, inputs) {
foreach (const NodeInput::KeyframeTrack& track, i->keyframe_tracks()) {
foreach (NodeKeyframePtr key, track) {
RemoveKeyframe(key);
}
}
}
}
void KeyframeViewBase::RemoveKeyframe(NodeKeyframePtr key)
{
KeyframeAboutToBeRemoved(key.get());
@@ -101,11 +96,20 @@ void KeyframeViewBase::RemoveKeyframe(NodeKeyframePtr key)
KeyframeViewItem *KeyframeViewBase::AddKeyframeInternal(NodeKeyframePtr key)
{
KeyframeViewItem* item = new KeyframeViewItem(key);
item->SetTimeTarget(GetTimeTarget());
item->SetScale(GetScale());
item_map_.insert(key.get(), item);
scene()->addItem(item);
KeyframeViewItem* item = item_map_.value(key.get());
if (!item) {
item = new KeyframeViewItem(key);
item->SetTimeTarget(GetTimeTarget());
item->SetScale(GetScale());
item_map_.insert(key.get(), item);
scene()->addItem(item);
if (hidden_tracks_.contains(key->track())) {
item->setVisible(false);
}
}
return item;
}
@@ -185,7 +189,7 @@ void KeyframeViewBase::mouseMoveEvent(QMouseEvent *event)
keypair.key->key()->set_time(node_time);
if (y_axis_enabled_) {
if (IsYAxisEnabled()) {
keypair.key->key()->set_value(keypair.value - mouse_diff_scaled.y());
}
@@ -248,7 +252,7 @@ void KeyframeViewBase::mouseReleaseEvent(QMouseEvent *event)
command);
// Commit value if we're setting a value
if (y_axis_enabled_) {
if (IsYAxisEnabled()) {
item->key()->set_value(keypair.value);
new NodeParamSetKeyframeValueCommand(item->key(),
keypair.value - mouse_diff_scaled.y(),
@@ -279,10 +283,6 @@ void KeyframeViewBase::ScaleChangedEvent(const double &scale)
}
}
void KeyframeViewBase::VerticalScaleChangedEvent(double)
{
}
const QMap<NodeKeyframe *, KeyframeViewItem *> &KeyframeViewBase::item_map() const
{
return item_map_;
@@ -301,9 +301,30 @@ void KeyframeViewBase::TimeTargetChangedEvent(Node *target)
}
}
void KeyframeViewBase::SetYAxisEnabled(bool e)
void KeyframeViewBase::SetKeyframeTrackVisible(int track, bool visible)
{
y_axis_enabled_ = e;
if (!visible == hidden_tracks_.contains(track)) {
return;
}
QMap<NodeKeyframe*, KeyframeViewItem*>::const_iterator i;
for (i=item_map_.constBegin(); i!=item_map_.constEnd(); i++) {
if (i.key()->track() == track) {
i.value()->setVisible(visible);
}
}
if (visible) {
hidden_tracks_.removeOne(track);
} else {
hidden_tracks_.append(track);
}
}
void KeyframeViewBase::ContextMenuEvent(Menu& m)
{
Q_UNUSED(m)
}
rational KeyframeViewBase::CalculateNewTimeFromScreen(const rational &old_time, double cursor_diff)
@@ -403,7 +424,7 @@ void KeyframeViewBase::ProcessBezierDrag(QPointF mouse_diff_scaled, bool include
QPointF KeyframeViewBase::GetScaledCursorPos(const QPoint &cursor_pos)
{
return QPointF(static_cast<double>(cursor_pos.x()) / GetScale(),
static_cast<double>(cursor_pos.y()) / y_scale_);
static_cast<double>(cursor_pos.y()) / GetYScale());
}
void KeyframeViewBase::ShowContextMenu()
@@ -450,7 +471,11 @@ void KeyframeViewBase::ShowContextMenu()
break;
}
}
}
ContextMenuEvent(m);
if (!items.isEmpty()) {
m.addSeparator();
QAction* properties_action = m.addAction(tr("P&roperties"));
@@ -502,7 +527,7 @@ void KeyframeViewBase::ShowKeyframePropertiesDialog()
void KeyframeViewBase::AutoSelectKeyTimeNeighbors()
{
if (currently_autoselecting_ || y_axis_enabled_) {
if (currently_autoselecting_ || IsYAxisEnabled()) {
return;
}
+9 -11
View File
@@ -23,9 +23,10 @@
#include "keyframeviewitem.h"
#include "node/keyframe.h"
#include "widget/timetarget/timetarget.h"
#include "widget/curvewidget/beziercontrolpointitem.h"
#include "widget/menu/menu.h"
#include "widget/timelinewidget/view/timelineviewbase.h"
#include "widget/timetarget/timetarget.h"
OLIVE_NAMESPACE_ENTER
@@ -37,11 +38,10 @@ public:
virtual void Clear();
const double& GetYScale() const;
void SetYScale(const double& y_scale);
void DeleteSelected();
void RemoveKeyframesOfNode(Node* n);
public slots:
void RemoveKeyframe(NodeKeyframePtr key);
@@ -54,15 +54,15 @@ protected:
virtual void ScaleChangedEvent(const double& scale) override;
virtual void VerticalScaleChangedEvent(double scale);
const QMap<NodeKeyframe*, KeyframeViewItem*>& item_map() const;
virtual void KeyframeAboutToBeRemoved(NodeKeyframe* key);
virtual void TimeTargetChangedEvent(Node*) override;
void SetYAxisEnabled(bool e);
void SetKeyframeTrackVisible(int track, bool visible);
virtual void ContextMenuEvent(Menu &m);
private:
rational CalculateNewTimeFromScreen(const rational& old_time, double cursor_diff);
@@ -94,12 +94,10 @@ private:
QVector<KeyframeItemAndTime> selected_keys_;
bool y_axis_enabled_;
double y_scale_;
bool currently_autoselecting_;
QList<int> hidden_tracks_;
private slots:
void ShowContextMenu();
+3 -3
View File
@@ -50,13 +50,13 @@ Menu::Menu(const QString &s, QWidget *parent) :
Init();
}
QAction *Menu::AddActionWithData(const QString &text, const QVariant &data, const QVariant &compare)
QAction *Menu::AddActionWithData(const QString &text, const QVariant &d, const QVariant &compare)
{
QAction* a = addAction(text);
a->setData(data);
a->setData(d);
a->setCheckable(true);
a->setChecked(data == compare);
a->setChecked(d == compare);
return a;
}
+1 -1
View File
@@ -132,7 +132,7 @@ public:
}
QAction* AddActionWithData(const QString& text,
const QVariant& data,
const QVariant& d,
const QVariant& compare);
QAction *InsertAlphabetically(const QString& s);
+52 -73
View File
@@ -43,18 +43,21 @@ NodeParamView::NodeParamView(QWidget *parent) :
// 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 widget
QWidget* param_widget_area = new QWidget();
scroll_area->setWidget(param_widget_area);
param_widget_area_ = new QWidget();
scroll_area->setWidget(param_widget_area_);
// Set up scroll area layout
param_layout_ = new QVBoxLayout(param_widget_area);
param_layout_ = new QVBoxLayout(param_widget_area_);
param_layout_->setSpacing(0);
param_layout_->setMargin(0);
// KeyframeView is offset by a ruler, so to stay synchronized with it, we should be too
param_layout_->setContentsMargins(0, ruler()->height(), 0, 0);
// Add a stretch to allow empty space at the bottom of the layout
param_layout_->addStretch();
@@ -73,7 +76,6 @@ NodeParamView::NodeParamView(QWidget *parent) :
keyframe_view_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
ConnectTimelineView(keyframe_view_);
connect(keyframe_view_, &KeyframeView::RequestCenterScrollOnPlayhead, this, &NodeParamView::CenterScrollOnPlayhead);
bottom_item_ = keyframe_view_->scene()->addRect(0, 0, 1, 1);
keyframe_area_layout->addWidget(keyframe_view_);
// Connect ruler and keyframe view together
@@ -120,76 +122,41 @@ NodeParamView::NodeParamView(QWidget *parent) :
SetMaximumScale(TimelineViewBase::kMaximumScale);
}
void NodeParamView::SetNodes(QList<Node *> nodes)
void NodeParamView::SelectNodes(const QList<Node *> &nodes)
{
ConnectViewerNode(nullptr);
foreach (Node* n, nodes) {
NodeParamViewItem* item = new NodeParamViewItem(n);
// If we already have item widgets, delete them all now
foreach (NodeParamViewItem* item, items_) {
emit ClosedNode(item->GetNode());
emit FoundGizmos(nullptr);
delete item;
// Insert the widget before the stretch
param_layout_->insertWidget(param_layout_->count() - 1, item);
connect(item, &NodeParamViewItem::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe);
connect(item, &NodeParamViewItem::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe);
connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::ItemRequestedTimeChanged);
connect(item, &NodeParamViewItem::InputDoubleClicked, this, &NodeParamView::InputDoubleClicked);
connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::RequestSelectNode);
items_.insert(n, item);
}
items_.clear();
emit TimeTargetChanged(nullptr);
// Reset keyframe view
SetTimebase(rational());
keyframe_view_->Clear();
UpdateItemTime(GetTimestamp());
// Set the internal list to the one we've received
nodes_ = nodes;
// Re-arrange keyframes
QMetaObject::invokeMethod(this, "PlaceKeyframesOnView", Qt::QueuedConnection);
}
if (!nodes_.isEmpty()) {
// For each node, create a widget
bool found_gizmos = false;
void NodeParamView::DeselectNodes(const QList<Node *> &nodes)
{
// Remove item from map and delete the widget
foreach (Node* n, nodes) {
// Remove all keyframes from this node
keyframe_view_->RemoveKeyframesOfNode(n);
foreach (Node* node, nodes_) {
NodeParamViewItem* item = new NodeParamViewItem(node);
// Insert the widget before the stretch
param_layout_->insertWidget(param_layout_->count() - 1, item);
connect(item, &NodeParamViewItem::KeyframeAdded, keyframe_view_, &KeyframeView::AddKeyframe);
connect(item, &NodeParamViewItem::KeyframeRemoved, keyframe_view_, &KeyframeView::RemoveKeyframe);
connect(item, &NodeParamViewItem::RequestSetTime, this, &NodeParamView::ItemRequestedTimeChanged);
connect(item, &NodeParamViewItem::InputDoubleClicked, this, &NodeParamView::InputDoubleClicked);
connect(item, &NodeParamViewItem::RequestSelectNode, this, &NodeParamView::RequestSelectNode);
items_.append(item);
QMetaObject::invokeMethod(item,
"SignalAllKeyframes",
Qt::QueuedConnection);
emit OpenedNode(node);
if (!found_gizmos && node->HasGizmos()) {
emit FoundGizmos(node);
found_gizmos = true;
}
}
ViewerOutput* viewer = nodes_.first()->FindOutputNode<ViewerOutput>();
ConnectViewerNode(viewer);
if (viewer) {
SetTimebase(viewer->video_params().time_base());
// Set viewer as a time target
keyframe_view_->SetTimeTarget(viewer);
foreach (NodeParamViewItem* item, items_) {
item->SetTimeTarget(viewer);
}
emit TimeTargetChanged(viewer);
}
// Forces the scroll to update to this time
keyframe_view_->SetTime(ruler()->GetTime());
delete items_.take(n);
}
// Re-arrange keyframes
QMetaObject::invokeMethod(this, "PlaceKeyframesOnView", Qt::QueuedConnection);
}
void NodeParamView::resizeEvent(QResizeEvent *event)
@@ -211,6 +178,8 @@ void NodeParamView::TimebaseChangedEvent(const rational &timebase)
TimeBasedWidget::TimebaseChangedEvent(timebase);
keyframe_view_->SetTimebase(timebase);
UpdateItemTime(GetTimestamp());
}
void NodeParamView::TimeChangedEvent(const int64_t &timestamp)
@@ -222,9 +191,14 @@ void NodeParamView::TimeChangedEvent(const int64_t &timestamp)
UpdateItemTime(timestamp);
}
const QList<Node *> &NodeParamView::nodes()
void NodeParamView::ConnectedNodeChanged(ViewerOutput *n)
{
return nodes_;
// Set viewer as a time target
keyframe_view_->SetTimeTarget(n);
foreach (NodeParamViewItem* item, items_) {
item->SetTimeTarget(n);
}
}
Node *NodeParamView::GetTimeTarget() const
@@ -239,7 +213,7 @@ void NodeParamView::DeleteSelected()
void NodeParamView::UpdateItemTime(const int64_t &timestamp)
{
rational time = Timecode::timestamp_to_time(timestamp, keyframe_view_->timebase());
rational time = Timecode::timestamp_to_time(timestamp, timebase());
foreach (NodeParamViewItem* item, items_) {
item->SetTime(time);
@@ -251,11 +225,16 @@ void NodeParamView::ItemRequestedTimeChanged(const rational &time)
SetTimeAndSignal(Timecode::time_to_timestamp(time, keyframe_view_->timebase()));
}
void NodeParamView::ForceKeyframeViewToScroll(int min, int max)
void NodeParamView::ForceKeyframeViewToScroll()
{
Q_UNUSED(min)
keyframe_view_->SetMaxScroll(param_widget_area_->height() - ruler()->height());
}
bottom_item_->setY(keyframe_view_->viewport()->height() + max);
void NodeParamView::PlaceKeyframesOnView()
{
foreach (NodeParamViewItem* item, items_) {
QMetaObject::invokeMethod(item, "SignalAllKeyframes", Qt::QueuedConnection);
}
}
OLIVE_NAMESPACE_EXIT
+15 -16
View File
@@ -37,8 +37,13 @@ class NodeParamView : public TimeBasedWidget
public:
NodeParamView(QWidget* parent = nullptr);
void SetNodes(QList<Node*> nodes);
const QList<Node*>& nodes();
void SelectNodes(const QList<Node*>& nodes);
void DeselectNodes(const QList<Node*>& nodes);
const QMap<Node*, NodeParamViewItem*>& GetItemMap() const
{
return items_;
}
Node* GetTimeTarget() const;
@@ -47,16 +52,8 @@ public:
signals:
void InputDoubleClicked(NodeInput* input);
void TimeTargetChanged(Node* target);
void RequestSelectNode(const QList<Node*>& target);
void OpenedNode(Node* n);
void ClosedNode(Node* n);
void FoundGizmos(Node* n);
protected:
virtual void resizeEvent(QResizeEvent *event) override;
@@ -64,6 +61,8 @@ protected:
virtual void TimebaseChangedEvent(const rational&) override;
virtual void TimeChangedEvent(const int64_t &) override;
virtual void ConnectedNodeChanged(ViewerOutput* n) override;
private:
void UpdateItemTime(const int64_t &timestamp);
@@ -71,20 +70,20 @@ private:
KeyframeView* keyframe_view_;
QList<Node*> nodes_;
QList<NodeParamViewItem*> items_;
QMap<Node*, NodeParamViewItem*> items_;
QScrollBar* vertical_scrollbar_;
QGraphicsRectItem* bottom_item_;
int last_scroll_val_;
QWidget* param_widget_area_;
private slots:
void ItemRequestedTimeChanged(const rational& time);
void ForceKeyframeViewToScroll(int min, int max);
void ForceKeyframeViewToScroll();
void PlaceKeyframesOnView();
};
@@ -23,7 +23,10 @@
#include <QHBoxLayout>
#include "common/qtutils.h"
#include "core.h"
#include "node/node.h"
#include "widget/menu/menu.h"
#include "widget/nodeview/nodeviewundo.h"
OLIVE_NAMESPACE_ENTER
@@ -39,7 +42,9 @@ NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(NodeInput *input, QWidg
connected_to_lbl_ = new ClickableLabel();
connected_to_lbl_->setCursor(Qt::PointingHandCursor);
connected_to_lbl_->setContextMenuPolicy(Qt::CustomContextMenu);
connect(connected_to_lbl_, &ClickableLabel::MouseClicked, this, &NodeParamViewConnectedLabel::ConnectionClicked);
connect(connected_to_lbl_, &ClickableLabel::customContextMenuRequested, this, &NodeParamViewConnectedLabel::ShowLabelContextMenu);
layout->addWidget(connected_to_lbl_);
layout->addStretch();
@@ -69,4 +74,16 @@ void NodeParamViewConnectedLabel::UpdateConnected()
connected_to_lbl_->setText(connection_str);
}
void NodeParamViewConnectedLabel::ShowLabelContextMenu()
{
Menu m(this);
QAction* disconnect_action = m.addAction(tr("Disconnect"));
connect(disconnect_action, &QAction::triggered, this, [this](){
Core::instance()->undo_stack()->push(new NodeEdgeRemoveCommand(input_->get_connected_output(), input_));
});
m.exec(QCursor::pos());
}
OLIVE_NAMESPACE_EXIT
@@ -37,6 +37,8 @@ signals:
private slots:
void UpdateConnected();
void ShowLabelContextMenu();
private:
ClickableLabel* connected_to_lbl_;
@@ -48,8 +48,10 @@ void NodeParamViewRichText::ShowRichTextDialog()
{
RichTextDialog d(line_edit_->text(), this);
if (d.exec() == QDialog::Accepted) {
line_edit_->setText(d.text());
emit textEdited(d.text());
QString s = d.text();
line_edit_->setText(s);
emit textEdited(s);
}
}
@@ -45,6 +45,13 @@ public slots:
line_edit_->setText(s);
}
void setTextPreservingCursor(const QString &s)
{
int cursor_pos = line_edit_->cursorPosition();
line_edit_->setText(s);
line_edit_->setCursorPosition(cursor_pos);
}
signals:
void textEdited(const QString &);
@@ -96,7 +96,7 @@ void NodeParamViewWidgetBridge::CreateWidgets()
{
IntegerSlider* slider = new IntegerSlider();
slider->SetDefaultValue(input_->GetDefaultValue());
slider->SetLadderEnabled(true);
slider->SetLadderElementCount(2);
widgets_.append(slider);
connect(slider, &IntegerSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
break;
@@ -163,6 +163,7 @@ void NodeParamViewWidgetBridge::CreateWidgets()
{
QFontComboBox* font_combobox = new QFontComboBox();
widgets_.append(font_combobox);
connect(font_combobox, &QFontComboBox::currentFontChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
break;
}
case NodeParam::kFootage:
@@ -350,7 +351,7 @@ void NodeParamViewWidgetBridge::WidgetCallback()
case NodeParam::kFont:
{
// Widget is a QFontComboBox
SetInputValue(static_cast<QFontComboBox*>(sender())->currentFont(), 0);
SetInputValue(static_cast<QFontComboBox*>(sender())->currentFont().family(), 0);
break;
}
case NodeParam::kFootage:
@@ -362,7 +363,17 @@ void NodeParamViewWidgetBridge::WidgetCallback()
case NodeParam::kCombo:
{
// Widget is a QComboBox
SetInputValue(static_cast<QComboBox*>(widgets_.first())->currentIndex(), 0);
QComboBox* cb = static_cast<QComboBox*>(widgets_.first());
int index = cb->currentIndex();
// Subtract any splitters up until this point
for (int i=index-1; i>=0; i--) {
if (cb->itemData(i, Qt::AccessibleDescriptionRole).toString() == QStringLiteral("separator")) {
index--;
}
}
SetInputValue(index, 0);
break;
}
}
@@ -373,7 +384,7 @@ void NodeParamViewWidgetBridge::CreateSliders(int count)
for (int i=0;i<count;i++) {
FloatSlider* fs = new FloatSlider();
fs->SetDefaultValue(input_->GetDefaultValueForTrack(i));
fs->SetLadderEnabled(true);
fs->SetLadderElementCount(2);
widgets_.append(fs);
connect(fs, &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
}
@@ -459,7 +470,7 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues()
case NodeParam::kText:
{
NodeParamViewRichText* e = static_cast<NodeParamViewRichText*>(widgets_.first());
e->setText(input_->get_value_at_time(node_time).toString());
e->setTextPreservingCursor(input_->get_value_at_time(node_time).toString());
break;
}
case NodeParam::kBoolean:
@@ -467,7 +478,10 @@ void NodeParamViewWidgetBridge::UpdateWidgetValues()
break;
case NodeParam::kFont:
{
// FIXME: Implement this
QFontComboBox* fc = static_cast<QFontComboBox*>(widgets_.first());
fc->blockSignals(true);
fc->setCurrentFont(input_->get_value_at_time(node_time).toString());
fc->blockSignals(false);
break;
}
case NodeParam::kCombo:
+26
View File
@@ -0,0 +1,26 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2019 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/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
widget/nodetableview/nodetabletraverser.h
widget/nodetableview/nodetabletraverser.cpp
widget/nodetableview/nodetableview.h
widget/nodetableview/nodetableview.cpp
widget/nodetableview/nodetablewidget.h
widget/nodetableview/nodetablewidget.cpp
PARENT_SCOPE
)
@@ -0,0 +1,45 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 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 "nodetabletraverser.h"
OLIVE_NAMESPACE_ENTER
QVariant NodeTableTraverser::ProcessVideoFootage(StreamPtr stream, const rational &input_time)
{
ImageStreamPtr video_stream = std::static_pointer_cast<ImageStream>(stream);
return QVariant::fromValue(VideoParams(video_stream->width(),
video_stream->height(),
video_stream->timebase(),
video_stream->format(),
video_stream->pixel_aspect_ratio()));
}
QVariant NodeTableTraverser::ProcessAudioFootage(StreamPtr stream, const TimeRange &input_time)
{
AudioStreamPtr audio_stream = std::static_pointer_cast<AudioStream>(stream);
return QVariant::fromValue(AudioParams(audio_stream->sample_rate(),
audio_stream->channel_layout(),
SampleFormat::kInternalFormat));
}
OLIVE_NAMESPACE_EXIT
@@ -0,0 +1,42 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 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 NODETABLETRAVERSER_H
#define NODETABLETRAVERSER_H
#include "node/traverser.h"
OLIVE_NAMESPACE_ENTER
class NodeTableTraverser : public NodeTraverser
{
public:
NodeTableTraverser() = default;
protected:
virtual QVariant ProcessVideoFootage(StreamPtr stream, const rational &input_time);
virtual QVariant ProcessAudioFootage(StreamPtr stream, const TimeRange &input_time);
};
OLIVE_NAMESPACE_EXIT
#endif // NODETABLETRAVERSER_H
+266
View File
@@ -0,0 +1,266 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 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 "nodetableview.h"
#include <QCheckBox>
#include <QHeaderView>
#include "node/param.h"
#include "nodetabletraverser.h"
OLIVE_NAMESPACE_ENTER
NodeTableView::NodeTableView(QWidget* parent) :
QTreeWidget(parent)
{
setColumnCount(3);
setHeaderLabels({tr("Type"),
tr("Source"),
tr("R/X"),
tr("G/Y"),
tr("B/Z"),
tr("A/W")});
}
void NodeTableView::SelectNodes(const QList<Node *> &nodes)
{
foreach (Node* n, nodes) {
QTreeWidgetItem* top_item = new QTreeWidgetItem();
top_item->setText(0, n->Name());
top_item->setFirstColumnSpanned(true);
this->addTopLevelItem(top_item);
top_level_item_map_.insert(n, top_item);
}
SetTime(last_time_);
}
void NodeTableView::DeselectNodes(const QList<Node *> &nodes)
{
foreach (Node* n, nodes) {
delete top_level_item_map_.take(n);
}
}
void NodeTableView::SetTime(const rational &time)
{
last_time_ = time;
NodeTableTraverser traverser;
QMap<Node*, QTreeWidgetItem*>::const_iterator i;
for (i=top_level_item_map_.constBegin(); i!=top_level_item_map_.constEnd(); i++) {
Node* node = i.key();
QTreeWidgetItem* item = i.value();
// Generate a value database for this node at this time
NodeValueDatabase db = traverser.GenerateDatabase(node, TimeRange(time, time));
// Delete any children of this item that aren't in this database
for (int j=0; j<item->childCount(); j++) {
if (!db.contains(item->child(j)->data(0, Qt::UserRole).toString())) {
delete item->takeChild(j);
j--;
}
}
// Update all inputs
NodeValueDatabase::const_iterator l;
for (l=db.begin(); l!=db.end(); l++) {
const NodeValueTable& table = l.value();
NodeInput* input = node->GetInputWithID(l.key());
if (!input) {
// Filters out table entries that aren't inputs (like "global")
continue;
}
QTreeWidgetItem* input_item = nullptr;
for (int j=0; j<item->childCount(); j++) {
QTreeWidgetItem* compare = item->child(j);
if (compare->data(0, Qt::UserRole).toString() == input->id()) {
input_item = compare;
break;
}
}
if (!input_item) {
input_item = new QTreeWidgetItem();
input_item->setText(0, input->name());
input_item->setData(0, Qt::UserRole, input->id());
input_item->setFirstColumnSpanned(true);
item->addChild(input_item);
}
// Create children if necessary
while (input_item->childCount() < table.Count()) {
input_item->addChild(new QTreeWidgetItem());
}
// Remove children if necessary
while (input_item->childCount() > table.Count()) {
delete input_item->takeChild(input_item->childCount() - 1);
}
for (int j=0;j<table.Count();j++) {
const NodeValue& value = table.at(table.Count() - 1 - j);
// Create item
QTreeWidgetItem* sub_item = input_item->child(j);
// Set data type name
sub_item->setText(0, NodeParam::GetPrettyDataTypeName(value.type()));
// Determine source
QString source_name;
if (value.source()) {
source_name = value.source()->Name();
} else {
source_name = tr("(unknown)");
}
sub_item->setText(1, source_name);
switch (value.type()) {
case NodeParam::kTexture:
{
// NodeTableTraverser puts video params in here
VideoParams p = value.data().value<VideoParams>();
int channel_count = PixelFormat::ChannelCount(p.format());
for (int k=0;k<channel_count;k++) {
this->setItemWidget(sub_item, 2 + k, new QCheckBox());
}
break;
}
default:
{
QVector<QVariant> split_values = input->split_normal_value_into_track_values(value.data());
for (int k=0;k<split_values.size();k++) {
sub_item->setText(2 + k, NodeInput::ValueToString(value.type(), split_values.at(k), true));
}
}
}
}
}
}
}
/*
void NodeTableView::SetNode(Node *n, const rational &time)
{
NodeTableTraverser traverser;
NodeValueDatabase db = traverser.GenerateDatabase(n, TimeRange(time, time));
// Remove top items if necessary
for (int i=0;i<this->topLevelItemCount();i++) {
if (!db.contains(this->topLevelItem(i)->data(0, Qt::UserRole).toString())) {
delete this->takeTopLevelItem(i);
i--;
}
}
NodeValueDatabase::const_iterator i;
for (i=db.begin(); i!=db.end(); i++) {
const NodeValueTable& table = i.value();
NodeInput* input = n->GetInputWithID(i.key());
if (!input) {
// Filters out table entries that aren't inputs (like "global")
continue;
}
QTreeWidgetItem* top_item = nullptr;
for (int j=0;j<this->topLevelItemCount();j++) {
QTreeWidgetItem* compare = this->topLevelItem(j);
if (compare->data(0, Qt::UserRole).toString() == input->id()) {
top_item = compare;
break;
}
}
if (!top_item) {
top_item = new QTreeWidgetItem();
top_item->setText(0, input->name());
top_item->setData(0, Qt::UserRole, input->id());
top_item->setFirstColumnSpanned(true);
this->addTopLevelItem(top_item);
}
// Create children if necessary
while (top_item->childCount() < table.Count()) {
top_item->addChild(new QTreeWidgetItem());
}
// Remove children if necessary
while (top_item->childCount() > table.Count()) {
delete top_item->takeChild(top_item->childCount() - 1);
}
for (int j=0;j<table.Count();j++) {
const NodeValue& value = table.at(table.Count() - 1 - j);
// Create item
QTreeWidgetItem* sub_item = top_item->child(j);
// Set data type name
sub_item->setText(0, NodeParam::GetPrettyDataTypeName(value.type()));
// Determine source
QString source_name;
if (value.source()) {
source_name = value.source()->Name();
} else {
source_name = tr("(unknown)");
}
sub_item->setText(1, source_name);
switch (value.type()) {
case NodeParam::kTexture:
{
// NodeTableTraverser puts video params in here
VideoParams p = value.data().value<VideoParams>();
int channel_count = PixelFormat::ChannelCount(p.format());
for (int k=0;k<channel_count;k++) {
this->setItemWidget(sub_item, 2 + k, new QCheckBox());
}
break;
}
default:
{
QVector<QVariant> split_values = input->split_normal_value_into_track_values(value.data());
for (int k=0;k<split_values.size();k++) {
sub_item->setText(2 + k, NodeInput::ValueToString(value.type(), split_values.at(k)));
}
}
}
}
}
}
*/
OLIVE_NAMESPACE_EXIT
+50
View File
@@ -0,0 +1,50 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 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 NODETABLEVIEW_H
#define NODETABLEVIEW_H
#include <QTreeWidget>
#include "node/node.h"
OLIVE_NAMESPACE_ENTER
class NodeTableView : public QTreeWidget
{
public:
NodeTableView(QWidget* parent = nullptr);
void SelectNodes(const QList<Node*>& nodes);
void DeselectNodes(const QList<Node*>& nodes);
void SetTime(const rational& time);
private:
QMap<Node*, QTreeWidgetItem*> top_level_item_map_;
rational last_time_;
};
OLIVE_NAMESPACE_EXIT
#endif // NODETABLEVIEW_H
@@ -0,0 +1,38 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 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 "nodetablewidget.h"
#include <QVBoxLayout>
OLIVE_NAMESPACE_ENTER
NodeTableWidget::NodeTableWidget(QWidget* parent) :
TimeBasedWidget(parent)
{
QVBoxLayout* layout = new QVBoxLayout(this);
layout->setSpacing(0);
layout->setMargin(0);
view_ = new NodeTableView();
layout->addWidget(view_);
}
OLIVE_NAMESPACE_EXIT
@@ -0,0 +1,62 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 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 NODETABLEWIDGET_H
#define NODETABLEWIDGET_H
#include "nodetableview.h"
#include "widget/timebased/timebased.h"
OLIVE_NAMESPACE_ENTER
class NodeTableWidget : public TimeBasedWidget
{
public:
NodeTableWidget(QWidget* parent = nullptr);
void SelectNodes(const QList<Node*>& nodes)
{
view_->SelectNodes(nodes);
}
void DeselectNodes(const QList<Node*>& nodes)
{
view_->DeselectNodes(nodes);
}
protected:
virtual void TimeChangedEvent(const int64_t& ts) override
{
UpdateView();
}
private:
void UpdateView()
{
view_->SetTime(GetTime());
}
NodeTableView* view_;
};
OLIVE_NAMESPACE_EXIT
#endif // NODETABLEWIDGET_H
+128 -61
View File
@@ -74,6 +74,7 @@ void NodeView::SetGraph(NodeGraph *graph)
if (graph_ != nullptr) {
disconnect(graph_, &NodeGraph::NodeAdded, &scene_, &NodeViewScene::AddNode);
disconnect(graph_, &NodeGraph::NodeRemoved, &scene_, &NodeViewScene::RemoveNode);
disconnect(graph_, &NodeGraph::NodeRemoved, this, &NodeView::GraphNodeRemoved);
disconnect(graph_, &NodeGraph::EdgeAdded, this, &NodeView::GraphEdgeAdded);
disconnect(graph_, &NodeGraph::EdgeRemoved, this, &NodeView::GraphEdgeRemoved);
}
@@ -89,6 +90,7 @@ void NodeView::SetGraph(NodeGraph *graph)
if (graph_ != nullptr) {
connect(graph_, &NodeGraph::NodeAdded, &scene_, &NodeViewScene::AddNode);
connect(graph_, &NodeGraph::NodeRemoved, &scene_, &NodeViewScene::RemoveNode);
connect(graph_, &NodeGraph::NodeRemoved, this, &NodeView::GraphNodeRemoved);
connect(graph_, &NodeGraph::EdgeAdded, this, &NodeView::GraphEdgeAdded);
connect(graph_, &NodeGraph::EdgeRemoved, this, &NodeView::GraphEdgeRemoved);
@@ -144,7 +146,8 @@ void NodeView::SelectAll()
scene_.SelectAll();
ReconnectSelectionChangedSignal();
ConnectSelectionChangedSignal();
SceneSelectionChangedSlot();
}
void NodeView::DeselectAll()
@@ -155,7 +158,8 @@ void NodeView::DeselectAll()
scene_.DeselectAll();
ReconnectSelectionChangedSignal();
ConnectSelectionChangedSignal();
SceneSelectionChangedSlot();
}
void NodeView::Select(const QList<Node *> &nodes)
@@ -176,7 +180,8 @@ void NodeView::Select(const QList<Node *> &nodes)
item->setSelected(true);
}
ReconnectSelectionChangedSignal();
ConnectSelectionChangedSignal();
SceneSelectionChangedSlot();
}
void NodeView::SelectWithDependencies(QList<Node *> nodes)
@@ -195,10 +200,13 @@ void NodeView::SelectWithDependencies(QList<Node *> nodes)
void NodeView::SelectBlocks(const QList<Block *> &blocks)
{
if (selected_blocks_ == blocks) {
return;
}
selected_blocks_.append(blocks);
SelectBlocksInternal();
}
void NodeView::DeselectBlocks(const QList<Block *> &blocks)
{
// Remove temporary associations
foreach (Block* b, selected_blocks_) {
if (!blocks.contains(b)) {
@@ -206,35 +214,12 @@ void NodeView::SelectBlocks(const QList<Block *> &blocks)
}
}
selected_blocks_ = blocks;
// Block scene signals while our selection is changing a lot
scene_.blockSignals(true);
if (filter_mode_ == kFilterShowSelectedBlocks) {
UpdateBlockFilter();
}
QList<Node*> nodes;
nodes.reserve(blocks.size());
// Remove blocks from selected array
foreach (Block* b, blocks) {
nodes.append(b);
nodes.append(b->GetDependencies());
selected_blocks_.removeOne(b);
}
SelectWithDependencies(nodes);
// Stop blocking signals and send a change signal now that all of our processing is done
scene_.blockSignals(false);
SceneSelectionChangedSlot();
if (!blocks.isEmpty()) {
NodeViewItem* item = scene_.NodeToUIObject(blocks.first());
if (item) {
centerOn(item);
}
}
SelectBlocksInternal();
}
void NodeView::CopySelected(bool cut)
@@ -356,30 +341,17 @@ void NodeView::mousePressEvent(QMouseEvent *event)
{
if (HandPress(event)) return;
if (!attached_items_.isEmpty()) {
if (attached_items_.size() == 1) {
Node* dropping_node = attached_items_.first().item->GetNode();
if (drop_edge_) {
NodeEdgePtr old_edge = drop_edge_->edge();
// We have everything we need to place the node in between
QUndoCommand* command = new QUndoCommand();
// Remove old edge
new NodeEdgeRemoveCommand(old_edge, command);
// Place new edges
new NodeEdgeAddCommand(old_edge->output(), drop_input_, command);
new NodeEdgeAddCommand(dropping_node->output(), old_edge->input(), command);
Core::instance()->undo_stack()->push(command);
}
drop_edge_ = nullptr;
if (event->button() == Qt::RightButton) {
// Qt doesn't do this by default for some reason
if (!(event->modifiers() & Qt::ShiftModifier)) {
scene_.clearSelection();
}
DetachItemsFromCursor();
// If there's an item here, select it
QGraphicsItem* item = itemAt(event->pos());
if (item) {
item->setSelected(true);
}
}
super::mousePressEvent(event);
@@ -456,6 +428,32 @@ void NodeView::mouseReleaseEvent(QMouseEvent *event)
{
if (HandRelease(event)) return;
if (!attached_items_.isEmpty()) {
if (attached_items_.size() == 1) {
Node* dropping_node = attached_items_.first().item->GetNode();
if (drop_edge_) {
NodeEdgePtr old_edge = drop_edge_->edge();
// We have everything we need to place the node in between
QUndoCommand* command = new QUndoCommand();
// Remove old edge
new NodeEdgeRemoveCommand(old_edge, command);
// Place new edges
new NodeEdgeAddCommand(old_edge->output(), drop_input_, command);
new NodeEdgeAddCommand(dropping_node->output(), old_edge->input(), command);
Core::instance()->undo_stack()->push(command);
}
drop_edge_ = nullptr;
}
DetachItemsFromCursor();
}
super::mouseReleaseEvent(event);
}
@@ -478,7 +476,44 @@ void NodeView::wheelEvent(QWheelEvent *event)
void NodeView::SceneSelectionChangedSlot()
{
emit SelectionChanged(scene_.GetSelectedNodes());
QList<Node*> current_selection = scene_.GetSelectedNodes();
QList<Node*> selected;
QList<Node*> deselected;
// Determine which nodes are newly selected
if (selected_nodes_.isEmpty()) {
// All nodes in the current selection have just been selected
selected = current_selection;
} else {
foreach (Node* n, current_selection) {
if (!selected_nodes_.contains(n)) {
selected.append(n);
}
}
}
// Determine which nodes are newly deselected
if (current_selection.isEmpty()) {
// All nodes that were selected have been deselected
deselected = selected_nodes_;
} else {
foreach (Node* n, selected_nodes_) {
if (!current_selection.contains(n)) {
deselected.append(n);
}
}
}
selected_nodes_ = current_selection;
if (!selected.isEmpty()) {
emit NodesSelected(selected);
}
if (!deselected.isEmpty()) {
emit NodesDeselected(deselected);
}
}
void NodeView::ShowContextMenu(const QPoint &pos)
@@ -803,12 +838,6 @@ void NodeView::ConnectSelectionChangedSignal()
connect(&scene_, &QGraphicsScene::selectionChanged, this, &NodeView::SceneSelectionChangedSlot);
}
void NodeView::ReconnectSelectionChangedSignal()
{
ConnectSelectionChangedSignal();
SceneSelectionChangedSlot();
}
void NodeView::DisconnectSelectionChangedSignal()
{
disconnect(&scene_, &QGraphicsScene::selectionChanged, this, &NodeView::SceneSelectionChangedSlot);
@@ -913,6 +942,37 @@ void NodeView::DisassociateNode(Node *n, bool remove_from_map)
disconnect(n, &Node::destroyed, this, &NodeView::AssociatedNodeDestroyed);
}
void NodeView::SelectBlocksInternal()
{
// Block scene signals while our selection is changing a lot
scene_.blockSignals(true);
if (filter_mode_ == kFilterShowSelectedBlocks) {
UpdateBlockFilter();
}
QList<Node*> nodes;
nodes.reserve(selected_blocks_.size());
foreach (Block* b, selected_blocks_) {
nodes.append(b);
nodes.append(b->GetDependencies());
}
SelectWithDependencies(nodes);
// Stop blocking signals and send a change signal now that all of our processing is done
scene_.blockSignals(false);
SceneSelectionChangedSlot();
if (!selected_blocks_.isEmpty()) {
NodeViewItem* item = scene_.NodeToUIObject(selected_blocks_.first());
if (item) {
centerOn(item);
}
}
}
void NodeView::ValidateFilter()
{
// Force auto-positioning
@@ -932,6 +992,13 @@ void NodeView::AssociatedNodeDestroyed()
DisassociateNode(static_cast<Node*>(sender()), true);
}
void NodeView::GraphNodeRemoved(Node *node)
{
if (selected_blocks_.contains(static_cast<Block*>(node))) {
DeselectBlocks({static_cast<Block*>(node)});
}
}
void NodeView::GraphEdgeAdded(NodeEdgePtr edge)
{
Node* input_node = edge->input()->parentNode();
+15 -7
View File
@@ -61,18 +61,19 @@ public:
void Select(const QList<Node*>& nodes);
void SelectWithDependencies(QList<Node *> nodes);
void SelectBlocks(const QList<Block*>& blocks);
void CopySelected(bool cut);
void Paste();
void Duplicate();
void SelectBlocks(const QList<Block*>& blocks);
void DeselectBlocks(const QList<Block*>& blocks);
signals:
/**
* @brief Signal emitted when the selected nodes have changed
*/
void SelectionChanged(QList<Node*> selected_nodes);
void NodesSelected(const QList<Node*>& nodes);
void NodesDeselected(const QList<Node*>& nodes);
protected:
virtual void keyPressEvent(QKeyEvent *event) override;
@@ -83,6 +84,8 @@ protected:
virtual void wheelEvent(QWheelEvent* event) override;
//virtual void scrollContentsBy(int dx, int dy) override;
private:
void PlaceNode(NodeViewItem* n, const QPointF& pos);
@@ -97,7 +100,6 @@ private:
void MoveAttachedNodesToCursor(const QPoint &p);
void ConnectSelectionChangedSignal();
void ReconnectSelectionChangedSignal();
void DisconnectSelectionChangedSignal();
void UpdateBlockFilter();
@@ -105,6 +107,8 @@ private:
void AssociateNodeWithSelectedBlocks(Node* n);
void DisassociateNode(Node* n, bool remove_from_map);
void SelectBlocksInternal();
NodeGraph* graph_;
struct AttachedItem {
@@ -119,6 +123,8 @@ private:
NodeViewScene scene_;
QList<Node*> selected_nodes_;
QList<Block*> selected_blocks_;
QHash<Node*, QList<Block*> > association_map_;
@@ -139,6 +145,8 @@ private slots:
void AssociatedNodeDestroyed();
void GraphNodeRemoved(Node* node);
void GraphEdgeAdded(NodeEdgePtr edge);
void GraphEdgeRemoved(NodeEdgePtr edge);
+22
View File
@@ -0,0 +1,22 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2019 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/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
widget/path/pathwidget.h
widget/path/pathwidget.cpp
PARENT_SCOPE
)
+68
View File
@@ -0,0 +1,68 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 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 "pathwidget.h"
#include <QDir>
#include <QFileDialog>
#include <QHBoxLayout>
#include "common/filefunctions.h"
OLIVE_NAMESPACE_ENTER
PathWidget::PathWidget(const QString &path, QWidget *parent) :
QWidget(parent)
{
QHBoxLayout* layout = new QHBoxLayout(this);
layout->setMargin(0);
path_edit_ = new QLineEdit();
path_edit_->setText(path);
layout->addWidget(path_edit_);
connect(path_edit_, &QLineEdit::textChanged, this, &PathWidget::LineEditChanged);
browse_btn_ = new QPushButton(tr("Browse"));
layout->addWidget(browse_btn_);
connect(browse_btn_, &QPushButton::clicked, this, &PathWidget::BrowseClicked);
}
void PathWidget::BrowseClicked()
{
QString dir = QFileDialog::getExistingDirectory(static_cast<QWidget*>(parent()),
tr("Browse for path"),
path_edit_->text());
if (!dir.isEmpty()) {
path_edit_->setText(dir);
}
}
void PathWidget::LineEditChanged()
{
if (FileFunctions::DirectoryIsValid(text(), false)) {
path_edit_->setStyleSheet(QString());
} else {
path_edit_->setStyleSheet(QStringLiteral("QLineEdit {color: red;}"));
}
}
OLIVE_NAMESPACE_EXIT
+57
View File
@@ -0,0 +1,57 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 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 PATHWIDGET_H
#define PATHWIDGET_H
#include <QLineEdit>
#include <QPushButton>
#include "common/define.h"
OLIVE_NAMESPACE_ENTER
class PathWidget : public QWidget
{
Q_OBJECT
public:
PathWidget(const QString& path,
QWidget* parent = nullptr);
QString text() const
{
return path_edit_->text();
}
private slots:
void BrowseClicked();
void LineEditChanged();
private:
QLineEdit* path_edit_;
QPushButton* browse_btn_;
};
OLIVE_NAMESPACE_EXIT
#endif // PATHWIDGET_H
@@ -97,7 +97,10 @@ PlaybackControls::PlaybackControls(QWidget *parent) :
// Default to showing play button
playpause_stack_->setCurrentWidget(play_btn_);
playpause_stack_->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
// Hack to conform the play/pause button size to the other buttons (QStackedWidget has a
// different size policy by default)
playpause_stack_->setSizePolicy(prev_frame_btn_->sizePolicy());
// Next Frame Button
next_frame_btn_ = new QPushButton();
@@ -31,7 +31,7 @@
#include "core.h"
#include "dialog/footageproperties/footageproperties.h"
#include "dialog/sequence/sequence.h"
#include "task/cache/footagecache.h"
#include "task/precache/precachetask.h"
#include "task/taskmanager.h"
#include "widget/menu/menu.h"
#include "widget/menu/menushared.h"
@@ -427,7 +427,7 @@ void ProjectExplorer::ContextMenuStartProxy(QAction *a)
// Start a background task for proxying
foreach (VideoStreamPtr video_stream, video_streams) {
FootageCacheTask* proxy_task = new FootageCacheTask(video_stream, sequence);
PreCacheTask* proxy_task = new PreCacheTask(video_stream, sequence);
TaskManager::instance()->AddTask(proxy_task);
}
}
+169 -2
View File
@@ -23,8 +23,9 @@
#include <QPainter>
#include <QtMath>
#include "common/clamp.h"
#include "common/functiontimer.h"
#include "common/qtutils.h"
#include "node/node.h"
#include "render/backend/opengl/openglrenderfunctions.h"
OLIVE_NAMESPACE_ENTER
@@ -33,4 +34,170 @@ HistogramScope::HistogramScope(QWidget* parent) :
{
}
HistogramScope::~HistogramScope()
{
CleanUp();
if (context()) {
disconnect(context(), &QOpenGLContext::aboutToBeDestroyed, this,
&HistogramScope::CleanUp);
}
}
void HistogramScope::initializeGL()
{
ScopeBase::initializeGL();
pipeline_secondary_ = CreateSecondaryShader();
connect(context(), &QOpenGLContext::aboutToBeDestroyed, this,
&HistogramScope::CleanUp, Qt::DirectConnection);
}
void HistogramScope::AssertAdditionalTextures()
{
if (!texture_row_sums_.IsCreated()
|| texture_row_sums_.width() != width()
|| texture_row_sums_.height() != height()) {
texture_row_sums_.Destroy();
texture_row_sums_.Create(context(), VideoParams(width(),
height(), managed_tex().format()));
}
}
void HistogramScope::CleanUp()
{
makeCurrent();
pipeline_secondary_ = nullptr;
texture_row_sums_.Destroy();
doneCurrent();
}
OpenGLShaderPtr HistogramScope::CreateShader()
{
OpenGLShaderPtr pipeline = OpenGLShader::Create();
pipeline->create();
pipeline->addShaderFromSourceCode(QOpenGLShader::Vertex,
OpenGLShader::CodeDefaultVertex());
pipeline->addShaderFromSourceCode(QOpenGLShader::Fragment,
Node::ReadFileAsString(":/shaders/rgbhistogram.frag"));
pipeline->link();
return pipeline;
}
OpenGLShaderPtr HistogramScope::CreateSecondaryShader()
{
OpenGLShaderPtr shader = OpenGLShader::Create();
shader->create();
shader->addShaderFromSourceCode(QOpenGLShader::Vertex,
Node::ReadFileAsString(":/shaders/rgbhistogram.vert"));
shader->addShaderFromSourceCode(QOpenGLShader::Fragment,
Node::ReadFileAsString(":/shaders/rgbhistogram_secondary.frag"));
shader->link();
return shader;
}
void HistogramScope::DrawScope()
{
float histogram_scale = 0.80f;
// This value is eyeballed for usefulness. Until we have a geometry
// shader approach, it is impossible to normalize against a peak
// sum of image values.
float histogram_base = 2.5f;
float histogram_power = 1.0f / histogram_base;
pipeline()->bind();
pipeline()->setUniformValue("ove_resolution", managed_tex().width(),
managed_tex().height());
pipeline()->setUniformValue("ove_viewport", width(), height());
pipeline()->setUniformValue("histogram_scale", histogram_scale);
pipeline()->release();
AssertAdditionalTextures();
framebuffer().Attach(&texture_row_sums_, true);
framebuffer().Bind();
managed_tex().Bind();
OpenGLRenderFunctions::Blit(pipeline());
managed_tex().Release();
framebuffer().Release();
framebuffer().Detach();
pipeline_secondary_->bind();
pipeline_secondary_->setUniformValue("ove_resolution",
texture_row_sums_.width(), texture_row_sums_.height());
pipeline_secondary_->setUniformValue("ove_viewport", width(), height());
pipeline_secondary_->setUniformValue("histogram_scale", histogram_scale);
pipeline_secondary_->setUniformValue("histogram_power", histogram_power);
pipeline_secondary_->release();
texture_row_sums_.Bind();
OpenGLRenderFunctions::Blit(pipeline_secondary_);
texture_row_sums_.Release();
// Draw line overlays
QPainter p(this);
QFont font = p.font();
font.setPixelSize(10);
QFontMetrics font_metrics = QFontMetrics(font);
QString label;
std::vector<float> histogram_increments = {
0.00,
0.25,
0.50,
1.0
};
int histogram_steps = histogram_increments.size();
QVector<QLine> histogram_lines(histogram_steps + 1);
int font_x_offset = 0;
int font_y_offset = font_metrics.capHeight() / 2.0f;
p.setCompositionMode(QPainter::CompositionMode_Plus);
p.setPen(QColor(0.0, 0.6 * 255.0, 0.0));
p.setFont(font);
float histogram_dim_x = ceil((width() - 1.0) * histogram_scale);
float histogram_dim_y = ceil((height() - 1.0) * histogram_scale);
float histogram_start_dim_x =
((width() - 1.0) - histogram_dim_x) / 2.0f;
float histogram_start_dim_y =
((height() - 1.0) - histogram_dim_y) / 2.0f;
float histogram_end_dim_x = (width() - 1.0) - histogram_start_dim_x;
// for (int i=0; i <= histogram_steps; i++) {
for(std::vector<float>::iterator it = histogram_increments.begin();
it != histogram_increments.end(); it++) {
histogram_lines[it - histogram_increments.begin()].setLine(
histogram_start_dim_x,
(histogram_dim_y * pow(1.0 - *it, histogram_base)) +
histogram_start_dim_y,
histogram_end_dim_x,
(histogram_dim_y * pow(1.0 - *it, histogram_base)) +
histogram_start_dim_y);
label = QString::number(
*it * 100, 'f', 1) + "%";
font_x_offset = QFontMetricsWidth(font_metrics, label) + 4;
p.drawText(
histogram_start_dim_x - font_x_offset,
(histogram_dim_y * pow(1.0 - *it, histogram_base)) +
histogram_start_dim_y + font_y_offset, label);
}
p.drawLines(histogram_lines);
}
OLIVE_NAMESPACE_EXIT
+15 -2
View File
@@ -31,11 +31,24 @@ class HistogramScope : public ScopeBase
public:
HistogramScope(QWidget* parent = nullptr);
virtual ~HistogramScope() override;
protected:
//virtual OpenGLShaderPtr CreateShader() override;
virtual void initializeGL() override;
//virtual void DrawScope() override;
virtual OpenGLShaderPtr CreateShader() override;
OpenGLShaderPtr CreateSecondaryShader();
void AssertAdditionalTextures();
virtual void DrawScope() override;
private:
OpenGLShaderPtr pipeline_secondary_;
OpenGLTexture texture_row_sums_;
private slots:
void CleanUp();
};
OLIVE_NAMESPACE_EXIT
-10
View File
@@ -68,16 +68,6 @@ void ScopeBase::DrawScope()
managed_tex().Release();
}
OpenGLShaderPtr ScopeBase::pipeline()
{
return pipeline_;
}
OpenGLTexture &ScopeBase::managed_tex()
{
return managed_tex_;
}
void ScopeBase::UploadTextureFromBuffer()
{
if (!isVisible()) {
+13 -2
View File
@@ -51,9 +51,20 @@ protected:
virtual void DrawScope();
OpenGLShaderPtr pipeline();
OpenGLShaderPtr pipeline()
{
return pipeline_;
}
OpenGLTexture& managed_tex();
OpenGLTexture& managed_tex()
{
return managed_tex_;
}
OpenGLFramebuffer& framebuffer()
{
return framebuffer_;
}
private:
void UploadTextureFromBuffer();
+16 -26
View File
@@ -41,8 +41,10 @@ OpenGLShaderPtr WaveformScope::CreateShader()
OpenGLShaderPtr pipeline = OpenGLShader::Create();
pipeline->create();
pipeline->addShaderFromSourceCode(QOpenGLShader::Vertex, OpenGLShader::CodeDefaultVertex());
pipeline->addShaderFromSourceCode(QOpenGLShader::Fragment, Node::ReadFileAsString(":/shaders/rgbwaveform.frag"));
pipeline->addShaderFromSourceCode(QOpenGLShader::Vertex,
Node::ReadFileAsString(":/shaders/rgbwaveform.vert"));
pipeline->addShaderFromSourceCode(QOpenGLShader::Fragment,
Node::ReadFileAsString(":/shaders/rgbwaveform.frag"));
pipeline->link();
return pipeline;
@@ -51,12 +53,6 @@ OpenGLShaderPtr WaveformScope::CreateShader()
void WaveformScope::DrawScope()
{
float waveform_scale = 0.80f;
float waveform_dim_x = width() * waveform_scale;
float waveform_dim_y = height() * waveform_scale;
float waveform_start_dim_x = (width() - waveform_dim_x) / 2.0f;
float waveform_start_dim_y = (height() - waveform_dim_y) / 2.0f;
float waveform_end_dim_x = width() - waveform_start_dim_x;
float waveform_end_dim_y = height() - waveform_start_dim_y;
// Draw waveform through shader
pipeline()->bind();
@@ -68,22 +64,6 @@ void WaveformScope::DrawScope()
// Scale of the waveform relative to the viewport surface.
pipeline()->setUniformValue("waveform_scale", waveform_scale);
pipeline()->setUniformValue(
"waveform_dims", waveform_dim_x, waveform_dim_y);
pipeline()->setUniformValue(
"waveform_region",
waveform_start_dim_x, waveform_start_dim_y,
waveform_end_dim_x, waveform_end_dim_y);
float waveform_start_uv_x = waveform_start_dim_x / width();
float waveform_start_uv_y = waveform_start_dim_y / height();
float waveform_end_uv_x = waveform_end_dim_x / width();
float waveform_end_uv_y = waveform_end_dim_y / height();
pipeline()->setUniformValue(
"waveform_uv",
waveform_start_uv_x, waveform_start_uv_y,
waveform_end_uv_x, waveform_end_uv_y);
pipeline()->release();
@@ -93,9 +73,19 @@ void WaveformScope::DrawScope()
managed_tex().Release();
float waveform_dim_x = ceil((width() - 1.0) * waveform_scale);
float waveform_dim_y = ceil((height() - 1.0) * waveform_scale);
float waveform_start_dim_x =
((width() - 1.0) - waveform_dim_x) / 2.0f;
float waveform_start_dim_y =
((height() - 1.0) - waveform_dim_y) / 2.0f;
float waveform_end_dim_x = (width() - 1.0) - waveform_start_dim_x;
// Draw line overlays
QPainter p(this);
QFontMetrics font_metrics = QFontMetrics(QFont());
QFont font;
font.setPixelSize(10);
QFontMetrics font_metrics = QFontMetrics(font);
QString label;
float ire_increment = 0.1f;
int ire_steps = qRound(1.0 / ire_increment);
@@ -106,7 +96,7 @@ void WaveformScope::DrawScope()
p.setCompositionMode(QPainter::CompositionMode_Plus);
p.setPen(QColor(0.0, 0.6 * 255.0, 0.0));
p.setFont(QFont());
p.setFont(font);
for (int i=0; i <= ire_steps; i++) {
ire_lines[i].setLine(
+78 -53
View File
@@ -40,8 +40,8 @@ SliderBase::SliderBase(Mode mode, QWidget *parent) :
require_valid_input_(true),
tristate_(false),
drag_ladder_(nullptr),
enable_ladder_(false),
ladder_element_count_(2)
ladder_element_count_(0),
dragged_(false)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
@@ -52,9 +52,8 @@ SliderBase::SliderBase(Mode mode, QWidget *parent) :
editor_ = new FocusableLineEdit(this);
addWidget(editor_);
connect(label_, &SliderLabel::LabelMoved, this, &SliderBase::LabelDragged);
connect(label_, &SliderLabel::LabelReleased, this, &SliderBase::LabelClicked);
connect(label_, &SliderLabel::focused, this, &SliderBase::LabelClicked);
connect(label_, &SliderLabel::LabelPressed, this, &SliderBase::LabelPressed);
connect(label_, &SliderLabel::focused, this, &SliderBase::ShowEditor);
connect(label_, &SliderLabel::RequestReset, this, &SliderBase::ResetValue);
connect(editor_, &FocusableLineEdit::Confirmed, this, &SliderBase::LineEditConfirmed);
connect(editor_, &FocusableLineEdit::Cancelled, this, &SliderBase::LineEditCancelled);
@@ -201,6 +200,22 @@ QString SliderBase::GetFormat() const
}
}
void SliderBase::RepositionLadder()
{
QPoint label_global_pos = label_->mapToGlobal(label_->pos());
int text_width = QFontMetricsWidth(label_->fontMetrics(), label_->text());
QPoint ladder_pos(label_global_pos.x(),
label_global_pos.y() + label_->height() / 2 - drag_ladder_->height() / 2);
if (ladder_element_count_ > 0) {
ladder_pos.setX(ladder_pos.x() + text_width + QFontMetricsWidth(label_->fontMetrics(), QStringLiteral("H")));
} else {
ladder_pos.setX(ladder_pos.x() + text_width / 2 - drag_ladder_->width() / 2);
}
drag_ladder_->move(ladder_pos);
}
void SliderBase::UpdateLabel(const QVariant &v)
{
if (tristate_) {
@@ -226,44 +241,21 @@ QVariant SliderBase::StringToValue(const QString &s, bool *ok)
return s;
}
void SliderBase::LabelClicked()
void SliderBase::ShowEditor()
{
if (!drag_ladder_) {
// This was a simple click
// Load label's text into editor
editor_->setText(ValueToString(value_));
// This was a simple click
// Load label's text into editor
editor_->setText(ValueToString(value_));
// Show editor
setCurrentWidget(editor_);
// Show editor
setCurrentWidget(editor_);
// Select all text in the editor
editor_->setFocus();
editor_->selectAll();
}
// Select all text in the editor
editor_->setFocus();
editor_->selectAll();
}
void SliderBase::LabelDragged()
{
switch (mode_) {
case kString:
// No dragging supported for strings
break;
case kInteger:
case kFloat:
drag_ladder_ = new SliderLadder(value_.toDouble(), drag_multiplier_, enable_ladder_ ? ladder_element_count_ : 0);
drag_ladder_->show();
QPoint label_global_pos = label_->mapToGlobal(label_->pos());
drag_ladder_->move(label_global_pos.x() + QFontMetricsWidth(label_->fontMetrics(), label_->text()) / 2 - drag_ladder_->width() / 2,
label_global_pos.y() + label_->height() / 2 - drag_ladder_->height() / 2);
connect(drag_ladder_, &SliderLadder::DraggedByValue, this, &SliderBase::LadderDragged);
connect(drag_ladder_, &SliderLadder::Released, this, &SliderBase::LadderReleased);
break;
}
}
void SliderBase::LadderDragged(int value, double multiplier)
void SliderBase::LabelPressed()
{
switch (mode_) {
case kString:
@@ -272,7 +264,31 @@ void SliderBase::LadderDragged(int value, double multiplier)
case kInteger:
case kFloat:
{
dragged_diff_ += static_cast<double>(value) * drag_multiplier_ * multiplier;
drag_ladder_ = new SliderLadder(drag_multiplier_, ladder_element_count_);
drag_ladder_->SetValue(ValueToString(value_));
drag_ladder_->show();
RepositionLadder();
connect(drag_ladder_, &SliderLadder::DraggedByValue, this, &SliderBase::LadderDragged);
connect(drag_ladder_, &SliderLadder::Released, this, &SliderBase::LadderReleased);
break;
}
}
}
void SliderBase::LadderDragged(int value, double multiplier)
{
dragged_ = true;
switch (mode_) {
case kString:
// No dragging supported for strings
break;
case kInteger:
case kFloat:
{
dragged_diff_ += value * drag_multiplier_ * multiplier;
double drag_val = AdjustDragDistanceInternal(value_.toDouble(), dragged_diff_);
@@ -291,7 +307,10 @@ void SliderBase::LadderDragged(int value, double multiplier)
}
UpdateLabel(temp_dragged_value_);
drag_ladder_->SetValue(temp_dragged_value_.toDouble());
drag_ladder_->SetValue(ValueToString(temp_dragged_value_));
RepositionLadder();
emit ValueChanged(temp_dragged_value_);
break;
}
@@ -304,20 +323,26 @@ void SliderBase::LadderReleased()
drag_ladder_ = nullptr;
dragged_diff_ = 0;
// This was a drag
switch (mode_) {
case kString:
// No-op
break;
case kInteger:
SetValue(temp_dragged_value_.toInt());
break;
case kFloat:
SetValue(temp_dragged_value_.toDouble());
break;
}
if (dragged_) {
// This was a drag
switch (mode_) {
case kString:
// No-op
break;
case kInteger:
SetValue(temp_dragged_value_.toInt());
break;
case kFloat:
SetValue(temp_dragged_value_.toDouble());
break;
}
emit ValueChanged(value_);
emit ValueChanged(value_);
dragged_ = false;
} else {
ShowEditor();
}
}
void SliderBase::LineEditConfirmed()
+7 -10
View File
@@ -56,11 +56,6 @@ public:
void SetFormat(const QString& s);
void ClearFormat();
void SetLadderEnabled(bool e)
{
enable_ladder_ = e;
}
void SetLadderElementCount(int b)
{
ladder_element_count_ = b;
@@ -97,6 +92,8 @@ private:
QString GetFormat() const;
void RepositionLadder();
SliderLabel* label_;
FocusableLineEdit* editor_;
@@ -124,14 +121,14 @@ private:
SliderLadder* drag_ladder_;
bool enable_ladder_;
int ladder_element_count_;
private slots:
void LabelClicked();
bool dragged_;
void LabelDragged();
private slots:
void ShowEditor();
void LabelPressed();
void LadderDragged(int value, double multiplier);
+2 -23
View File
@@ -24,15 +24,10 @@
#include <QMouseEvent>
#include <QDebug>
#ifdef Q_OS_MAC
#include <ApplicationServices/ApplicationServices.h>
#endif
OLIVE_NAMESPACE_ENTER
SliderLabel::SliderLabel(QWidget *parent) :
QLabel(parent),
dragging_(false)
QLabel(parent)
{
QPalette p = palette();
@@ -43,7 +38,7 @@ SliderLabel::SliderLabel(QWidget *parent) :
setPalette(p);
// Use highlight color as font color
setForegroundRole(QPalette::Highlight);
setForegroundRole(QPalette::Link);
// Set underlined
QFont f = font();
@@ -59,26 +54,10 @@ void SliderLabel::mousePressEvent(QMouseEvent *e)
if (e->modifiers() & Qt::AltModifier) {
emit RequestReset();
} else {
dragging_ = true;
emit LabelPressed();
}
}
void SliderLabel::mouseMoveEvent(QMouseEvent *)
{
if (dragging_) {
emit LabelMoved();
}
}
void SliderLabel::mouseReleaseEvent(QMouseEvent *)
{
if (dragging_) {
emit LabelReleased();
dragging_ = false;
}
}
void SliderLabel::focusInEvent(QFocusEvent *event)
{
QWidget::focusInEvent(event);
-11
View File
@@ -36,26 +36,15 @@ public:
protected:
virtual void mousePressEvent(QMouseEvent *ev) override;
virtual void mouseMoveEvent(QMouseEvent *ev) override;
virtual void mouseReleaseEvent(QMouseEvent *ev) override;
virtual void focusInEvent(QFocusEvent *event) override;
signals:
void LabelPressed();
void LabelMoved();
void LabelReleased();
void focused();
void RequestReset();
private:
bool dragging_;
};
OLIVE_NAMESPACE_EXIT
+47 -53
View File
@@ -26,16 +26,18 @@
#include <QtMath>
#include <QVBoxLayout>
#ifdef Q_OS_MAC
#include <ApplicationServices/ApplicationServices.h>
#endif
#include "common/clamp.h"
#include "common/lerp.h"
OLIVE_NAMESPACE_ENTER
SliderLadder::SliderLadder(double start_val, double drag_multiplier, int nb_outer_values, QWidget* parent) :
SliderLadder::SliderLadder(double drag_multiplier, int nb_outer_values, QWidget* parent) :
QFrame(parent, Qt::Popup),
start_val_(start_val),
active_element_(nullptr),
relative_y_(-1)
y_mobility_(0)
{
QVBoxLayout* layout = new QVBoxLayout(this);
layout->setMargin(0);
@@ -48,14 +50,17 @@ SliderLadder::SliderLadder(double start_val, double drag_multiplier, int nb_oute
elements_.append(new SliderLadderElement(qPow(10, i + 1) * drag_multiplier));
}
elements_.append(new SliderLadderElement(drag_multiplier));
// Create center entry
SliderLadderElement* start_element = new SliderLadderElement(drag_multiplier);
active_element_ = elements_.size();
start_element->SetHighlighted(true);
elements_.append(start_element);
for (int i=0;i<nb_outer_values;i++) {
elements_.append(new SliderLadderElement(qPow(10, - i - 1) * drag_multiplier));
}
foreach (SliderLadderElement* e, elements_) {
e->SetValue(start_val_);
layout->addWidget(e);
}
@@ -87,10 +92,10 @@ SliderLadder::~SliderLadder()
#endif
}
void SliderLadder::SetValue(double val)
void SliderLadder::SetValue(const QString &s)
{
foreach (SliderLadderElement* e, elements_) {
e->SetValue(val);
e->SetValue(s);
}
}
@@ -98,6 +103,8 @@ void SliderLadder::mouseReleaseEvent(QMouseEvent *event)
{
Q_UNUSED(event)
drag_timer_.stop();
emit Released();
}
@@ -105,38 +112,7 @@ void SliderLadder::showEvent(QShowEvent *event)
{
QWidget::showEvent(event);
QMetaObject::invokeMethod(this, "InitRelativeY", Qt::QueuedConnection);
QMetaObject::invokeMethod(&drag_timer_, "start", Qt::QueuedConnection);
}
void SliderLadder::SetActiveElement()
{
if (!active_element_
|| relative_y_ < active_element_->y()
|| relative_y_ >= active_element_->y() + active_element_->height()) {
if (active_element_) {
// Un-highlight active element if one is set
active_element_->SetHighlighted(false);
}
// Find new active element
foreach (SliderLadderElement* ele, elements_) {
if (relative_y_ >= ele->y() && relative_y_ < ele->y() + ele->height()) {
// This is the element!
active_element_ = ele;
active_element_->SetHighlighted(true);
relative_y_ = active_element_->y() + active_element_->height() / 2;
break;
}
}
}
}
void SliderLadder::InitRelativeY()
{
relative_y_ = QCursor::pos().y() - this->y();
SetActiveElement();
drag_timer_.start();
}
void SliderLadder::TimerUpdate()
@@ -155,22 +131,40 @@ void SliderLadder::TimerUpdate()
QCursor::setPos(drag_start_);
#endif
int target = active_element_->y() + active_element_->height() / 2;
relative_y_ = lerp(relative_y_, static_cast<float>(target), 0.1f);
if (!x_mvmt && !y_mvmt) {
return;
}
// Determine which element we're in
relative_y_ = clamp(relative_y_ + y_mvmt,
static_cast<float>(elements_.first()->y()),
static_cast<float>(elements_.last()->y() + elements_.last()->height() - 1));
if (qApp->keyboardModifiers() & Qt::ControlModifier) {
// Movement is vertical
y_mobility_ += y_mvmt;
SetActiveElement();
if (qAbs(y_mobility_) > fontMetrics().height()) {
int new_active_element;
if (qAbs(x_mvmt) > qAbs(y_mvmt)) {
emit DraggedByValue(x_mvmt, active_element_->GetMultiplier());
if (y_mvmt < 0) {
// Movement is UP
new_active_element = active_element_ - 1;
} else {
// Movement is DOWN
new_active_element = active_element_ + 1;
}
// Check if the proposed element is valid
if (new_active_element >= 0 && new_active_element < elements_.size()) {
elements_.at(active_element_)->SetHighlighted(false);
active_element_ = new_active_element;
elements_.at(active_element_)->SetHighlighted(true);
}
y_mobility_ = 0;
}
} else {
y_mobility_ = 0;
emit DraggedByValue(x_mvmt + y_mvmt, elements_.at(active_element_)->GetMultiplier());
}
}
@@ -210,7 +204,7 @@ void SliderLadderElement::SetHighlighted(bool e)
UpdateLabel();
}
void SliderLadderElement::SetValue(double value)
void SliderLadderElement::SetValue(const QString &value)
{
value_ = value;
@@ -230,13 +224,13 @@ void SliderLadderElement::UpdateLabel()
QString val_text;
if (highlighted_) {
val_text = QString::number(value_);
val_text = value_;
}
label_->setText(QStringLiteral("%1\n%2").arg(QString::number(multiplier_),
val_text));
} else {
label_->setText(QString::number(value_));
label_->setText(value_);
}
}
+7 -13
View File
@@ -37,7 +37,7 @@ public:
void SetHighlighted(bool e);
void SetValue(double value);
void SetValue(const QString& value);
void SetMultiplierVisible(bool e);
@@ -52,7 +52,7 @@ private:
QLabel* label_;
double multiplier_;
double value_;
QString value_;
bool highlighted_;
@@ -64,11 +64,11 @@ class SliderLadder : public QFrame
{
Q_OBJECT
public:
SliderLadder(double start_val, double drag_multiplier, int nb_outer_values, QWidget* parent = nullptr);
SliderLadder(double drag_multiplier, int nb_outer_values, QWidget* parent = nullptr);
virtual ~SliderLadder() override;
void SetValue(double val);
void SetValue(const QString& s);
protected:
virtual void mouseReleaseEvent(QMouseEvent *event) override;
@@ -81,23 +81,17 @@ signals:
void Released();
private:
void SetActiveElement();
QPoint drag_start_;
double start_val_;
QList<SliderLadderElement*> elements_;
SliderLadderElement* active_element_;
float relative_y_;
int active_element_;
QTimer drag_timer_;
private slots:
void InitRelativeY();
int y_mobility_;
private slots:
void TimerUpdate();
};
+28
View File
@@ -0,0 +1,28 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2019 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/>.
set(OLIVE_SOURCES
${OLIVE_SOURCES}
widget/standardcombos/channellayoutcombobox.h
widget/standardcombos/frameratecombobox.h
widget/standardcombos/interlacedcombobox.h
widget/standardcombos/pixelaspectratiocombobox.h
widget/standardcombos/pixelformatcombobox.h
widget/standardcombos/sampleratecombobox.h
widget/standardcombos/standardcombos.h
widget/standardcombos/videodividercombobox.h
PARENT_SCOPE
)
@@ -0,0 +1,62 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 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 CHANNELLAYOUTCOMBOBOX_H
#define CHANNELLAYOUTCOMBOBOX_H
#include <QComboBox>
#include "render/audioparams.h"
OLIVE_NAMESPACE_ENTER
class ChannelLayoutComboBox : public QComboBox
{
Q_OBJECT
public:
ChannelLayoutComboBox(QWidget* parent = nullptr) :
QComboBox(parent)
{
foreach (const uint64_t& ch_layout, AudioParams::kSupportedChannelLayouts) {
this->addItem(AudioParams::ChannelLayoutToString(ch_layout),
QVariant::fromValue(ch_layout));
}
}
uint64_t GetChannelLayout() const
{
return this->currentData().toULongLong();
}
void SetChannelLayout(uint64_t ch)
{
for (int i=0; i<this->count(); i++) {
if (this->itemData(i).toULongLong() == ch) {
this->setCurrentIndex(i);
break;
}
}
}
};
OLIVE_NAMESPACE_EXIT
#endif // CHANNELLAYOUTCOMBOBOX_H
@@ -0,0 +1,62 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 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 FRAMERATECOMBOBOX_H
#define FRAMERATECOMBOBOX_H
#include <QComboBox>
#include "common/rational.h"
#include "render/videoparams.h"
OLIVE_NAMESPACE_ENTER
class FrameRateComboBox : public QComboBox
{
Q_OBJECT
public:
FrameRateComboBox(QWidget* parent = nullptr) :
QComboBox(parent)
{
foreach (const rational& fr, VideoParams::kSupportedFrameRates) {
this->addItem(VideoParams::FrameRateToString(fr), QVariant::fromValue(fr));
}
}
rational GetFrameRate() const
{
return this->currentData().value<rational>();
}
void SetFrameRate(const rational& r)
{
for (int i=0; i<this->count(); i++) {
if (this->itemData(i).value<rational>() == r) {
this->setCurrentIndex(i);
break;
}
}
}
};
OLIVE_NAMESPACE_EXIT
#endif // FRAMERATECOMBOBOX_H
@@ -0,0 +1,57 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 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 INTERLACEDCOMBOBOX_H
#define INTERLACEDCOMBOBOX_H
#include <QComboBox>
#include "render/videoparams.h"
OLIVE_NAMESPACE_ENTER
class InterlacedComboBox : public QComboBox
{
Q_OBJECT
public:
InterlacedComboBox(QWidget* parent = nullptr) :
QComboBox(parent)
{
// These must match the Interlacing enum in VideoParams
this->addItem(tr("None (Progressive)"));
this->addItem(tr("Top-Field First"));
this->addItem(tr("Bottom-Field First"));
}
VideoParams::Interlacing GetInterlaceMode() const
{
return static_cast<VideoParams::Interlacing>(this->currentIndex());
}
void SetInterlaceMode(VideoParams::Interlacing mode)
{
this->setCurrentIndex(mode);
}
};
OLIVE_NAMESPACE_EXIT
#endif // INTERLACEDCOMBOBOX_H
@@ -0,0 +1,127 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 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 PIXELASPECTRATIOCOMBOBOX_H
#define PIXELASPECTRATIOCOMBOBOX_H
#include <QComboBox>
#include "common/ratiodialog.h"
#include "render/videoparams.h"
OLIVE_NAMESPACE_ENTER
class PixelAspectRatioComboBox : public QComboBox
{
Q_OBJECT
public:
PixelAspectRatioComboBox(QWidget* parent = nullptr) :
QComboBox(parent),
dont_prompt_custom_par_(false)
{
QStringList par_names = VideoParams::GetStandardPixelAspectRatioNames();
for (int i=0; i<VideoParams::kStandardPixelAspects.size(); i++) {
const rational& ratio = VideoParams::kStandardPixelAspects.at(i);
this->addItem(par_names.at(i),
QVariant::fromValue(ratio));
}
// Always add custom item last, much of the logic relies on this. Set this to the current AR so
// that if none of the above are ==, it will eventually select this item
this->addItem(QString());
UpdateCustomItem(rational());
// Pick up index signal to query for custom aspect ratio if requested
connect(this, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this, &PixelAspectRatioComboBox::IndexChanged);
}
rational GetPixelAspectRatio() const
{
return this->currentData().value<rational>();
}
void SetPixelAspectRatio(const rational& r)
{
// Determine which index to select on startup
for (int i=0; i<this->count(); i++) {
if (this->itemData(i).value<rational>() == r) {
this->setCurrentIndex(i);
return;
}
}
// Must not have found the ratio, so it must be custom
UpdateCustomItem(r);
dont_prompt_custom_par_ = true;
this->setCurrentIndex(this->count() - 1);
dont_prompt_custom_par_ = false;
}
private slots:
void IndexChanged(int index)
{
if (dont_prompt_custom_par_) {
return;
}
// Detect if custom was selected, in which case query what the new AR should be
if (index == this->count() - 1) {
// Query for custom pixel aspect ratio
bool ok;
double custom_ratio = GetFloatRatioFromUser(this,
tr("Set Custom Pixel Aspect Ratio"),
&ok);
if (ok) {
UpdateCustomItem(rational::fromDouble(custom_ratio));
}
}
}
private:
void UpdateCustomItem(const rational &ratio)
{
const int custom_index = this->count() - 1;
if (ratio.isNull()) {
this->setItemText(custom_index,
tr("Custom..."));
// Use 1:1 to prevent any real chance of the PAR being set to 0
this->setItemData(custom_index,
QVariant::fromValue(rational(1)));
} else {
this->setItemText(custom_index,
VideoParams::FormatPixelAspectRatioString(tr("Custom (%1)"), ratio));
this->setItemData(custom_index,
QVariant::fromValue(ratio));
}
}
bool dont_prompt_custom_par_;
};
OLIVE_NAMESPACE_EXIT
#endif // PIXELASPECTRATIOCOMBOBOX_H
@@ -0,0 +1,67 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 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 PIXELFORMATCOMBOBOX_H
#define PIXELFORMATCOMBOBOX_H
#include <QComboBox>
#include "render/pixelformat.h"
OLIVE_NAMESPACE_ENTER
class PixelFormatComboBox : public QComboBox
{
Q_OBJECT
public:
PixelFormatComboBox(bool alpha_only, bool float_only, QWidget* parent = nullptr) :
QComboBox(parent)
{
// Set up preview formats
for (int i=0;i<PixelFormat::PIX_FMT_COUNT;i++) {
PixelFormat::Format pix_fmt = static_cast<PixelFormat::Format>(i);
if ((!alpha_only || PixelFormat::FormatHasAlphaChannel(pix_fmt))
&& (!float_only || PixelFormat::FormatIsFloat(pix_fmt))) {
this->addItem(PixelFormat::GetName(pix_fmt), pix_fmt);
}
}
}
PixelFormat::Format GetPixelFormat() const
{
return static_cast<PixelFormat::Format>(this->currentData().toInt());
}
void SetPixelFormat(PixelFormat::Format fmt)
{
for (int i=0; i<this->count(); i++) {
if (this->itemData(i).toInt() == fmt) {
this->setCurrentIndex(i);
break;
}
}
}
};
OLIVE_NAMESPACE_EXIT
#endif // PIXELFORMATCOMBOBOX_H
@@ -0,0 +1,61 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 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 SAMPLERATECOMBOBOX_H
#define SAMPLERATECOMBOBOX_H
#include <QComboBox>
#include "render/audioparams.h"
OLIVE_NAMESPACE_ENTER
class SampleRateComboBox : public QComboBox
{
Q_OBJECT
public:
SampleRateComboBox(QWidget* parent = nullptr) :
QComboBox(parent)
{
foreach (int sr, AudioParams::kSupportedSampleRates) {
this->addItem(AudioParams::SampleRateToString(sr), sr);
}
}
int GetSampleRate() const
{
return this->currentData().toInt();
}
void SetSampleRate(int rate)
{
for (int i=0; i<this->count(); i++) {
if (this->itemData(i).toInt() == rate) {
this->setCurrentIndex(i);
break;
}
}
}
};
OLIVE_NAMESPACE_EXIT
#endif // SAMPLERATECOMBOBOX_H
@@ -0,0 +1,32 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 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 STANDARDCOMBOS_H
#define STANDARDCOMBOS_H
#include "channellayoutcombobox.h"
#include "frameratecombobox.h"
#include "interlacedcombobox.h"
#include "pixelaspectratiocombobox.h"
#include "pixelformatcombobox.h"
#include "sampleratecombobox.h"
#include "videodividercombobox.h"
#endif // STANDARDCOMBOS_H
@@ -0,0 +1,69 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 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 VIDEODIVIDERCOMBOBOX_H
#define VIDEODIVIDERCOMBOBOX_H
#include <QComboBox>
#include "render/videoparams.h"
OLIVE_NAMESPACE_ENTER
class VideoDividerComboBox : public QComboBox
{
Q_OBJECT
public:
VideoDividerComboBox(QWidget* parent = nullptr) :
QComboBox(parent)
{
foreach (int d, VideoParams::kSupportedDividers) {
QString name;
if (d == 1) {
name = tr("Full");
} else {
name = tr("1/%1").arg(d);
}
this->addItem(name, d);
}
}
int GetDivider() const
{
return this->currentData().toInt();
}
void SetDivider(int d)
{
for (int i=0; i<this->count(); i++) {
if (this->itemData(i).toInt() == d) {
this->setCurrentIndex(i);
break;
}
}
}
};
OLIVE_NAMESPACE_EXIT
#endif // VIDEODIVIDERCOMBOBOX_H
+26 -3
View File
@@ -36,7 +36,8 @@ TimeBasedWidget::TimeBasedWidget(bool ruler_text_visible, bool ruler_cache_statu
viewer_node_(nullptr),
auto_max_scrollbar_(false),
points_(nullptr),
toggle_show_all_(false)
toggle_show_all_(false),
auto_set_timebase_(true)
{
ruler_ = new TimeRuler(ruler_text_visible, ruler_cache_status_visible, this);
connect(ruler_, &TimeRuler::TimeChanged, this, &TimeBasedWidget::SetTimeAndSignal);
@@ -79,6 +80,11 @@ void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node)
DisconnectNodeInternal(viewer_node_);
disconnect(viewer_node_, &ViewerOutput::LengthChanged, this, &TimeBasedWidget::UpdateMaximumScroll);
disconnect(viewer_node_, &ViewerOutput::TimebaseChanged, this, &TimeBasedWidget::SetTimebase);
if (auto_set_timebase_) {
SetTimebase(rational());
}
points_ = nullptr;
ruler()->ConnectTimelinePoints(nullptr);
@@ -95,6 +101,18 @@ void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node)
ruler()->ConnectTimelinePoints(points_);
}
if (auto_set_timebase_) {
if (!viewer_node_->video_params().time_base().isNull()) {
SetTimebase(viewer_node_->video_params().time_base());
} else if (viewer_node_->audio_params().sample_rate() > 0) {
SetTimebase(viewer_node_->audio_params().time_base());
} else {
SetTimebase(rational());
}
connect(viewer_node_, &ViewerOutput::TimebaseChanged, this, &TimeBasedWidget::SetTimebase);
}
ConnectNodeInternal(viewer_node_);
}
@@ -332,6 +350,11 @@ void TimeBasedWidget::CenterScrollOnPlayhead()
scrollbar_->setValue(qRound(TimeToScene(Timecode::timestamp_to_time(ruler_->GetTime(), timebase()))) - scrollbar_->width()/2);
}
void TimeBasedWidget::SetAutoSetTimebase(bool e)
{
auto_set_timebase_ = e;
}
void TimeBasedWidget::SetPoint(Timeline::MovementMode m, const rational& time)
{
if (!points_) {
@@ -459,12 +482,12 @@ void TimeBasedWidget::ToggleShowAll()
w = timeline_views_.first()->width();
}
w = w / 10 * 9;
toggle_show_all_old_scale_ = GetScale();
toggle_show_all_old_scroll_ = scrollbar_->value();
SetScale(w / GetConnectedNode()->GetLength().toDouble());
SetScaleFromDimensions(w, GetConnectedNode()->GetLength().toDouble());
scrollbar_->setValue(0);
// Must explicitly do this because SetScale() will automatically set this to false
+10 -2
View File
@@ -52,6 +52,8 @@ public:
void SetScaleAndCenterOnPlayhead(const double& scale);
TimeRuler* ruler() const;
public slots:
void SetTimestamp(int64_t timestamp);
@@ -89,8 +91,6 @@ public slots:
void GoToOut();
TimeRuler* ruler() const;
protected slots:
void SetTimeAndSignal(const int64_t& t);
@@ -127,6 +127,12 @@ protected slots:
*/
void CenterScrollOnPlayhead();
/**
* @brief By default, TimeBasedWidget will set the timebase to the viewer node's video timebase.
* Set this to false if you want to set your own timebase.
*/
void SetAutoSetTimebase(bool e);
signals:
void TimeChanged(const int64_t&);
@@ -170,6 +176,8 @@ private:
double toggle_show_all_old_scale_;
int toggle_show_all_old_scroll_;
bool auto_set_timebase_;
private slots:
void UpdateMaximumScroll();
@@ -27,6 +27,8 @@
OLIVE_NAMESPACE_ENTER
const int TimelineScaledObject::kCalculateDimensionsPadding = 10;
TimelineScaledObject::TimelineScaledObject() :
scale_(1.0),
min_scale_(0),
@@ -112,6 +114,21 @@ void TimelineScaledObject::SetScale(const double& scale)
ScaleChangedEvent(scale_);
}
void TimelineScaledObject::SetScaleFromDimensions(double viewport_width, double content_width)
{
SetScale(CalculateScaleFromDimensions(viewport_width, content_width));
}
double TimelineScaledObject::CalculateScaleFromDimensions(double viewport_sz, double content_sz)
{
return static_cast<double>(viewport_sz / kCalculateDimensionsPadding * (kCalculateDimensionsPadding-1)) / static_cast<double>(content_sz);
}
double TimelineScaledObject::CalculatePaddingFromDimensionScale(double viewport_sz)
{
return (viewport_sz / (kCalculateDimensionsPadding * 2));
}
TimelineScaledWidget::TimelineScaledWidget(QWidget *parent) :
QWidget(parent)
{
@@ -44,6 +44,10 @@ public:
void SetScale(const double& scale);
void SetScaleFromDimensions(double viewport_width, double content_width);
static double CalculateScaleFromDimensions(double viewport_sz, double content_sz);
static double CalculatePaddingFromDimensionScale(double viewport_sz);
protected:
double TimeToScene(const rational& time);
rational SceneToTime(const double &x, bool round = false);
@@ -67,6 +71,8 @@ private:
double max_scale_;
static const int kCalculateDimensionsPadding;
};
class TimelineScaledWidget : public QWidget, public TimelineScaledObject
+138 -131
View File
@@ -63,9 +63,9 @@ TimelineWidget::TimelineWidget(QWidget *parent) :
// Create list of TimelineViews - these MUST correspond to the ViewType enum
QSplitter* view_splitter = new QSplitter(Qt::Vertical);
view_splitter->setChildrenCollapsible(false);
vert_layout->addWidget(view_splitter);
view_splitter_ = new QSplitter(Qt::Vertical);
view_splitter_->setChildrenCollapsible(false);
vert_layout->addWidget(view_splitter_);
// Video view
views_.append(new TimelineAndTrackView(Qt::AlignBottom));
@@ -109,7 +109,7 @@ TimelineWidget::TimelineWidget(QWidget *parent) :
view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
view->SetSnapService(this);
view_splitter->addWidget(tview);
view_splitter_->addWidget(tview);
ConnectTimelineView(view);
@@ -129,7 +129,6 @@ TimelineWidget::TimelineWidget(QWidget *parent) :
connect(view, &TimelineView::DragMoved, this, &TimelineWidget::ViewDragMoved);
connect(view, &TimelineView::DragLeft, this, &TimelineWidget::ViewDragLeft);
connect(view, &TimelineView::DragDropped, this, &TimelineWidget::ViewDragDropped);
ConnectViewSelectionSignal(view);
connect(tview->splitter(), &QSplitter::splitterMoved, this, &TimelineWidget::UpdateHorizontalSplitters);
@@ -144,11 +143,13 @@ TimelineWidget::TimelineWidget(QWidget *parent) :
}
// Split viewer 50/50
view_splitter->setSizes({INT_MAX, INT_MAX});
view_splitter_->setSizes({INT_MAX, INT_MAX});
// FIXME: Magic number
SetMaximumScale(TimelineViewBase::kMaximumScale);
SetScale(90.0);
SetMaximumScale(TimelineViewBase::kMaximumScale);
SetAutoSetTimebase(false);
}
TimelineWidget::~TimelineWidget()
@@ -161,21 +162,19 @@ TimelineWidget::~TimelineWidget()
void TimelineWidget::Clear()
{
foreach (TimelineAndTrackView* tview, views_) {
DisconnectViewSelectionSignal(tview->view());
}
QList<Block*> deselected_blocks;
QMap<Block*, TimelineViewBlockItem*>::const_iterator iterator;
for (iterator=block_items_.begin(); iterator!=block_items_.end(); iterator++) {
if (iterator.value()->isSelected()) {
deselected_blocks.append(iterator.key());
}
delete iterator.value();
}
block_items_.clear();
foreach (TimelineAndTrackView* tview, views_) {
ConnectViewSelectionSignal(tview->view());
}
emit SelectionChanged(QList<Block*>());
emit BlocksDeselected(deselected_blocks);
SetTimebase(0);
}
@@ -377,24 +376,34 @@ rational TimelineWidget::GetToolTipTimebase() const
void TimelineWidget::SelectAll()
{
foreach (TimelineAndTrackView* view, views_) {
DisconnectViewSelectionSignal(view->view());
view->view()->SelectAll();
ConnectViewSelectionSignal(view->view());
QList<Block*> blocks_selected;
QMap<Block*, TimelineViewBlockItem*>::const_iterator i;
for (i=block_items_.constBegin(); i!=block_items_.end(); i++) {
if (!i.value()->isSelected()) {
i.value()->setSelected(true);
blocks_selected.append(i.key());
}
}
ViewSelectionChanged();
emit BlocksSelected(blocks_selected);
}
void TimelineWidget::DeselectAll()
{
foreach (TimelineAndTrackView* view, views_) {
DisconnectViewSelectionSignal(view->view());
view->view()->DeselectAll();
ConnectViewSelectionSignal(view->view());
QList<Block*> blocks_deselected;
QMap<Block*, TimelineViewBlockItem*>::const_iterator i;
for (i=block_items_.constBegin(); i!=block_items_.end(); i++) {
if (i.value()->isSelected()) {
i.value()->setSelected(false);
blocks_deselected.append(i.key());
}
}
emit SelectionChanged(QList<Block*>());
emit BlocksDeselected(blocks_deselected);
}
void TimelineWidget::RippleToIn()
@@ -470,67 +479,13 @@ void TimelineWidget::SplitAtPlayhead()
}
}
void TimelineWidget::DeleteSelectedInternal(const QList<Block *> &blocks,
bool transition_aware,
void TimelineWidget::ReplaceBlocksWithGaps(const QList<Block *> &blocks,
bool remove_from_graph,
QUndoCommand *command)
{
foreach (Block* b, blocks) {
TrackOutput* original_track = TrackOutput::TrackFromBlock(b);
/*if (transition_aware && b->type() == Block::kTransition) {
// Deleting transitions restores their in/out offsets to their attached blocks
TransitionBlock* transition = static_cast<TransitionBlock*>(b);
// Ripple remove transition
new TrackRippleRemoveBlockCommand(original_track,
transition,
command);
// Resize attached blocks to make up length
if (transition->connected_in_block()) {
new BlockResizeWithMediaInCommand(transition->connected_in_block(),
transition->connected_in_block()->length() + transition->in_offset(),
command);
}
if (transition->connected_out_block()) {
new BlockResizeCommand(transition->connected_out_block(),
transition->connected_out_block()->length() + transition->out_offset(),
command);
}
} else */
/*
if (b->next()) {
new TrackRippleRemoveBlockCommand(original_track, b, command);
if (b->previous() && b->previous()->type() == Block::kGap
&& b->next() && b->next()->type() == Block::kGap) {
// Both previous AND next are blocks. We'll want to merge them together.
new TrackRippleRemoveBlockCommand(original_track, b->next(), command);
} else {
// Make new gap and replace old Block with it for now
GapBlock* gap = new GapBlock();
gap->set_length_and_media_out(b->length());
new NodeAddCommand(static_cast<NodeGraph*>(b->parent()),
gap,
command);
new TrackReplaceBlockCommand(original_track,
b,
gap,
command);
}
}
*/
new TrackReplaceBlockWithGapCommand(original_track, b, command);
if (remove_from_graph) {
@@ -564,17 +519,30 @@ void TimelineWidget::DeleteSelected(bool ripple)
QUndoCommand* command = new QUndoCommand();
// Replace blocks with gaps (effectively deleting them)
DeleteSelectedInternal(blocks_to_delete, true, true, command);
QList<Block*> clips_to_delete;
QList<TransitionBlock*> transitions_to_delete;
/*
// Clean each track
foreach (const TrackReference& track, tracks_affected) {
new TrackCleanGapsCommand(GetConnectedNode()->track_list(track.type()),
track.index(),
command);
foreach (Block* b, blocks_to_delete) {
if (b->type() == Block::kClip) {
clips_to_delete.append(b);
} else if (b->type() == Block::kTransition) {
transitions_to_delete.append(static_cast<TransitionBlock*>(b));
}
}
*/
// For transitions, remove them but extend their attached blocks to fill their place
foreach (TransitionBlock* transition, transitions_to_delete) {
new TransitionRemoveCommand(TrackOutput::TrackFromBlock(transition),
transition,
command);
new NodeRemoveWithExclusiveDeps(static_cast<NodeGraph*>(GetConnectedNode()->parent()),
transition,
command);
}
// Replace clips with gaps (effectively deleting them)
ReplaceBlocksWithGaps(clips_to_delete, true, command);
// Insert ripple command now that it's all cleaned up gaps
if (ripple) {
@@ -632,12 +600,16 @@ void TimelineWidget::ToggleLinksOnSelected()
{
QList<TimelineViewBlockItem*> sel = GetSelectedBlocks();
// Prioritize unlinking
QList<Block*> blocks;
bool link = true;
foreach (TimelineViewBlockItem* item, sel) {
// Only clips can be linked
if (item->block()->type() != Block::kClip) {
continue;
}
// Prioritize unlinking, if any block has links, assume we're unlinking
if (link && item->block()->HasLinks()) {
link = false;
}
@@ -838,16 +810,6 @@ TrackOutput *TimelineWidget::GetTrackFromReference(const TrackReference &ref)
return GetConnectedNode()->track_list(ref.type())->GetTrackAt(ref.index());
}
void TimelineWidget::ConnectViewSelectionSignal(TimelineView *view)
{
connect(view, &TimelineView::SelectionChanged, this, &TimelineWidget::ViewSelectionChanged);
}
void TimelineWidget::DisconnectViewSelectionSignal(TimelineView *view)
{
disconnect(view, &TimelineView::SelectionChanged, this, &TimelineWidget::ViewSelectionChanged);
}
int TimelineWidget::GetTrackY(const TrackReference &ref)
{
return views_.at(ref.type())->view()->GetTrackY(ref.index());
@@ -893,6 +855,12 @@ void TimelineWidget::ViewMousePressed(TimelineViewMouseEvent *event)
if (GetConnectedNode() && active_tool_ != nullptr) {
active_tool_->MousePress(event);
}
if (event->GetButton() != Qt::LeftButton) {
// Suspend tool immediately if the cursor isn't the primary button
active_tool_->MouseRelease(event);
active_tool_ = nullptr;
}
}
void TimelineWidget::ViewMouseMoved(TimelineViewMouseEvent *event)
@@ -976,7 +944,14 @@ void TimelineWidget::RemoveBlock(Block *block)
disconnect(block, &Block::LabelChanged, this, &TimelineWidget::BlockUpdated);
disconnect(block, &Block::EnabledChanged, this, &TimelineWidget::BlockUpdated);
delete block_items_.take(block);
TimelineViewBlockItem* item = block_items_.take(block);
if (item->isSelected()) {
// Sending a list of one item all the time is not very efficient
emit BlocksDeselected({block});
}
delete item;
}
void TimelineWidget::AddTrack(TrackOutput *track, Timeline::TrackType type)
@@ -1012,22 +987,6 @@ void TimelineWidget::TrackIndexChanged()
}
}
void TimelineWidget::ViewSelectionChanged()
{
if (rubberband_.isVisible()) {
return;
}
QList<TimelineViewBlockItem*> selected_items = GetSelectedBlocks();
QList<Block*> selected_blocks;
foreach (TimelineViewBlockItem* item, selected_items) {
selected_blocks.append(item->block());
}
emit SelectionChanged(selected_blocks);
}
void TimelineWidget::BlockRefreshed()
{
TimelineViewRect* rect = block_items_.value(static_cast<Block*>(sender()));
@@ -1241,6 +1200,13 @@ void TimelineWidget::UpdateViewTimebases()
}
}
void TimelineWidget::SetViewBeamCursor(const TimelineCoordinate &coord)
{
foreach (TimelineAndTrackView* tview, views_) {
tview->view()->SetBeamCursor(coord);
}
}
void TimelineWidget::SetBlockLinksSelected(Block* block, bool selected)
{
TimelineViewBlockItem* link_item;
@@ -1400,11 +1366,29 @@ void TimelineWidget::HideSnaps()
}
}
QByteArray TimelineWidget::SaveSplitterState() const
{
return view_splitter_->saveState();
}
void TimelineWidget::RestoreSplitterState(const QByteArray &state)
{
view_splitter_->restoreState(state);
}
void TimelineWidget::StartRubberBandSelect(bool enable_selecting, bool select_links)
{
drag_origin_ = QCursor::pos();
rubberband_.show();
// We don't touch any blocks that are already selected. If you want these to be deselected by
// default, call DeselectAll() befoer calling StartRubberBandSelect()
foreach (TimelineViewBlockItem* block, block_items_) {
if (block->isSelected()) {
rubberband_already_selected_.append(block);
}
}
MoveRubberBandSelect(enable_selecting, select_links);
}
@@ -1420,10 +1404,11 @@ void TimelineWidget::MoveRubberBandSelect(bool enable_selecting, bool select_lin
QList<QGraphicsItem*> new_selected_list;
// Determine all items in the rubberband
foreach (TimelineAndTrackView* tview, views_) {
// Map global mouse coordinates to viewport
TimelineView* view = tview->view();
// Map global mouse coordinates to viewport
QRect mapped_rect(view->viewport()->mapFromGlobal(drag_origin_),
view->viewport()->mapFromGlobal(rubberband_now));
@@ -1433,13 +1418,25 @@ void TimelineWidget::MoveRubberBandSelect(bool enable_selecting, bool select_lin
new_selected_list.append(rubberband_items);
}
// Filter out any items that were already selected
if (!rubberband_already_selected_.isEmpty()) {
for (int i=0; i<new_selected_list.size(); i++) {
if (rubberband_already_selected_.contains(new_selected_list.at(i))) {
new_selected_list.removeAt(i);
i--;
}
}
}
foreach (QGraphicsItem* item, rubberband_now_selected_) {
item->setSelected(false);
}
foreach (QGraphicsItem* item, new_selected_list) {
TimelineViewBlockItem* block_item = dynamic_cast<TimelineViewBlockItem*>(item);
if (!block_item || block_item->block()->type() == Block::kGap) {
// Cache limit because we append to this array in this loop and don't need to process those
int lim = new_selected_list.size();
for (int i=0;i<lim;i++) {
TimelineViewBlockItem* block_item = static_cast<TimelineViewBlockItem*>(new_selected_list.at(i));
if (block_item->block()->type() == Block::kGap) {
continue;
}
@@ -1448,18 +1445,22 @@ void TimelineWidget::MoveRubberBandSelect(bool enable_selecting, bool select_lin
continue;
}
// Since new_selected_list is filtered by rubberband_already_selected_, this should certainly
// be deselected by now
block_item->setSelected(true);
if (select_links) {
// Select the block's links
Block* b = block_item->block();
SetBlockLinksSelected(b, true);
// Add its links to the list
TimelineViewBlockItem* link_item;
foreach (Block* link, b->linked_clips()) {
if ((link_item = block_items_[link]) != nullptr) {
if (!new_selected_list.contains(link_item)) {
link_item->setSelected(true);
if (!new_selected_list.contains(link_item)
&& !rubberband_already_selected_.contains(link_item)) {
new_selected_list.append(link_item);
}
}
@@ -1470,13 +1471,19 @@ void TimelineWidget::MoveRubberBandSelect(bool enable_selecting, bool select_lin
rubberband_now_selected_ = new_selected_list;
}
void TimelineWidget::EndRubberBandSelect(bool enable_selecting, bool select_links)
void TimelineWidget::EndRubberBandSelect()
{
MoveRubberBandSelect(enable_selecting, select_links);
rubberband_.hide();
rubberband_now_selected_.clear();
ViewSelectionChanged();
// Emit any blocks that were newly selected
QList<Block*> selected_blocks;
foreach (QGraphicsItem* item, rubberband_now_selected_) {
selected_blocks.append(static_cast<TimelineViewBlockItem*>(item)->block());
}
emit BlocksSelected(selected_blocks);
rubberband_now_selected_.clear();
rubberband_already_selected_.clear();
}
struct SnapData {
@@ -1571,9 +1578,9 @@ bool TimelineWidget::SnapPoint(QList<rational> start_times, rational* movement,
// Find all points at this movement
QList<rational> snap_times;
foreach (const SnapData& data, potential_snaps) {
if (data.movement == *movement) {
snap_times.append(data.time);
foreach (const SnapData& d, potential_snaps) {
if (d.movement == *movement) {
snap_times.append(d.time);
}
}
+48 -28
View File
@@ -26,6 +26,7 @@
#include <QWidget>
#include "core.h"
#include "node/block/transition/transition.h"
#include "node/output/viewer/viewer.h"
#include "snapservice.h"
#include "timeline/timelinecommon.h"
@@ -98,8 +99,14 @@ public:
virtual void HideSnaps() override;
QByteArray SaveSplitterState() const;
void RestoreSplitterState(const QByteArray& state);
signals:
void SelectionChanged(const QList<Block*>& selected_blocks);
void BlocksSelected(const QList<Block*>& selected_blocks);
void BlocksDeselected(const QList<Block*>& deselected_blocks);
protected:
virtual void resizeEvent(QResizeEvent *event) override;
@@ -185,18 +192,18 @@ private:
* Validation is the process of ensuring that whatever movements the user is making are "valid" and "legal". This
* function's validation ensures that no Ghost's in point ends up in a negative timecode.
*/
rational ValidateTimeMovement(rational movement, const QVector<TimelineViewGhostItem*> ghosts);
rational ValidateTimeMovement(rational movement);
/**
* @brief Validates Ghosts that are moving vertically (track-based)
*
* This function's validation ensures that no Ghost's track ends up in a negative (non-existent) track.
*/
int ValidateTrackMovement(int movement, const QVector<TimelineViewGhostItem*> ghosts);
int ValidateTrackMovement(int movement, const QVector<TimelineViewGhostItem *> &ghosts);
void GetGhostData(const QVector<TimelineViewGhostItem*>& ghosts, rational *earliest_point, rational *latest_point);
void GetGhostData(rational *earliest_point, rational *latest_point);
void InsertGapsAtGhostDestination(const QVector<TimelineViewGhostItem*>& ghosts, QUndoCommand* command);
void InsertGapsAtGhostDestination(QUndoCommand* command);
QList<rational> snap_points_;
@@ -209,6 +216,18 @@ private:
};
class BeamTool : public Tool
{
public:
BeamTool(TimelineWidget *parent);
virtual void HoverMove(TimelineViewMouseEvent *event) override;
protected:
TimelineCoordinate ValidatedCoordinate(TimelineCoordinate coord);
};
class PointerTool : public Tool
{
public:
@@ -226,7 +245,7 @@ private:
virtual void InitiateDrag(TimelineViewBlockItem* clicked_item,
Timeline::MovementMode trim_mode);
TimelineViewGhostItem* AddGhostFromBlock(Block *block, const TrackReference& track, Timeline::MovementMode mode);
TimelineViewGhostItem* AddGhostFromBlock(Block *block, const TrackReference& track, Timeline::MovementMode mode, bool check_if_exists = false);
TimelineViewGhostItem* AddGhostFromNull(const rational& in, const rational& out, const TrackReference& track, Timeline::MovementMode mode);
@@ -236,7 +255,7 @@ private:
* Assumes ghost->data() is a Block. Ensures no Ghost's in point becomes a negative timecode. Also ensures no
* Ghost's length becomes 0 or negative.
*/
rational ValidateInTrimming(rational movement, const QVector<TimelineViewGhostItem*> ghosts, bool prevent_overwriting);
rational ValidateInTrimming(rational movement);
/**
* @brief Validates Ghosts that are getting their out points trimmed
@@ -244,10 +263,15 @@ private:
* Assumes ghost->data() is a Block. Ensures no Ghost's in point becomes a negative timecode. Also ensures no
* Ghost's length becomes 0 or negative.
*/
rational ValidateOutTrimming(rational movement, const QVector<TimelineViewGhostItem*> ghosts, bool prevent_overwriting);
rational ValidateOutTrimming(rational movement);
virtual void ProcessDrag(const TimelineCoordinate &mouse_pos);
void InitiateDragInternal(TimelineViewBlockItem* clicked_item,
Timeline::MovementMode trim_mode,
bool dont_roll_trims,
bool allow_nongap_rolling, bool slide_instead_of_moving);
const Timeline::MovementMode& drag_movement_mode() const
{
return drag_movement_mode_;
@@ -268,11 +292,6 @@ private:
track_movement_allowed_ = e;
}
void SetTrimOverwriteAllowed(bool e)
{
trim_overwrite_allowed_ = e;
}
void SetGapTrimmingAllowed(bool e)
{
gap_trimming_allowed_ = e;
@@ -287,10 +306,15 @@ private:
const QList<TimelineViewBlockItem*>& items,
const Timeline::MovementMode& mode);
void ProcessGhostsForSliding();
void ProcessGhostsForRolling();
bool AddMovingTransitionsToClipGhost(Block *block, const TrackReference &track, Timeline::MovementMode movement, const QList<TimelineViewBlockItem *> &selected_items);
bool movement_allowed_;
bool trimming_allowed_;
bool track_movement_allowed_;
bool trim_overwrite_allowed_;
bool gap_trimming_allowed_;
bool rubberband_selecting_;
@@ -327,7 +351,7 @@ private:
};
class EditTool : public Tool
class EditTool : public BeamTool
{
public:
EditTool(TimelineWidget* parent);
@@ -337,7 +361,7 @@ private:
virtual void MouseRelease(TimelineViewMouseEvent *event) override;
};
class RazorTool : public Tool
class RazorTool : public BeamTool
{
public:
RazorTool(TimelineWidget* parent);
@@ -367,8 +391,6 @@ private:
RollingTool(TimelineWidget* parent);
protected:
virtual void FinishDrag(TimelineViewMouseEvent *event) override;
virtual void InitiateDrag(TimelineViewBlockItem* clicked_item,
Timeline::MovementMode trim_mode) override;
};
@@ -379,7 +401,6 @@ private:
SlideTool(TimelineWidget* parent);
protected:
virtual void FinishDrag(TimelineViewMouseEvent *event) override;
virtual void InitiateDrag(TimelineViewBlockItem* clicked_item,
Timeline::MovementMode trim_mode) override;
@@ -406,7 +427,7 @@ private:
};
class AddTool : public Tool
class AddTool : public BeamTool
{
public:
AddTool(TimelineWidget* parent);
@@ -439,7 +460,7 @@ private:
void InsertGapsAt(const rational& time, const rational& length, QUndoCommand* command);
void DeleteSelectedInternal(const QList<Block *>& blocks, bool transition_aware, bool remove_from_graph, QUndoCommand* command);
void ReplaceBlocksWithGaps(const QList<Block *>& blocks, bool remove_from_graph, QUndoCommand* command);
void SetBlockLinksSelected(Block *block, bool selected);
@@ -455,8 +476,9 @@ private:
void StartRubberBandSelect(bool enable_selecting, bool select_links);
void MoveRubberBandSelect(bool enable_selecting, bool select_links);
void EndRubberBandSelect(bool enable_selecting, bool select_links);
void EndRubberBandSelect();
QRubberBand rubberband_;
QList<QGraphicsItem*> rubberband_already_selected_;
QList<QGraphicsItem*> rubberband_now_selected_;
Tool* GetActiveTool();
@@ -477,10 +499,6 @@ private:
TrackOutput* GetTrackFromReference(const TrackReference& ref);
void ConnectViewSelectionSignal(TimelineView* view);
void DisconnectViewSelectionSignal(TimelineView* view);
QList<TimelineAndTrackView*> views_;
TimeSlider* timecode_label_;
@@ -489,6 +507,8 @@ private:
bool use_audio_time_units_;
QSplitter* view_splitter_;
int GetTrackY(const TrackReference& ref);
int GetTrackHeight(const TrackReference& ref);
@@ -498,6 +518,8 @@ private:
void UpdateViewTimebases();
void SetViewBeamCursor(const TimelineCoordinate& coord);
private slots:
void ViewMousePressed(TimelineViewMouseEvent* event);
void ViewMouseMoved(TimelineViewMouseEvent* event);
@@ -516,8 +538,6 @@ private slots:
void RemoveTrack(TrackOutput* track);
void TrackIndexChanged();
void ViewSelectionChanged();
/**
* @brief Slot for when a Block node changes its parameters and the graphics need to update
*
@@ -17,6 +17,7 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
widget/timelinewidget/tool/add.cpp
widget/timelinewidget/tool/beam.cpp
widget/timelinewidget/tool/edit.cpp
widget/timelinewidget/tool/import.cpp
widget/timelinewidget/tool/pointer.cpp
+10 -18
View File
@@ -22,12 +22,14 @@
#include "core.h"
#include "node/factory.h"
#include "node/generator/solid/solid.h"
#include "node/generator/text/text.h"
#include "widget/nodeview/nodeviewundo.h"
OLIVE_NAMESPACE_ENTER
TimelineWidget::AddTool::AddTool(TimelineWidget *parent) :
Tool(parent),
BeamTool(parent),
ghost_(nullptr)
{
}
@@ -44,7 +46,7 @@ void TimelineWidget::AddTool::MousePress(TimelineViewMouseEvent *event)
Timeline::TrackType add_type = Timeline::kTrackTypeNone;
switch (Core::instance()->selected_addable_object()) {
switch (Core::instance()->GetSelectedAddableObject()) {
case OLIVE_NAMESPACE::Tool::kAddableBars:
case OLIVE_NAMESPACE::Tool::kAddableSolid:
case OLIVE_NAMESPACE::Tool::kAddableTitle:
@@ -63,15 +65,7 @@ void TimelineWidget::AddTool::MousePress(TimelineViewMouseEvent *event)
if (add_type == Timeline::kTrackTypeNone
|| add_type == track.type()) {
drag_start_point_ = event->GetFrame();
if (Core::instance()->snapping()) {
rational movement;
parent()->SnapPoint({drag_start_point_}, &movement);
if (!movement.isNull()) {
drag_start_point_ += movement;
}
}
drag_start_point_ = ValidatedCoordinate(event->GetCoordinates(true)).GetFrame();
ghost_ = new TimelineViewGhostItem();
ghost_->SetIn(drag_start_point_);
@@ -95,8 +89,6 @@ void TimelineWidget::AddTool::MouseMove(TimelineViewMouseEvent *event)
void TimelineWidget::AddTool::MouseRelease(TimelineViewMouseEvent *event)
{
MouseMove(event);
const TrackReference& track = ghost_->Track();
if (ghost_) {
@@ -105,7 +97,7 @@ void TimelineWidget::AddTool::MouseRelease(TimelineViewMouseEvent *event)
ClipBlock* clip = new ClipBlock();
clip->set_length_and_media_out(ghost_->AdjustedLength());
clip->SetLabel(OLIVE_NAMESPACE::Tool::GetAddableObjectName(Core::instance()->selected_addable_object()));
clip->SetLabel(OLIVE_NAMESPACE::Tool::GetAddableObjectName(Core::instance()->GetSelectedAddableObject()));
NodeGraph* graph = static_cast<NodeGraph*>(parent()->GetConnectedNode()->parent());
@@ -119,13 +111,13 @@ void TimelineWidget::AddTool::MouseRelease(TimelineViewMouseEvent *event)
ghost_->GetAdjustedIn(),
command);
switch (Core::instance()->selected_addable_object()) {
switch (Core::instance()->GetSelectedAddableObject()) {
case OLIVE_NAMESPACE::Tool::kAddableEmpty:
// Empty, nothing to be done
break;
case OLIVE_NAMESPACE::Tool::kAddableSolid:
{
Node* solid = NodeFactory::CreateFromID(QStringLiteral("org.olivevideoeditor.Olive.solidgenerator"));
Node* solid = new SolidGenerator();
new NodeAddCommand(graph,
solid,
@@ -136,7 +128,7 @@ void TimelineWidget::AddTool::MouseRelease(TimelineViewMouseEvent *event)
}
case OLIVE_NAMESPACE::Tool::kAddableTitle:
{
Node* text = NodeFactory::CreateFromID(QStringLiteral("org.olivevideoeditor.Olive.textgenerator"));
Node* text = new TextGenerator();
new NodeAddCommand(graph,
text,
@@ -148,7 +140,7 @@ void TimelineWidget::AddTool::MouseRelease(TimelineViewMouseEvent *event)
case OLIVE_NAMESPACE::Tool::kAddableBars:
case OLIVE_NAMESPACE::Tool::kAddableTone:
// Not implemented yet
qWarning() << "Unimplemented add object:" << Core::instance()->selected_addable_object();
qWarning() << "Unimplemented add object:" << Core::instance()->GetSelectedAddableObject();
break;
case OLIVE_NAMESPACE::Tool::kAddableCount:
// Invalid value, do nothing
+48
View File
@@ -0,0 +1,48 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 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 "widget/timelinewidget/timelinewidget.h"
OLIVE_NAMESPACE_ENTER
TimelineWidget::BeamTool::BeamTool(TimelineWidget *parent) :
Tool(parent)
{
}
void TimelineWidget::BeamTool::HoverMove(TimelineViewMouseEvent *event)
{
parent()->SetViewBeamCursor(ValidatedCoordinate(event->GetCoordinates(true)));
}
TimelineCoordinate TimelineWidget::BeamTool::ValidatedCoordinate(TimelineCoordinate coord)
{
if (Core::instance()->snapping()) {
rational movement;
parent()->SnapPoint({coord.GetFrame()}, &movement);
if (!movement.isNull()) {
coord.SetFrame(coord.GetFrame() + movement);
}
}
return coord;
}
OLIVE_NAMESPACE_EXIT
+1 -1
View File
@@ -23,7 +23,7 @@
OLIVE_NAMESPACE_ENTER
TimelineWidget::EditTool::EditTool(TimelineWidget* parent) :
Tool(parent)
BeamTool(parent)
{
}
+26 -13
View File
@@ -123,14 +123,14 @@ void TimelineWidget::ImportTool::DragMove(TimelineViewMouseEvent *event)
rational time_movement = event->GetFrame() - drag_start_.GetFrame();
int track_movement = event->GetTrack().index() - drag_start_.GetTrack().index();
time_movement = ValidateTimeMovement(time_movement, parent()->ghost_items_);
time_movement = ValidateTimeMovement(time_movement);
track_movement = ValidateTrackMovement(track_movement, parent()->ghost_items_);
// If snapping is enabled, check for snap points
if (Core::instance()->snapping()) {
parent()->SnapPoint(snap_points_, &time_movement);
time_movement = ValidateTimeMovement(time_movement, parent()->ghost_items_);
time_movement = ValidateTimeMovement(time_movement);
track_movement = ValidateTrackMovement(track_movement, parent()->ghost_items_);
}
@@ -216,7 +216,9 @@ void TimelineWidget::ImportTool::FootageToGhosts(rational ghost_start, const QLi
QVector<int> track_offsets(Timeline::kTrackTypeCount);
track_offsets.fill(track_start);
QVector<TimelineViewGhostItem*> footage_ghosts;
rational footage_duration;
bool contains_image_stream = false;
quint64 enabled_streams = footage.streams();
@@ -236,37 +238,48 @@ void TimelineWidget::ImportTool::FootageToGhosts(rational ghost_start, const QLi
TimelineViewGhostItem* ghost = new TimelineViewGhostItem();
if (stream->type() == Stream::kImage) {
// Stream is essentially length-less - use config's default image length
footage_duration = Config::Current()["DefaultStillLength"].value<rational>();
// Stream is essentially length-less - we may use the default still image length in config,
// or we may use another stream's length depending on the circumstance
contains_image_stream = true;
} else {
// Rescale stream duration to timeline timebase
// Convert to rational time
if (footage.footage()->workarea()->enabled()) {
footage_duration = footage.footage()->workarea()->range().length();
footage_duration = qMax(footage_duration, footage.footage()->workarea()->range().length());
ghost->SetMediaIn(footage.footage()->workarea()->in());
} else {
int64_t stream_duration = Timecode::rescale_timestamp_ceil(stream->duration(), stream->timebase(), dest_tb);
footage_duration = Timecode::timestamp_to_time(stream_duration, dest_tb);
footage_duration = qMax(footage_duration, Timecode::timestamp_to_time(stream_duration, dest_tb));
}
}
ghost->SetIn(ghost_start);
ghost->SetOut(ghost_start + footage_duration);
ghost->SetTrack(TrackReference(track_type, track_offsets.at(track_type)));
// Increment track count for this track type
track_offsets[track_type]++;
snap_points_.append(ghost->In());
snap_points_.append(ghost->Out());
ghost->setData(TimelineViewGhostItem::kAttachedFootage, QVariant::fromValue(stream));
ghost->SetMode(Timeline::kMove);
parent()->AddGhost(ghost);
footage_ghosts.append(ghost);
}
if (contains_image_stream && footage_duration.isNull()) {
// Footage must ONLY be image streams so no duration value was found, use default in config
footage_duration = Config::Current()["DefaultStillLength"].value<rational>();
}
foreach (TimelineViewGhostItem* ghost, footage_ghosts) {
ghost->SetIn(ghost_start);
ghost->SetOut(ghost_start + footage_duration);
snap_points_.append(ghost->In());
snap_points_.append(ghost->Out());
parent()->AddGhost(ghost);
}
// Stack each ghost one after the other
ghost_start += footage_duration;
@@ -385,7 +398,7 @@ void TimelineWidget::ImportTool::DropGhosts(bool insert)
// Check if we're inserting
if (insert) {
InsertGapsAtGhostDestination(parent()->ghost_items_, command);
InsertGapsAtGhostDestination(command);
}
for (int i=0;i<parent()->ghost_items_.size();i++) {
+492 -228
View File
@@ -41,7 +41,6 @@ TimelineWidget::PointerTool::PointerTool(TimelineWidget *parent) :
movement_allowed_(true),
trimming_allowed_(true),
track_movement_allowed_(true),
trim_overwrite_allowed_(false),
gap_trimming_allowed_(false),
rubberband_selecting_(false)
{
@@ -79,16 +78,23 @@ void TimelineWidget::PointerTool::MousePress(TimelineViewMouseEvent *event)
// If this item is already selected, no further selection needs to be made
if (clicked_item_->isSelected()) {
// Collect item deselections
QList<Block*> deselected_blocks;
// If shift is held, deselect it
if (event->GetModifiers() & Qt::ShiftModifier) {
clicked_item_->setSelected(false);
deselected_blocks.append(clicked_item_->block());
// If not holding alt, deselect all links as well
if (!(event->GetModifiers() & Qt::AltModifier)) {
parent()->SetBlockLinksSelected(clicked_item_->block(), false);
deselected_blocks.append(clicked_item_->block()->linked_clips().toList());
}
}
emit parent()->BlocksDeselected(deselected_blocks);
return;
}
}
@@ -99,18 +105,29 @@ void TimelineWidget::PointerTool::MousePress(TimelineViewMouseEvent *event)
}
if (selectable_item) {
// Collect item selections
QList<Block*> selected_blocks;
// Select this item
clicked_item_->setSelected(true);
selected_blocks.append(clicked_item_->block());
// If not holding alt, select all links as well
if (!(event->GetModifiers() & Qt::AltModifier)) {
parent()->SetBlockLinksSelected(clicked_item_->block(), true);
selected_blocks.append(clicked_item_->block()->linked_clips().toList());
}
emit parent()->BlocksSelected(selected_blocks);
} else if (event->GetButton() == Qt::LeftButton) {
// Start rubberband drag
parent()->StartRubberBandSelect(true, !(event->GetModifiers() & Qt::AltModifier));
rubberband_selecting_ = true;
}
}
@@ -119,31 +136,31 @@ void TimelineWidget::PointerTool::MouseMove(TimelineViewMouseEvent *event)
if (rubberband_selecting_) {
// Process rubberband select
parent()->MoveRubberBandSelect(true, !(event->GetModifiers() & Qt::AltModifier));
return;
}
} else {
// Process drag
if (!dragging_) {
if (!dragging_) {
// Now that the cursor has moved, we will assume the intention is to drag
// Now that the cursor has moved, we will assume the intention is to drag
// Clear snap points
snap_points_.clear();
// Clear snap points
snap_points_.clear();
// If we're performing an action, we can initiate ghosts
if (drag_movement_mode_ != Timeline::kNone) {
InitiateDrag(clicked_item_, drag_movement_mode_);
}
// Set dragging to true here so no matter what, the drag isn't re-initiated until it's completed
dragging_ = true;
// If we're performing an action, we can initiate ghosts
if (drag_movement_mode_ != Timeline::kNone) {
InitiateDrag(clicked_item_, drag_movement_mode_);
}
// Set dragging to true here so no matter what, the drag isn't re-initiated until it's completed
dragging_ = true;
if (dragging_ && !parent()->ghost_items_.isEmpty()) {
}
if (dragging_ && !parent()->ghost_items_.isEmpty()) {
// We're already dragging AND we have ghosts to work with
ProcessDrag(event->GetCoordinates());
// We're already dragging AND we have ghosts to work with
ProcessDrag(event->GetCoordinates());
}
}
}
@@ -151,16 +168,18 @@ void TimelineWidget::PointerTool::MouseRelease(TimelineViewMouseEvent *event)
{
if (rubberband_selecting_) {
// Finish rubberband select
parent()->EndRubberBandSelect(true, !(event->GetModifiers() & Qt::AltModifier));
parent()->EndRubberBandSelect();
rubberband_selecting_ = false;
return;
}
if (dragging_) {
// If we were dragging, process the end of the drag
if (!parent()->ghost_items_.isEmpty()) {
FinishDrag(event);
}
// Clean up
parent()->ClearGhosts();
snap_points_.clear();
@@ -193,92 +212,236 @@ void TimelineWidget::PointerTool::HoverMove(TimelineViewMouseEvent *event)
}
}
void TimelineWidget::PointerTool::FinishDrag(TimelineViewMouseEvent *event)
void SetGhostToSlideMode(TimelineViewGhostItem* g)
{
QList<TimelineViewGhostItem*> ghosts_moving;
QList<Block*> blocks_moving;
QList<TimelineViewGhostItem*> ghosts_trimming;
QList<Block*> blocks_trimming;
g->SetCanMoveTracks(false);
g->setData(TimelineViewGhostItem::kGhostIsSliding, true);
}
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
if (!ghost->HasBeenAdjusted()) {
continue;
void TimelineWidget::PointerTool::InitiateDragInternal(TimelineViewBlockItem *clicked_item,
Timeline::MovementMode trim_mode,
bool dont_roll_trims,
bool allow_nongap_rolling,
bool slide_instead_of_moving)
{
// Get list of selected blocks
QList<TimelineViewBlockItem*> clips = parent()->GetSelectedBlocks();
if (trim_mode == Timeline::kMove) {
// Each block type has different behavior, so we determine the type of the block that was
// clicked and filter out any others.
Block::Type clicked_block_type = clicked_item->block()->type();
// Gaps are not allowed to move, and since we only allow moving one block type at a time,
// dragging a gap is a no-op
if (clicked_block_type == Block::kGap) {
return;
}
Block* b = Node::ValueToPtr<Block>(ghost->data(TimelineViewGhostItem::kAttachedBlock));
// Determine if this move is a slide, which is determined by either
bool clips_are_sliding = (slide_instead_of_moving || clicked_block_type == Block::kTransition);
if (ghost->mode() == Timeline::kMove) {
ghosts_moving.append(ghost);
blocks_moving.append(b);
} else if (Timeline::IsATrimMode(ghost->mode())) {
ghosts_trimming.append(ghost);
blocks_trimming.append(b);
}
}
if (clips_are_sliding) {
// This is a slide. What we do here is move clips within their own track, between the clips
// that they're already next to. We don't allow changing tracks or changing the order of
// blocks.
//
// For slides to be legal, we make all blocks "contiguous". This means that only one series
// of blocks can move at a time and prevents.
if (blocks_moving.isEmpty() && blocks_trimming.isEmpty()) {
// Likely means no block was adjusted, so we can skip the rest of the processing
return;
}
QHash<TrackReference, Block*> earliest_block_on_track;
QHash<TrackReference, Block*> latest_block_on_track;
// See if we're duplicated because ALT is held (only moved blocks can duplicate)
bool duplicate_clips = (!blocks_moving.isEmpty() && event->GetModifiers() & Qt::AltModifier);
bool inserting = (!blocks_moving.isEmpty() && event->GetModifiers() & Qt::ControlModifier);
foreach (TimelineViewBlockItem* item, clips) {
Block* this_block = item->block();
const TrackReference& track = item->Track();
QUndoCommand* command = new QUndoCommand();
Block* current_earliest = earliest_block_on_track.value(track, nullptr);
if (!current_earliest || this_block->in() < current_earliest->in()) {
earliest_block_on_track.insert(track, item->block());
}
for (int i=0;i<ghosts_trimming.size();i++) {
TimelineViewGhostItem* ghost = ghosts_trimming.at(i);
new BlockTrimCommand(parent()->GetTrackFromReference(ghost->GetAdjustedTrack()),
blocks_trimming.at(i),
ghost->AdjustedLength(),
ghost->mode(),
command);
}
if (!blocks_moving.isEmpty()) {
// If we're not duplicating, "remove" the clips and replace them with gaps
if (!duplicate_clips) {
parent()->DeleteSelectedInternal(blocks_moving, false, false, command);
}
if (inserting) {
// If we're inserting, ripple everything at the destination with gaps
InsertGapsAtGhostDestination(parent()->ghost_items_, command);
}
// Now we can re-add each clip
for (int i=0;i<ghosts_moving.size();i++) {
TimelineViewGhostItem* ghost = ghosts_moving.at(i);
Block* block = blocks_moving.at(i);
if (duplicate_clips) {
// Duplicate rather than move
Node* copy = block->copy();
new NodeAddCommand(static_cast<NodeGraph*>(block->parent()),
copy,
command);
new NodeCopyInputsCommand(block, copy, true, command);
// Place the copy instead of the original block
block = static_cast<Block*>(copy);
Block* current_latest = latest_block_on_track.value(track, nullptr);
if (!current_latest || this_block->out() > current_earliest->out()) {
latest_block_on_track.insert(track, item->block());
}
}
const TrackReference& track_ref = ghost->GetAdjustedTrack();
new TrackPlaceBlockCommand(parent()->GetConnectedNode()->track_list(track_ref.type()),
track_ref.index(),
block,
ghost->GetAdjustedIn(),
command);
QHash<TrackReference, Block*>::const_iterator i;
for (i=earliest_block_on_track.constBegin(); i!=earliest_block_on_track.constEnd(); i++) {
// Make a contiguous stream
const TrackReference& track = i.key();
Block* earliest = i.value();
Block* latest = latest_block_on_track.value(i.key());
// First we add the block that's out trimming, the one prior to the earliest
TimelineViewGhostItem* earliest_ghost;
if (earliest->previous()) {
earliest_ghost = AddGhostFromBlock(earliest->previous(), track, Timeline::kTrimOut);
} else {
earliest_ghost = AddGhostFromNull(earliest->in(), earliest->in(), track, Timeline::kTrimOut);
}
SetGhostToSlideMode(earliest_ghost);
// Then we add the block that's in trimming, the one after the latest
if (latest->next()) {
TimelineViewGhostItem* latest_ghost = AddGhostFromBlock(latest->next(), track, Timeline::kTrimIn);
SetGhostToSlideMode(latest_ghost);
}
// Finally, we add all of the moving blocks in between
Block* b = nullptr;
do {
// On first run-through, set to earliest only. From then on, set to the next of the last
// in the loop.
if (b) {
b = b->next();
} else {
b = earliest;
}
TimelineViewGhostItem* between_ghost = AddGhostFromBlock(b, track, Timeline::kMove);
SetGhostToSlideMode(between_ghost);
} while (b != latest);
}
} else {
// Prepare for a standard pointer move
foreach (TimelineViewBlockItem* clip_item, clips) {
Block* block = clip_item->block();
if (block->type() == Block::kGap || block->type() == Block::kTransition) {
// Gaps cannot move, and we handle transitions further down
continue;
}
// Create ghost
TimelineViewGhostItem* ghost = AddGhostFromBlock(block,
clip_item->Track(),
trim_mode);
Q_UNUSED(ghost)
// Add transitions if this has any
TransitionBlock* opening_transition = TransitionBlock::GetBlockInTransition(block);
TransitionBlock* closing_transition = TransitionBlock::GetBlockOutTransition(block);
if (opening_transition) {
TimelineViewGhostItem* ot_ghost = AddGhostFromBlock(opening_transition,
clip_item->Track(),
trim_mode);
Q_UNUSED(ot_ghost)
}
if (closing_transition) {
TimelineViewGhostItem* cl_ghost = AddGhostFromBlock(closing_transition,
clip_item->Track(),
trim_mode);
Q_UNUSED(cl_ghost)
}
}
}
// FIXME: Heavy optimization since MOST of the timeline does NOT change in this time
}
} else {
Core::instance()->undo_stack()->pushIfHasChildren(command);
// "Multi-trim" is trimming a clip on more than one track. Only the earliest (for in trimming)
// or latest (for out trimming) clip on each track can be trimmed. Therefore, it's only enabled
// if the clicked item is the earliest/latest on its track.
bool multitrim_enabled = IsClipTrimmable(clicked_item, clips, trim_mode);
// Create ghosts for trimming
foreach (TimelineViewBlockItem* clip_item, clips) {
if (clip_item != clicked_item
&& (!multitrim_enabled || !IsClipTrimmable(clip_item, clips, trim_mode))) {
// Either multitrim is disabled or this clip is NOT the earliest/latest in its track. We
// won't include it.
continue;
}
Block* block = clip_item->block();
// Create ghost for this block
TimelineViewGhostItem* ghost = AddGhostFromBlock(block, clip_item->Track(), trim_mode);
// If this side of the clip has a transition, we treat it more like a slide for that
// transition than a trim/roll
bool treat_trim_as_slide = false;
if (block->type() == Block::kClip) {
// See if this clip has a transition attached, and move it with the trim if so
TransitionBlock* connected_transition;
// Get appropriate transition for the side of the clip
if (trim_mode == Timeline::kTrimIn) {
connected_transition = TransitionBlock::GetBlockInTransition(block);
} else {
connected_transition = TransitionBlock::GetBlockOutTransition(block);
}
if (connected_transition) {
// We found a transition, we'll make this a "slide" action
TimelineViewGhostItem* transition_ghost = AddGhostFromBlock(connected_transition, clip_item->Track(), Timeline::kMove);
// This will in effect be a slide with the transition moving between two other blocks
SetGhostToSlideMode(ghost);
SetGhostToSlideMode(transition_ghost);
treat_trim_as_slide = true;
// Further processing will apply to this transition rather than the clip
block = connected_transition;
}
}
// Standard pointer trimming in reality is a "roll" edit with an adjacent gap (one that may
// or may not exist already)
if (!dont_roll_trims) {
Block* adjacent = nullptr;
// Determine which block is adjacent
if (trim_mode == Timeline::kTrimIn) {
adjacent = block->previous();
} else {
adjacent = block->next();
}
// See if we can roll the adjacent or if we'll need to create our own gap
if (block->type() != Block::kGap
&& !allow_nongap_rolling && adjacent && adjacent->type() != Block::kGap
&& !(block->type() == Block::kTransition
&& ((trim_mode == Timeline::kTrimIn && static_cast<TransitionBlock*>(block)->connected_out_block() == adjacent)
|| (trim_mode == Timeline::kTrimOut && static_cast<TransitionBlock*>(block)->connected_in_block() == adjacent)))) {
adjacent = nullptr;
}
Timeline::MovementMode flipped_mode = FlipTrimMode(trim_mode);
TimelineViewGhostItem* adjacent_ghost;
if (adjacent) {
adjacent_ghost = AddGhostFromBlock(adjacent, clip_item->Track(), flipped_mode);
} else if (trim_mode == Timeline::kTrimIn || block->next()) {
rational null_ghost_pos = (trim_mode == Timeline::kTrimIn) ? block->in() : block->out();
adjacent_ghost = AddGhostFromNull(null_ghost_pos, null_ghost_pos, clip_item->Track(), flipped_mode);
} else {
adjacent_ghost = nullptr;
}
// If we have an adjacent block (for any reason), this is a roll edit and the adjacent is
// expected to fill the remaining space (no gap needs to be created)
ghost->setData(TimelineViewGhostItem::kTrimIsARollEdit, static_cast<bool>(adjacent));
if (adjacent_ghost) {
if (treat_trim_as_slide) {
// We're sliding a transition rather than a pure trim/roll
SetGhostToSlideMode(adjacent_ghost);
} else if (block->type() == Block::kGap) {
ghost->setData(TimelineViewGhostItem::kTrimShouldBeIgnored, true);
} else {
adjacent_ghost->setData(TimelineViewGhostItem::kTrimShouldBeIgnored, true);
}
}
}
}
}
}
void TimelineWidget::PointerTool::ProcessDrag(const TimelineCoordinate &mouse_pos)
@@ -292,17 +455,17 @@ void TimelineWidget::PointerTool::ProcessDrag(const TimelineCoordinate &mouse_po
rational time_movement = mouse_pos.GetFrame() - drag_start_.GetFrame();
// Validate movement (enforce all ghosts moving in legal ways)
time_movement = ValidateTimeMovement(time_movement, parent()->ghost_items_);
time_movement = ValidateInTrimming(time_movement, parent()->ghost_items_, !trim_overwrite_allowed_);
time_movement = ValidateOutTrimming(time_movement, parent()->ghost_items_, !trim_overwrite_allowed_);
time_movement = ValidateTimeMovement(time_movement);
time_movement = ValidateInTrimming(time_movement);
time_movement = ValidateOutTrimming(time_movement);
// Perform snapping if enabled (adjusts time_movement if it's close to any potential snap points)
if (Core::instance()->snapping()) {
parent()->SnapPoint(snap_points_, &time_movement);
time_movement = ValidateTimeMovement(time_movement, parent()->ghost_items_);
time_movement = ValidateInTrimming(time_movement, parent()->ghost_items_, !trim_overwrite_allowed_);
time_movement = ValidateOutTrimming(time_movement, parent()->ghost_items_, !trim_overwrite_allowed_);
time_movement = ValidateTimeMovement(time_movement);
time_movement = ValidateInTrimming(time_movement);
time_movement = ValidateOutTrimming(time_movement);
}
// Validate ghosts that are being moved (clips from other track types do NOT get moved)
@@ -357,6 +520,165 @@ void TimelineWidget::PointerTool::ProcessDrag(const TimelineCoordinate &mouse_po
parent());
}
struct GhostBlockPair {
TimelineViewGhostItem* ghost;
Block* block;
};
void TimelineWidget::PointerTool::FinishDrag(TimelineViewMouseEvent *event)
{
QList<GhostBlockPair> blocks_moving;
QList<GhostBlockPair> blocks_sliding;
QList<GhostBlockPair> blocks_trimming;
// Sort ghosts depending on which ones are trimming, which are moving, and which are sliding
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
if (ghost->HasBeenAdjusted()) {
Block* b = Node::ValueToPtr<Block>(ghost->data(TimelineViewGhostItem::kAttachedBlock));
if (ghost->data(TimelineViewGhostItem::kGhostIsSliding).toBool()) {
blocks_sliding.append({ghost, b});
} else if (ghost->mode() == Timeline::kMove) {
blocks_moving.append({ghost, b});
} else if (Timeline::IsATrimMode(ghost->mode())) {
blocks_trimming.append({ghost, b});
}
}
}
if (blocks_moving.isEmpty()
&& blocks_trimming.isEmpty()
&& blocks_sliding.isEmpty()) {
// No blocks were adjusted, so nothing to do
return;
}
QUndoCommand* command = new QUndoCommand();
foreach (const GhostBlockPair& p, blocks_trimming) {
TimelineViewGhostItem* ghost = p.ghost;
if (!ghost->data(TimelineViewGhostItem::kTrimShouldBeIgnored).toBool()) {
// Must be an ordinary trim/roll
BlockTrimCommand* c = new BlockTrimCommand(parent()->GetTrackFromReference(ghost->GetAdjustedTrack()),
p.block,
ghost->AdjustedLength(),
ghost->mode(),
command);
c->SetTrimIsARollEdit(ghost->data(TimelineViewGhostItem::kTrimIsARollEdit).toBool());
}
}
if (!blocks_moving.isEmpty()) {
// See if we're duplicated because ALT is held (only moved blocks can duplicate)
bool duplicate_clips = (event->GetModifiers() & Qt::AltModifier);
bool inserting = (event->GetModifiers() & Qt::ControlModifier);
// If we're not duplicating, "remove" the clips and replace them with gaps
if (!duplicate_clips) {
QList<Block*> blocks_to_delete;
foreach (const GhostBlockPair& p, blocks_moving) {
blocks_to_delete.append(p.block);
}
parent()->ReplaceBlocksWithGaps(blocks_to_delete, false, command);
}
if (inserting) {
// If we're inserting, ripple everything at the destination with gaps
InsertGapsAtGhostDestination(command);
}
// Now we can re-add each clip
foreach (const GhostBlockPair& p, blocks_moving) {
Block* block = p.block;
if (duplicate_clips) {
// Duplicate rather than move
Node* copy = block->copy();
new NodeAddCommand(static_cast<NodeGraph*>(block->parent()),
copy,
command);
new NodeCopyInputsCommand(block, copy, true, command);
// Place the copy instead of the original block
block = static_cast<Block*>(copy);
}
const TrackReference& track_ref = p.ghost->GetAdjustedTrack();
new TrackPlaceBlockCommand(parent()->GetConnectedNode()->track_list(track_ref.type()),
track_ref.index(),
block,
p.ghost->GetAdjustedIn(),
command);
}
}
if (!blocks_sliding.isEmpty()) {
// Assume that the blocks are contiguous per track as set up in InitiateGhostsInternal()
// All we need to do is sort them by track and order them
QHash<TrackReference, QList<Block*> > slide_info;
QHash<TrackReference, Block*> in_adjacents;
QHash<TrackReference, Block*> out_adjacents;
rational movement;
foreach (const GhostBlockPair& p, blocks_sliding) {
const TrackReference& track = p.ghost->Track();
switch (p.ghost->mode()) {
case Timeline::kNone:
break;
case Timeline::kMove:
{
// These all should have moved uniformly, so as long as this is set, it should be fine
movement = p.ghost->InAdjustment();
QList<Block*>& blocks_on_this_track = slide_info[track];
bool inserted = false;
for (int i=0;i<blocks_on_this_track.size();i++) {
if (blocks_on_this_track.at(i)->in() > p.block->in()) {
blocks_on_this_track.insert(i, p.block);
inserted = true;
break;
}
}
if (!inserted) {
blocks_on_this_track.append(p.block);
}
break;
}
case Timeline::kTrimIn:
out_adjacents.insert(track, p.block);
break;
case Timeline::kTrimOut:
in_adjacents.insert(track, p.block);
break;
}
}
if (!movement.isNull()) {
QHash<TrackReference, QList<Block*> >::const_iterator i;
for (i=slide_info.constBegin(); i!=slide_info.constEnd(); i++) {
new TrackSlideCommand(parent()->GetTrackFromReference(i.key()),
i.value(),
in_adjacents.value(i.key()),
out_adjacents.value(i.key()),
movement,
command);
}
}
}
Core::instance()->undo_stack()->pushIfHasChildren(command);
}
Timeline::MovementMode TimelineWidget::PointerTool::IsCursorInTrimHandle(TimelineViewBlockItem *block, qreal cursor_x)
{
double kTrimHandle = QFontMetricsWidth(parent()->fontMetrics(), "H");
@@ -378,67 +700,32 @@ Timeline::MovementMode TimelineWidget::PointerTool::IsCursorInTrimHandle(Timelin
void TimelineWidget::PointerTool::InitiateDrag(TimelineViewBlockItem* clicked_item,
Timeline::MovementMode trim_mode)
{
// Get list of selected blocks
QList<TimelineViewBlockItem*> clips = parent()->GetSelectedBlocks();
if (trim_mode == Timeline::kMove) {
// Create ghosts for moving
foreach (TimelineViewBlockItem* clip_item, clips) {
// Gaps are not allowed to move, so we ignore those here
if (clip_item->block()->type() == Block::kGap) {
continue;
}
AddGhostFromBlock(clip_item->block(), clip_item->Track(), trim_mode);
}
} else {
// "Multi-trim" is trimming a clip on more than one track. Only the earliest (for in trimming)
// or latest (for out trimming) clip on each track can be trimmed. Therefore, it's only enabled
// if the clicked item is the earliest/latest on its track.
bool multitrim_enabled = IsClipTrimmable(clicked_item, clips, trim_mode);
// Create ghosts for trimming
foreach (TimelineViewBlockItem* clip_item, clips) {
if (clip_item != clicked_item
&& (!multitrim_enabled || !IsClipTrimmable(clip_item, clips, trim_mode))) {
// Either multitrim is disabled or this clip is NOT the earliest/latest in its track. We
// won't include it.
continue;
}
Block* block = clip_item->block();
Timeline::MovementMode block_mode = trim_mode;
// Some tools interpret "gap trimming" as equivalent to resizing the adjacent block. In that
// scenario, we include the adjacent block instead.
if (block->type() == Block::kGap && !gap_trimming_allowed_) {
block = (trim_mode == Timeline::kTrimIn) ? block->previous() : block->next();
block_mode = FlipTrimMode(trim_mode);
// If there's no adjacent block, do nothing here
if (!block) {
continue;
}
}
// Create ghost for this block
AddGhostFromBlock(block, clip_item->Track(), block_mode);
}
}
InitiateDragInternal(clicked_item, trim_mode, false, false, false);
}
TimelineViewGhostItem* TimelineWidget::PointerTool::AddGhostFromBlock(Block* block, const TrackReference& track, Timeline::MovementMode mode)
//#define HIDE_GAP_GHOSTS
TimelineViewGhostItem* TimelineWidget::PointerTool::AddGhostFromBlock(Block* block, const TrackReference& track, Timeline::MovementMode mode, bool check_if_exists)
{
if (check_if_exists) {
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
if (Node::ValueToPtr<Block>(ghost->data(TimelineViewGhostItem::kAttachedBlock)) == block) {
return ghost;
}
}
}
TimelineViewGhostItem* ghost = TimelineViewGhostItem::FromBlock(block,
track,
parent()->GetTrackY(track),
parent()->GetTrackHeight(track));
#ifdef HIDE_GAP_GHOSTS
if (block->type() == Block::kGap) {
ghost->SetInvisible(true);
}
#endif
AddGhostInternal(ghost, mode);
return ghost;
@@ -453,6 +740,10 @@ TimelineViewGhostItem* TimelineWidget::PointerTool::AddGhostFromNull(const ratio
ghost->SetTrack(track);
ghost->SetYCoords(parent()->GetTrackY(track), parent()->GetTrackHeight(track));
#ifdef HIDE_GAP_GHOSTS
ghost->SetInvisible(true);
#endif
AddGhostInternal(ghost, mode);
return ghost;
@@ -497,63 +788,70 @@ bool TimelineWidget::PointerTool::IsClipTrimmable(TimelineViewBlockItem* clip,
return true;
}
rational GetEarliestPointForClip(Block* block)
bool TimelineWidget::PointerTool::AddMovingTransitionsToClipGhost(Block* block,
const TrackReference& track,
Timeline::MovementMode movement,
const QList<TimelineViewBlockItem*>& selected_items)
{
return qMax(rational(0), block->in() - block->media_in());
// Assume block is a clip and see if it has any transitions
TransitionBlock* transitions[2];
if (movement == Timeline::kMove || movement == Timeline::kTrimOut) {
transitions[0] = TransitionBlock::GetBlockOutTransition(block);
} else {
transitions[0] = nullptr;
}
if (movement == Timeline::kMove || movement == Timeline::kTrimIn) {
transitions[1] = TransitionBlock::GetBlockInTransition(block);
} else {
transitions[1] = nullptr;
}
bool ret = false;
for (int i=0;i<2;i++) {
if (!transitions[i]) {
continue;
}
bool found = false;
foreach (TimelineViewBlockItem* item, selected_items) {
if (item->block() == transitions[i]) {
// Do nothing
found = true;
break;
}
}
if (!found) {
TimelineViewGhostItem* transition_ghost = AddGhostFromBlock(transitions[i], track,
Timeline::kMove);
Q_UNUSED(transition_ghost)
ret = true;
}
}
return ret;
}
rational TimelineWidget::PointerTool::ValidateInTrimming(rational movement,
const QVector<TimelineViewGhostItem *> ghosts,
bool prevent_overwriting)
rational TimelineWidget::PointerTool::ValidateInTrimming(rational movement)
{
foreach (TimelineViewGhostItem* ghost, ghosts) {
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
if (ghost->mode() != Timeline::kTrimIn) {
continue;
}
Block* block = Node::ValueToPtr<Block>(ghost->data(TimelineViewGhostItem::kAttachedBlock));
rational earliest_in = RATIONAL_MIN;
rational latest_in = ghost->Out();
if (block->type() == Block::kTransition) {
// For transitions, validate with the attached block
TransitionBlock* transition = static_cast<TransitionBlock*>(block);
if (transition->connected_in_block() && transition->connected_out_block()) {
// Here, we try to get the latest earliest point for both the in and out blocks, we do in here and out will
// be calculated later
earliest_in = GetEarliestPointForClip(transition->connected_in_block());
// We set the block to the out block since that will be before the in block and will be the one we use to
// prevent overwriting since we're trimming the in side of this transition
block = transition->connected_out_block();
latest_in = transition->in() + transition->out_offset();
} else {
// Use whatever block is attached
block = transition->connected_in_block() ? transition->connected_in_block() : transition->connected_out_block();
}
}
earliest_in = qMax(earliest_in, GetEarliestPointForClip(block));
if (!ghost->CanHaveZeroLength()) {
latest_in -= parent()->timebase();
}
if (prevent_overwriting) {
// Look for a Block in the way
Block* prev = block->previous();
while (prev != nullptr) {
if (prev->type() == Block::kClip) {
earliest_in = qMax(earliest_in, prev->out());
break;
}
prev = prev->previous();
}
}
// Clamp adjusted value between the earliest and latest values
rational adjusted = ghost->In() + movement;
rational clamped = clamp(adjusted, earliest_in, latest_in);
@@ -566,17 +864,13 @@ rational TimelineWidget::PointerTool::ValidateInTrimming(rational movement,
return movement;
}
rational TimelineWidget::PointerTool::ValidateOutTrimming(rational movement,
const QVector<TimelineViewGhostItem *> ghosts,
bool prevent_overwriting)
rational TimelineWidget::PointerTool::ValidateOutTrimming(rational movement)
{
foreach (TimelineViewGhostItem* ghost, ghosts) {
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
if (ghost->mode() != Timeline::kTrimOut) {
continue;
}
Block* block = Node::ValueToPtr<Block>(ghost->data(TimelineViewGhostItem::kAttachedBlock));
// Determine earliest and latest out points
rational earliest_out = ghost->In();
@@ -586,36 +880,6 @@ rational TimelineWidget::PointerTool::ValidateOutTrimming(rational movement,
rational latest_out = RATIONAL_MAX;
if (block->type() == Block::kTransition) {
// For transitions, validate with the attached block
TransitionBlock* transition = static_cast<TransitionBlock*>(block);
if (transition->connected_in_block() && transition->connected_out_block()) {
// We set the block to the out block since that will be before the in block and will be the one we use to
// prevent overwriting since we're trimming the in side of this transition
// FIXME: At some point we may add some better logic to `latest_out` akin to the logic in ValidateInTrimming
// which is why this hasn't yet been collapsed into the ternary below.
block = transition->connected_in_block();
earliest_out = transition->out() - transition->in_offset();
} else {
block = transition->connected_in_block() ? transition->connected_in_block() : transition->connected_out_block();
}
}
if (prevent_overwriting) {
// Determine if there's a block in the way
Block* next = block->next();
while (next != nullptr) {
if (next->type() == Block::kClip) {
latest_out = qMin(latest_out, next->in());
break;
}
next = next->next();
}
}
// Clamp adjusted value between the earliest and latest values
rational adjusted = ghost->Out() + movement;
rational clamped = clamp(adjusted, earliest_out, latest_out);
+2 -2
View File
@@ -23,7 +23,7 @@
OLIVE_NAMESPACE_ENTER
TimelineWidget::RazorTool::RazorTool(TimelineWidget* parent) :
Tool(parent)
BeamTool(parent)
{
}
@@ -37,7 +37,7 @@ void TimelineWidget::RazorTool::MousePress(TimelineViewMouseEvent *event)
void TimelineWidget::RazorTool::MouseMove(TimelineViewMouseEvent *event)
{
if (!dragging_) {
drag_start_ = event->GetCoordinates(true);
drag_start_ = ValidatedCoordinate(event->GetCoordinates(true));
dragging_ = true;
}
+12 -8
View File
@@ -29,14 +29,13 @@ TimelineWidget::RippleTool::RippleTool(TimelineWidget* parent) :
PointerTool(parent)
{
SetMovementAllowed(false);
SetTrimOverwriteAllowed(true);
SetGapTrimmingAllowed(true);
}
void TimelineWidget::RippleTool::InitiateDrag(TimelineViewBlockItem *clicked_item,
Timeline::MovementMode trim_mode)
{
PointerTool::InitiateDrag(clicked_item, trim_mode);
InitiateDragInternal(clicked_item, trim_mode, true, true, false);
if (parent()->ghost_items_.isEmpty()) {
return;
@@ -87,13 +86,18 @@ void TimelineWidget::RippleTool::InitiateDrag(TimelineViewBlockItem *clicked_ite
if (block_before_ripple->type() == Block::kGap) {
// If this Block is already a Gap, ghost it now
ghost = AddGhostFromBlock(block_before_ripple, track_ref, trim_mode);
} else {
// If there's no gap here, we'll need to create one
ghost = AddGhostFromNull(block_before_ripple->out(), block_before_ripple->out(), track_ref, trim_mode);
ghost->setData(TimelineViewGhostItem::kReferenceBlock, Node::PtrToValue(block_before_ripple));
}
} else if (block_before_ripple->next()) {
// Assuming this block is NOT at the end of the track (i.e. next != null)
ghost->SetInvisible(true);
// We're going to create a gap after it. If next is a gap, we can just use that
if (block_before_ripple->next()->type() == Block::kGap) {
ghost = AddGhostFromBlock(block_before_ripple->next(), track_ref, trim_mode);
} else {
// If next is NOT a gap, we'll need to create one, for which we'll use a null ghost
ghost = AddGhostFromNull(block_before_ripple->out(), block_before_ripple->out(), track_ref, trim_mode);
ghost->setData(TimelineViewGhostItem::kReferenceBlock, Node::PtrToValue(block_before_ripple));
}
}
}
}
}
+1 -35
View File
@@ -29,47 +29,13 @@ TimelineWidget::RollingTool::RollingTool(TimelineWidget* parent) :
PointerTool(parent)
{
SetMovementAllowed(false);
SetTrimOverwriteAllowed(true);
SetGapTrimmingAllowed(true);
}
void TimelineWidget::RollingTool::InitiateDrag(TimelineViewBlockItem *clicked_item,
Timeline::MovementMode trim_mode)
{
PointerTool::InitiateDrag(clicked_item, trim_mode);
// For each ghost, we make an equivalent Ghost on the next/previous block
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
Block* ghost_block = Node::ValueToPtr<Block>(ghost->data(TimelineViewGhostItem::kAttachedBlock));
if (ghost->mode() == Timeline::kTrimIn && ghost_block->previous()) {
// Add an extra Ghost for the previous block
AddGhostFromBlock(ghost_block->previous(), ghost->Track(), Timeline::kTrimOut);
} else if (ghost->mode() == Timeline::kTrimOut && ghost_block->next()) {
AddGhostFromBlock(ghost_block->next(), ghost->Track(), Timeline::kTrimIn);
}
}
}
void TimelineWidget::RollingTool::FinishDrag(TimelineViewMouseEvent *event)
{
QUndoCommand* command = new QUndoCommand();
// Find earliest point to ripple around
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
if (ghost->mode() == drag_movement_mode()) {
Block* b = Node::ValueToPtr<Block>(ghost->data(TimelineViewGhostItem::kAttachedBlock));
BlockTrimCommand* c = new BlockTrimCommand(parent()->GetTrackFromReference(ghost->Track()),
b,
ghost->AdjustedLength(),
drag_movement_mode(),
command);
c->SetAllowNonGapTrimming(true);
}
}
Core::instance()->undo_stack()->pushIfHasChildren(command);
InitiateDragInternal(clicked_item, trim_mode, false, true, false);
}
OLIVE_NAMESPACE_EXIT
+1 -93
View File
@@ -30,105 +30,13 @@ TimelineWidget::SlideTool::SlideTool(TimelineWidget* parent) :
{
SetTrimmingAllowed(false);
SetTrackMovementAllowed(false);
SetTrimOverwriteAllowed(true);
SetGapTrimmingAllowed(true);
}
struct TrackBlockListPair {
TrackReference track;
QList<Block*> blocks;
};
void TimelineWidget::SlideTool::InitiateDrag(TimelineViewBlockItem *clicked_item,
Timeline::MovementMode trim_mode)
{
PointerTool::InitiateDrag(clicked_item, trim_mode);
// Sort blocks into tracks
QList<TrackBlockListPair> blocks_per_track;
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
Block* b = Node::ValueToPtr<Block>(ghost->data(TimelineViewGhostItem::kAttachedBlock));
bool found = false;
for (int i=0;i<blocks_per_track.size();i++) {
if (blocks_per_track.at(i).track == ghost->Track()) {
blocks_per_track[i].blocks.append(b);
found = true;
break;
}
}
if (!found) {
blocks_per_track.append({ghost->Track(), {b}});
}
}
// Make contiguous runs of blocks per each track
foreach (const TrackBlockListPair& p, blocks_per_track) {
// Blocks must be merged if any are non-adjacent
const TrackReference& track = p.track;
const QList<Block*>& blocks = p.blocks;
Block* earliest_block = blocks.first();
Block* latest_block = blocks.first();
// Find the earliest and latest selected blocks
for (int j=1;j<blocks.size();j++) {
Block* compare = blocks.at(j);
if (compare->in() < earliest_block->in()) {
earliest_block = compare;
}
if (compare->in() > latest_block->in()) {
latest_block = compare;
}
}
// Add any blocks between these blocks that aren't already in the list
if (earliest_block != latest_block) {
Block* b = earliest_block;
while ((b = b->next()) != latest_block) {
if (!blocks.contains(b)) {
AddGhostFromBlock(b, track, Timeline::kMove);
}
}
}
// Add surrounding blocks that will be trimming instead of moving
if (earliest_block->previous()) {
AddGhostFromBlock(earliest_block->previous(), track, Timeline::kTrimOut);
}
if (latest_block->next()) {
AddGhostFromBlock(latest_block->next(), track, Timeline::kTrimIn);
}
}
}
void TimelineWidget::SlideTool::FinishDrag(TimelineViewMouseEvent *event)
{
Q_UNUSED(event)
QVector<TrackSlideCommand::BlockSlideInfo> info;
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
if (!ghost->HasBeenAdjusted()) {
continue;
}
Block* b = Node::ValueToPtr<Block>(ghost->data(TimelineViewGhostItem::kAttachedBlock));
info.append({parent()->GetTrackFromReference(ghost->Track()),
b,
ghost->mode(),
ghost->mode() == Timeline::kMove ? ghost->GetAdjustedIn() : ghost->AdjustedLength(),
ghost->mode() == Timeline::kMove ? ghost->In() : ghost->Length()});
}
if (!info.isEmpty()) {
Core::instance()->undo_stack()->push(new TrackSlideCommand(info));
}
InitiateDragInternal(clicked_item, trim_mode, false, true, true);
}
OLIVE_NAMESPACE_EXIT
+7 -24
View File
@@ -73,30 +73,13 @@ TimelineViewBlockItem *TimelineWidget::Tool::GetItemAtScenePos(const TimelineCoo
return nullptr;
}
rational TimelineWidget::Tool::ValidateTimeMovement(rational movement, const QVector<TimelineViewGhostItem *> ghosts)
rational TimelineWidget::Tool::ValidateTimeMovement(rational movement)
{
foreach (TimelineViewGhostItem* ghost, ghosts) {
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
if (ghost->mode() != Timeline::kMove) {
continue;
}
Block* block = Node::ValueToPtr<Block>(ghost->data(TimelineViewGhostItem::kAttachedBlock));
if (block && block->type() == Block::kTransition) {
TransitionBlock* transition = static_cast<TransitionBlock*>(block);
// Dual transitions are only allowed to move so that neither of their offsets are < 0
if (transition->connected_in_block() && transition->connected_out_block()) {
if (movement > transition->out_offset()) {
movement = transition->out_offset();
}
if (movement < -transition->in_offset()) {
movement = -transition->in_offset();
}
}
}
// Prevents any ghosts from going below 0:00:00 time
if (ghost->In() + movement < 0) {
movement = -ghost->In();
@@ -106,7 +89,7 @@ rational TimelineWidget::Tool::ValidateTimeMovement(rational movement, const QVe
return movement;
}
int TimelineWidget::Tool::ValidateTrackMovement(int movement, const QVector<TimelineViewGhostItem *> ghosts)
int TimelineWidget::Tool::ValidateTrackMovement(int movement, const QVector<TimelineViewGhostItem*>& ghosts)
{
foreach (TimelineViewGhostItem* ghost, ghosts) {
if (ghost->mode() != Timeline::kMove) {
@@ -115,7 +98,7 @@ int TimelineWidget::Tool::ValidateTrackMovement(int movement, const QVector<Time
if (!ghost->CanMoveTracks()) {
movement = 0;
return 0;
} else if (ghost->Track().index() + movement < 0) {
@@ -128,7 +111,7 @@ int TimelineWidget::Tool::ValidateTrackMovement(int movement, const QVector<Time
return movement;
}
void TimelineWidget::Tool::GetGhostData(const QVector<TimelineViewGhostItem *> &ghosts, rational *earliest_point, rational *latest_point)
void TimelineWidget::Tool::GetGhostData(rational *earliest_point, rational *latest_point)
{
rational ep = RATIONAL_MAX;
rational lp = RATIONAL_MIN;
@@ -147,11 +130,11 @@ void TimelineWidget::Tool::GetGhostData(const QVector<TimelineViewGhostItem *> &
}
}
void TimelineWidget::Tool::InsertGapsAtGhostDestination(const QVector<TimelineViewGhostItem *> &ghosts, QUndoCommand *command)
void TimelineWidget::Tool::InsertGapsAtGhostDestination(QUndoCommand *command)
{
rational earliest_point, latest_point;
GetGhostData(ghosts, &earliest_point, &latest_point);
GetGhostData(&earliest_point, &latest_point);
parent()->InsertGapsAt(earliest_point, latest_point - earliest_point, command);
}
@@ -20,6 +20,7 @@
#include "widget/timelinewidget/timelinewidget.h"
#include "node/block/transition/crossdissolve/crossdissolvetransition.h"
#include "node/block/transition/transition.h"
#include "node/factory.h"
#include "widget/nodeview/nodeviewundo.h"
@@ -105,13 +106,18 @@ void TimelineWidget::TransitionTool::MouseMove(TimelineViewMouseEvent *event)
void TimelineWidget::TransitionTool::MouseRelease(TimelineViewMouseEvent *event)
{
MouseMove(event);
const TrackReference& track = ghost_->Track();
if (ghost_) {
if (!ghost_->AdjustedLength().isNull()) {
TransitionBlock* transition = static_cast<TransitionBlock*>(NodeFactory::CreateFromID("org.olivevideoeditor.Olive.crossdissolve"));
TransitionBlock* transition;
if (Core::instance()->GetSelectedTransition().isEmpty()) {
// Fallback if the user hasn't selected one yet
transition = new CrossDissolveTransition();
} else {
transition = static_cast<TransitionBlock*>(NodeFactory::CreateFromID(Core::instance()->GetSelectedTransition()));
}
QUndoCommand* command = new QUndoCommand();
+1 -1
View File
@@ -51,7 +51,7 @@ void TimelineWidget::ZoomTool::MouseRelease(TimelineViewMouseEvent *event)
// Zoom into the rubberband selection
QRect screen_coords = parent()->rubberband_.geometry();
parent()->EndRubberBandSelect(false, false);
parent()->EndRubberBandSelect();
TimelineView* reference_view = parent()->views_.first()->view();
QPointF scene_topleft = reference_view->mapToScene(reference_view->mapFrom(parent(), screen_coords.topLeft()));
+227 -101
View File
@@ -202,7 +202,7 @@ void TrackRippleRemoveAreaCommand::redo_internal()
}
}
track_->BlockInvalidateCache();
track_->BeginOperation();
// If we picked up a block to splice
if (splice_) {
@@ -273,7 +273,7 @@ void TrackRippleRemoveAreaCommand::redo_internal()
}
}
track_->UnblockInvalidateCache();
track_->EndOperation();
track_->InvalidateCache(TimeRange(in_, insert_ ? out_ : RATIONAL_MAX),
track_->block_input(),
@@ -282,7 +282,7 @@ void TrackRippleRemoveAreaCommand::redo_internal()
void TrackRippleRemoveAreaCommand::undo_internal()
{
track_->BlockInvalidateCache();
track_->BeginOperation();
// If we were given a block to insert, insert it here
if (insert_ != nullptr) {
@@ -330,7 +330,7 @@ void TrackRippleRemoveAreaCommand::undo_internal()
}
track_->UnblockInvalidateCache();
track_->EndOperation();
track_->InvalidateCache(TimeRange(in_, insert_ ? out_ : RATIONAL_MAX), track_->block_input(), track_->block_input());
}
@@ -416,12 +416,9 @@ BlockSplitCommand::BlockSplitCommand(TrackOutput* track, Block *block, rational
new_block_->setParent(&memory_manager_);
// Determine if the block outputs to an "out" transition
foreach (NodeEdgePtr edge, block_->output()->edges()) {
if (edge->input()->parentNode()->IsBlock()
&& static_cast<Block*>(edge->input()->parentNode())->type() == Block::kTransition
&& edge->input() == static_cast<TransitionBlock*>(edge->input()->parentNode())->out_block_input()) {
transitions_to_move_.append(edge->input());
}
TransitionBlock* transition = TransitionBlock::GetBlockOutTransition(block_);
if (transition) {
transitions_to_move_.append(transition->out_block_input());
}
}
@@ -432,7 +429,7 @@ Project *BlockSplitCommand::GetRelevantProject() const
void BlockSplitCommand::redo_internal()
{
track_->BlockInvalidateCache();
track_->BeginOperation();
static_cast<NodeGraph*>(block_->parent())->AddNode(new_block_);
Node::CopyInputs(block_, new_block_);
@@ -450,12 +447,12 @@ void BlockSplitCommand::redo_internal()
NodeParam::ConnectEdge(new_block_->output(), transition);
}
track_->UnblockInvalidateCache();
track_->EndOperation();
}
void BlockSplitCommand::undo_internal()
{
track_->BlockInvalidateCache();
track_->BeginOperation();
block_->set_length_and_media_out(old_length_);
track_->RippleRemoveBlock(new_block_);
@@ -467,7 +464,7 @@ void BlockSplitCommand::undo_internal()
NodeParam::ConnectEdge(block_->output(), transition);
}
track_->UnblockInvalidateCache();
track_->EndOperation();
}
Block *BlockSplitCommand::new_block()
@@ -845,7 +842,7 @@ BlockTrimCommand::BlockTrimCommand(TrackOutput* track, Block *block, rational ne
adjacent_(nullptr),
we_created_adjacent_(false),
we_deleted_adjacent_(false),
allow_nongap_trimming_(false)
trim_is_a_roll_edit_(false)
{
}
@@ -856,7 +853,7 @@ Project *BlockTrimCommand::GetRelevantProject() const
void BlockTrimCommand::redo_internal()
{
track_->BlockInvalidateCache();
track_->BeginOperation();
// Will be POSITIVE if trimming shorter and NEGATIVE if trimming longer
rational trim_diff = old_length_ - new_length_;
@@ -875,7 +872,7 @@ void BlockTrimCommand::redo_internal()
if (trim_diff > rational()) {
// If trimming SHORTER, we'll need to create/modify a gap
if (adjacent_ && (adjacent_->type() == Block::kGap || allow_nongap_trimming_)) {
if (adjacent_ && (adjacent_->type() == Block::kGap || trim_is_a_roll_edit_)) {
// A gap (or equivalent) exists, simply increase the size of it
if (mode_ == Timeline::kTrimIn) {
@@ -923,14 +920,26 @@ void BlockTrimCommand::redo_internal()
}
}
track_->UnblockInvalidateCache();
track_->EndOperation();
if (block_->type() == Block::kTransition) {
// Whole transition needs to be invalidated
invalidate_range = TimeRange(block_->in(), block_->out());
}
track_->InvalidateCache(invalidate_range, track_->block_input(), track_->block_input());
}
void BlockTrimCommand::undo_internal()
{
track_->BlockInvalidateCache();
TimeRange invalidate_range;
if (block_->type() == Block::kTransition) {
// Whole transition needs to be invalidated
invalidate_range = TimeRange(block_->in(), block_->out());
}
track_->BeginOperation();
// Will be POSITIVE if trimming shorter and NEGATIVE if trimming longer
rational trim_diff = old_length_ - new_length_;
@@ -969,17 +978,21 @@ void BlockTrimCommand::undo_internal()
}
}
TimeRange invalidate_range;
if (mode_ == Timeline::kTrimIn) {
block_->set_length_and_media_in(old_length_);
invalidate_range = TimeRange(block_->in(), block_->in() + trim_diff);
if (block_->type() != Block::kTransition) {
invalidate_range = TimeRange(block_->in(), block_->in() + trim_diff);
}
} else {
block_->set_length_and_media_out(old_length_);
invalidate_range = TimeRange(block_->out(), block_->out() - trim_diff);
if (block_->type() != Block::kTransition) {
invalidate_range = TimeRange(block_->out(), block_->out() - trim_diff);
}
}
track_->UnblockInvalidateCache();
track_->EndOperation();
track_->InvalidateCache(invalidate_range, track_->block_input(), track_->block_input());
}
@@ -1003,7 +1016,7 @@ void TrackReplaceBlockWithGapCommand::redo_internal()
{
TimeRange invalidate_range;
track_->BlockInvalidateCache();
track_->BeginOperation();
// If the block has no next, it's at the end of the track and there's no need to create a gap
if (block_->next()) {
@@ -1062,7 +1075,7 @@ void TrackReplaceBlockWithGapCommand::redo_internal()
invalidate_range = TimeRange(earliest_change, RATIONAL_MAX);
}
track_->UnblockInvalidateCache();
track_->EndOperation();
track_->InvalidateCache(invalidate_range, track_->block_input(), track_->block_input());
}
@@ -1071,7 +1084,7 @@ void TrackReplaceBlockWithGapCommand::undo_internal()
{
TimeRange invalidate_range;
track_->BlockInvalidateCache();
track_->BeginOperation();
if (gap_) {
@@ -1116,20 +1129,27 @@ void TrackReplaceBlockWithGapCommand::undo_internal()
merged_gap_ = nullptr;
track_->UnblockInvalidateCache();
track_->EndOperation();
track_->InvalidateCache(invalidate_range, track_->block_input(), track_->block_input());
}
TrackSlideCommand::TrackSlideCommand(const QVector<TrackSlideCommand::BlockSlideInfo> &blocks, QUndoCommand *parent) :
TrackSlideCommand::TrackSlideCommand(TrackOutput* track, const QList<Block*>& moving_blocks, Block *in_adjacent, Block *out_adjacent, const rational& movement, QUndoCommand* parent) :
UndoCommand(parent),
blocks_(blocks)
track_(track),
blocks_(moving_blocks),
movement_(movement),
we_created_in_adjacent_(false),
in_adjacent_(in_adjacent),
we_created_out_adjacent_(false),
out_adjacent_(out_adjacent)
{
Q_ASSERT(!movement_.isNull());
}
Project *TrackSlideCommand::GetRelevantProject() const
{
return static_cast<Sequence*>(blocks_.first().track->parent())->project();
return static_cast<Sequence*>(track_->parent())->project();
}
void TrackSlideCommand::redo_internal()
@@ -1144,71 +1164,98 @@ void TrackSlideCommand::undo_internal()
void TrackSlideCommand::slide_internal(bool undo)
{
QMap<TrackOutput*, TimeRangeList> invalidate_ranges;
// Make sure all movement blocks' old positions are invalidated
foreach (const BlockSlideInfo& info, blocks_) {
if (info.mode == Timeline::kMove) {
invalidate_ranges[info.track].InsertTimeRange(TimeRange(info.block->in(),
info.block->out()));
}
}
TimeRange invalidate_range(blocks_.first()->in(), blocks_.last()->out());
// Perform trims
foreach (const BlockSlideInfo& info, blocks_) {
info.track->BlockInvalidateCache();
if (info.mode == Timeline::kTrimIn || info.mode == Timeline::kTrimOut) {
rational new_len = undo ? info.old_time : info.new_time;
if (info.mode == Timeline::kTrimIn) {
info.block->set_length_and_media_in(new_len);
} else {
info.block->set_length_and_media_out(new_len);
}
} else if (!undo && info.mode == Timeline::kMove && !info.block->previous()) {
// If this is a moving block and there was nothing before it to offset its time correctly,
// insert a gap here
GapBlock* gap = new GapBlock();
gap->set_length_and_media_out(info.new_time);
static_cast<NodeGraph*>(info.block->parent())->AddNode(gap);
info.track->PrependBlock(gap);
added_gaps_.append(gap);
}
info.track->UnblockInvalidateCache();
}
track_->BeginOperation();
if (undo) {
// If undoing, remove added gaps
foreach (GapBlock* gap, added_gaps_) {
TrackOutput* track = TrackOutput::TrackFromBlock(gap);
track->BlockInvalidateCache();
// Undo code
if (we_created_in_adjacent_) {
// This is a gap we made, we can just delete it entirely
track_->RippleRemoveBlock(in_adjacent_);
delete TakeNodeFromParentGraph(in_adjacent_);
we_created_in_adjacent_ = false;
in_adjacent_ = nullptr;
} else if (in_adjacent_->parent() == &memory_manager_) {
// This is a gap we removed, we can re-insert it now
static_cast<NodeGraph*>(track_->parent())->AddNode(in_adjacent_);
track_->InsertBlockBefore(in_adjacent_, blocks_.first());
} else {
// We must have just resized this block
in_adjacent_->set_length_and_media_out(in_adjacent_->length() - movement_);
}
track->RippleRemoveBlock(gap);
delete TakeNodeFromParentGraph(gap);
if (we_created_out_adjacent_) {
// This is a gap we made, we can just delete it entirely
track_->RippleRemoveBlock(out_adjacent_);
delete TakeNodeFromParentGraph(out_adjacent_);
we_created_out_adjacent_ = false;
out_adjacent_ = nullptr;
} else if (out_adjacent_) {
// We may not have created an out adjacent if this was the last clip in the track so we have
// to check here
if (out_adjacent_->parent() == &memory_manager_) {
// This is a gap we removed, we can re-insert it now
static_cast<NodeGraph*>(track_->parent())->AddNode(out_adjacent_);
track_->InsertBlockAfter(out_adjacent_, blocks_.last());
} else {
// We must have just resized this block
out_adjacent_->set_length_and_media_in(out_adjacent_->length() + movement_);
}
}
track->UnblockInvalidateCache();
} else {
// Redo code
if (!in_adjacent_) {
// For any slide operation to have occurred at all with no in_adjacent, a gap will need to
// be created
GapBlock* gap = new GapBlock();
gap->set_length_and_media_out(movement_);
static_cast<NodeGraph*>(track_->parent())->AddNode(gap);
track_->InsertBlockBefore(gap, blocks_.first());
we_created_in_adjacent_ = true;
in_adjacent_ = gap;
} else if (-movement_ == in_adjacent_->length()) {
// Remove in adjacent entirely
track_->RippleRemoveBlock(in_adjacent_);
TakeNodeFromParentGraph(in_adjacent_, &memory_manager_);
} else {
// Resize in adjacent
in_adjacent_->set_length_and_media_out(in_adjacent_->length() + movement_);
}
if (!out_adjacent_) {
// For any slide operation to have occurred at all with no out_adjacent, a gap will need to
// be created UNLESS this is at the end of the track already
if (blocks_.last()->next()) {
GapBlock* gap = new GapBlock();
gap->set_length_and_media_out(-movement_);
static_cast<NodeGraph*>(track_->parent())->AddNode(gap);
track_->InsertBlockAfter(gap, blocks_.last());
we_created_out_adjacent_ = true;
out_adjacent_ = gap;
}
} else if (movement_ == out_adjacent_->length()) {
// Remove out adjacent entirely
track_->RippleRemoveBlock(out_adjacent_);
TakeNodeFromParentGraph(out_adjacent_, &memory_manager_);
} else {
// Resize out adjacent
out_adjacent_->set_length_and_media_in(out_adjacent_->length() - movement_);
}
added_gaps_.clear();
}
track_->EndOperation();
// Make sure all movement blocks' new positions are invalidated
foreach (const BlockSlideInfo& info, blocks_) {
if (info.mode == Timeline::kMove) {
invalidate_ranges[info.track].InsertTimeRange(TimeRange(info.block->in(),
info.block->out()));
}
}
invalidate_range.set_range(qMin(invalidate_range.in(), blocks_.first()->in()),
qMax(invalidate_range.out(), blocks_.last()->out()));
QMap<TrackOutput*, TimeRangeList>::const_iterator i;
for (i=invalidate_ranges.constBegin(); i!=invalidate_ranges.constEnd(); i++) {
foreach (const TimeRange& r, i.value()) {
i.key()->InvalidateCache(r, i.key()->block_input(), i.key()->block_input());
}
}
track_->InvalidateCache(invalidate_range, track_->block_input(), track_->block_input());
}
TrackListRippleRemoveAreaCommand::TrackListRippleRemoveAreaCommand(TrackList *list, rational in, rational out, QUndoCommand *parent) :
@@ -1253,7 +1300,7 @@ void TrackListRippleRemoveAreaCommand::redo_internal()
}
foreach (TrackOutput* track, working_tracks_) {
track->BlockInvalidateCache();
track->BeginOperation();
}
}
@@ -1263,8 +1310,7 @@ void TrackListRippleRemoveAreaCommand::redo_internal()
if (all_tracks_unlocked_) {
foreach (TrackOutput* track, working_tracks_) {
track->UnblockInvalidateCache();
track->PushLengthChangeSignal();
track->EndOperation();
}
}
}
@@ -1281,7 +1327,7 @@ void TrackListRippleRemoveAreaCommand::undo_internal()
}
foreach (TrackOutput* track, working_tracks_) {
track->BlockInvalidateCache();
track->BeginOperation();
}
}
@@ -1291,8 +1337,7 @@ void TrackListRippleRemoveAreaCommand::undo_internal()
if (all_tracks_unlocked_) {
foreach (TrackOutput* track, working_tracks_) {
track->UnblockInvalidateCache();
track->PushLengthChangeSignal();
track->EndOperation();
}
}
}
@@ -1338,7 +1383,7 @@ void TrackListRippleToolCommand::redo_internal()
if (all_tracks_unlocked_) {
// We can do some optimization here
foreach (const RippleInfo& info, info_) {
info.track->BlockInvalidateCache();
info.track->BeginOperation();
}
old_latest_pt = RATIONAL_MIN;
@@ -1398,8 +1443,12 @@ void TrackListRippleToolCommand::redo_internal()
const RippleInfo& info = info_.at(i);
if (info.block) {
new_latest_pt = qMax(new_latest_pt, info.block->out());
} else {
if (info.new_length > 0) {
new_latest_pt = qMax(new_latest_pt, info.block->out());
} else {
new_latest_pt = qMax(new_latest_pt, info.block->in());
}
} else if (info.new_length > 0) {
new_latest_pt = qMax(new_latest_pt, working_data_.at(i).created_gap->out());
}
}
@@ -1411,15 +1460,13 @@ void TrackListRippleToolCommand::redo_internal()
}
foreach (const RippleInfo& info, info_) {
info.track->UnblockInvalidateCache();
info.track->EndOperation();
// FIXME: Untested, is this desirable behavior?
if (earliest_pt < new_latest_pt) {
info.track->InvalidateCache(TimeRange(earliest_pt, new_latest_pt),
info.track->block_input(),
info.track->block_input());
} else {
info.track->PushLengthChangeSignal();
}
}
}
@@ -1427,6 +1474,8 @@ void TrackListRippleToolCommand::redo_internal()
void TrackListRippleToolCommand::undo_internal()
{
// FIXME: Add cache shift optimization
// Clean created gaps
for (int i=info_.size()-1; i>=0; i--) {
const RippleInfo& info = info_.at(i);
@@ -1499,7 +1548,7 @@ void TrackListInsertGaps::redo_internal()
}
foreach (TrackOutput* track, working_tracks_) {
track->BlockInvalidateCache();
track->BeginOperation();
}
}
@@ -1539,8 +1588,7 @@ void TrackListInsertGaps::redo_internal()
if (all_tracks_unlocked_) {
foreach (TrackOutput* track, working_tracks_) {
track->UnblockInvalidateCache();
track->PushLengthChangeSignal(false);
track->EndOperation();
}
}
}
@@ -1556,7 +1604,7 @@ void TrackListInsertGaps::undo_internal()
}
foreach (TrackOutput* track, working_tracks_) {
track->BlockInvalidateCache();
track->BeginOperation();
}
}
@@ -1582,10 +1630,88 @@ void TrackListInsertGaps::undo_internal()
if (all_tracks_unlocked_) {
foreach (TrackOutput* track, working_tracks_) {
track->UnblockInvalidateCache();
track->PushLengthChangeSignal(false);
track->EndOperation();
}
}
}
TransitionRemoveCommand::TransitionRemoveCommand(TrackOutput* track, TransitionBlock *block, QUndoCommand* parent) :
UndoCommand(parent),
track_(track),
block_(block),
out_block_(block_->connected_out_block()),
in_block_(block_->connected_in_block())
{
// Can't remove a transition in this way unless it's connected to at least one other block
Q_ASSERT(out_block_ || in_block_);
}
Project *TransitionRemoveCommand::GetRelevantProject() const
{
return static_cast<Sequence*>(track_->parent())->project();
}
void TransitionRemoveCommand::redo_internal()
{
track_->BeginOperation();
TimeRange invalidate_range(block_->in(), block_->out());
if (in_block_) {
in_block_->set_length_and_media_in(in_block_->length() + block_->in_offset());
}
if (out_block_) {
out_block_->set_length_and_media_out(out_block_->length() + block_->out_offset());
}
if (in_block_) {
NodeParam::DisconnectEdge(in_block_->output(), block_->in_block_input());
}
if (out_block_) {
NodeParam::DisconnectEdge(out_block_->output(), block_->out_block_input());
}
track_->RippleRemoveBlock(block_);
track_->EndOperation();
track_->InvalidateCache(invalidate_range, track_->block_input(), track_->block_input());
}
void TransitionRemoveCommand::undo_internal()
{
track_->BeginOperation();
if (in_block_) {
track_->InsertBlockBefore(block_, in_block_);
} else {
track_->InsertBlockAfter(block_, out_block_);
}
if (in_block_) {
NodeParam::ConnectEdge(in_block_->output(), block_->in_block_input());
}
if (out_block_) {
NodeParam::ConnectEdge(out_block_->output(), block_->out_block_input());
}
// These if statements must be separated because in_offset and out_offset report different things
// if only one block is connected vs two. So we have to connect the blocks first before we have
// an accurate return value from these offset functions.
if (in_block_) {
in_block_->set_length_and_media_in(in_block_->length() - block_->in_offset());
}
if (out_block_) {
out_block_->set_length_and_media_out(out_block_->length() - block_->out_offset());
}
track_->EndOperation();
track_->InvalidateCache(TimeRange(block_->in(), block_->out()), track_->block_input(), track_->block_input());
}
OLIVE_NAMESPACE_EXIT
+35 -14
View File
@@ -25,6 +25,7 @@
#include "node/block/block.h"
#include "node/block/gap/gap.h"
#include "node/block/transition/transition.h"
#include "node/output/track/track.h"
#include "node/output/track/tracklist.h"
#include "timeline/timelinepoints.h"
@@ -70,9 +71,9 @@ public:
virtual Project* GetRelevantProject() const override;
void SetAllowNonGapTrimming(bool e)
void SetTrimIsARollEdit(bool e)
{
allow_nongap_trimming_ = e;
trim_is_a_roll_edit_ = e;
}
protected:
@@ -90,7 +91,7 @@ private:
bool we_created_adjacent_;
bool we_deleted_adjacent_;
bool allow_nongap_trimming_;
bool trim_is_a_roll_edit_;
QObject memory_manager_;
@@ -553,15 +554,7 @@ private:
class TrackSlideCommand : public UndoCommand {
public:
struct BlockSlideInfo {
TrackOutput* track;
Block* block;
Timeline::MovementMode mode;
rational new_time;
rational old_time;
};
TrackSlideCommand(const QVector<BlockSlideInfo>& blocks, QUndoCommand* parent = nullptr);
TrackSlideCommand(TrackOutput* track, const QList<Block*>& moving_blocks, Block* in_adjacent, Block* out_adjacent, const rational& movement, QUndoCommand* parent = nullptr);
virtual Project* GetRelevantProject() const override;
@@ -572,8 +565,16 @@ protected:
private:
void slide_internal(bool undo);
QVector<BlockSlideInfo> blocks_;
QList<GapBlock*> added_gaps_;
TrackOutput* track_;
QList<Block*> blocks_;
rational movement_;
bool we_created_in_adjacent_;
Block* in_adjacent_;
bool we_created_out_adjacent_;
Block* out_adjacent_;
QObject memory_manager_;
};
@@ -606,6 +607,26 @@ private:
};
class TransitionRemoveCommand : public UndoCommand {
public:
TransitionRemoveCommand(TrackOutput *track, TransitionBlock* block, QUndoCommand *parent = nullptr);
virtual Project* GetRelevantProject() const override;
protected:
virtual void redo_internal() override;
virtual void undo_internal() override;
private:
TrackOutput* track_;
TransitionBlock* block_;
Block* out_block_;
Block* in_block_;
};
OLIVE_NAMESPACE_EXIT
#endif // TIMELINEUNDOABLE_H
@@ -18,8 +18,6 @@ set(OLIVE_SOURCES
${OLIVE_SOURCES}
widget/timelinewidget/view/handmovableview.h
widget/timelinewidget/view/handmovableview.cpp
widget/timelinewidget/view/timelineplayhead.h
widget/timelinewidget/view/timelineplayhead.cpp
widget/timelinewidget/view/timelineview.h
widget/timelinewidget/view/timelineview.cpp
widget/timelinewidget/view/timelineviewmouseevent.h
@@ -1,54 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 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 "timelineplayhead.h"
#include <QPainter>
const QColor &TimelinePlayhead::GetPlayheadColor() const
{
return playhead_color_;
}
const QColor &TimelinePlayhead::GetPlayheadHighlightColor() const
{
return playhead_highlight_color_;
}
void TimelinePlayhead::SetPlayheadColor(QColor c)
{
playhead_color_ = c;
}
void TimelinePlayhead::SetPlayheadHighlightColor(QColor c)
{
playhead_highlight_color_ = c;
}
void TimelinePlayhead::Draw(QPainter* painter, const QRectF& playhead_rect) const
{
painter->setPen(Qt::NoPen);
painter->setBrush(GetPlayheadHighlightColor());
painter->drawRect(playhead_rect);
painter->setPen(GetPlayheadColor());
painter->setBrush(Qt::NoBrush);
painter->drawLine(QLineF(playhead_rect.topLeft(), playhead_rect.bottomLeft()));
}
@@ -1,52 +0,0 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 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 TIMELINEPLAYHEADSTYLE_H
#define TIMELINEPLAYHEADSTYLE_H
#include <QWidget>
/**
* @brief A QWidget proxy for TimeRuler and TimelinePlayheadItem to allow them to share CSS values
*
* To allow Qt CSS customization (which is only available to QWidgets) to be accessed by TimeRuler
*/
class TimelinePlayhead : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor playheadColor READ GetPlayheadColor WRITE SetPlayheadColor DESIGNABLE true)
Q_PROPERTY(QColor playheadHighlightColor READ GetPlayheadHighlightColor WRITE SetPlayheadHighlightColor DESIGNABLE true)
public:
TimelinePlayhead() = default;
const QColor& GetPlayheadColor() const;
const QColor& GetPlayheadHighlightColor() const;
void SetPlayheadColor(QColor c);
void SetPlayheadHighlightColor(QColor c);
void Draw(QPainter *painter, const QRectF &rect) const;
private:
QColor playhead_color_;
QColor playhead_highlight_color_;
};
#endif // TIMELINEPLAYHEADSTYLE_H
+64 -45
View File
@@ -37,6 +37,7 @@ OLIVE_NAMESPACE_ENTER
TimelineView::TimelineView(Qt::Alignment vertical_alignment, QWidget *parent) :
TimelineViewBase(parent),
show_beam_cursor_(false),
connected_track_list_(nullptr)
{
Q_ASSERT(vertical_alignment == Qt::AlignTop || vertical_alignment == Qt::AlignBottom);
@@ -45,28 +46,7 @@ TimelineView::TimelineView(Qt::Alignment vertical_alignment, QWidget *parent) :
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
setBackgroundRole(QPalette::Window);
setContextMenuPolicy(Qt::CustomContextMenu);
SetLimitYAxis(true);
viewport()->setMouseTracking(true);
connect(scene(), &QGraphicsScene::selectionChanged, this, &TimelineView::SelectionChanged);
}
void TimelineView::SelectAll()
{
QList<QGraphicsItem*> all_items = items();
foreach (QGraphicsItem* i, all_items) {
i->setSelected(true);
}
}
void TimelineView::DeselectAll()
{
QList<QGraphicsItem*> all_items = items();
foreach (QGraphicsItem* i, all_items) {
i->setSelected(false);
}
}
void TimelineView::mousePressEvent(QMouseEvent *event)
@@ -141,21 +121,21 @@ void TimelineView::wheelEvent(QWheelEvent *event)
}
QWheelEvent e(
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
event->position(),
event->globalPosition(),
#else
event->pos(),
event->globalPos(),
#endif
event->pixelDelta(),
angle_delta,
event->buttons(),
event->modifiers(),
event->phase(),
event->inverted(),
event->source()
);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
event->position(),
event->globalPosition(),
#else
event->pos(),
event->globalPos(),
#endif
event->pixelDelta(),
angle_delta,
event->buttons(),
event->modifiers(),
event->phase(),
event->inverted(),
event->source()
);
#else
@@ -166,15 +146,15 @@ void TimelineView::wheelEvent(QWheelEvent *event)
}
QWheelEvent e(
event->pos(),
event->globalPos(),
event->pixelDelta(),
event->angleDelta(),
event->delta(),
orientation,
event->buttons(),
event->modifiers()
);
event->pos(),
event->globalPos(),
event->pixelDelta(),
event->angleDelta(),
event->delta(),
orientation,
event->buttons(),
event->modifiers()
);
#endif
QGraphicsView::wheelEvent(&e);
@@ -244,6 +224,26 @@ void TimelineView::drawBackground(QPainter *painter, const QRectF &rect)
}
}
void TimelineView::drawForeground(QPainter *painter, const QRectF &rect)
{
TimelineViewBase::drawForeground(painter, rect);
if (show_beam_cursor_
&& connected_track_list_
&& cursor_coord_.GetTrack().type() == connected_track_list_->type()) {
painter->setPen(Qt::gray);
double cursor_x = TimeToScene(cursor_coord_.GetFrame());
int track_index = cursor_coord_.GetTrack().index();
int track_y = GetTrackY(track_index);
painter->drawLine(cursor_x,
track_y,
cursor_x,
track_y + GetTrackHeight(track_index));
}
}
void TimelineView::ToolChangedEvent(Tool::Item tool)
{
switch (tool) {
@@ -261,6 +261,12 @@ void TimelineView::ToolChangedEvent(Tool::Item tool)
default:
unsetCursor();
}
// Hide/show cursor if necessary
if (show_beam_cursor_) {
show_beam_cursor_ = false;
viewport()->update();
}
}
void TimelineView::SceneRectUpdateEvent(QRectF &rect)
@@ -399,6 +405,19 @@ void TimelineView::ConnectTrackList(TrackList *list)
}
}
void TimelineView::SetBeamCursor(const TimelineCoordinate &coord)
{
bool update_required = coord.GetTrack().type() == connected_track_list_->type()
|| cursor_coord_.GetTrack().type() == connected_track_list_->type();
show_beam_cursor_ = true;
cursor_coord_ = coord;
if (update_required) {
viewport()->update();
}
}
int TimelineView::SceneToTrack(double y)
{
int track = -1;
@@ -49,10 +49,6 @@ public:
TimelineView(Qt::Alignment vertical_alignment = Qt::AlignTop,
QWidget* parent = nullptr);
void SelectAll();
void DeselectAll();
int GetTrackY(int track_index) const;
int GetTrackHeight(int track_index) const;
@@ -61,6 +57,8 @@ public:
void ConnectTrackList(TrackList* list);
void SetBeamCursor(const TimelineCoordinate& coord);
signals:
void MousePressed(TimelineViewMouseEvent* event);
void MouseMoved(TimelineViewMouseEvent* event);
@@ -72,8 +70,6 @@ signals:
void DragLeft(QDragLeaveEvent* event);
void DragDropped(TimelineViewMouseEvent* event);
void SelectionChanged();
protected:
virtual void mousePressEvent(QMouseEvent *event) override;
virtual void mouseMoveEvent(QMouseEvent *event) override;
@@ -88,6 +84,7 @@ protected:
virtual void dropEvent(QDropEvent *event) override;
virtual void drawBackground(QPainter *painter, const QRectF &rect) override;
virtual void drawForeground(QPainter *painter, const QRectF &rect) override;
virtual void ToolChangedEvent(Tool::Item tool) override;
@@ -111,6 +108,10 @@ private:
void UpdatePlayheadRect();
bool show_beam_cursor_;
TimelineCoordinate cursor_coord_;
TrackList* connected_track_list_;
};
@@ -39,9 +39,10 @@ TimelineViewBase::TimelineViewBase(QWidget *parent) :
playhead_scene_left_(-1),
playhead_scene_right_(-1),
dragging_playhead_(false),
limit_y_axis_(false),
snapped_(false),
snap_service_(nullptr)
snap_service_(nullptr),
y_axis_enabled_(false),
y_scale_(1.0)
{
setScene(&scene_);
@@ -81,6 +82,26 @@ void TimelineViewBase::SetSnapService(SnapService *service)
snap_service_ = service;
}
const double &TimelineViewBase::GetYScale() const
{
return y_scale_;
}
void TimelineViewBase::VerticalScaleChangedEvent(double)
{
}
void TimelineViewBase::SetYScale(const double &y_scale)
{
y_scale_ = y_scale;
if (y_axis_enabled_) {
VerticalScaleChangedEvent(y_scale_);
viewport()->update();
}
}
void TimelineViewBase::SetTime(const int64_t time)
{
playhead_ = time;
@@ -111,7 +132,19 @@ void TimelineViewBase::drawForeground(QPainter *painter, const QRectF &rect)
playhead_scene_left_ = GetPlayheadX();
playhead_scene_right_ = playhead_scene_left_ + width;
playhead_style_.Draw(painter, QRectF(playhead_scene_left_, rect.top(), width, rect.height()));
QRectF playhead_rect(playhead_scene_left_, rect.top(), width, rect.height());
// Get playhead highlight color
QColor highlight = palette().text().color();
highlight.setAlpha(32);
painter->setPen(Qt::NoPen);
painter->setBrush(highlight);
painter->drawRect(playhead_rect);
// FIXME: Hardcoded...
painter->setPen(PLAYHEAD_COLOR);
painter->setBrush(Qt::NoBrush);
painter->drawLine(QLineF(playhead_rect.topLeft(), playhead_rect.bottomLeft()));
}
if (snapped_) {
@@ -134,7 +167,9 @@ bool TimelineViewBase::PlayheadPress(QMouseEvent *event)
{
QPointF scene_pos = mapToScene(event->pos());
dragging_playhead_ = (scene_pos.x() >= playhead_scene_left_ && scene_pos.x() < playhead_scene_right_);
dragging_playhead_ = (event->button() == Qt::LeftButton
&& scene_pos.x() >= playhead_scene_left_
&& scene_pos.x() < playhead_scene_right_);
return dragging_playhead_;
}
@@ -251,10 +286,56 @@ bool TimelineViewBase::HandleZoomFromScroll(QWheelEvent *event)
if (WheelEventIsAZoomEvent(event)) {
// If CTRL is held (or a preference is set to swap CTRL behavior), we zoom instead of scrolling
if (!event->angleDelta().isNull()) {
if (event->angleDelta().x() + event->angleDelta().y() > 0) {
emit ScaleChanged(GetScale() * 1.1);
bool only_vertical = false;
bool only_horizontal = false;
// Ctrl+Shift limits to only one axis
// Alt switches between horizontal only (alt held) or vertical only (alt not held)
if (y_axis_enabled_) {
if (event->modifiers() & Qt::ShiftModifier) {
if (event->modifiers() & Qt::AltModifier) {
only_horizontal = true;
} else {
only_vertical = true;
}
}
} else {
emit ScaleChanged(GetScale() * 0.9);
only_horizontal = true;
}
double scale_multiplier;
if (event->angleDelta().x() + event->angleDelta().y() > 0) {
scale_multiplier = 1.1;
} else {
scale_multiplier = 0.9;
}
QPointF cursor_pos;
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
cursor_pos = event->position();
#else
cursor_pos = event->posF();
#endif
if (!only_vertical) {
double new_x_scale = GetScale() * scale_multiplier;
int new_x_scroll = qRound(horizontalScrollBar()->value() / GetScale() * new_x_scale + (cursor_pos.x() - cursor_pos.x() / new_x_scale * GetScale()));
emit ScaleChanged(new_x_scale);
horizontalScrollBar()->setValue(new_x_scroll);
}
if (!only_horizontal) {
double new_y_scale = GetYScale() * scale_multiplier;
int new_y_scroll = qRound(verticalScrollBar()->value() / GetYScale() * new_y_scale + (cursor_pos.y() - cursor_pos.y() / new_y_scale * GetYScale()));
SetYScale(new_y_scale);
verticalScrollBar()->setValue(new_y_scroll);
}
}
@@ -269,10 +350,4 @@ bool TimelineViewBase::WheelEventIsAZoomEvent(QWheelEvent *event)
return (static_cast<bool>(event->modifiers() & Qt::ControlModifier) == !Config::Current()["ScrollZooms"].toBool());
}
void TimelineViewBase::SetLimitYAxis(bool)
{
limit_y_axis_ = true;
UpdateSceneRect();
}
OLIVE_NAMESPACE_EXIT
@@ -25,7 +25,6 @@
#include "core.h"
#include "handmovableview.h"
#include "timelineplayhead.h"
#include "widget/timelinewidget/snapservice.h"
#include "widget/timelinewidget/timelinescaledobject.h"
@@ -48,6 +47,9 @@ public:
void SetSnapService(SnapService* service);
const double& GetYScale() const;
void SetYScale(const double& y_scale);
public slots:
void SetTime(const int64_t time);
@@ -69,12 +71,12 @@ protected:
virtual void SceneRectUpdateEvent(QRectF&){}
virtual void VerticalScaleChangedEvent(double scale);
bool HandleZoomFromScroll(QWheelEvent* event);
bool WheelEventIsAZoomEvent(QWheelEvent* event);
void SetLimitYAxis(bool e);
rational GetPlayheadTime() const;
bool PlayheadPress(QMouseEvent* event);
@@ -83,13 +85,21 @@ protected:
virtual void TimebaseChangedEvent(const rational &) override;
bool IsYAxisEnabled() const
{
return y_axis_enabled_;
}
void SetYAxisEnabled(bool e)
{
y_axis_enabled_ = e;
}
private:
qreal GetPlayheadX();
int64_t playhead_;
TimelinePlayhead playhead_style_;
double playhead_scene_left_;
double playhead_scene_right_;
@@ -97,8 +107,6 @@ private:
QGraphicsScene scene_;
bool limit_y_axis_;
bool snapped_;
QList<rational> snap_time_;
@@ -106,6 +114,10 @@ private:
SnapService* snap_service_;
bool y_axis_enabled_;
double y_scale_;
private slots:
/**
* @brief Slot called whenever the view resizes or the scene contents change to enforce minimum scene sizes
@@ -46,8 +46,16 @@ TimelineViewGhostItem *TimelineViewGhostItem::FromBlock(Block *block, const Trac
ghost->SetYCoords(y, height);
ghost->setData(kAttachedBlock, Node::PtrToValue(block));
if (block->type() == Block::kClip) {
switch (block->type()) {
case Block::kClip:
ghost->can_have_zero_length_ = false;
break;
case Block::kTransition:
ghost->can_have_zero_length_ = false;
ghost->SetCanMoveTracks(false);
break;
case Block::kGap:
break;
}
return ghost;
@@ -38,7 +38,10 @@ public:
enum DataType {
kAttachedBlock,
kReferenceBlock,
kAttachedFootage
kAttachedFootage,
kGhostIsSliding,
kTrimIsARollEdit,
kTrimShouldBeIgnored
};
TimelineViewGhostItem(QGraphicsItem* parent = nullptr);
-13
View File
@@ -24,7 +24,6 @@
#include "common/rational.h"
#include "timeline/timelinepoints.h"
#include "widget/timelinewidget/snapservice.h"
#include "widget/timelinewidget/view/timelineplayhead.h"
#include "widget/timelinewidget/timelinescaledobject.h"
OLIVE_NAMESPACE_ENTER
@@ -80,16 +79,6 @@ protected:
return playhead_width_;
}
inline const QColor& GetPlayheadColor() const
{
return style_.GetPlayheadColor();
}
inline const QColor& GetPlayheadHighlightColor() const
{
return style_.GetPlayheadHighlightColor();
}
signals:
/**
* @brief Signal emitted whenever the time changes on this ruler, either by user or programmatically
@@ -99,8 +88,6 @@ signals:
private:
int64_t time_;
TimelinePlayhead style_;
TimelinePoints* timeline_points_;
int scroll_;
+1 -1
View File
@@ -281,7 +281,7 @@ void TimeRuler::paintEvent(QPaintEvent *)
// Draw the playhead if it's on screen at the moment
int playhead_pos = UnitToScreen(GetTime());
p.setPen(Qt::NoPen);
p.setBrush(GetPlayheadColor());
p.setBrush(PLAYHEAD_COLOR);
DrawPlayhead(&p, playhead_pos, line_bottom);
}
+21 -1
View File
@@ -25,8 +25,9 @@
#include <QButtonGroup>
#include <QEvent>
#include "widget/menu/menu.h"
#include "node/factory.h"
#include "ui/icons/icons.h"
#include "widget/menu/menu.h"
OLIVE_NAMESPACE_ENTER
@@ -54,6 +55,9 @@ Toolbar::Toolbar(QWidget *parent) :
btn_snapping_toggle_ = CreateNonToolButton();
connect(btn_snapping_toggle_, &QPushButton::clicked, this, &Toolbar::SnappingButtonClicked);
// Connect transition button to menu signal
connect(btn_transition_tool_, &QPushButton::clicked, this, &Toolbar::TransitionButtonClicked);
// Connect add button to menu signal
connect(btn_add_, &QPushButton::clicked, this, &Toolbar::AddButtonClicked);
@@ -181,9 +185,25 @@ void Toolbar::AddButtonClicked()
m.exec(QCursor::pos());
}
void Toolbar::TransitionButtonClicked()
{
Menu* m = NodeFactory::CreateMenu(this, false, Node::kCategoryTransition);
connect(m, &QMenu::triggered, this, &Toolbar::TransitionMenuItemTriggered);
m->exec(QCursor::pos());
delete m;
}
void Toolbar::AddMenuItemTriggered(QAction* a)
{
emit AddableObjectChanged(static_cast<Tool::AddableObject>(a->data().toInt()));
}
void Toolbar::TransitionMenuItemTriggered(QAction *a)
{
emit SelectedTransitionChanged(NodeFactory::GetIDFromMenuAction(a));
}
OLIVE_NAMESPACE_EXIT
+17
View File
@@ -113,6 +113,11 @@ signals:
*/
void AddableObjectChanged(const Tool::AddableObject& obj);
/**
* @brief Emitted when the selected transition is changed from the transition tool menu
*/
void SelectedTransitionChanged(const QString& id);
private:
/**
* @brief Reset all strings based on the currently selected language
@@ -210,11 +215,23 @@ private slots:
*/
void AddButtonClicked();
/**
* @brief Receiver for the transition button
*
* The transition button pops up a list for which transition to create.
*/
void TransitionButtonClicked();
/**
* @brief Receiver for the menu created by AddButtonClicked()
*/
void AddMenuItemTriggered(QAction* a);
/**
* @brief Receiver for the menu created by TransitionButtonClicked()
*/
void TransitionMenuItemTriggered(QAction* a);
};
OLIVE_NAMESPACE_EXIT
+4 -3
View File
@@ -26,6 +26,7 @@
#include "common/clamp.h"
#include "config/config.h"
#include "timeline/timelinecommon.h"
OLIVE_NAMESPACE_ENTER
@@ -65,7 +66,7 @@ void AudioWaveformView::paintEvent(QPaintEvent *event)
const AudioParams& params = playback_->GetParameters();
if (!playback_
|| playback_->GetCacheFilename().isEmpty()
|| playback_->GetPCMFilename().isEmpty()
|| !params.is_valid()) {
return;
}
@@ -77,7 +78,7 @@ void AudioWaveformView::paintEvent(QPaintEvent *event)
cached_waveform_ = QPixmap(size());
cached_waveform_.fill(Qt::transparent);
QFile fs(playback_->GetCacheFilename());
QFile fs(playback_->GetPCMFilename());
if (fs.open(QFile::ReadOnly)) {
@@ -130,7 +131,7 @@ void AudioWaveformView::paintEvent(QPaintEvent *event)
p.drawPixmap(0, 0, cached_waveform_);
// Draw playhead
p.setPen(GetPlayheadColor());
p.setPen(PLAYHEAD_COLOR);
int playhead_x = UnitToScreen(GetTime());
p.drawLine(playhead_x, 0, playhead_x, height());
+4
View File
@@ -88,6 +88,8 @@ void FootageViewerWidget::SetFootage(Footage *footage)
video_stream->height(),
video_stream->frame_rate().flipped(),
static_cast<PixelFormat::Format>(Config::Current()["DefaultSequencePreviewFormat"].toInt()),
video_stream->pixel_aspect_ratio(),
video_stream->interlacing(),
VideoParams::generate_auto_divider(video_stream->width(), video_stream->height())));
NodeParam::ConnectEdge(video_node_->output(), viewer_node_->texture_input());
} else {
@@ -98,6 +100,8 @@ void FootageViewerWidget::SetFootage(Footage *footage)
height,
Config::Current()["DefaultSequenceFrameRate"].value<rational>(),
static_cast<PixelFormat::Format>(Config::Current()["DefaultSequencePreviewFormat"].toInt()),
Config::Current()["DefaultSequencePixelAspect"].value<rational>(),
Config::Current()["DefaultSequenceInterlacing"].value<VideoParams::Interlacing>(),
VideoParams::generate_auto_divider(width, height)));
}
+152 -247
View File
@@ -32,6 +32,7 @@
#include "audio/audiomanager.h"
#include "common/power.h"
#include "common/ratiodialog.h"
#include "common/timecodefunctions.h"
#include "config/config.h"
#include "project/item/sequence/sequence.h"
@@ -43,12 +44,10 @@
OLIVE_NAMESPACE_ENTER
QVector<ViewerWidget*> ViewerWidget::instances_;
const int kMaxPreQueueSize = 16;
CacheTask* ViewerWidget::cache_background_task_ = nullptr;
int ViewerWidget::busy_viewers_ = 0;
ViewerWidget::ViewerWidget(QWidget *parent) :
TimeBasedWidget(false, true, parent),
playback_speed_(0),
@@ -56,10 +55,8 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
color_menu_enabled_(true),
override_color_manager_(nullptr),
time_changed_from_timer_(false),
prequeuing_(false),
busy_(false),
our_cache_background_task_(nullptr),
autocache_(true)
pause_autocache_during_playback_(true),
prequeuing_(false)
{
// Set up main layout
QVBoxLayout* layout = new QVBoxLayout(this);
@@ -112,26 +109,21 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
// Start background renderer
renderer_ = new OpenGLBackend(this);
renderer_->SetUpdateWithGraph(true);
renderer_->SetAutoCacheEnabled(true);
renderer_->SetRenderMode(RenderMode::kOffline);
// Setup cache wait timer (waits a few seconds of inactivity before caching)
cache_wait_timer_.setInterval(Config::Current()["AutoCacheInterval"].toInt());
cache_wait_timer_.setSingleShot(true);
connect(&cache_wait_timer_, &QTimer::timeout, this, &ViewerWidget::StartBackgroundCaching);
// Remove pointer to cache task if it's removed from the task manager
connect(TaskManager::instance(), &TaskManager::TaskRemoved, this, &ViewerWidget::BackgroundCacheFinished);
renderer_->SetPreviewGenerationEnabled(true);
// Ensures that seeking on the waveform view updates the time as expected
connect(waveform_view_, &AudioWaveformView::TimeChanged, this, &ViewerWidget::SetTimeAndSignal);
// Ensures renderer is updated if the global pixel format is changed
connect(PixelFormat::instance(), &PixelFormat::FormatChanged, this, &ViewerWidget::UpdateRendererParameters);
connect(PixelFormat::instance(), &PixelFormat::FormatChanged, this, &ViewerWidget::UpdateRendererVideoParameters);
connect(&playback_backup_timer_, &QTimer::timeout, this, &ViewerWidget::PlaybackTimerUpdate);
SetAutoMaxScrollBar(true);
instances_.append(this);
}
ViewerWidget::~ViewerWidget()
@@ -161,6 +153,10 @@ void ViewerWidget::TimeChangedEvent(const int64_t &i)
PushScrubbedAudio();
}
if (!pause_autocache_during_playback_ || !IsPlaying()) {
renderer_->SetAutoCachePlayhead(time_set);
}
display_widget_->SetTime(time_set);
}
@@ -169,28 +165,24 @@ void ViewerWidget::TimeChangedEvent(const int64_t &i)
void ViewerWidget::ConnectNodeInternal(ViewerOutput *n)
{
if (!n->video_params().time_base().isNull()) {
SetTimebase(n->video_params().time_base());
} else if (n->audio_params().sample_rate() > 0) {
SetTimebase(n->audio_params().time_base());
} else {
SetTimebase(rational());
}
connect(n, &ViewerOutput::TimebaseChanged, this, &ViewerWidget::SetTimebase);
connect(n, &ViewerOutput::SizeChanged, this, &ViewerWidget::SizeChangedSlot);
connect(n, &ViewerOutput::SizeChanged, this, &ViewerWidget::SetViewerResolution);
connect(n, &ViewerOutput::PixelAspectChanged, this, &ViewerWidget::SetViewerPixelAspect);
connect(n, &ViewerOutput::LengthChanged, this, &ViewerWidget::LengthChangedSlot);
connect(n, &ViewerOutput::ParamsChanged, this, &ViewerWidget::UpdateRendererParameters);
connect(n, &ViewerOutput::InterlacingChanged, this, &ViewerWidget::InterlacingChangedSlot);
connect(n, &ViewerOutput::VideoParamsChanged, this, &ViewerWidget::UpdateRendererVideoParameters);
connect(n, &ViewerOutput::AudioParamsChanged, this, &ViewerWidget::UpdateRendererAudioParameters);
connect(n->video_frame_cache(), &FrameHashCache::Invalidated, this, &ViewerWidget::ViewerInvalidatedVideoRange);
connect(n->video_frame_cache(), &FrameHashCache::Shifted, this, &ViewerWidget::ViewerShiftedRange);
connect(n->audio_playback_cache(), &FrameHashCache::Invalidated, this, &ViewerWidget::ViewerInvalidatedRange);
connect(n, &ViewerOutput::GraphChangedFrom, this, &ViewerWidget::UpdateStack);
InterlacingChangedSlot(n->video_params().interlacing());
ruler()->SetPlaybackCache(n->video_frame_cache());
n->audio_playback_cache()->SetParameters(n->audio_params());
SizeChangedSlot(n->video_params().width(), n->video_params().height());
SetViewerResolution(n->video_params().width(), n->video_params().height());
SetViewerPixelAspect(n->video_params().pixel_aspect_ratio());
last_length_ = rational();
LengthChangedSlot(n->GetLength());
@@ -216,7 +208,8 @@ void ViewerWidget::ConnectNodeInternal(ViewerOutput *n)
waveform_view_->ConnectTimelinePoints(GetConnectedTimelinePoints());
}
UpdateRendererParameters();
UpdateRendererVideoParameters();
UpdateRendererAudioParameters();
// Set texture to new texture (or null if no viewer node is available)
ForceUpdate();
@@ -224,29 +217,22 @@ void ViewerWidget::ConnectNodeInternal(ViewerOutput *n)
void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n)
{
Pause();
PauseInternal();
if (cache_background_task_ == our_cache_background_task_) {
StopAllBackgroundCacheTasks(true);
cache_background_task_ = nullptr;
}
cache_wait_timer_.stop();
SetTimebase(rational());
disconnect(n, &ViewerOutput::TimebaseChanged, this, &ViewerWidget::SetTimebase);
disconnect(n, &ViewerOutput::SizeChanged, this, &ViewerWidget::SizeChangedSlot);
disconnect(n, &ViewerOutput::SizeChanged, this, &ViewerWidget::SetViewerResolution);
disconnect(n, &ViewerOutput::PixelAspectChanged, this, &ViewerWidget::SetViewerPixelAspect);
disconnect(n, &ViewerOutput::LengthChanged, this, &ViewerWidget::LengthChangedSlot);
disconnect(n, &ViewerOutput::ParamsChanged, this, &ViewerWidget::UpdateRendererParameters);
disconnect(n, &ViewerOutput::InterlacingChanged, this, &ViewerWidget::InterlacingChangedSlot);
disconnect(n, &ViewerOutput::VideoParamsChanged, this, &ViewerWidget::UpdateRendererVideoParameters);
disconnect(n, &ViewerOutput::AudioParamsChanged, this, &ViewerWidget::UpdateRendererAudioParameters);
disconnect(n->video_frame_cache(), &FrameHashCache::Invalidated, this, &ViewerWidget::ViewerInvalidatedVideoRange);
disconnect(n->video_frame_cache(), &FrameHashCache::Shifted, this, &ViewerWidget::ViewerShiftedRange);
disconnect(n->audio_playback_cache(), &FrameHashCache::Invalidated, this, &ViewerWidget::ViewerInvalidatedRange);
disconnect(n, &ViewerOutput::GraphChangedFrom, this, &ViewerWidget::UpdateStack);
ruler()->SetPlaybackCache(nullptr);
// Effectively disables the viewer and clears the state
SizeChangedSlot(0, 0);
SetViewerResolution(0, 0);
display_widget_->DisconnectColorManager();
foreach (ViewerWindow* window, windows_) {
@@ -273,23 +259,9 @@ void ViewerWidget::resizeEvent(QResizeEvent *event)
{
TimeBasedWidget::resizeEvent(event);
/*
int new_div = CalculateDivider();
if (new_div != divider_) {
divider_ = new_div;
UpdateRendererParameters();
}
*/
UpdateMinimumScale();
}
ViewerDisplayWidget *ViewerWidget::display_widget() const
{
return display_widget_;
}
void ViewerWidget::TogglePlayPause()
{
if (IsPlaying()) {
@@ -316,11 +288,6 @@ void ViewerWidget::SetColorMenuEnabled(bool enabled)
color_menu_enabled_ = enabled;
}
void ViewerWidget::SetOverrideSize(int width, int height)
{
SizeChangedSlot(width, height);
}
void ViewerWidget::SetMatrix(const QMatrix4x4 &mat)
{
display_widget_->SetMatrixZoom(mat);
@@ -371,13 +338,23 @@ void ViewerWidget::ForceUpdate()
void ViewerWidget::SetAutoCacheEnabled(bool e)
{
autocache_ = e;
renderer_->SetAutoCachePaused(!e);
}
if (autocache_) {
StartBackgroundCaching();
} else if (cache_background_task_ == our_cache_background_task_) {
StopAllBackgroundCacheTasks(false);
cache_background_task_ = nullptr;
void ViewerWidget::CacheEntireSequence()
{
renderer_->AutoCacheRange(TimeRange(rational(), GetConnectedNode()->video_frame_cache()->GetLength()));
}
void ViewerWidget::CacheSequenceInOut()
{
if (GetConnectedTimelinePoints() && GetConnectedTimelinePoints()->workarea()->enabled()) {
renderer_->AutoCacheRange(GetConnectedTimelinePoints()->workarea()->range());
} else {
QMessageBox::warning(this,
tr("Error"),
tr("No in or out points are set to cache."),
QMessageBox::Ok);
}
}
@@ -387,35 +364,20 @@ void ViewerWidget::SetGizmos(Node *node)
display_widget_->SetGizmos(node);
}
void ViewerWidget::StopAllBackgroundCacheTasks(bool wait)
FramePtr ViewerWidget::DecodeCachedImage(const QString &fn, const rational& time) const
{
if (cache_background_task_) {
if (wait) {
TaskManager::instance()->CancelTaskAndWait(cache_background_task_);
} else {
cache_background_task_->Cancel();
}
cache_background_task_ = nullptr;
}
}
void ViewerWidget::SetBackgroundCacheTask(CacheTask *t)
{
cache_background_task_ = t;
}
FramePtr DecodeCachedImage(const QString &fn, const rational& time)
{
FramePtr frame = FrameHashCache::LoadCacheFrame(fn);
FramePtr frame = GetConnectedNode()->video_frame_cache()->LoadCacheFrame(fn);
if (frame) {
frame->set_timestamp(time);
} else {
qWarning() << "Tried to load cached frame from file but it was null";
}
return frame;
}
void DecodeCachedImage(RenderTicketPtr ticket, const QString &fn, const rational& time)
void ViewerWidget::DecodeCachedImage(RenderTicketPtr ticket, const QString &fn, const rational& time) const
{
ticket->Finish(QVariant::fromValue(DecodeCachedImage(fn, time)));
}
@@ -478,6 +440,13 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only)
return;
}
// Kindly tell all viewers to stop caching
if (pause_autocache_during_playback_) {
foreach (ViewerWidget* viewer, instances_) {
viewer->renderer_->ClearVideoQueue();
}
}
// If the playhead is beyond the end, restart at 0
if (!in_to_out_only && GetTime() >= GetConnectedNode()->GetLength()) {
if (speed > 0) {
@@ -507,14 +476,6 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only)
}
}
if (!busy_) {
busy_ = true;
busy_viewers_++;
}
StopAllBackgroundCacheTasks(false);
cache_wait_timer_.stop();
if (!prequeuing_) {
FinishPlayPreprocess();
}
@@ -544,7 +505,7 @@ void ViewerWidget::PushScrubbedAudio()
{
if (!IsPlaying() && Config::Current()["AudioScrubbing"].toBool()) {
// Get audio src device from renderer
QString audio_fn = GetConnectedNode()->audio_playback_cache()->GetCacheFilename();
QString audio_fn = GetConnectedNode()->audio_playback_cache()->GetPCMFilename();
QFile audio_src(audio_fn);
if (audio_src.open(QFile::ReadOnly)) {
@@ -564,20 +525,6 @@ void ViewerWidget::PushScrubbedAudio()
}
}
/*
int ViewerWidget::CalculateDivider()
{
if (GetConnectedNode() && Config::Current()["AutoSelectDivider"].toBool()) {
int long_side_of_video = qMax(GetConnectedNode()->video_params().width(), GetConnectedNode()->video_params().height());
int long_side_of_widget = qMax(display_widget_->width(), display_widget_->height());
return qMax(1, int(qPow(2, qFloor(log2(double(long_side_of_video) / double(long_side_of_widget))))));
}
return divider_;
}
*/
void ViewerWidget::UpdateMinimumScale()
{
if (!GetConnectedNode()) {
@@ -648,21 +595,23 @@ PixelFormat::Format ViewerWidget::GetCurrentPixelFormat() const
RenderTicketPtr ViewerWidget::GetFrame(const rational &t, bool clear_render_queue)
{
QByteArray cached_hash = GetConnectedNode()->video_frame_cache()->GetHash(t);
if (cached_hash.isEmpty()) {
QString cache_fn = GetConnectedNode()->video_frame_cache()->CachePathName(cached_hash);
if (cached_hash.isEmpty() || !QFileInfo::exists(cache_fn)) {
// Frame hasn't been cached, start render job
if (clear_render_queue) {
renderer_->ClearVideoQueue();
}
return renderer_->RenderFrame(t);
return renderer_->RenderFrame(t, true);
} else {
// Frame has been cached, grab the frame
QString cache_fn = GetConnectedNode()->video_frame_cache()->CachePathName(cached_hash);
RenderTicketPtr ticket = std::make_shared<RenderTicket>(RenderTicket::kTypeVideo,
QVariant::fromValue(t));
QtConcurrent::run(this, &ViewerWidget::DecodeCachedImage, ticket, cache_fn, t);
RenderTicketPtr ticket = std::make_shared<RenderTicket>(RenderTicket::kTypeVideo, TimeRange(t, t));
QtConcurrent::run(DecodeCachedImage, ticket, cache_fn, t);
return ticket;
}
}
@@ -670,7 +619,7 @@ void ViewerWidget::FinishPlayPreprocess()
{
int64_t playback_start_time = ruler()->GetTime();
QString audio_fn = GetConnectedNode()->audio_playback_cache()->GetCacheFilename();
QString audio_fn = GetConnectedNode()->audio_playback_cache()->GetPCMFilename();
if (!audio_fn.isEmpty()) {
AudioManager::instance()->SetOutputParams(GetConnectedNode()->audio_playback_cache()->GetParameters());
AudioManager::instance()->StartOutput(audio_fn,
@@ -755,45 +704,14 @@ void ViewerWidget::ContextMenuSetSafeMargins()
void ViewerWidget::ContextMenuSetCustomSafeMargins()
{
QString s;
bool ok;
forever {
bool ok;
double new_ratio = GetFloatRatioFromUser(this,
tr("Safe Margins"),
&ok);
s = QInputDialog::getText(this,
tr("Safe Margins"),
tr("Enter custom ratio (e.g. \"4:3\", \"16/9\", etc.):"),
QLineEdit::Normal,
s,
&ok);
if (!ok) {
// User cancelled dialog, do nothing
return;
}
QStringList ratio_components = s.split(QRegExp(QStringLiteral(":|;|\\/")));
if (ratio_components.size() == 2) {
bool numer_ok, denom_ok;
double num = ratio_components.at(0).toDouble(&numer_ok);
double den = ratio_components.at(1).toDouble(&denom_ok);
if (numer_ok
&& denom_ok
&& num > 0) {
// Exit loop and set this ratio
context_menu_widget_->SetSafeMargins(ViewerSafeMarginInfo(true, num / den));
return;
}
}
QMessageBox::warning(this,
tr("Invalid custom ratio"),
tr("Failed to parse \"%1\" into an aspect ratio. Please format a "
"rational fraction with a ':' or a '/' separator.").arg(s),
QMessageBox::Ok);
if (ok) {
context_menu_widget_->SetSafeMargins(ViewerSafeMarginInfo(true, new_ratio));
}
}
@@ -825,7 +743,7 @@ void ViewerWidget::RendererGeneratedFrame()
}
}
ticket->deleteLater();
delete ticket;
}
void ViewerWidget::RendererGeneratedFrameForQueue()
@@ -850,76 +768,7 @@ void ViewerWidget::RendererGeneratedFrameForQueue()
}
}
watcher->deleteLater();
}
//#define PRINT_INVALID_RANGES
void ViewerWidget::StartBackgroundCaching()
{
if (busy_) {
busy_viewers_--;
busy_ = false;
}
#ifdef PRINT_INVALID_RANGES
if (GetConnectedNode()->video_frame_cache()->HasInvalidatedRanges()) {
qDebug() << "Video invalid:";
foreach (const TimeRange& r, GetConnectedNode()->video_frame_cache()->GetInvalidatedRanges()) {
qDebug() << " " << r;
}
}
if (GetConnectedNode()->audio_playback_cache()->HasInvalidatedRanges()) {
qDebug() << "Audio invalid:";
foreach (const TimeRange& r, GetConnectedNode()->audio_playback_cache()->GetInvalidatedRanges()) {
qDebug() << " " << r;
}
}
#endif
if (autocache_
&& GetConnectedNode()
&& (GetConnectedNode()->video_frame_cache()->HasInvalidatedRanges()
|| GetConnectedNode()->audio_playback_cache()->HasInvalidatedRanges())) {
if (cache_background_task_ || busy_viewers_) {
// Something else is caching right now, we don't want to do multiple at once so we'll check
// again in our next interval
cache_wait_timer_.start();
} else {
cache_background_task_ = new CacheTask(GetConnectedNode(),
GetConnectedNode()->video_params(),
GetConnectedNode()->audio_params(),
false);
our_cache_background_task_ = cache_background_task_;
TaskManager::instance()->AddTask(cache_background_task_);
}
}
}
void ViewerWidget::BackgroundCacheFinished(Task* t)
{
if (cache_background_task_ == t) {
cache_background_task_ = nullptr;
}
}
void ViewerWidget::UpdateRendererParameters()
{
if (cache_background_task_ == our_cache_background_task_) {
StopAllBackgroundCacheTasks(false);
}
GetConnectedNode()->video_frame_cache()->InvalidateAll();
GetConnectedNode()->audio_playback_cache()->InvalidateAll();
renderer_->SetVideoParams(GetConnectedNode()->video_params());
renderer_->SetAudioParams(GetConnectedNode()->audio_params());
display_widget_->SetVideoParams(GetConnectedNode()->video_params());
delete watcher;
}
void ViewerWidget::ShowContextMenu(const QPoint &pos)
@@ -980,6 +829,16 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos)
connect(full_screen_menu, &QMenu::triggered, this, &ViewerWidget::ContextMenuSetFullScreen);
}
{
// Deinterlace Option
if (GetConnectedNode()->video_params().interlacing() != VideoParams::kInterlaceNone) {
QAction* deinterlace_action = menu.addAction(tr("Deinterlace"));
deinterlace_action->setCheckable(true);
deinterlace_action->setChecked(display_widget_->IsDeinterlacing());
connect(deinterlace_action, &QAction::triggered, display_widget_, &ViewerDisplayWidget::SetDeinterlacing);
}
}
menu.addSeparator();
{
@@ -998,11 +857,34 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos)
menu.addSeparator();
{
Menu* cache_menu = new Menu(tr("Cache"), &menu);
menu.addMenu(cache_menu);
// Auto-cache
QAction* autocache_action = menu.addAction(tr("Auto-Cache"));
QAction* autocache_action = cache_menu->addAction(tr("Auto-Cache"));
autocache_action->setCheckable(true);
autocache_action->setChecked(autocache_);
autocache_action->setChecked(!renderer_->IsAutoCachePaused());
connect(autocache_action, &QAction::triggered, this, &ViewerWidget::SetAutoCacheEnabled);
cache_menu->addSeparator();
// Stop auto-cache while playing
QAction* pause_autocache_while_playing = cache_menu->addAction(tr("Pause Auto-Cache During Playback"));
pause_autocache_while_playing->setCheckable(true);
pause_autocache_while_playing->setChecked(pause_autocache_during_playback_);
connect(pause_autocache_while_playing, &QAction::triggered, this, [this](bool e){
pause_autocache_during_playback_ = e;
});
cache_menu->addSeparator();
// Cache Entire Sequence
QAction* cache_entire_sequence = cache_menu->addAction(tr("Cache Entire Sequence"));
connect(cache_entire_sequence, &QAction::triggered, this, &ViewerWidget::CacheEntireSequence);
// Cache In/Out Sequence
QAction* cache_inout_sequence = cache_menu->addAction(tr("Cache Sequence In/Out"));
connect(cache_inout_sequence, &QAction::triggered, this, &ViewerWidget::CacheSequenceInOut);
}
menu.addSeparator();
@@ -1035,7 +917,7 @@ void ViewerWidget::Play(bool in_to_out_only)
{
if (in_to_out_only) {
if (GetConnectedTimelinePoints()
&& GetConnectedTimelinePoints()->workarea()->enabled()) {
&& GetConnectedTimelinePoints()->workarea()->enabled()) {
// Jump to in point
SetTimeAndSignal(Timecode::time_to_timestamp(GetConnectedTimelinePoints()->workarea()->in(), timebase()));
} else {
@@ -1055,7 +937,7 @@ void ViewerWidget::Pause()
{
PauseInternal();
StartBackgroundCaching();
renderer_->SetAutoCachePlayhead(GetTime());
}
void ViewerWidget::ShuttleLeft()
@@ -1192,7 +1074,7 @@ void ViewerWidget::PlaybackTimerUpdate()
}
}
void ViewerWidget::SizeChangedSlot(int width, int height)
void ViewerWidget::SetViewerResolution(int width, int height)
{
sizer_->SetChildSize(width, height);
@@ -1201,6 +1083,13 @@ void ViewerWidget::SizeChangedSlot(int width, int height)
}
}
void ViewerWidget::SetViewerPixelAspect(const rational &ratio)
{
sizer_->SetPixelAspectRatio(ratio);
// FIXME: Update windows too
}
void ViewerWidget::LengthChangedSlot(const rational &length)
{
if (last_length_ != length) {
@@ -1215,6 +1104,34 @@ void ViewerWidget::LengthChangedSlot(const rational &length)
}
}
void ViewerWidget::InterlacingChangedSlot(VideoParams::Interlacing interlacing)
{
// Automatically set a "sane" deinterlacing option
display_widget_->SetDeinterlacing(interlacing != VideoParams::kInterlaceNone);
// FIXME: Set windows too
}
void ViewerWidget::UpdateRendererVideoParameters()
{
renderer_->ClearVideoQueue();
renderer_->SetVideoParams(GetConnectedNode()->video_params());
GetConnectedNode()->video_frame_cache()->InvalidateAll();
display_widget_->SetVideoParams(GetConnectedNode()->video_params());
}
void ViewerWidget::UpdateRendererAudioParameters()
{
renderer_->ClearAudioQueue();
renderer_->SetAudioParams(GetConnectedNode()->audio_params());
GetConnectedNode()->audio_playback_cache()->InvalidateAll();
}
void ViewerWidget::SetZoomFromMenu(QAction *action)
{
sizer_->SetZoom(action->data().toInt());
@@ -1222,22 +1139,10 @@ void ViewerWidget::SetZoomFromMenu(QAction *action)
void ViewerWidget::ViewerInvalidatedVideoRange(const TimeRange &range)
{
// If our current frame is within this range, we need to update
if (GetTime() >= range.in() && (GetTime() < range.out() || range.in() == range.out())) {
QMetaObject::invokeMethod(this, "ForceUpdate", Qt::QueuedConnection);
}
ViewerInvalidatedRange();
}
void ViewerWidget::ViewerInvalidatedRange()
{
// Restart the cache wait timer
cache_wait_timer_.stop();
StopAllBackgroundCacheTasks(false);
if (!(qApp->mouseButtons() & Qt::LeftButton)) {
StartBackgroundCaching();
}
}
void ViewerWidget::ViewerShiftedRange(const rational &from, const rational &to)
+25 -30
View File
@@ -34,7 +34,6 @@
#include "panel/scope/scope.h"
#include "render/backend/opengl/openglbackend.h"
#include "render/backend/renderticketwatcher.h"
#include "task/cache/cache.h"
#include "viewerdisplay.h"
#include "viewerplaybacktimer.h"
#include "viewerqueue.h"
@@ -74,8 +73,6 @@ public:
*/
void SetColorMenuEnabled(bool enabled);
void SetOverrideSize(int width, int height);
void SetMatrix(const QMatrix4x4& mat);
/**
@@ -97,9 +94,6 @@ public:
void SetGizmos(Node* node);
static void StopAllBackgroundCacheTasks(bool wait);
static void SetBackgroundCacheTask(CacheTask* t);
public slots:
void Play(bool in_to_out_only);
@@ -124,6 +118,14 @@ public slots:
void SetAutoCacheEnabled(bool e);
void CacheEntireSequence();
void CacheSequenceInOut();
void SetViewerResolution(int width, int height);
void SetViewerPixelAspect(const rational& ratio);
signals:
/**
* @brief Wrapper for ViewerGLWidget::CursorColor()
@@ -166,7 +168,10 @@ protected:
PlaybackControls* controls_;
ViewerDisplayWidget* display_widget() const;
ViewerDisplayWidget* display_widget() const
{
return display_widget_;
}
private:
void UpdateTimeInternal(int64_t i);
@@ -179,8 +184,6 @@ private:
void PushScrubbedAudio();
int CalculateDivider();
void UpdateMinimumScale();
void SetColorTransform(const ColorTransform& transform, ViewerDisplayWidget* sender);
@@ -203,6 +206,10 @@ private:
void PopOldestFrameFromPlaybackQueue();
FramePtr DecodeCachedImage(const QString &fn, const rational& time) const;
void DecodeCachedImage(RenderTicketPtr ticket, const QString &fn, const rational& time) const;
QStackedWidget* stack_;
ViewerSizer* sizer_;
@@ -221,6 +228,8 @@ private:
bool play_in_to_out_only_;
bool pause_autocache_during_playback_;
AudioWaveformView* waveform_view_;
QList<ViewerWindow*> windows_;
@@ -241,39 +250,27 @@ private:
QList<RenderTicketWatcher*> nonqueue_watchers_;
QTimer cache_wait_timer_;
bool busy_;
CacheTask* our_cache_background_task_;
rational last_length_;
int prequeue_length_;
bool autocache_;
static CacheTask* cache_background_task_;
static int busy_viewers_;
static QVector<ViewerWidget*> instances_;
private slots:
void PlaybackTimerUpdate();
void SizeChangedSlot(int width, int height);
void LengthChangedSlot(const rational& length);
void UpdateRendererParameters();
void InterlacingChangedSlot(VideoParams::Interlacing interlacing);
void UpdateRendererVideoParameters();
void UpdateRendererAudioParameters();
void ShowContextMenu(const QPoint& pos);
void SetZoomFromMenu(QAction* action);
void ViewerInvalidatedVideoRange(const OLIVE_NAMESPACE::TimeRange &range);
void ViewerInvalidatedRange();
void ViewerShiftedRange(const OLIVE_NAMESPACE::rational& from, const OLIVE_NAMESPACE::rational& to);
void UpdateStack();
@@ -294,9 +291,7 @@ private slots:
void RendererGeneratedFrameForQueue();
void StartBackgroundCaching();
void BackgroundCacheFinished(Task *t);
void ViewerInvalidatedVideoRange(const OLIVE_NAMESPACE::TimeRange &range);
};
+14 -32
View File
@@ -39,10 +39,6 @@
OLIVE_NAMESPACE_ENTER
#ifdef Q_OS_LINUX
bool ViewerDisplayWidget::nouveau_check_done_ = false;
#endif
ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent) :
ManagedDisplayWidget(parent),
signal_cursor_color_(false),
@@ -50,7 +46,8 @@ ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent) :
gizmo_click_(false),
last_loaded_buffer_(nullptr),
zoomed_(false),
hand_tool_clicked_(false)
hand_tool_clicked_(false),
deinterlace_(false)
{
connect(Core::instance(), &Core::ToolChanged, this, &ViewerDisplayWidget::ToolChanged);
// Initilises hand_tool_ based on currently selected tool.
@@ -139,6 +136,12 @@ void ViewerDisplayWidget::SetImage(FramePtr in_buffer)
update();
}
void ViewerDisplayWidget::SetDeinterlacing(bool e)
{
deinterlace_ = e;
update();
}
const ViewerSafeMarginInfo &ViewerDisplayWidget::GetSafeMargin() const
{
return safe_margin_;
@@ -321,21 +324,6 @@ void ViewerDisplayWidget::initializeGL()
ManagedDisplayWidget::initializeGL();
connect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &ViewerDisplayWidget::ContextCleanup, Qt::DirectConnection);
#ifdef Q_OS_LINUX
if (!nouveau_check_done_) {
const char* vendor = reinterpret_cast<const char*>(context()->functions()->glGetString(GL_VENDOR));
if (!strcmp(vendor, "nouveau")) {
// Working with Qt widgets in this function segfaults, so we queue the messagebox for later
QMetaObject::invokeMethod(this,
"ShowNouveauWarning",
Qt::QueuedConnection);
}
nouveau_check_done_ = true;
}
#endif
}
void ViewerDisplayWidget::paintGL()
@@ -353,6 +341,12 @@ void ViewerDisplayWidget::paintGL()
// Bind retrieved texture
f->glBindTexture(GL_TEXTURE_2D, texture_.texture());
// Set some parameters
color_service()->pipeline()->bind();
color_service()->pipeline()->setUniformValue("ove_resolution", texture_.width(), texture_.height());
color_service()->pipeline()->setUniformValue("ove_deinterlace", deinterlace_);
color_service()->pipeline()->release();
// Blit using the color service
color_service()->ProcessOpenGL(true, GetCompleteMatrixFlippedYTranslation());
@@ -432,18 +426,6 @@ rational ViewerDisplayWidget::GetGizmoTime()
return GetAdjustedTime(GetTimeTarget(), gizmos_, time_, NodeParam::kInput);
}
#ifdef Q_OS_LINUX
void ViewerDisplayWidget::ShowNouveauWarning()
{
QMessageBox::warning(this,
tr("Driver Warning"),
tr("Olive has detected your system is using the Nouveau graphics driver.\n\nThis driver is "
"known to have stability and performance issues with Olive. It is highly recommended "
"you install the proprietary NVIDIA driver before continuing to use Olive."),
QMessageBox::Ok);
}
#endif
void ViewerDisplayWidget::ContextCleanup()
{
makeCurrent();
+13 -10
View File
@@ -105,6 +105,11 @@ public:
*/
QPoint TransformViewerSpaceToBufferSpace(QPoint pos);
bool IsDeinterlacing() const
{
return deinterlace_;
}
public slots:
/**
* @brief Set the transformation matrix to draw with
@@ -148,6 +153,11 @@ public slots:
*/
void ToolChanged(Tool::Item tool);
/**
* @brief Enables/disables a basic deinterlace on the viewer
*/
void SetDeinterlacing(bool e);
signals:
/**
* @brief Signal emitted when the user starts dragging from the viewer
@@ -211,9 +221,7 @@ private:
*/
QMatrix4x4 scale_matrix_;
#ifdef Q_OS_LINUX
static bool nouveau_check_done_;
#endif
bool signal_cursor_color_;
@@ -249,19 +257,14 @@ private:
*/
bool hand_tool_clicked_;
bool deinterlace_;
private slots:
/**
* @brief Slot to connect just before the OpenGL context is destroyed to clean up resources
*/
void ContextCleanup();
#ifdef Q_OS_LINUX
/**
* @brief Shows warning messagebox if Nouveau is detected
*/
void ShowNouveauWarning();
#endif
};
OLIVE_NAMESPACE_EXIT
+15 -10
View File
@@ -27,7 +27,9 @@ OLIVE_NAMESPACE_ENTER
ViewerSizer::ViewerSizer(QWidget *parent) :
QWidget(parent),
widget_(nullptr),
aspect_ratio_(0),
width_(0),
height_(0),
pixel_aspect_(1),
zoom_(0)
{
}
@@ -51,11 +53,12 @@ void ViewerSizer::SetChildSize(int width, int height)
width_ = width;
height_ = height;
if (!width_ || !height_) {
aspect_ratio_ = 0;
} else {
aspect_ratio_ = static_cast<double>(width_) / static_cast<double>(height_);
}
UpdateSize();
}
void ViewerSizer::SetPixelAspectRatio(const rational &pixel_aspect)
{
pixel_aspect_ = pixel_aspect;
UpdateSize();
}
@@ -81,7 +84,7 @@ void ViewerSizer::UpdateSize()
}
// If the aspect ratio is 0, the widget is always hidden
if (qIsNull(aspect_ratio_)) {
if (!width_ || !height_) {
widget_->setVisible(false);
return;
}
@@ -91,6 +94,8 @@ void ViewerSizer::UpdateSize()
QSize child_size;
QMatrix4x4 child_matrix;
double sequence_aspect_ratio = static_cast<double>(width_) / static_cast<double>(height_) * pixel_aspect_.toDouble();
if (zoom_ <= 0) {
// If zoom is 0, we auto-fit
@@ -98,12 +103,12 @@ void ViewerSizer::UpdateSize()
child_size = size();
if (our_aspect_ratio > aspect_ratio_) {
if (our_aspect_ratio > sequence_aspect_ratio) {
// This container is wider than the image, scale by height
child_size = QSize(qRound(child_size.height() * aspect_ratio_), height());
child_size = QSize(qRound(child_size.height() * sequence_aspect_ratio), height());
} else {
// This container is taller than the image, scale by width
child_size = QSize(width(), qRound(child_size.width() / aspect_ratio_));
child_size = QSize(width(), qRound(child_size.width() / sequence_aspect_ratio));
}
emit IsZoomed(false);
+7 -4
View File
@@ -24,6 +24,7 @@
#include <QWidget>
#include "common/define.h"
#include "common/rational.h"
OLIVE_NAMESPACE_ENTER
@@ -56,6 +57,11 @@ public:
*/
void SetChildSize(int width, int height);
/**
* @brief Set pixel aspect ratio
*/
void SetPixelAspectRatio(const rational& pixel_aspect);
/**
* @brief Set the zoom value of the child widget
*
@@ -96,10 +102,7 @@ private:
int width_;
int height_;
/**
* @brief Aspect ratio calculated from the size provided by SetChildSize()
*/
double aspect_ratio_;
rational pixel_aspect_;
/**
* @brief Internal zoom value