Merge branch 'master' into otio

This commit is contained in:
itsmattkc
2020-10-22 20:26:16 +11:00
149 changed files with 4350 additions and 2079 deletions
+3 -2
View File
@@ -29,9 +29,10 @@ add_subdirectory(manageddisplay)
add_subdirectory(menu)
add_subdirectory(nodecombobox)
add_subdirectory(nodecopypaste)
add_subdirectory(nodeview)
add_subdirectory(nodeparamview)
add_subdirectory(nodetableview)
add_subdirectory(nodetreeview)
add_subdirectory(nodeparamview)
add_subdirectory(nodeview)
add_subdirectory(panel)
add_subdirectory(path)
add_subdirectory(pixelsampler)
+15 -19
View File
@@ -35,6 +35,7 @@ const int kMaximumSmoothness = 8;
AudioMonitor::AudioMonitor(QWidget *parent) :
QOpenGLWidget(parent),
file_(nullptr),
cached_channels_(0)
{
values_.resize(kMaximumSmoothness);
@@ -45,11 +46,6 @@ AudioMonitor::AudioMonitor(QWidget *parent) :
connect(AudioManager::instance(), &AudioManager::Stopped, this, &AudioMonitor::Stop);
}
AudioMonitor::~AudioMonitor()
{
Stop();
}
void AudioMonitor::SetParams(const AudioParams &params)
{
params_ = params;
@@ -63,18 +59,19 @@ void AudioMonitor::SetParams(const AudioParams &params)
peaked_.fill(false);
}
void AudioMonitor::OutputDeviceSet(const QString &filename, qint64 offset, int playback_speed)
void AudioMonitor::OutputDeviceSet(AudioPlaybackCache *cache, qint64 offset, int playback_speed)
{
Stop();
file_.setFileName(filename);
file_ = cache->CreatePlaybackDevice(this);
if (!file_.open(QFile::ReadOnly)) {
qWarning() << "Failed to open" << filename;
if (!file_->open(QFile::ReadOnly)) {
qWarning() << "Failed to open IO device for AudioMonitor display";
Stop();
return;
}
file_.seek(offset);
file_->seek(offset);
playback_speed_ = playback_speed;
@@ -85,9 +82,8 @@ void AudioMonitor::OutputDeviceSet(const QString &filename, qint64 offset, int p
void AudioMonitor::Stop()
{
if (file_.isOpen()) {
file_.close();
}
delete file_;
file_ = nullptr;
}
void AudioMonitor::OutputPushed(const QByteArray &d)
@@ -215,7 +211,7 @@ void AudioMonitor::paintGL()
QVector<double> v(params_.channel_count(), 0);
if (file_.isOpen()) {
if (file_) {
UpdateValuesFromFile(v);
}
@@ -258,7 +254,7 @@ void AudioMonitor::paintGL()
}
}
if (all_zeroes && !file_.isOpen()) {
if (all_zeroes && !file_) {
// Optimize by disabling the update loop
SetUpdateLoop(false);
}
@@ -287,18 +283,18 @@ void AudioMonitor::UpdateValuesFromFile(QVector<double>& v)
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());
bytes_to_read = qMin(bytes_to_read, file_->pos());
file_.seek(file_.pos() - bytes_to_read);
file_->seek(file_->pos() - bytes_to_read);
}
// Read bytes in from file
QByteArray b = file_.read(bytes_to_read);
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);
file_->seek(file_->pos() - bytes_to_read);
}
// If speed is not 1, transform it here
+3 -4
View File
@@ -27,6 +27,7 @@
#include "common/define.h"
#include "render/audioparams.h"
#include "render/audioplaybackcache.h"
OLIVE_NAMESPACE_ENTER
@@ -36,12 +37,10 @@ class AudioMonitor : public QOpenGLWidget
public:
AudioMonitor(QWidget* parent = nullptr);
virtual ~AudioMonitor() override;
public slots:
void SetParams(const AudioParams& params);
void OutputDeviceSet(const QString& filename, qint64 offset, int playback_speed);
void OutputDeviceSet(AudioPlaybackCache* cache, qint64 offset, int playback_speed);
void Stop();
@@ -66,7 +65,7 @@ private:
AudioParams params_;
QFile file_;
QIODevice* file_;
qint64 last_time_;
int playback_speed_;
+110 -117
View File
@@ -60,19 +60,58 @@ void CurveView::Clear()
lines_.clear();
}
void CurveView::SetTrackCount(int count)
void CurveView::ConnectInput(NodeInput *input)
{
track_count_ = count;
if (connected_inputs_.contains(input)) {
// Input wasn't connected, do nothing
return;
}
track_visible_.resize(track_count_);
track_visible_.fill(true);
// Add keyframes from this input
foreach (const NodeInput::KeyframeTrack& track, input->keyframe_tracks()) {
foreach (NodeKeyframePtr key, track) {
this->AddKeyframe(key);
}
if (!keyframe_colors_.contains(&track)) {
// Generate a random color for this input
keyframe_colors_.insert(&track, QColor::fromHsv(std::rand()%360, std::rand()%255, 255));
}
}
// Append to the list
connected_inputs_.append(input);
// Connect add/remove signals
connect(input, &NodeInput::KeyframeAdded, this, &CurveView::AddKeyframe);
connect(input, &NodeInput::KeyframeRemoved, this, &CurveView::RemoveKeyframe);
}
void CurveView::SetTrackVisible(int track, bool visible)
void CurveView::DisconnectNode(Node *node)
{
track_visible_[track] = visible;
QList<NodeInput*> inputs = node->GetInputsIncludingArrays();
SetKeyframeTrackVisible(track, visible);
foreach (NodeInput* i, inputs) {
DisconnectInput(i);
}
}
void CurveView::DisconnectInput(NodeInput *input)
{
if (!connected_inputs_.contains(input)) {
// Input wasn't connected, do nothing
return;
}
// Remove keyframes belonging to this input
RemoveKeyframesOfInput(input);
// Remove from the list
connected_inputs_.removeOne(input);
// Disconnect add/remove signals
disconnect(input, &NodeInput::KeyframeAdded, this, &CurveView::AddKeyframe);
disconnect(input, &NodeInput::KeyframeRemoved, this, &CurveView::RemoveKeyframe);
}
void CurveView::drawBackground(QPainter *painter, const QRectF &rect)
@@ -126,87 +165,85 @@ void CurveView::drawBackground(QPainter *painter, const QRectF &rect)
painter->drawLines(lines);
// Draw keyframe lines
foreach (NodeInput* input, connected_inputs_) {
if (input->is_keyframing()) {
foreach (const NodeInput::KeyframeTrack& track, input->keyframe_tracks()) {
if (!track.isEmpty()) {
painter->setPen(QPen(keyframe_colors_.value(&track), qMax(1, fontMetrics().height() / 4)));
for (int j=0;j<track_count_;j++) {
if (!track_visible_.at(j)) {
continue;
}
QVector<QLineF> keyframe_lines;
painter->setPen(QPen(GetKeyframeColor(j), qMax(1, fontMetrics().height() / 4)));
QList<NodeKeyframe*> keys = GetKeyframesSortedByTime(j);
// Draw straight line leading to first keyframe
QPointF first_key_pos = item_map().value(track.first().get())->pos();
keyframe_lines.append(QLineF(QPointF(scene_bottom_left.x(), first_key_pos.y()), first_key_pos));
if (!keys.isEmpty()) {
QVector<QLineF> keyframe_lines;
// Draw lines between each keyframe
for (int i=1;i<track.size();i++) {
NodeKeyframePtr before = track.at(i-1);
NodeKeyframePtr after = track.at(i);
// Draw straight line leading to first keyframe
QPointF first_key_pos = item_map().value(keys.first())->pos();
keyframe_lines.append(QLineF(QPointF(scene_bottom_left.x(), first_key_pos.y()), first_key_pos));
KeyframeViewItem* before_item = item_map().value(before.get());
KeyframeViewItem* after_item = item_map().value(after.get());
// Draw lines between each keyframe
for (int i=1;i<keys.size();i++) {
NodeKeyframe* before = keys.at(i-1);
NodeKeyframe* after = keys.at(i);
if (before->type() == NodeKeyframe::kHold) {
// Draw a hold keyframe (basically a right angle)
keyframe_lines.append(QLineF(before_item->pos().x(),
before_item->pos().y(),
after_item->pos().x(),
before_item->pos().y()));
keyframe_lines.append(QLineF(after_item->pos().x(),
before_item->pos().y(),
after_item->pos().x(),
after_item->pos().y()));
} else if (before->type() == NodeKeyframe::kBezier && after->type() == NodeKeyframe::kBezier) {
// Draw a cubic bezier
KeyframeViewItem* before_item = item_map().value(before);
KeyframeViewItem* after_item = item_map().value(after);
// Cubic beziers have two control points, so we can just use both
QPointF before_control_point = before_item->pos() + ScalePoint(before->bezier_control_out());
QPointF after_control_point = after_item->pos() + ScalePoint(after->bezier_control_in());
if (before->type() == NodeKeyframe::kHold) {
// Draw a hold keyframe (basically a right angle)
keyframe_lines.append(QLineF(before_item->pos().x(),
before_item->pos().y(),
after_item->pos().x(),
before_item->pos().y()));
keyframe_lines.append(QLineF(after_item->pos().x(),
before_item->pos().y(),
after_item->pos().x(),
after_item->pos().y()));
} else if (before->type() == NodeKeyframe::kBezier && after->type() == NodeKeyframe::kBezier) {
// Draw a cubic bezier
QPainterPath path;
path.moveTo(before_item->pos());
path.cubicTo(before_control_point, after_control_point, after_item->pos());
painter->drawPath(path);
// Cubic beziers have two control points, so we can just use both
QPointF before_control_point = before_item->pos() + ScalePoint(before->bezier_control_out());
QPointF after_control_point = after_item->pos() + ScalePoint(after->bezier_control_in());
} else if (before->type() == NodeKeyframe::kBezier || after->type() == NodeKeyframe::kBezier) {
// Draw a quadratic bezier
QPainterPath path;
path.moveTo(before_item->pos());
path.cubicTo(before_control_point, after_control_point, after_item->pos());
painter->drawPath(path);
// Quadratic beziers have a single control point, we just have to determine which it is
QPointF key_anchor;
QPointF control_point;
} else if (before->type() == NodeKeyframe::kBezier || after->type() == NodeKeyframe::kBezier) {
// Draw a quadratic bezier
if (before->type() == NodeKeyframe::kBezier) {
key_anchor = before_item->pos();
control_point = before->bezier_control_out();
} else {
key_anchor = after_item->pos();
control_point = after->bezier_control_in();
}
// Quadratic beziers have a single control point, we just have to determine which it is
QPointF key_anchor;
QPointF control_point;
// Scale control point
control_point = key_anchor + ScalePoint(control_point);
if (before->type() == NodeKeyframe::kBezier) {
key_anchor = before_item->pos();
control_point = before->bezier_control_out();
} else {
key_anchor = after_item->pos();
control_point = after->bezier_control_in();
// Create the path from both keyframes
QPainterPath path;
path.moveTo(before_item->pos());
path.quadTo(control_point, after_item->pos());
painter->drawPath(path);
} else {
// Linear to linear
keyframe_lines.append(QLineF(before_item->pos(), after_item->pos()));
}
}
// Scale control point
control_point = key_anchor + ScalePoint(control_point);
// Draw straight line leading from end keyframe
QPointF last_key_pos = item_map().value(track.last().get())->pos();
keyframe_lines.append(QLineF(last_key_pos, QPointF(scene_top_right.x(), last_key_pos.y())));
// Create the path from both keyframes
QPainterPath path;
path.moveTo(before_item->pos());
path.quadTo(control_point, after_item->pos());
painter->drawPath(path);
} else {
// Linear to linear
keyframe_lines.append(QLineF(before_item->pos(), after_item->pos()));
painter->drawLines(keyframe_lines);
}
}
// Draw straight line leading from end keyframe
QPointF last_key_pos = item_map().value(keys.last())->pos();
keyframe_lines.append(QLineF(last_key_pos, QPointF(scene_top_right.x(), last_key_pos.y())));
painter->drawLines(keyframe_lines);
}
}
@@ -269,37 +306,6 @@ void CurveView::ContextMenuEvent(Menu &m)
//QAction* reset_zoom_action = m.addAction(tr("Reset Zoom"));
}
QList<NodeKeyframe *> CurveView::GetKeyframesSortedByTime(int track)
{
QList<NodeKeyframe *> sorted;
QMap<NodeKeyframe*, KeyframeViewItem*>::const_iterator iterator;
for (iterator=item_map().begin();iterator!=item_map().end();iterator++) {
NodeKeyframe* key = iterator.key();
if (key->track() != track) {
continue;
}
bool inserted = false;
for (int i=0;i<sorted.size();i++) {
if (sorted.at(i)->time() > key->time()) {
sorted.insert(i, key);
inserted = true;
break;
}
}
if (!inserted) {
sorted.append(key);
}
}
return sorted;
}
qreal CurveView::GetItemYFromKeyframeValue(NodeKeyframe *key)
{
return GetItemYFromKeyframeValue(key->value().toDouble());
@@ -334,17 +340,6 @@ void CurveView::CreateBezierControlPoints(KeyframeViewItem* item)
connect(bezier_out_pt, &QObject::destroyed, this, &CurveView::BezierControlPointDestroyed, Qt::DirectConnection);
}
QColor CurveView::GetKeyframeColor(int track) const
{
if (track_count_) {
QColor c;
c.setHsvF(static_cast<double>(track) / static_cast<double>(track_count_), 0.5, 1.0);
return c;
}
return palette().text().color();
}
void CurveView::KeyframeValueChanged()
{
NodeKeyframe* key = static_cast<NodeKeyframe*>(sender());
@@ -396,15 +391,13 @@ void CurveView::ZoomToFit()
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++) {
for (auto i=item_map().constBegin(); i!=item_map().constEnd(); i++) {
rational transformed_time = GetAdjustedTime(i.key()->parent()->parentNode(),
GetTimeTarget(),
i.key()->time(),
@@ -432,7 +425,7 @@ void CurveView::AddKeyframe(NodeKeyframePtr key)
{
KeyframeViewItem* item = AddKeyframeInternal(key);
SetItemYFromKeyframeValue(key.get(), item);
item->SetOverrideBrush(GetKeyframeColor(key->track()));
item->SetOverrideBrush(keyframe_colors_.value(&key->parent()->keyframe_tracks().at(key->track())));
connect(key.get(), &NodeKeyframe::ValueChanged, this, &CurveView::KeyframeValueChanged);
connect(key.get(), &NodeKeyframe::TypeChanged, this, &CurveView::KeyframeTypeChanged);
+6 -8
View File
@@ -38,9 +38,11 @@ public:
virtual void Clear() override;
void SetTrackCount(int count);
void ConnectInput(NodeInput* input);
void SetTrackVisible(int track, bool visible);
void DisconnectNode(Node* node);
void DisconnectInput(NodeInput* input);
public slots:
void AddKeyframe(NodeKeyframePtr key);
@@ -61,8 +63,6 @@ protected:
virtual void ContextMenuEvent(Menu &m) override;
private:
QList<NodeKeyframe*> GetKeyframesSortedByTime(int track);
qreal GetItemYFromKeyframeValue(NodeKeyframe* key);
qreal GetItemYFromKeyframeValue(double value);
@@ -74,7 +74,7 @@ private:
void CreateBezierControlPoints(KeyframeViewItem *item);
QColor GetKeyframeColor(int track) const;
QMap<const NodeInput::KeyframeTrack*, QColor> keyframe_colors_;
int text_padding_;
@@ -84,9 +84,7 @@ private:
QList<BezierControlPointItem*> bezier_control_points_;
QVector<bool> track_visible_;
int track_count_;
QList<NodeInput*> connected_inputs_;
private slots:
void KeyframeValueChanged();
+70 -97
View File
@@ -23,6 +23,7 @@
#include <QEvent>
#include <QLabel>
#include <QScrollBar>
#include <QSplitter>
#include <QVBoxLayout>
#include "core.h"
@@ -34,12 +35,23 @@
OLIVE_NAMESPACE_ENTER
CurveWidget::CurveWidget(QWidget *parent) :
TimeBasedWidget(parent),
input_(nullptr),
bridge_(nullptr)
TimeBasedWidget(parent)
{
QVBoxLayout* layout = new QVBoxLayout(this);
QHBoxLayout* outer_layout = new QHBoxLayout(this);
QSplitter* splitter = new QSplitter();
outer_layout->addWidget(splitter);
tree_view_ = new NodeTreeView();
tree_view_->SetOnlyShowKeyframable(true);
connect(tree_view_, &NodeTreeView::NodeEnableChanged, this, &CurveWidget::NodeEnabledChanged);
connect(tree_view_, &NodeTreeView::InputEnableChanged, this, &CurveWidget::InputEnabledChanged);
splitter->addWidget(tree_view_);
QWidget* workarea = new QWidget();
QVBoxLayout* layout = new QVBoxLayout(workarea);
layout->setMargin(0);
splitter->addWidget(workarea);
QHBoxLayout* top_controls = new QHBoxLayout();
@@ -92,12 +104,8 @@ CurveWidget::CurveWidget(QWidget *parent) :
view_->setHorizontalScrollBar(scrollbar());
connect(view_->horizontalScrollBar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll);
widget_bridge_layout_ = new QHBoxLayout();
widget_bridge_layout_->addStretch();
input_label_ = new QLabel();
widget_bridge_layout_->addWidget(input_label_);
widget_bridge_layout_->addStretch();
layout->addLayout(widget_bridge_layout_);
// Disable collapsing the main curve view (but allow collapsing the tree)
splitter->setCollapsible(1, false);
SetScale(120.0);
}
@@ -108,71 +116,6 @@ CurveWidget::~CurveWidget()
view_->Clear();
}
NodeInput *CurveWidget::GetInput() const
{
return input_;
}
void CurveWidget::SetInput(NodeInput *input)
{
if (bridge_) {
foreach (QWidget* bridge_widget, bridge_->widgets()) {
bridge_widget->deleteLater();
}
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);
}
view_->Clear();
input_ = input;
key_control_->SetInput(input_);
if (input_) {
view_->SetTrackCount(input_->get_number_of_keyframe_tracks());
bridge_ = new NodeParamViewWidgetBridge(input_, this);
bridge_->SetTimeTarget(GetTimeTarget());
for (int i=0;i<bridge_->widgets().size();i++) {
// Insert between two stretches to center the widget
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);
connect(input_, &NodeInput::KeyframeRemoved, view_, &CurveView::RemoveKeyframe);
foreach (const NodeInput::KeyframeTrack& track, input_->keyframe_tracks()) {
foreach (NodeKeyframePtr key, track) {
view_->AddKeyframe(key);
}
}
}
UpdateInputLabel();
QMetaObject::invokeMethod(view_, "ZoomToFit", Qt::QueuedConnection);
}
const double &CurveWidget::GetVerticalScale()
{
return view_->GetYScale();
@@ -188,12 +131,26 @@ void CurveWidget::DeleteSelected()
view_->DeleteSelected();
}
void CurveWidget::changeEvent(QEvent *e)
void CurveWidget::SetNodes(const QList<Node *> &nodes)
{
if (e->type() == QEvent::LanguageChange) {
UpdateInputLabel();
tree_view_->SetNodes(nodes);
// Detect removed nodes
foreach (Node* n, nodes_) {
if (!nodes.contains(n)) {
view_->DisconnectNode(n);
}
}
QWidget::changeEvent(e);
// Detect added nodes
foreach (Node* n, nodes) {
if (tree_view_->IsNodeEnabled(n) && !nodes_.contains(n)) {
ConnectNode(n);
}
}
// Save new node list
nodes_ = nodes;
}
void CurveWidget::TimeChangedEvent(const int64_t &timestamp)
@@ -223,10 +180,6 @@ void CurveWidget::TimeTargetChangedEvent(Node *target)
key_control_->SetTimeTarget(target);
view_->SetTimeTarget(target);
if (bridge_) {
bridge_->SetTimeTarget(target);
}
}
void CurveWidget::ConnectedNodeChanged(ViewerOutput *n)
@@ -234,15 +187,6 @@ void CurveWidget::ConnectedNodeChanged(ViewerOutput *n)
SetTimeTarget(n);
}
void CurveWidget::UpdateInputLabel()
{
if (input_) {
input_label_->setText(QStringLiteral("%1 :: %2:").arg(input_->parentNode()->Name(), input_->name()));
} else {
input_label_->clear();
}
}
void CurveWidget::SetKeyframeButtonEnabled(bool enable)
{
linear_button_->setEnabled(enable);
@@ -266,15 +210,26 @@ void CurveWidget::SetKeyframeButtonCheckedFromType(NodeKeyframe::Type type)
void CurveWidget::UpdateBridgeTime(const int64_t &timestamp)
{
if (!input_) {
return;
}
rational time = Timecode::timestamp_to_time(timestamp, view_->timebase());
bridge_->SetTime(time);
key_control_->SetTime(time);
}
void CurveWidget::ConnectNode(Node *n)
{
QList<NodeInput*> inputs = n->GetInputsIncludingArrays();
foreach (NodeInput* i, inputs) {
if (tree_view_->IsInputEnabled(i)) {
view_->ConnectInput(i);
}
}
}
void CurveWidget::DisconnectNode(Node *n)
{
view_->DisconnectNode(n);
}
void CurveWidget::SelectionChanged()
{
QList<QGraphicsItem*> selected = view_->scene()->selectedItems();
@@ -349,4 +304,22 @@ void CurveWidget::KeyControlRequestedTimeChanged(const rational &time)
SetTimeAndSignal(Timecode::time_to_timestamp(time, view_->timebase()));
}
void CurveWidget::NodeEnabledChanged(Node* n, bool e)
{
if (e) {
ConnectNode(n);
} else {
DisconnectNode(n);
}
}
void CurveWidget::InputEnabledChanged(NodeInput *i, bool e)
{
if (e) {
view_->ConnectInput(i);
} else {
view_->DisconnectInput(i);
}
}
OLIVE_NAMESPACE_EXIT
+16 -15
View File
@@ -30,6 +30,7 @@
#include "node/input.h"
#include "widget/nodeparamview/nodeparamviewkeyframecontrol.h"
#include "widget/nodeparamview/nodeparamviewwidgetbridge.h"
#include "widget/nodetreeview/nodetreeview.h"
#include "widget/timebased/timebased.h"
OLIVE_NAMESPACE_ENTER
@@ -42,17 +43,15 @@ public:
virtual ~CurveWidget() override;
NodeInput* GetInput() const;
void SetInput(NodeInput* input);
const double& GetVerticalScale();
void SetVerticalScale(const double& vscale);
void DeleteSelected();
protected:
virtual void changeEvent(QEvent *) override;
public slots:
void SetNodes(const QList<Node*>& nodes);
protected:
virtual void TimeChangedEvent(const int64_t &) override;
virtual void TimebaseChangedEvent(const rational &) override;
virtual void ScaleChangedEvent(const double &) override;
@@ -62,8 +61,6 @@ protected:
virtual void ConnectedNodeChanged(ViewerOutput* n) override;
private:
void UpdateInputLabel();
void SetKeyframeButtonEnabled(bool enable);
void SetKeyframeButtonChecked(bool checked);
@@ -72,6 +69,12 @@ private:
void UpdateBridgeTime(const int64_t& timestamp);
void ConnectNode(Node* n);
void DisconnectNode(Node* n);
NodeTreeView* tree_view_;
QPushButton* linear_button_;
QPushButton* bezier_button_;
@@ -80,18 +83,12 @@ private:
CurveView* view_;
NodeInput* input_;
QLabel* input_label_;
QHBoxLayout* widget_bridge_layout_;
NodeParamViewWidgetBridge* bridge_;
NodeParamViewKeyframeControl* key_control_;
QList<QCheckBox*> checkboxes_;
QList<Node*> nodes_;
private slots:
void SelectionChanged();
@@ -99,6 +96,10 @@ private slots:
void KeyControlRequestedTimeChanged(const rational& time);
void NodeEnabledChanged(Node* n, bool e);
void InputEnabledChanged(NodeInput* i, bool e);
};
OLIVE_NAMESPACE_EXIT
+9 -29
View File
@@ -79,10 +79,15 @@ 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);
}
RemoveKeyframesOfInput(i);
}
}
void KeyframeViewBase::RemoveKeyframesOfInput(NodeInput *i)
{
foreach (const NodeInput::KeyframeTrack& track, i->keyframe_tracks()) {
foreach (NodeKeyframePtr key, track) {
RemoveKeyframe(key);
}
}
}
@@ -104,10 +109,6 @@ KeyframeViewItem *KeyframeViewBase::AddKeyframeInternal(NodeKeyframePtr key)
item->SetScale(GetScale());
item_map_.insert(key.get(), item);
scene()->addItem(item);
if (hidden_tracks_.contains(key->track())) {
item->setVisible(false);
}
}
return item;
@@ -301,27 +302,6 @@ void KeyframeViewBase::TimeTargetChangedEvent(Node *target)
}
}
void KeyframeViewBase::SetKeyframeTrackVisible(int track, bool visible)
{
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)
+2 -4
View File
@@ -42,6 +42,8 @@ public:
void RemoveKeyframesOfNode(Node* n);
void RemoveKeyframesOfInput(NodeInput* i);
public slots:
void RemoveKeyframe(NodeKeyframePtr key);
@@ -60,8 +62,6 @@ protected:
virtual void TimeTargetChangedEvent(Node*) override;
void SetKeyframeTrackVisible(int track, bool visible);
virtual void ContextMenuEvent(Menu &m);
private:
@@ -96,8 +96,6 @@ private:
bool currently_autoselecting_;
QList<int> hidden_tracks_;
private slots:
void ShowContextMenu();
+6 -8
View File
@@ -100,16 +100,14 @@ void Menu::ConformItem(QAction *a, const QString &id, const QString &key)
}
}
void Menu::SetBooleanAction(QAction *a, bool* boolean)
{
// FIXME: Connect to some boolean function
a->setCheckable(true);
a->setChecked(*boolean);
a->setProperty("boolptr", reinterpret_cast<quintptr>(boolean));
}
void Menu::Init()
{
// HACK: Disables embossing on disabled text for a slightly nicer UI
QPalette p = palette();
p.setColor(QPalette::Disabled, QPalette::Light, QColor(0, 0, 0, 0));
setPalette(p);
// If a native palette is available, it should override the one above
StyleManager::UseOSNativeStyling(this);
}
-2
View File
@@ -226,8 +226,6 @@ public:
const QString& id,
const QString& key = QString());
static void SetBooleanAction(QAction* a, bool *boolean);
private:
void Init();
+67
View File
@@ -58,6 +58,34 @@ MenuShared::MenuShared()
clip_enable_disable_item_ = Menu::CreateItem(this, "enabledisable", this, &MenuShared::EnableDisableTriggered, "Shift+E");
clip_nest_item_ = Menu::CreateItem(this, "nest", this, &MenuShared::NestTriggered);
// TimeRuler menu shared items
frame_view_mode_group_ = new QActionGroup(this);
view_timecode_view_dropframe_item_ = Menu::CreateItem(this, "modedropframe", this, &MenuShared::TimecodeDisplayTriggered);
view_timecode_view_dropframe_item_->setData(Timecode::kTimecodeDropFrame);
view_timecode_view_dropframe_item_->setCheckable(true);
frame_view_mode_group_->addAction(view_timecode_view_dropframe_item_);
view_timecode_view_nondropframe_item_ = Menu::CreateItem(this, "modenondropframe", this, &MenuShared::TimecodeDisplayTriggered);
view_timecode_view_nondropframe_item_->setData(Timecode::kTimecodeNonDropFrame);
view_timecode_view_nondropframe_item_->setCheckable(true);
frame_view_mode_group_->addAction(view_timecode_view_nondropframe_item_);
view_timecode_view_seconds_item_ = Menu::CreateItem(this, "modeseconds", this, &MenuShared::TimecodeDisplayTriggered);
view_timecode_view_seconds_item_->setData(Timecode::kTimecodeSeconds);
view_timecode_view_seconds_item_->setCheckable(true);
frame_view_mode_group_->addAction(view_timecode_view_seconds_item_);
view_timecode_view_frames_item_ = Menu::CreateItem(this, "modeframes", this, &MenuShared::TimecodeDisplayTriggered);
view_timecode_view_frames_item_->setData(Timecode::kFrames);
view_timecode_view_frames_item_->setCheckable(true);
frame_view_mode_group_->addAction(view_timecode_view_frames_item_);
view_timecode_view_milliseconds_item_ = Menu::CreateItem(this, "milliseconds", this, &MenuShared::TimecodeDisplayTriggered);
view_timecode_view_milliseconds_item_->setData(Timecode::kMilliseconds);
view_timecode_view_milliseconds_item_->setCheckable(true);
frame_view_mode_group_->addAction(view_timecode_view_milliseconds_item_);
Retranslate();
}
@@ -117,6 +145,26 @@ void MenuShared::AddItemsForClipEditMenu(Menu *m)
m->addAction(clip_nest_item_);
}
void MenuShared::AddItemsForTimeRulerMenu(Menu *m)
{
m->addAction(view_timecode_view_dropframe_item_);
m->addAction(view_timecode_view_nondropframe_item_);
m->addAction(view_timecode_view_seconds_item_);
m->addAction(view_timecode_view_frames_item_);
m->addAction(view_timecode_view_milliseconds_item_);
}
void MenuShared::AboutToShowTimeRulerActions()
{
QList<QAction*> timecode_display_actions = frame_view_mode_group_->actions();
foreach (QAction* a, timecode_display_actions) {
if (a->data() == Core::instance()->GetTimecodeDisplay()) {
a->setChecked(true);
break;
}
}
}
MenuShared *MenuShared::instance()
{
return instance_;
@@ -211,6 +259,18 @@ void MenuShared::DefaultTransitionTriggered()
qDebug() << "FIXME: Stub";
}
void MenuShared::TimecodeDisplayTriggered()
{
// Assume the sender is a QAction
QAction* action = static_cast<QAction*>(sender());
// Assume its data() is a member of Timecode::Display
Timecode::Display display = static_cast<Timecode::Display>(action->data().toInt());
// Set the current display mode
Core::instance()->SetTimecodeDisplay(display);
}
void MenuShared::Retranslate()
{
// "New" menu shared items
@@ -240,6 +300,13 @@ void MenuShared::Retranslate()
clip_link_unlink_item_->setText(tr("Link/Unlink"));
clip_enable_disable_item_->setText(tr("Enable/Disable"));
clip_nest_item_->setText(tr("Nest"));
// TimeRuler menu shared items
view_timecode_view_frames_item_->setText(tr("Frames"));
view_timecode_view_dropframe_item_->setText(tr("Drop Frame"));
view_timecode_view_nondropframe_item_->setText(tr("Non-Drop Frame"));
view_timecode_view_milliseconds_item_->setText(tr("Milliseconds"));
view_timecode_view_seconds_item_->setText(tr("Seconds"));
}
OLIVE_NAMESPACE_EXIT
+19
View File
@@ -42,6 +42,9 @@ public:
void AddItemsForEditMenu(Menu* m, bool for_clips);
void AddItemsForInOutMenu(Menu* m);
void AddItemsForClipEditMenu(Menu* m);
void AddItemsForTimeRulerMenu(Menu* m);
void AboutToShowTimeRulerActions();
static MenuShared* instance();
@@ -74,6 +77,14 @@ private:
QAction* clip_enable_disable_item_;
QAction* clip_nest_item_;
// TimeRuler menu shared items
QActionGroup* frame_view_mode_group_;
QAction* view_timecode_view_dropframe_item_;
QAction* view_timecode_view_nondropframe_item_;
QAction* view_timecode_view_seconds_item_;
QAction* view_timecode_view_frames_item_;
QAction* view_timecode_view_milliseconds_item_;
static MenuShared* instance_;
private slots:
@@ -111,6 +122,14 @@ private slots:
void DefaultTransitionTriggered();
/**
* @brief A slot for the timecode display menu items
*
* Assumes a QAction* sender() and its data() is a member of enum Timecode::Display. Uses the data() to signal a
* timecode change throughout the rest of the application.
*/
void TimecodeDisplayTriggered();
};
OLIVE_NAMESPACE_EXIT
+184 -35
View File
@@ -20,6 +20,7 @@
#include "nodeparamview.h"
#include <QApplication>
#include <QScrollArea>
#include <QScrollBar>
#include <QSplitter>
@@ -31,7 +32,8 @@ OLIVE_NAMESPACE_ENTER
NodeParamView::NodeParamView(QWidget *parent) :
TimeBasedWidget(true, false, parent),
last_scroll_val_(0)
last_scroll_val_(0),
focused_node_(nullptr)
{
// Create horizontal layout to place scroll area in (and keyframe editing eventually)
QHBoxLayout* layout = new QHBoxLayout(this);
@@ -49,18 +51,26 @@ NodeParamView::NodeParamView(QWidget *parent) :
splitter->addWidget(scroll_area);
// Param widget
param_widget_area_ = new QWidget();
scroll_area->setWidget(param_widget_area_);
param_widget_container_ = new NodeParamViewParamContainer();
connect(param_widget_container_, &NodeParamViewParamContainer::Resized, this, &NodeParamView::UpdateGlobalScrollBar);
scroll_area->setWidget(param_widget_container_);
// Set up scroll area layout
param_layout_ = new QVBoxLayout(param_widget_area_);
param_layout_->setSpacing(0);
param_widget_area_ = new QMainWindow();
// KeyframeView is offset by a ruler, so to stay synchronized with it, we should be too
param_layout_->setContentsMargins(0, ruler()->height(), 0, 0);
// Disable dock widgets from tabbing and disable glitchy animations
param_widget_area_->setDockOptions(static_cast<QMainWindow::DockOption>(0));
// Add a stretch to allow empty space at the bottom of the layout
param_layout_->addStretch();
// HACK: Hide the main window separators (unfortunately the cursors still appear)
param_widget_area_->setStyleSheet(QStringLiteral("QMainWindow::separator {background: rgba(0, 0, 0, 0)}"));
QVBoxLayout* param_widget_container_layout = new QVBoxLayout(param_widget_container_);
QMargins param_widget_margin = param_widget_container_layout->contentsMargins();
param_widget_margin.setTop(ruler()->height());
param_widget_container_layout->setContentsMargins(param_widget_margin);
param_widget_container_layout->setSpacing(0);
param_widget_container_layout->addWidget(param_widget_area_);
param_widget_container_layout->addStretch(INT_MAX);
// Set up keyframe view
QWidget* keyframe_area = new QWidget();
@@ -100,9 +110,6 @@ NodeParamView::NodeParamView(QWidget *parent) :
layout->addWidget(vertical_scrollbar_);
// Connect scrollbars together
connect(scroll_area->verticalScrollBar(), &QScrollBar::rangeChanged, vertical_scrollbar_, &QScrollBar::setRange);
connect(scroll_area->verticalScrollBar(), &QScrollBar::rangeChanged, this, &NodeParamView::ForceKeyframeViewToScroll);
connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue);
connect(keyframe_view_->verticalScrollBar(), &QScrollBar::valueChanged, scroll_area->verticalScrollBar(), &QScrollBar::setValue);
connect(scroll_area->verticalScrollBar(), &QScrollBar::valueChanged, vertical_scrollbar_, &QScrollBar::setValue);
@@ -120,46 +127,89 @@ NodeParamView::NodeParamView(QWidget *parent) :
SetScale(120);
SetMaximumScale(TimelineViewBase::kMaximumScale);
// Pickup on widget focus changes
connect(qApp,
&QApplication::focusChanged,
this,
&NodeParamView::FocusChanged);
}
void NodeParamView::SelectNodes(const QList<Node *> &nodes)
{
active_nodes_.append(nodes);
bool changes_made = false;
foreach (Node* n, nodes) {
NodeParamViewItem* item = new NodeParamViewItem(n);
if (!pinned_nodes_.contains(n)) {
NodeParamViewItem* item = new NodeParamViewItem(n, param_widget_area_);
// Insert the widget before the stretch
param_layout_->insertWidget(param_layout_->count() - 1, item);
item->setAllowedAreas(Qt::LeftDockWidgetArea);
item->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
item->SetExpanded(node_expanded_state_.value(n, true));
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);
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);
connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::QueueKeyframePositionUpdate);
connect(item, &NodeParamViewItem::dockLocationChanged, this, &NodeParamView::SignalNodeOrder);
connect(item, &NodeParamViewItem::PinToggled, this, &NodeParamView::PinNode);
// Set time target
item->SetTimeTarget(GetTimeTarget());
// Set time target
item->SetTimeTarget(GetTimeTarget());
items_.insert(n, item);
items_.insert(n, item);
param_widget_area_->addDockWidget(Qt::LeftDockWidgetArea, item);
changes_made = true;
if (!focused_node_ && n->HasGizmos()) {
// We'll focus this node now
item->SetHighlighted(true);
focused_node_ = n;
emit FocusedNodeChanged(focused_node_);
}
}
}
UpdateItemTime(GetTimestamp());
if (changes_made) {
UpdateItemTime(GetTimestamp());
// Re-arrange keyframes
QMetaObject::invokeMethod(this, "PlaceKeyframesOnView", Qt::QueuedConnection);
// Re-arrange keyframes
QueueKeyframePositionUpdate();
SignalNodeOrder();
}
}
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);
bool changes_made = false;
delete items_.take(n);
foreach (Node* n, nodes) {
if (!pinned_nodes_.contains(n)) {
// Store expanded state
node_expanded_state_.insert(n, items_.value(n)->IsExpanded());
// Remove all keyframes from this node
RemoveNode(n);
changes_made = true;
}
active_nodes_.removeOne(n);
}
// Re-arrange keyframes
QMetaObject::invokeMethod(this, "PlaceKeyframesOnView", Qt::QueuedConnection);
if (changes_made) {
// Re-arrange keyframes
QueueKeyframePositionUpdate();
SignalNodeOrder();
}
}
void NodeParamView::resizeEvent(QResizeEvent *event)
@@ -167,6 +217,8 @@ void NodeParamView::resizeEvent(QResizeEvent *event)
QWidget::resizeEvent(event);
vertical_scrollbar_->setPageStep(vertical_scrollbar_->height());
UpdateGlobalScrollBar();
}
void NodeParamView::ScaleChangedEvent(const double &scale)
@@ -223,14 +275,63 @@ void NodeParamView::UpdateItemTime(const int64_t &timestamp)
}
}
void NodeParamView::QueueKeyframePositionUpdate()
{
QMetaObject::invokeMethod(this, "PlaceKeyframesOnView", Qt::QueuedConnection);
}
void NodeParamView::SignalNodeOrder()
{
// Sort by item Y (apparently there's no way in Qt to get the order of dock widgets)
QList<Node*> nodes;
QList<int> item_ys;
for (auto it=items_.cbegin(); it!=items_.cend(); it++) {
int item_y = it.value()->pos().y();
bool inserted = false;
for (int i=0; i<item_ys.size(); i++) {
if (item_ys.at(i) > item_y) {
item_ys.insert(i, item_y);
nodes.insert(i, it.key());
inserted = true;
break;
}
}
if (!inserted) {
item_ys.append(item_y);
nodes.append(it.key());
}
}
emit NodeOrderChanged(nodes);
}
void NodeParamView::RemoveNode(Node *n)
{
keyframe_view_->RemoveKeyframesOfNode(n);
delete items_.take(n);
if (focused_node_ == n) {
focused_node_ = nullptr;
emit FocusedNodeChanged(nullptr);
}
}
void NodeParamView::ItemRequestedTimeChanged(const rational &time)
{
SetTimeAndSignal(Timecode::time_to_timestamp(time, keyframe_view_->timebase()));
}
void NodeParamView::ForceKeyframeViewToScroll()
void NodeParamView::UpdateGlobalScrollBar()
{
keyframe_view_->SetMaxScroll(param_widget_area_->height() - ruler()->height());
int height_offscreen = param_widget_container_->height() - ruler()->height() + scrollbar()->height();
keyframe_view_->SetMaxScroll(height_offscreen);
vertical_scrollbar_->setRange(0, height_offscreen - keyframe_view_->height());
}
void NodeParamView::PlaceKeyframesOnView()
@@ -240,4 +341,52 @@ void NodeParamView::PlaceKeyframesOnView()
}
}
void NodeParamView::PinNode(bool pin)
{
NodeParamViewItem* item = static_cast<NodeParamViewItem*>(sender());
Node* node = item->GetNode();
if (pin) {
pinned_nodes_.append(node);
} else {
pinned_nodes_.removeOne(node);
if (!active_nodes_.contains(node)) {
RemoveNode(node);
}
}
}
void NodeParamView::FocusChanged(QWidget* old, QWidget* now)
{
Q_UNUSED(old)
QObject* parent = now;
NodeParamViewItem* item;
while (parent) {
item = dynamic_cast<NodeParamViewItem*>(parent);
if (item) {
// Found it!
if (item->GetNode() != focused_node_) {
if (focused_node_) {
// De-focus current node
items_.value(focused_node_)->SetHighlighted(false);
}
focused_node_ = item->GetNode();
item->SetHighlighted(true);
emit FocusedNodeChanged(focused_node_);
}
break;
}
parent = parent->parent();
}
}
OLIVE_NAMESPACE_EXIT
+50 -3
View File
@@ -21,6 +21,7 @@
#ifndef NODEPARAMVIEW_H
#define NODEPARAMVIEW_H
#include <QMainWindow>
#include <QVBoxLayout>
#include <QWidget>
@@ -31,6 +32,28 @@
OLIVE_NAMESPACE_ENTER
class NodeParamViewParamContainer : public QWidget
{
Q_OBJECT
public:
NodeParamViewParamContainer(QWidget* parent = nullptr) :
QWidget(parent)
{
}
protected:
virtual void resizeEvent(QResizeEvent *event) override
{
QWidget::resizeEvent(event);
emit Resized(event->size().height());
}
signals:
void Resized(int new_height);
};
class NodeParamView : public TimeBasedWidget
{
Q_OBJECT
@@ -54,6 +77,10 @@ signals:
void RequestSelectNode(const QList<Node*>& target);
void NodeOrderChanged(const QList<Node*>& nodes);
void FocusedNodeChanged(Node* n);
protected:
virtual void resizeEvent(QResizeEvent *event) override;
@@ -66,7 +93,11 @@ protected:
private:
void UpdateItemTime(const int64_t &timestamp);
QVBoxLayout* param_layout_;
void QueueKeyframePositionUpdate();
void SignalNodeOrder();
void RemoveNode(Node* n);
KeyframeView* keyframe_view_;
@@ -76,15 +107,31 @@ private:
int last_scroll_val_;
QWidget* param_widget_area_;
NodeParamViewParamContainer* param_widget_container_;
// This may look weird, but QMainWindow is just a QWidget with a fancy layout that allows
// docking windows
QMainWindow* param_widget_area_;
QList<Node*> pinned_nodes_;
QList<Node*> active_nodes_;
QMap<Node*, bool> node_expanded_state_;
Node* focused_node_;
private slots:
void ItemRequestedTimeChanged(const rational& time);
void ForceKeyframeViewToScroll();
void UpdateGlobalScrollBar();
void PlaceKeyframesOnView();
void PinNode(bool pin);
void FocusChanged(QWidget *old, QWidget *now);
};
OLIVE_NAMESPACE_EXIT
+94 -27
View File
@@ -32,27 +32,15 @@
OLIVE_NAMESPACE_ENTER
NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) :
QWidget(parent),
node_(node)
QDockWidget(parent),
node_(node),
highlighted_(false)
{
QVBoxLayout* main_layout = new QVBoxLayout(this);
main_layout->setSpacing(0);
main_layout->setMargin(0);
// Create title bar widget
title_bar_ = new NodeParamViewItemTitleBar(this);
title_bar_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
QHBoxLayout* title_bar_layout = new QHBoxLayout(title_bar_);
title_bar_collapse_btn_ = new CollapseButton();
title_bar_layout->addWidget(title_bar_collapse_btn_);
title_bar_lbl_ = new QLabel(title_bar_);
title_bar_layout->addWidget(title_bar_lbl_);
// Add title bar to widget
main_layout->addWidget(title_bar_);
this->setTitleBarWidget(title_bar_);
// Create and add contents widget
QVector<NodeInput*> inputs;
@@ -70,11 +58,26 @@ NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) :
connect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime);
connect(body_, &NodeParamViewItemBody::KeyframeAdded, this, &NodeParamViewItem::KeyframeAdded);
connect(body_, &NodeParamViewItemBody::KeyframeRemoved, this, &NodeParamViewItem::KeyframeRemoved);
connect(title_bar_collapse_btn_, &QPushButton::toggled, body_, &NodeParamViewItemBody::setVisible);
main_layout->addWidget(body_);
connect(title_bar_, &NodeParamViewItemTitleBar::ExpandedStateChanged, this, &NodeParamViewItem::SetExpanded);
connect(title_bar_, &NodeParamViewItemTitleBar::PinToggled, this, &NodeParamViewItem::PinToggled);
QWidget* body_container = new QWidget();
body_container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
QHBoxLayout* body_container_layout = new QHBoxLayout(body_container);
body_container_layout->setSpacing(0);
body_container_layout->setMargin(0);
body_container_layout->addWidget(body_);
this->setWidget(body_container);
connect(node_, &Node::LabelChanged, this, &NodeParamViewItem::Retranslate);
setBackgroundRole(QPalette::Base);
setAutoFillBackground(true);
setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
setFocusPolicy(Qt::ClickFocus);
Retranslate();
}
@@ -109,34 +112,98 @@ void NodeParamViewItem::changeEvent(QEvent *e)
QWidget::changeEvent(e);
}
void NodeParamViewItem::paintEvent(QPaintEvent *event)
{
QDockWidget::paintEvent(event);
// Draw border if focused
if (highlighted_) {
QPainter p(this);
p.setBrush(Qt::NoBrush);
p.setPen(palette().highlight().color());
p.drawRect(rect().adjusted(0, 0, -1, -1));
}
}
void NodeParamViewItem::Retranslate()
{
node_->Retranslate();
if (node_->GetLabel().isEmpty()) {
title_bar_lbl_->setText(node_->Name());
title_bar_->SetText(node_->Name());
} else {
title_bar_lbl_->setText(tr("%1 (%2)").arg(node_->GetLabel(), node_->Name()));
title_bar_->SetText(tr("%1 (%2)").arg(node_->GetLabel(), node_->Name()));
}
body_->Retranslate();
}
NodeParamViewItemTitleBar::NodeParamViewItemTitleBar(QWidget *parent) :
QWidget(parent)
void NodeParamViewItem::SetExpanded(bool e)
{
body_->setVisible(e);
title_bar_->SetExpanded(e);
}
bool NodeParamViewItem::IsExpanded() const
{
return body_->isVisible();
}
void NodeParamViewItem::ToggleExpanded()
{
SetExpanded(!IsExpanded());
}
NodeParamViewItemTitleBar::NodeParamViewItemTitleBar(QWidget *parent) :
QWidget(parent),
draw_border_(true)
{
QHBoxLayout* layout = new QHBoxLayout(this);
collapse_btn_ = new CollapseButton();
connect(collapse_btn_, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::ExpandedStateChanged);
layout->addWidget(collapse_btn_);
lbl_ = new QLabel();
layout->addWidget(lbl_);
// Place next buttons on the far side
layout->addStretch();
QPushButton* pin_btn = new QPushButton(QStringLiteral("P"));
pin_btn->setCheckable(true);
pin_btn->setFixedSize(pin_btn->sizeHint().height(), pin_btn->sizeHint().height());
layout->addWidget(pin_btn);
connect(pin_btn, &QPushButton::clicked, this, &NodeParamViewItemTitleBar::PinToggled);
}
void NodeParamViewItemTitleBar::SetExpanded(bool e)
{
draw_border_ = e;
collapse_btn_->setChecked(e);
update();
}
void NodeParamViewItemTitleBar::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
QPainter p(this);
if (draw_border_) {
QPainter p(this);
// Draw bottom border using text color
int bottom = height() - 1;
p.setPen(palette().text().color());
p.drawLine(0, bottom, width(), bottom);
// Draw bottom border using text color
int bottom = height() - 1;
p.setPen(palette().text().color());
p.drawLine(0, bottom, width(), bottom);
}
}
void NodeParamViewItemTitleBar::mouseDoubleClickEvent(QMouseEvent *event)
{
QWidget::mouseDoubleClickEvent(event);
collapse_btn_->click();
}
NodeParamViewItemBody::NodeParamViewItemBody(const QVector<NodeInput *> &inputs, QWidget *parent) :
+46 -6
View File
@@ -21,6 +21,7 @@
#ifndef NODEPARAMVIEWITEM_H
#define NODEPARAMVIEWITEM_H
#include <QDockWidget>
#include <QGridLayout>
#include <QLabel>
#include <QPushButton>
@@ -36,12 +37,36 @@
OLIVE_NAMESPACE_ENTER
class NodeParamViewItemTitleBar : public QWidget {
class NodeParamViewItemTitleBar : public QWidget
{
Q_OBJECT
public:
NodeParamViewItemTitleBar(QWidget* parent = nullptr);
void SetExpanded(bool e);
void SetText(const QString& s)
{
lbl_->setText(s);
}
signals:
void ExpandedStateChanged(bool e);
void PinToggled(bool e);
protected:
virtual void paintEvent(QPaintEvent *event) override;
virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
private:
bool draw_border_;
QLabel* lbl_;
CollapseButton* collapse_btn_;
};
class NodeParamViewItemBody : public QWidget {
@@ -99,7 +124,7 @@ private slots:
};
class NodeParamViewItem : public QWidget
class NodeParamViewItem : public QDockWidget
{
Q_OBJECT
public:
@@ -111,9 +136,22 @@ public:
Node* GetNode() const;
bool IsExpanded() const;
void SetHighlighted(bool e)
{
highlighted_ = e;
update();
}
public slots:
void SignalAllKeyframes();
void SetExpanded(bool e);
void ToggleExpanded();
signals:
void KeyframeAdded(NodeKeyframePtr key, int y);
@@ -125,22 +163,24 @@ signals:
void RequestSelectNode(const QList<Node*>& node);
void PinToggled(bool e);
protected:
virtual void changeEvent(QEvent *e) override;
virtual void paintEvent(QPaintEvent *event) override;
private:
NodeParamViewItemTitleBar* title_bar_;
QLabel* title_bar_lbl_;
CollapseButton* title_bar_collapse_btn_;
NodeParamViewItemBody* body_;
Node* node_;
rational time_;
bool highlighted_;
private slots:
void Retranslate();
+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/nodetreeview/nodetreeview.h
widget/nodetreeview/nodetreeview.cpp
PARENT_SCOPE
)
+111
View File
@@ -0,0 +1,111 @@
#include "nodetreeview.h"
OLIVE_NAMESPACE_ENTER
NodeTreeView::NodeTreeView(QWidget *parent) :
QTreeWidget(parent),
only_show_keyframable_(false)
{
connect(this, &NodeTreeView::itemChanged, this, &NodeTreeView::ItemCheckStateChanged);
Retranslate();
}
bool NodeTreeView::IsNodeEnabled(Node *n) const
{
return !disabled_nodes_.contains(n);
}
bool NodeTreeView::IsInputEnabled(NodeInput *i) const
{
return !disabled_inputs_.contains(i);
}
void NodeTreeView::SetNodes(const QList<Node *> &nodes)
{
nodes_ = nodes;
this->clear();
foreach (Node* n, nodes_) {
QTreeWidgetItem* node_item = new QTreeWidgetItem();
node_item->setText(0, n->Name());
node_item->setCheckState(0, disabled_nodes_.contains(n) ? Qt::Unchecked : Qt::Checked);
node_item->setData(0, kItemType, kItemTypeNode);
node_item->setData(0, kItemPointer, reinterpret_cast<quintptr>(n));
QList<NodeInput*> inputs = n->GetInputsIncludingArrays();
foreach (NodeInput* i, inputs) {
if (only_show_keyframable_ && !i->is_keyframable()) {
continue;
}
QTreeWidgetItem* input_item = new QTreeWidgetItem(node_item);
input_item->setText(0, i->name());
input_item->setCheckState(0, disabled_inputs_.contains(i) ? Qt::Unchecked : Qt::Checked);
input_item->setData(0, kItemType, kItemTypeInput);
input_item->setData(0, kItemPointer, reinterpret_cast<quintptr>(i));
}
// Add at the end to prevent unnecessary signalling while we're setting these objects up
if (node_item->childCount() > 0) {
this->addTopLevelItem(node_item);
} else {
delete node_item;
}
}
}
void NodeTreeView::changeEvent(QEvent *e)
{
QTreeWidget::changeEvent(e);
if (e->type() == QEvent::LanguageChange) {
Retranslate();
}
}
void NodeTreeView::Retranslate()
{
setHeaderLabel(tr("Nodes"));
}
void NodeTreeView::ItemCheckStateChanged(QTreeWidgetItem *item, int column)
{
Q_UNUSED(column)
switch (item->data(0, kItemType).toInt()) {
case kItemTypeNode:
{
Node* n = reinterpret_cast<Node*>(item->data(0, kItemPointer).value<quintptr>());
if (item->checkState(0) == Qt::Checked) {
if (disabled_nodes_.contains(n)) {
disabled_nodes_.removeOne(n);
emit NodeEnableChanged(n, true);
}
} else if (!disabled_nodes_.contains(n)) {
disabled_nodes_.append(n);
emit NodeEnableChanged(n, false);
}
break;
}
case kItemTypeInput:
{
NodeInput* i = reinterpret_cast<NodeInput*>(item->data(0, kItemPointer).value<quintptr>());
if (item->checkState(0) == Qt::Checked) {
if (disabled_inputs_.contains(i)) {
disabled_inputs_.removeOne(i);
emit InputEnableChanged(i, true);
}
} else if (!disabled_inputs_.contains(i)) {
disabled_inputs_.append(i);
emit InputEnableChanged(i, false);
}
break;
}
}
}
OLIVE_NAMESPACE_EXIT
+62
View File
@@ -0,0 +1,62 @@
#ifndef NODETREEVIEW_H
#define NODETREEVIEW_H
#include <QTreeWidget>
#include "node/node.h"
OLIVE_NAMESPACE_ENTER
class NodeTreeView : public QTreeWidget
{
Q_OBJECT
public:
NodeTreeView(QWidget *parent = nullptr);
bool IsNodeEnabled(Node* n) const;
bool IsInputEnabled(NodeInput* i) const;
void SetOnlyShowKeyframable(bool e)
{
only_show_keyframable_ = e;
}
public slots:
void SetNodes(const QList<Node*>& nodes);
signals:
void NodeEnableChanged(Node* n, bool e);
void InputEnableChanged(NodeInput* i, bool e);
protected:
virtual void changeEvent(QEvent* e) override;
private:
void Retranslate();
enum ItemType {
kItemTypeNode,
kItemTypeInput
};
static const int kItemType = Qt::UserRole;
static const int kItemPointer = Qt::UserRole + 1;
QList<Node*> nodes_;
QList<Node*> disabled_nodes_;
QList<NodeInput*> disabled_inputs_;
bool only_show_keyframable_;
private slots:
void ItemCheckStateChanged(QTreeWidgetItem* item, int column);
};
OLIVE_NAMESPACE_EXIT
#endif // NODETREEVIEW_H
+32 -148
View File
@@ -57,6 +57,10 @@ NodeView::NodeView(QWidget *parent) :
scene_.setSceneRect(-1000000, -1000000, 2000000, 2000000);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
select_blocks_internal_timer_.setSingleShot(true);
select_blocks_internal_timer_.setInterval(100);
connect(&select_blocks_internal_timer_, &QTimer::timeout, this, &NodeView::SelectBlocksInternal);
}
NodeView::~NodeView()
@@ -200,13 +204,21 @@ void NodeView::SelectWithDependencies(QList<Node *> nodes)
void NodeView::SelectBlocks(const QList<Block *> &blocks)
{
if (!graph_) {
return;
}
selected_blocks_.append(blocks);
SelectBlocksInternal();
QueueSelectBlocksInternal();
}
void NodeView::DeselectBlocks(const QList<Block *> &blocks)
{
if (!graph_) {
return;
}
// Remove temporary associations
foreach (Block* b, selected_blocks_) {
if (!blocks.contains(b)) {
@@ -219,7 +231,7 @@ void NodeView::DeselectBlocks(const QList<Block *> &blocks)
selected_blocks_.removeOne(b);
}
SelectBlocksInternal();
QueueSelectBlocksInternal();
}
void NodeView::CopySelected(bool cut)
@@ -372,8 +384,8 @@ void NodeView::mouseMoveEvent(QMouseEvent *event)
QRect edge_detect_rect(event->pos(), event->pos());
// FIXME: Hardcoded numbers
edge_detect_rect.adjust(-20, -20, 20, 20);
int edge_detect_radius = fontMetrics().height();
edge_detect_rect.adjust(-edge_detect_radius, -edge_detect_radius, edge_detect_radius, edge_detect_radius);
QList<QGraphicsItem*> items = this->items(edge_detect_rect);
@@ -653,140 +665,6 @@ void NodeView::ContextMenuFilterChanged(QAction *action)
}
}
void NodeView::PlaceNode(NodeViewItem *n, const QPointF &pos)
{
QRectF destination_rect = n->rect();
destination_rect.translate(n->pos());
double x_movement = destination_rect.width() * 1.5;
double y_movement = destination_rect.height() * 1.5;
QList<QGraphicsItem*> items = scene()->items(destination_rect);
n->setPos(pos);
foreach (QGraphicsItem* item, items) {
if (item == n) {
continue;
}
NodeViewItem* node_item = dynamic_cast<NodeViewItem*>(item);
if (!node_item) {
continue;
}
qDebug() << "Moving" << node_item->GetNode() << "for" << n->GetNode();
QPointF new_pos;
if (item->pos() == pos) {
qDebug() << "Same pos, need more info";
// Item positions are exact, we'll need more information to determine where this item should go
Node* ours = n->GetNode();
Node* theirs = node_item->GetNode();
bool moved = false;
new_pos = item->pos();
// Heuristic to determine whether to move the other item above or below
foreach (NodeEdgePtr our_edge, ours->output()->edges()) {
foreach (NodeEdgePtr their_edge, theirs->output()->edges()) {
if (our_edge->output()->parentNode() == their_edge->output()->parentNode()) {
qDebug() << " They share a node that they output to";
if (our_edge->input()->index() > their_edge->input()->index()) {
// Their edge should go above ours
qDebug() << " Our edge goes BELOW theirs";
new_pos.setY(new_pos.y() - y_movement);
} else {
// Our edge should go below ours
qDebug() << " Our edge goes ABOVE theirs";
new_pos.setY(new_pos.y() + y_movement);
}
moved = true;
break;
}
}
}
// If we find anything, just move at random
if (!moved) {
new_pos.setY(new_pos.y() - y_movement);
}
} else if (item->pos().x() == pos.x()) {
qDebug() << "Same X, moving vertically";
// Move strictly up or down
new_pos = item->pos();
if (item->pos().y() < pos.y()) {
// Move further up
new_pos.setY(pos.y() - y_movement);
} else {
// Move further down
new_pos.setY(pos.y() + y_movement);
}
} else if (item->pos().y() == pos.y()) {
qDebug() << "Same Y, moving horizontally";
// Move strictly left or right
new_pos = item->pos();
if (item->pos().x() < pos.x()) {
// Move further up
new_pos.setX(pos.x() - x_movement);
} else {
// Move further down
new_pos.setX(pos.x() + x_movement);
}
} else {
qDebug() << "Diff pos, pushing in angle";
// The item does not have equal X or Y, attempt to push it away from `pos` in the direction it's in
double x_diff = item->pos().x() - pos.x();
double y_diff = item->pos().y() - pos.y();
double slope = y_diff / x_diff;
double y_int = item->pos().y() - slope * item->pos().x();
if (qAbs(slope) > 1.0) {
// Vertical difference is greater than horizontal difference, prioritize vertical movement
double desired_y = pos.y();
if (item->pos().y() > pos.y()) {
desired_y += y_movement;
} else {
desired_y -= y_movement;
}
double x = (desired_y - y_int) / slope;
new_pos = QPointF(x, desired_y);
} else {
// Horizontal difference is greater than vertical difference, prioritize horizontal movement
double desired_x = pos.x();
if (item->pos().x() > pos.x()) {
desired_x += x_movement;
} else {
desired_x -= x_movement;
}
double y = slope * desired_x + y_int;
new_pos = QPointF(desired_x, y);
}
}
PlaceNode(node_item, new_pos);
}
}
void NodeView::AttachNodesToCursor(const QList<Node *> &nodes)
{
QList<NodeViewItem*> items;
@@ -851,7 +729,7 @@ void NodeView::UpdateBlockFilter()
}
bool first = true;
QRectF last_rect;
QPointF last_bottom_right;
QList<Node*> currently_visible;
@@ -883,10 +761,13 @@ void NodeView::UpdateBlockFilter()
if (first) {
first = false;
} else {
QPointF desired_anchor_pos = last_rect.bottomRight() + QPointF(0, 2);
QPointF desired_anchor_pos = last_bottom_right + QPointF(0, 2);
QPointF necessary_movement = anchor.topRight() - desired_anchor_pos;
// Calculate the bottom right from which we'll anchor the next rect
last_bottom_right = anchor.bottomRight() - necessary_movement;
b->SetPosition(b->GetPosition() - necessary_movement);
foreach (Node* d, deps) {
d->SetPosition(d->GetPosition() - necessary_movement);
@@ -915,15 +796,12 @@ void NodeView::UpdateBlockFilter()
// And lastly, add them all to our currently visible list
currently_visible.append(deps);
// Cache this rect so we can calculate other rects
last_rect = anchor;
}
// Show only edges between those dependencies
foreach (NodeViewEdge* edge, scene_.edge_map()) {
edge->setVisible((currently_visible.contains(edge->edge()->input()->parentNode())
&& currently_visible.contains(edge->edge()->output()->parentNode())));
edge->setVisible((currently_visible.contains(edge->edge()->input_node())
&& currently_visible.contains(edge->edge()->output_node())));
}
}
@@ -942,10 +820,16 @@ void NodeView::DisassociateNode(Node *n, bool remove_from_map)
disconnect(n, &Node::destroyed, this, &NodeView::AssociatedNodeDestroyed);
}
void NodeView::QueueSelectBlocksInternal()
{
select_blocks_internal_timer_.stop();
select_blocks_internal_timer_.start();
}
void NodeView::SelectBlocksInternal()
{
// Block scene signals while our selection is changing a lot
scene_.blockSignals(true);
DisconnectSelectionChangedSignal();
if (filter_mode_ == kFilterShowSelectedBlocks) {
UpdateBlockFilter();
@@ -962,7 +846,7 @@ void NodeView::SelectBlocksInternal()
SelectWithDependencies(nodes);
// Stop blocking signals and send a change signal now that all of our processing is done
scene_.blockSignals(false);
ConnectSelectionChangedSignal();
SceneSelectionChangedSlot();
if (!selected_blocks_.isEmpty()) {
@@ -1024,7 +908,7 @@ void NodeView::GraphEdgeRemoved(NodeEdgePtr edge)
{
scene_.RemoveEdge(edge);
Node* output_node = edge->output()->parentNode();
Node* output_node = edge->output_node();
// Check if this disconnected node still connects to a selected block, in which case do nothing
foreach (Block* b, selected_blocks_) {
+5 -5
View File
@@ -84,11 +84,7 @@ protected:
virtual void wheelEvent(QWheelEvent* event) override;
//virtual void scrollContentsBy(int dx, int dy) override;
private:
void PlaceNode(NodeViewItem* n, const QPointF& pos);
void AttachNodesToCursor(const QList<Node*>& nodes);
void AttachItemsToCursor(const QList<NodeViewItem*>& items);
@@ -107,7 +103,9 @@ private:
void AssociateNodeWithSelectedBlocks(Node* n);
void DisassociateNode(Node* n, bool remove_from_map);
void SelectBlocksInternal();
void QueueSelectBlocksInternal();
QTimer select_blocks_internal_timer_;
NodeGraph* graph_;
@@ -188,6 +186,8 @@ private slots:
*/
void ContextMenuFilterChanged(QAction* action);
void SelectBlocksInternal();
};
OLIVE_NAMESPACE_EXIT
+29 -5
View File
@@ -261,8 +261,25 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti
Color node_color = Config::Current()[QStringLiteral("NodeCatColor%1")
.arg(node_->Category().first())].value<Color>();
QLinearGradient grad;
grad.setStart(0, rect().top());
grad.setFinalStop(0, rect().bottom());
QColor node_color_as_qcolor = node_color.toQColor();
{
// Generate lighter color for gradient
qreal hue, sat, lightness;
node_color_as_qcolor.getHslF(&hue, &sat, &lightness);
lightness = qMin(1.0, lightness + 0.2);
grad.setColorAt(0.0, QColor::fromHslF(hue, sat, lightness));
}
grad.setColorAt(1.0, node_color_as_qcolor);
painter->setPen(Qt::black);
painter->setBrush(node_color.toQColor());
painter->setBrush(grad);
painter->drawRect(title_bar_rect_);
@@ -270,10 +287,17 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti
QString node_label;
if (node_->GetLabel().isEmpty()) {
node_label = node_->ShortName();
} else {
if (!node_->GetLabel().isEmpty()) {
// Use label directly if node has one
node_label = node_->GetLabel();
} else if (node_->IsTrack()) {
// If node is a track, use a special track name to help users identify better
// Exception for tracks
TrackOutput* track = static_cast<TrackOutput*>(node_);
node_label = TrackOutput::GetDefaultTrackName(track->track_type(), track->Index());
} else {
// Otherwise, just use the node's short name
node_label = node_->ShortName();
}
QFont f;
@@ -308,7 +332,7 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti
}
// Determine the text color (automatically calculate from node background color)
if (node_color.GetRoughLuminance() > 0.66) {
if (node_color.GetRoughLuminance() > 0.5) {
painter->setPen(Qt::black);
} else {
painter->setPen(Qt::white);
+16
View File
@@ -154,6 +154,15 @@ void NodeRemoveCommand::redo_internal()
// Take nodes from graph (TakeNode() will automatically disconnect edges)
foreach (Node* n, nodes_) {
// If the node is a block, unlink any linked blocks before removing
if (n->IsBlock()) {
Block *b = static_cast<Block *>(n);
if (b->HasLinks()) {
BlockUnlinkAllCommand *unlink_command = new BlockUnlinkAllCommand(b);
unlink_command->redo();
block_unlink_commands_.append(unlink_command);
}
}
graph_->TakeNode(n, &memory_manager_);
}
}
@@ -165,12 +174,19 @@ void NodeRemoveCommand::undo_internal()
graph_->AddNode(n);
}
// Relink any blocks that were unlinked
foreach(BlockUnlinkAllCommand* command, block_unlink_commands_) {
command->undo();
delete command;
}
// Re-connect edges
foreach (NodeEdgePtr edge, edges_) {
NodeParam::ConnectEdge(edge->output(), edge->input());
}
edges_.clear();
block_unlink_commands_.clear();
}
Project *NodeRemoveCommand::GetRelevantProject() const
+60
View File
@@ -27,6 +27,7 @@
#include "node/node.h"
#include "nodeviewitem.h"
#include "undo/undocommand.h"
#include "widget/timelinewidget/undo/undo.h"
OLIVE_NAMESPACE_ENTER
@@ -112,6 +113,7 @@ private:
NodeGraph* graph_;
QList<Node*> nodes_;
QList<NodeEdgePtr> edges_;
QList<BlockUnlinkAllCommand*> block_unlink_commands_;
};
class NodeRemoveWithExclusiveDeps : public UndoCommand {
@@ -149,6 +151,64 @@ private:
};
class NodeGraphBeginOperationCommand : public UndoCommand {
public:
NodeGraphBeginOperationCommand(NodeGraph* graph, QUndoCommand* parent = nullptr) :
UndoCommand(parent),
graph_(graph)
{
}
virtual Project* GetRelevantProject() const override
{
return static_cast<Sequence*>(graph_)->project();
}
protected:
virtual void redo_internal() override
{
graph_->BeginOperation();
}
virtual void undo_internal() override
{
graph_->EndOperation();
}
private:
NodeGraph* graph_;
};
class NodeGraphEndOperationCommand : public UndoCommand {
public:
NodeGraphEndOperationCommand(NodeGraph* graph, QUndoCommand* parent = nullptr) :
UndoCommand(parent),
graph_(graph)
{
}
virtual Project* GetRelevantProject() const override
{
return static_cast<Sequence*>(graph_)->project();
}
protected:
virtual void redo_internal() override
{
graph_->EndOperation();
}
virtual void undo_internal() override
{
graph_->BeginOperation();
}
private:
NodeGraph* graph_;
};
OLIVE_NAMESPACE_EXIT
#endif // NODEVIEWUNDO_H
@@ -161,7 +161,10 @@ void PlaybackControls::SetTimebase(const rational &r)
time_base_ = r;
cur_tc_lbl_->SetTimebase(r);
cur_tc_lbl_->setEnabled(!r.isNull());
cur_tc_lbl_->setVisible(!r.isNull());
end_tc_lbl_->setVisible(!r.isNull());
setEnabled(!r.isNull());
}
void PlaybackControls::SetAudioVideoDragButtonsVisible(bool e)
@@ -32,5 +32,7 @@ set(OLIVE_SOURCES
widget/projectexplorer/projectexplorericonviewitemdelegate.cpp
widget/projectexplorer/projectexplorernavigation.h
widget/projectexplorer/projectexplorernavigation.cpp
widget/projectexplorer/projectexplorerundo.h
widget/projectexplorer/projectexplorerundo.cpp
PARENT_SCOPE
)
+125 -6
View File
@@ -23,6 +23,7 @@
#include <QDebug>
#include <QDesktopServices>
#include <QDir>
#include <QMessageBox>
#include <QProcess>
#include <QUrl>
#include <QVBoxLayout>
@@ -31,11 +32,14 @@
#include "core.h"
#include "dialog/footageproperties/footageproperties.h"
#include "dialog/sequence/sequence.h"
#include "projectexplorerundo.h"
#include "task/precache/precachetask.h"
#include "task/taskmanager.h"
#include "widget/menu/menu.h"
#include "widget/menu/menushared.h"
#include "window/mainwindow/mainwindow.h"
#include "widget/timelinewidget/timelinewidget.h"
#include "widget/nodeview/nodeviewundo.h"
OLIVE_NAMESPACE_ENTER
@@ -536,6 +540,30 @@ void ProjectExplorer::DeselectAll()
CurrentView()->selectionModel()->clearSelection();
}
QList<MediaInput *> ProjectExplorer::GetMediaNodesUsingFootage(Footage *item)
{
QList<MediaInput *> list;
// Get all sequences.
QList<ItemPtr> sequences = model_.project()->get_items_of_type(Item::kSequence);
// Footage can contain multiple streams, all of which need to be dealt with
foreach (ItemPtr s, sequences) {
const QList<Node*>& nodes = static_cast<Sequence*>(s.get())->nodes();
foreach (Node* n, nodes) {
if (n->IsMedia()) {
MediaInput* media_node = static_cast<MediaInput*>(n);
if (media_node->footage()->footage() == item) {
list.append(media_node);
}
}
}
}
return list;
}
void ProjectExplorer::DeleteSelected()
{
QList<Item*> selected = SelectedItems();
@@ -547,18 +575,109 @@ void ProjectExplorer::DeleteSelected()
QUndoCommand* command = new QUndoCommand();
foreach (Item* item, selected) {
ItemPtr item_ptr = item->get_shared_ptr();
// If this is a sequence, close it
if (item_ptr->type() == Item::kSequence) {
Sequence* s = static_cast<Sequence*>(item_ptr.get());
// Verify whether this item is in use anywhere
switch (item->type()) {
case Item::kSequence:
{
// If this is a sequence, check if it's open and close it if necessary
Sequence* s = static_cast<Sequence*>(item);
if (Core::instance()->main_window()->IsSequenceOpen(s)) {
Core::instance()->main_window()->CloseSequence(s);
}
break;
}
case Item::kFootage:
{
// If this is footage, check if it's used anywhere in any sequence
Footage* footage = static_cast<Footage*>(item);
QList<MediaInput*> footage_nodes = GetMediaNodesUsingFootage(footage);
if (!footage_nodes.isEmpty()) {
// Footage is in use, show messagebox asking what to do about it
QList<Sequence*> used_in_sequences;
// Compile list of sequences to assist the user in making this decision
foreach (MediaInput* i, footage_nodes) {
Sequence* media_parent = static_cast<Sequence*>(i->parent());
if (!used_in_sequences.contains(media_parent)) {
used_in_sequences.append(media_parent);
}
}
QString sequence_list_str;
foreach (Sequence* s, used_in_sequences) {
sequence_list_str.append(QStringLiteral("%1\n").arg(s->name()));
}
QMessageBox msgbox(this);
msgbox.setWindowTitle(tr("Confirm Footage Deletion"));
msgbox.setText(tr("The footage \"%1\" is currently used in the following sequence(s):\n\n"
"%2\nWhat would you like to do with these clips?")
.arg(footage->filename(), sequence_list_str));
msgbox.setIcon(QMessageBox::Warning);
// Set up buttons
QPushButton* offline_btn = msgbox.addButton(tr("Offline Footage"), QMessageBox::YesRole);
QPushButton* delete_clip_btn = msgbox.addButton(tr("Delete Clips"), QMessageBox::NoRole);
msgbox.addButton(QMessageBox::Cancel);
// Run messagebox
msgbox.exec();
if (msgbox.clickedButton() == offline_btn || msgbox.clickedButton() == delete_clip_btn) {
// For safety, even if we're deleting clips, we'll offline the footage nodes too
new OfflineFootageCommand(footage_nodes, command);
}
if (msgbox.clickedButton() == delete_clip_btn) {
// Delete any blocks that use this footage
QList<Block*> blocks_to_remove;
foreach (Sequence* s, used_in_sequences) {
foreach (TrackOutput* track, s->viewer_output()->GetTracks()) {
foreach (Block* b, track->Blocks()) {
QList<Node*> deps = b->GetDependencies();
foreach (MediaInput* i, footage_nodes) {
if (deps.contains(i)) {
blocks_to_remove.append(b);
break;
}
}
}
}
}
TimelineWidget::ReplaceBlocksWithGaps(blocks_to_remove, true, command);
} else if (msgbox.clickedButton() != offline_btn) {
// Must have cancelled
delete command;
return;
}
}
// Close footage if currently open in footage panel
FootageViewerPanel* footage_panel = PanelManager::instance()->GetPanelsOfType<FootageViewerPanel>().first();
if (footage_panel->GetSelectedFootage().contains(footage)) {
footage_panel->SetFootage(nullptr);
}
break;
}
case Item::kFolder:
// Do nothing
break;
}
new ProjectViewModel::RemoveItemCommand(&model_, item_ptr, command);
new ProjectViewModel::RemoveItemCommand(&model_, item->get_shared_ptr(), command);
}
Core::instance()->undo_stack()->pushIfHasChildren(command);
@@ -25,6 +25,7 @@
#include <QTimer>
#include <QTreeView>
#include "node/input/media/media.h"
#include "project/project.h"
#include "project/projectviewmodel.h"
#include "widget/projectexplorer/projectexplorericonview.h"
@@ -101,6 +102,18 @@ signals:
void DoubleClickedItem(Item* item);
private:
/**
* @brief Check if an item is in use anywhere and return any relevant input nodes
*/
QList<MediaInput*> GetMediaNodesUsingFootage(Footage* item);
/**
* @brief Get all the blocks that solely rely on an input node
*
* Ignores blocks that depend on multiple inputs
*/
QList<Block*> GetFootageBlocks(QList<Node*> nodes);
/**
* @brief Simple convenience function for adding a view to this stacked widget
*
@@ -0,0 +1,54 @@
/***
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 "projectexplorerundo.h"
OLIVE_NAMESPACE_ENTER
OfflineFootageCommand::OfflineFootageCommand(const QList<MediaInput *> &media, QUndoCommand* parent) :
UndoCommand(parent)
{
foreach (MediaInput* i, media) {
stream_data_.insert(i, i->footage());
}
project_ = static_cast<Sequence*>(media.first()->parent())->project();
}
Project *OfflineFootageCommand::GetRelevantProject() const
{
return project_;
}
void OfflineFootageCommand::redo_internal()
{
for (auto it=stream_data_.cbegin(); it!=stream_data_.cend(); it++) {
it.key()->SetFootage(nullptr);
}
}
void OfflineFootageCommand::undo_internal()
{
for (auto it=stream_data_.cbegin(); it!=stream_data_.cend(); it++) {
it.key()->SetFootage(it.value());
}
}
OLIVE_NAMESPACE_EXIT
@@ -0,0 +1,52 @@
/***
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 PROJECTEXPLORERUNDO_H
#define PROJECTEXPLORERUNDO_H
#include "node/input/media/media.h"
#include "undo/undocommand.h"
OLIVE_NAMESPACE_ENTER
/**
* @brief An undo command for offlining footage when it is deleted from the project explorer
*/
class OfflineFootageCommand : public UndoCommand {
public:
OfflineFootageCommand(const QList<MediaInput*>& media, QUndoCommand* parent = nullptr);
virtual Project* GetRelevantProject() const override;
protected:
virtual void redo_internal() override;
virtual void undo_internal() override;
private:
QMap<MediaInput*, StreamPtr> stream_data_;
Project* project_;
};
OLIVE_NAMESPACE_EXIT
#endif // PROJECTEXPLORERUNDO_H
+2
View File
@@ -29,5 +29,7 @@ set(OLIVE_SOURCES
widget/timelinewidget/timelinescaledobject.cpp
widget/timelinewidget/timelinewidget.h
widget/timelinewidget/timelinewidget.cpp
widget/timelinewidget/timelinewidgetselections.h
widget/timelinewidget/timelinewidgetselections.cpp
PARENT_SCOPE
)
@@ -48,10 +48,10 @@ public:
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);
protected:
virtual void TimebaseChangedEvent(const rational&){}
virtual void ScaleChangedEvent(const double&){}
+267 -158
View File
@@ -30,6 +30,17 @@
#include "common/timecodefunctions.h"
#include "dialog/sequence/sequence.h"
#include "node/block/transition/transition.h"
#include "tool/add.h"
#include "tool/beam.h"
#include "tool/edit.h"
#include "tool/pointer.h"
#include "tool/razor.h"
#include "tool/ripple.h"
#include "tool/rolling.h"
#include "tool/slide.h"
#include "tool/slip.h"
#include "tool/transition.h"
#include "tool/zoom.h"
#include "tool/tool.h"
#include "trackview/trackview.h"
#include "widget/menu/menu.h"
@@ -107,6 +118,8 @@ TimelineWidget::TimelineWidget(QWidget *parent) :
view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
view->SetSnapService(this);
view->SetSelectionList(&selections_);
view->SetGhostList(&ghost_items_);
view_splitter_->addWidget(tview);
@@ -149,6 +162,8 @@ TimelineWidget::TimelineWidget(QWidget *parent) :
SetMaximumScale(TimelineViewBase::kMaximumScale);
SetAutoSetTimebase(false);
connect(Core::instance(), &Core::ToolChanged, this, &TimelineWidget::ToolChanged);
}
TimelineWidget::~TimelineWidget()
@@ -161,20 +176,16 @@ TimelineWidget::~TimelineWidget()
void TimelineWidget::Clear()
{
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 all items
for (auto iterator=block_items_.begin(); iterator!=block_items_.end(); iterator++) {
delete iterator.value();
}
block_items_.clear();
emit BlocksDeselected(deselected_blocks);
// Emit that we've deselected any selected blocks
SignalDeselectedAllBlocks();
// Set null timebase
SetTimebase(0);
}
@@ -224,10 +235,6 @@ void TimelineWidget::ScaleChangedEvent(const double &scale)
}
}
foreach (TimelineViewGhostItem* ghost, ghost_items_) {
ghost->SetScale(scale);
}
foreach (TimelineAndTrackView* view, views_) {
view->view()->SetScale(scale);
}
@@ -271,6 +278,10 @@ void TimelineWidget::DisconnectNodeInternal(ViewerOutput *n)
disconnect(n, &ViewerOutput::TimebaseChanged, this, &TimelineWidget::SetTimebase);
disconnect(n, &ViewerOutput::TrackHeightChanged, this, &TimelineWidget::TrackHeightChanged);
foreach (TrackOutput* track, n->GetTracks()) {
RemoveTrack(track);
}
ruler()->SetPlaybackCache(nullptr);
SetTimebase(0);
@@ -341,22 +352,6 @@ void TimelineWidget::PasteNodesFromClipboardInternal(QXmlStreamReader *reader, X
}
}
TimelineWidget::DraggedFootage TimelineWidget::FootageToDraggedFootage(Footage *f)
{
return DraggedFootage(f, f->get_enabled_stream_flags());
}
QList<TimelineWidget::DraggedFootage> TimelineWidget::FootageToDraggedFootage(QList<Footage *> footage)
{
QList<DraggedFootage> df;
foreach (Footage* f, footage) {
df.append(FootageToDraggedFootage(f));
}
return df;
}
rational TimelineWidget::GetToolTipTimebase() const
{
if (GetConnectedNode() && use_audio_time_units_) {
@@ -367,34 +362,28 @@ rational TimelineWidget::GetToolTipTimebase() const
void TimelineWidget::SelectAll()
{
QList<Block*> blocks_selected;
QList<Block*> newly_selected_blocks;
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());
for (auto it=block_items_.cbegin(); it!=block_items_.cend(); it++) {
if (!selected_blocks_.contains(it.key())) {
newly_selected_blocks.append(it.key());
AddSelection(it.key()->range(), it.value()->Track());
}
}
emit BlocksSelected(blocks_selected);
SignalSelectedBlocks(newly_selected_blocks, false);
}
void TimelineWidget::DeselectAll()
{
QList<Block*> blocks_deselected;
// Clear selections
selections_.clear();
QMap<Block*, TimelineViewBlockItem*>::const_iterator i;
// Update all viewports
UpdateViewports();
for (i=block_items_.constBegin(); i!=block_items_.end(); i++) {
if (i.value()->isSelected()) {
i.value()->setSelected(false);
blocks_deselected.append(i.key());
}
}
emit BlocksDeselected(blocks_deselected);
// Clear list and emit signal
SignalDeselectedAllBlocks();
}
void TimelineWidget::RippleToIn()
@@ -486,8 +475,6 @@ void TimelineWidget::ReplaceBlocksWithGaps(const QList<Block *> &blocks,
new TrackReplaceBlockWithGapCommand(original_track, b, command);
if (remove_from_graph) {
new BlockUnlinkAllCommand(b, command);
new NodeRemoveWithExclusiveDeps(static_cast<NodeGraph*>(b->parent()), b, command);
}
}
@@ -497,16 +484,11 @@ void TimelineWidget::DeleteSelected(bool ripple)
{
QList<TimelineViewBlockItem *> selected_list = GetSelectedBlocks();
QList<Block*> blocks_to_delete;
QList<TrackReference> tracks_affected;
foreach (TimelineViewBlockItem* item, selected_list) {
Block* b = item->block();
blocks_to_delete.append(b);
if (!tracks_affected.contains(item->Track())) {
tracks_affected.append(item->Track());
}
}
// No-op if nothing is selected
@@ -541,6 +523,9 @@ void TimelineWidget::DeleteSelected(bool ripple)
// Replace clips with gaps (effectively deleting them)
ReplaceBlocksWithGaps(clips_to_delete, true, command);
// Remove all selections
new TimelineSetSelectionsCommand(this, TimelineWidgetSelections(), GetSelections(), command);
// Insert ripple command now that it's all cleaned up gaps
if (ripple) {
TimeRangeList range_list;
@@ -767,16 +752,8 @@ QList<TimelineViewBlockItem *> TimelineWidget::GetSelectedBlocks()
{
QList<TimelineViewBlockItem *> list;
QMapIterator<Block*, TimelineViewBlockItem*> iterator(block_items_);
while (iterator.hasNext()) {
iterator.next();
TimelineViewBlockItem* item = iterator.value();
if (item && item->isSelected()) {
list.append(item);
}
foreach (Block* b, selected_blocks_) {
list.append(block_items_.value(b));
}
return list;
@@ -825,12 +802,7 @@ void TimelineWidget::ClearGhosts()
HideSnaps();
}
bool TimelineWidget::HasGhosts()
{
return !ghost_items_.isEmpty();
}
TimelineWidget::Tool *TimelineWidget::GetActiveTool()
TimelineTool *TimelineWidget::GetActiveTool()
{
return tools_.at(Core::instance()->tool());
}
@@ -857,7 +829,7 @@ void TimelineWidget::ViewMouseMoved(TimelineViewMouseEvent *event)
active_tool_->MouseMove(event);
} else {
// Mouse is not down, attempt a hover event
Tool* hover_tool = GetActiveTool();
TimelineTool* hover_tool = GetActiveTool();
if (hover_tool) {
hover_tool->HoverMove(event);
@@ -876,9 +848,8 @@ void TimelineWidget::ViewMouseReleased(TimelineViewMouseEvent *event)
void TimelineWidget::ViewMouseDoubleClicked(TimelineViewMouseEvent *event)
{
if (GetConnectedNode() && active_tool_ != nullptr) {
active_tool_->MouseDoubleClick(event);
active_tool_ = nullptr;
if (GetConnectedNode()) {
GetActiveTool()->MouseDoubleClick(event);
}
}
@@ -905,40 +876,72 @@ void TimelineWidget::ViewDragDropped(TimelineViewMouseEvent *event)
void TimelineWidget::AddBlock(Block *block, TrackReference track)
{
// Set up clip with view parameters (clip item will automatically size its rect accordingly)
TimelineViewBlockItem* item = new TimelineViewBlockItem(block);
TimelineViewBlockItem* item = block_items_.value(block);
item->SetYCoords(GetTrackY(track), GetTrackHeight(track));
item->SetScale(GetScale());
item->SetTrack(track);
item->SetTimebase(timebase());
if (!item) {
// Add to list of clip items that can be iterated through
block_items_.insert(block, item);
// Add to list of clip items that can be iterated through
item = new TimelineViewBlockItem(block);
block_items_.insert(block, item);
// Add item to graphics scene
views_.at(track.type())->view()->scene()->addItem(item);
// Set scale parameters
item->SetScale(GetScale());
item->SetTimebase(timebase());
item->SetYCoords(GetTrackY(track), GetTrackHeight(track));
item->SetTrack(track);
connect(block, &Block::Refreshed, this, &TimelineWidget::BlockRefreshed);
connect(block, &Block::LinksChanged, this, &TimelineWidget::BlockUpdated);
connect(block, &Block::LabelChanged, this, &TimelineWidget::BlockUpdated);
connect(block, &Block::EnabledChanged, this, &TimelineWidget::BlockUpdated);
// Add item to graphics scene
views_.at(track.type())->view()->scene()->addItem(item);
connect(block, &Block::Refreshed, this, &TimelineWidget::BlockRefreshed);
connect(block, &Block::LinksChanged, this, &TimelineWidget::BlockUpdated);
connect(block, &Block::LabelChanged, this, &TimelineWidget::BlockUpdated);
connect(block, &Block::EnabledChanged, this, &TimelineWidget::BlockUpdated);
} else if (item->Track() != track) {
item->SetYCoords(GetTrackY(track), GetTrackHeight(track));
item->SetTrack(track);
}
}
void TimelineWidget::RemoveBlock(Block *block)
void TimelineWidget::RemoveBlock(const QList<Block *> &blocks)
{
disconnect(block, &Block::Refreshed, this, &TimelineWidget::BlockRefreshed);
disconnect(block, &Block::LinksChanged, this, &TimelineWidget::BlockUpdated);
disconnect(block, &Block::LabelChanged, this, &TimelineWidget::BlockUpdated);
disconnect(block, &Block::EnabledChanged, this, &TimelineWidget::BlockUpdated);
QList<TimelineViewBlockItem*> delete_items;
delete_items.reserve(blocks.size());
TimelineViewBlockItem* item = block_items_.take(block);
QList<Block*> deselect_blocks;
if (item->isSelected()) {
// Sending a list of one item all the time is not very efficient
emit BlocksDeselected({block});
foreach (Block* b, blocks) {
// Disconnect all signals
disconnect(b, &Block::Refreshed, this, &TimelineWidget::BlockRefreshed);
disconnect(b, &Block::LinksChanged, this, &TimelineWidget::BlockUpdated);
disconnect(b, &Block::LabelChanged, this, &TimelineWidget::BlockUpdated);
disconnect(b, &Block::EnabledChanged, this, &TimelineWidget::BlockUpdated);
// Take item from map
TimelineViewBlockItem* item = block_items_.take(b);
// If selected, deselect it
int select_index = selected_blocks_.indexOf(b);
if (select_index > -1) {
selected_blocks_.removeAt(select_index);
deselect_blocks.append(b);
RemoveSelection(item);
}
// Finally, delete item
delete item;
}
delete item;
if (!deselect_blocks.isEmpty()) {
// We already removed the blocks from selected_blocks_, so we can signal directly rather than
// through
emit BlocksDeselected(deselect_blocks);
}
qDeleteAll(delete_items);
}
void TimelineWidget::AddTrack(TrackOutput *track, Timeline::TrackType type)
@@ -956,9 +959,7 @@ void TimelineWidget::RemoveTrack(TrackOutput *track)
disconnect(track, &TrackOutput::IndexChanged, this, &TimelineWidget::TrackIndexChanged);
disconnect(track, &TrackOutput::PreviewChanged, this, &TimelineWidget::TrackPreviewUpdated);
foreach (Block* b, track->Blocks()) {
RemoveBlock(b);
}
RemoveBlock(track->Blocks());
}
void TimelineWidget::TrackIndexChanged()
@@ -1142,11 +1143,16 @@ void TimelineWidget::ViewTimestampChanged(int64_t ts)
emit TimeChanged(ts);
}
void TimelineWidget::ToolChanged()
{
HideSnaps();
}
void TimelineWidget::AddGhost(TimelineViewGhostItem *ghost)
{
ghost->SetScale(GetScale());
ghost_items_.append(ghost);
views_.at(ghost->Track().type())->view()->scene()->addItem(ghost);
UpdateViewports(ghost->GetTrack().type());
}
void TimelineWidget::UpdateViewTimebases()
@@ -1174,12 +1180,80 @@ void TimelineWidget::SetBlockLinksSelected(Block* block, bool selected)
TimelineViewBlockItem* link_item;
foreach (Block* link, block->linked_clips()) {
if ((link_item = block_items_[link]) != nullptr) {
link_item->setSelected(selected);
link_item = block_items_.value(link);
if (link_item) {
if (selected) {
AddSelection(link_item);
} else {
RemoveSelection(link_item);
}
}
}
}
void TimelineWidget::QueueScroll(int value)
{
// (using a hacky singleShot so the scroll occurs after the scene and its scrollbars have updated)
deferred_scroll_value_ = value;
QTimer::singleShot(0, this, &TimelineWidget::DeferredScrollAction);
}
TimelineView *TimelineWidget::GetFirstTimelineView()
{
return views_.first()->view();
}
const QRect& TimelineWidget::GetRubberBandGeometry() const
{
return rubberband_.geometry();
}
void TimelineWidget::SignalSelectedBlocks(QList<Block *> input, bool filter)
{
if (input.isEmpty()) {
return;
}
if (filter) {
// If filtering, remove all the blocks that are already selected
for (int i=0; i<input.size(); i++) {
Block* b = input.at(i);
if (selected_blocks_.contains(b)) {
input.removeAt(i);
i--;
}
}
}
selected_blocks_.append(input);
emit BlocksSelected(input);
}
void TimelineWidget::SignalDeselectedBlocks(const QList<Block *> &deselected_blocks)
{
if (deselected_blocks.isEmpty()) {
return;
}
foreach (Block* b, deselected_blocks) {
selected_blocks_.removeOne(b);
}
emit BlocksDeselected(deselected_blocks);
}
void TimelineWidget::SignalDeselectedAllBlocks()
{
if (!selected_blocks_.isEmpty()) {
emit BlocksDeselected(selected_blocks_);
selected_blocks_.clear();
}
}
QVector<Timeline::EditToInfo> TimelineWidget::GetEditToInfo(const rational& playhead_time,
Timeline::MovementMode mode)
{
@@ -1321,6 +1395,17 @@ void TimelineWidget::ShowSnap(const QList<rational> &times)
}
}
void TimelineWidget::UpdateViewports(const Timeline::TrackType &type)
{
if (type == Timeline::kTrackTypeNone) {
foreach (TimelineAndTrackView* tview, views_) {
tview->view()->viewport()->update();
}
} else {
views_.at(type)->view()->viewport()->update();
}
}
void TimelineWidget::HideSnaps()
{
foreach (TimelineAndTrackView* tview, views_) {
@@ -1344,12 +1429,8 @@ void TimelineWidget::StartRubberBandSelect(bool enable_selecting, bool select_li
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);
}
}
// default, call DeselectAll() before calling StartRubberBandSelect()
rubberband_old_selections_ = selections_;
MoveRubberBandSelect(enable_selecting, select_links);
}
@@ -1364,7 +1445,7 @@ void TimelineWidget::MoveRubberBandSelect(bool enable_selecting, bool select_lin
return;
}
QList<QGraphicsItem*> new_selected_list;
QList<QGraphicsItem*> items_in_rubberband;
// Determine all items in the rubberband
foreach (TimelineAndTrackView* tview, views_) {
@@ -1377,60 +1458,45 @@ void TimelineWidget::MoveRubberBandSelect(bool enable_selecting, bool select_lin
// Normalize and get items in rect
QList<QGraphicsItem*> rubberband_items = view->items(mapped_rect.normalized());
new_selected_list.append(rubberband_items);
items_in_rubberband.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--;
}
}
}
// Reset selection to whatever it was before
SetSelections(rubberband_old_selections_);
foreach (QGraphicsItem* item, rubberband_now_selected_) {
item->setSelected(false);
}
// Add any blocks in rubberband
rubberband_now_selected_.clear();
// 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;
}
foreach (QGraphicsItem* item, items_in_rubberband) {
TimelineViewBlockItem* block_item = dynamic_cast<TimelineViewBlockItem*>(item);
TrackOutput* t = GetTrackFromReference(block_item->Track());
if (t && t->IsLocked()) {
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
if (block_item) {
Block* b = block_item->block();
// Add its links to the list
TimelineViewBlockItem* link_item;
foreach (Block* link, b->linked_clips()) {
if ((link_item = block_items_[link]) != nullptr) {
link_item->setSelected(true);
if (b->type() == Block::kGap) {
continue;
}
if (!new_selected_list.contains(link_item)
&& !rubberband_already_selected_.contains(link_item)) {
new_selected_list.append(link_item);
TrackOutput* t = GetTrackFromReference(block_item->Track());
if (t && t->IsLocked()) {
continue;
}
if (!rubberband_now_selected_.contains(b)) {
AddSelection(block_item);
rubberband_now_selected_.append(b);
}
if (select_links) {
foreach (Block* link, b->linked_clips()) {
if (!rubberband_now_selected_.contains(link)) {
AddSelection(block_items_.value(link));
rubberband_now_selected_.append(link);
}
}
}
}
}
rubberband_now_selected_ = new_selected_list;
}
void TimelineWidget::EndRubberBandSelect()
@@ -1438,14 +1504,57 @@ void TimelineWidget::EndRubberBandSelect()
rubberband_.hide();
// 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);
SignalSelectedBlocks(rubberband_now_selected_);
rubberband_now_selected_.clear();
rubberband_already_selected_.clear();
rubberband_old_selections_.clear();
}
void TimelineWidget::AddSelection(const TimeRange &time, const TrackReference &track)
{
selections_[track].InsertTimeRange(time);
UpdateViewports(track.type());
}
void TimelineWidget::AddSelection(TimelineViewBlockItem *item)
{
AddSelection(item->block()->range(), item->Track());
}
void TimelineWidget::RemoveSelection(const TimeRange &time, const TrackReference &track)
{
selections_[track].RemoveTimeRange(time);
UpdateViewports(track.type());
}
void TimelineWidget::RemoveSelection(TimelineViewBlockItem *item)
{
RemoveSelection(item->block()->range(), item->Track());
}
void TimelineWidget::SetSelections(const TimelineWidgetSelections &s)
{
selections_ = s;
UpdateViewports();
}
TimelineViewBlockItem *TimelineWidget::GetItemAtScenePos(const TimelineCoordinate& coord)
{
for (auto it=block_items_.cbegin(); it!=block_items_.cend(); it++) {
Block* b = it.key();
TimelineViewBlockItem* item = it.value();
if (b->in() <= coord.GetFrame()
&& b->out() > coord.GetFrame()
&& item->Track() == coord.GetTrack()) {
return item;
}
}
return nullptr;
}
struct SnapData {
+115 -364
View File
@@ -34,6 +34,9 @@
#include "widget/nodecopypaste/nodecopypaste.h"
#include "widget/slider/timeslider.h"
#include "widget/timebased/timebased.h"
#include "widget/timelinewidget/timelinewidgetselections.h"
#include "widget/timelinewidget/tool/import.h"
#include "widget/timelinewidget/tool/tool.h"
OLIVE_NAMESPACE_ENTER
@@ -46,13 +49,6 @@ class TimelineWidget : public TimeBasedWidget, public NodeCopyPasteWidget, publi
{
Q_OBJECT
public:
enum DropWithoutSequenceBehavior {
kDWSAsk,
kDWSAuto,
kDWSManual,
kDWSDisable
};
TimelineWidget(QWidget* parent = nullptr);
virtual ~TimelineWidget() override;
@@ -103,6 +99,104 @@ public:
void RestoreSplitterState(const QByteArray& state);
static void ReplaceBlocksWithGaps(const QList<Block *>& blocks, bool remove_from_graph, QUndoCommand* command);
/**
* @brief Retrieve the QGraphicsItem at a particular scene position
*
* Requires a float-based scene position. If you have a screen position, use GetScenePos() first to convert it to a
* scene position
*/
TimelineViewBlockItem* GetItemAtScenePos(const TimelineCoordinate &coord);
const QMap<Block*, TimelineViewBlockItem*>& GetBlockItems() const
{
return block_items_;
}
void AddSelection(const TimeRange& time, const TrackReference& track);
void AddSelection(TimelineViewBlockItem* item);
void RemoveSelection(const TimeRange& time, const TrackReference& track);
void RemoveSelection(TimelineViewBlockItem* item);
const TimelineWidgetSelections& GetSelections() const
{
return selections_;
}
void SetSelections(const TimelineWidgetSelections &s);
TrackOutput* GetTrackFromReference(const TrackReference& ref);
void SetViewBeamCursor(const TimelineCoordinate& coord);
const QVector<TimelineViewGhostItem*>& GetGhostItems() const
{
return ghost_items_;
}
void InsertGapsAt(const rational& time, const rational& length, QUndoCommand* command);
void StartRubberBandSelect(bool enable_selecting, bool select_links);
void MoveRubberBandSelect(bool enable_selecting, bool select_links);
void EndRubberBandSelect();
int GetTrackY(const TrackReference& ref);
int GetTrackHeight(const TrackReference& ref);
void AddGhost(TimelineViewGhostItem* ghost);
void ClearGhosts();
bool HasGhosts() const
{
return !ghost_items_.isEmpty();
}
rational GetToolTipTimebase() const;
bool IsBlockSelected(Block* b) const
{
return selected_blocks_.contains(b);
}
void SetBlockLinksSelected(Block *block, bool selected);
void QueueScroll(int value);
TimelineView* GetFirstTimelineView();
const QRect &GetRubberBandGeometry() const;
/**
* @brief Track blocks that have newly been selected (this is preferred over emitting BlocksSelected directly)
*
* TimelineWidget keeps track of which blocks are selected internally. Calling this function will
* add to that list and emit a signal to other widgets that said blocks have been selected.
*
* @param selected_blocks
*
* The list of blocks to add to the internal selection list and signal.
*
* @param filter
*
* TRUE to automatically filter blocks that are already selected from the list. In most cases,
* this is preferable and should only be set to FALSE if the list is guaranteed not to contain
* already selected blocks (and therefore filtering can be skipped to save time).
*/
void SignalSelectedBlocks(QList<Block *> selected_blocks, bool filter = true);
/**
* @brief Track blocks that have been newly deselected
*/
void SignalDeselectedBlocks(const QList<Block*>& deselected_blocks);
/**
* @brief Convenience function to deselect all blocks and signal them
*/
void SignalDeselectedAllBlocks();
signals:
void BlocksSelected(const QList<Block*>& selected_blocks);
@@ -129,341 +223,6 @@ protected:
};
private:
class DraggedFootage {
public:
DraggedFootage(Footage* f, quint64 streams) :
footage_(f),
streams_(streams)
{
}
Footage* footage() const {
return footage_;
}
const quint64& streams() const {
return streams_;
}
private:
Footage* footage_;
quint64 streams_;
};
static DraggedFootage FootageToDraggedFootage(Footage* f);
static QList<DraggedFootage> FootageToDraggedFootage(QList<Footage*> footage);
class Tool
{
public:
Tool(TimelineWidget* parent);
virtual ~Tool();
virtual void MousePress(TimelineViewMouseEvent *){}
virtual void MouseMove(TimelineViewMouseEvent *){}
virtual void MouseRelease(TimelineViewMouseEvent *){}
virtual void MouseDoubleClick(TimelineViewMouseEvent *){}
virtual void HoverMove(TimelineViewMouseEvent *){}
virtual void DragEnter(TimelineViewMouseEvent *){}
virtual void DragMove(TimelineViewMouseEvent *){}
virtual void DragLeave(QDragLeaveEvent *){}
virtual void DragDrop(TimelineViewMouseEvent *){}
TimelineWidget* parent();
static Timeline::MovementMode FlipTrimMode(const Timeline::MovementMode& trim_mode);
protected:
/**
* @brief Retrieve the QGraphicsItem at a particular scene position
*
* Requires a float-based scene position. If you have a screen position, use GetScenePos() first to convert it to a
* scene position
*/
TimelineViewBlockItem* GetItemAtScenePos(const TimelineCoordinate &coord);
/**
* @brief Validates Ghosts that are moving horizontally (time-based)
*
* 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);
/**
* @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);
void GetGhostData(rational *earliest_point, rational *latest_point);
void InsertGapsAtGhostDestination(QUndoCommand* command);
QList<rational> snap_points_;
bool dragging_;
TimelineCoordinate drag_start_;
private:
TimelineWidget* parent_;
};
class BeamTool : public Tool
{
public:
BeamTool(TimelineWidget *parent);
virtual void HoverMove(TimelineViewMouseEvent *event) override;
protected:
TimelineCoordinate ValidatedCoordinate(TimelineCoordinate coord);
};
class PointerTool : public Tool
{
public:
PointerTool(TimelineWidget* parent);
virtual void MousePress(TimelineViewMouseEvent *event) override;
virtual void MouseMove(TimelineViewMouseEvent *event) override;
virtual void MouseRelease(TimelineViewMouseEvent *event) override;
virtual void HoverMove(TimelineViewMouseEvent *event) override;
protected:
virtual void FinishDrag(TimelineViewMouseEvent *event);
virtual void InitiateDrag(TimelineViewBlockItem* clicked_item,
Timeline::MovementMode trim_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);
/**
* @brief Validates Ghosts that are getting their in points trimmed
*
* 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);
/**
* @brief Validates Ghosts that are getting their out points trimmed
*
* 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);
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_;
}
void SetMovementAllowed(bool e)
{
movement_allowed_ = e;
}
void SetTrimmingAllowed(bool e)
{
trimming_allowed_ = e;
}
void SetTrackMovementAllowed(bool e)
{
track_movement_allowed_ = e;
}
void SetGapTrimmingAllowed(bool e)
{
gap_trimming_allowed_ = e;
}
private:
Timeline::MovementMode IsCursorInTrimHandle(TimelineViewBlockItem* block, qreal cursor_x);
void AddGhostInternal(TimelineViewGhostItem* ghost, Timeline::MovementMode mode);
bool IsClipTrimmable(TimelineViewBlockItem* clip,
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 gap_trimming_allowed_;
bool rubberband_selecting_;
Timeline::TrackType drag_track_type_;
Timeline::MovementMode drag_movement_mode_;
TimelineViewBlockItem* clicked_item_;
};
class ImportTool : public Tool
{
public:
ImportTool(TimelineWidget* parent);
virtual void DragEnter(TimelineViewMouseEvent *event) override;
virtual void DragMove(TimelineViewMouseEvent *event) override;
virtual void DragLeave(QDragLeaveEvent *event) override;
virtual void DragDrop(TimelineViewMouseEvent *event) override;
void PlaceAt(const QList<Footage*> &footage, const rational& start, bool insert);
void PlaceAt(const QList<DraggedFootage> &footage, const rational& start, bool insert);
private:
void FootageToGhosts(rational ghost_start, const QList<DraggedFootage>& footage, const rational &dest_tb, const int &track_start);
void PrepGhosts(const rational &frame, const int &track_index);
void DropGhosts(bool insert);
QList<DraggedFootage> dragged_footage_;
int import_pre_buffer_;
};
class EditTool : public BeamTool
{
public:
EditTool(TimelineWidget* parent);
virtual void MousePress(TimelineViewMouseEvent *event) override;
virtual void MouseMove(TimelineViewMouseEvent *event) override;
virtual void MouseRelease(TimelineViewMouseEvent *event) override;
};
class RazorTool : public BeamTool
{
public:
RazorTool(TimelineWidget* parent);
virtual void MousePress(TimelineViewMouseEvent *event) override;
virtual void MouseMove(TimelineViewMouseEvent *event) override;
virtual void MouseRelease(TimelineViewMouseEvent *event) override;
private:
QVector<TrackReference> split_tracks_;
};
class RippleTool : public PointerTool
{
public:
RippleTool(TimelineWidget* parent);
protected:
virtual void FinishDrag(TimelineViewMouseEvent *event) override;
virtual void InitiateDrag(TimelineViewBlockItem* clicked_item,
Timeline::MovementMode trim_mode) override;
};
class RollingTool : public PointerTool
{
public:
RollingTool(TimelineWidget* parent);
protected:
virtual void InitiateDrag(TimelineViewBlockItem* clicked_item,
Timeline::MovementMode trim_mode) override;
};
class SlideTool : public PointerTool
{
public:
SlideTool(TimelineWidget* parent);
protected:
virtual void InitiateDrag(TimelineViewBlockItem* clicked_item,
Timeline::MovementMode trim_mode) override;
};
class SlipTool : public PointerTool
{
public:
SlipTool(TimelineWidget* parent);
protected:
virtual void ProcessDrag(const TimelineCoordinate &mouse_pos) override;
virtual void FinishDrag(TimelineViewMouseEvent *event) override;
};
class ZoomTool : public Tool
{
public:
ZoomTool(TimelineWidget* parent);
virtual void MousePress(TimelineViewMouseEvent *event) override;
virtual void MouseMove(TimelineViewMouseEvent *event) override;
virtual void MouseRelease(TimelineViewMouseEvent *event) override;
};
class AddTool : public BeamTool
{
public:
AddTool(TimelineWidget* parent);
virtual void MousePress(TimelineViewMouseEvent *event) override;
virtual void MouseMove(TimelineViewMouseEvent *event) override;
virtual void MouseRelease(TimelineViewMouseEvent *event) override;
protected:
void MouseMoveInternal(const rational& cursor_frame, bool outwards);
TimelineViewGhostItem* ghost_;
rational drag_start_point_;
};
class TransitionTool : public AddTool
{
public:
TransitionTool(TimelineWidget* parent);
virtual void MousePress(TimelineViewMouseEvent *event) override;
virtual void MouseMove(TimelineViewMouseEvent *event) override;
virtual void MouseRelease(TimelineViewMouseEvent *event) override;
private:
bool dual_transition_;
};
rational GetToolTipTimebase() const;
void InsertGapsAt(const rational& time, const rational& length, QUndoCommand* command);
void ReplaceBlocksWithGaps(const QList<Block *>& blocks, bool remove_from_graph, QUndoCommand* command);
void SetBlockLinksSelected(Block *block, bool selected);
QVector<Timeline::EditToInfo> GetEditToInfo(const rational &playhead_time, Timeline::MovementMode mode);
void RippleTo(Timeline::MovementMode mode);
@@ -472,54 +231,44 @@ private:
void ShowSnap(const QList<rational>& times);
void UpdateViewports(const Timeline::TrackType& type = Timeline::kTrackTypeNone);
QPoint drag_origin_;
void StartRubberBandSelect(bool enable_selecting, bool select_links);
void MoveRubberBandSelect(bool enable_selecting, bool select_links);
void EndRubberBandSelect();
QRubberBand rubberband_;
QList<QGraphicsItem*> rubberband_already_selected_;
QList<QGraphicsItem*> rubberband_now_selected_;
TimelineWidgetSelections rubberband_old_selections_;
QList<Block*> rubberband_now_selected_;
Tool* GetActiveTool();
TimelineWidgetSelections selections_;
QVector<Tool*> tools_;
TimelineTool* GetActiveTool();
QVector<TimelineTool*> tools_;
ImportTool* import_tool_;
Tool* active_tool_;
void ClearGhosts();
bool HasGhosts();
TimelineTool* active_tool_;
QVector<TimelineViewGhostItem*> ghost_items_;
QMap<Block*, TimelineViewBlockItem*> block_items_;
TrackOutput* GetTrackFromReference(const TrackReference& ref);
QList<TimelineAndTrackView*> views_;
TimeSlider* timecode_label_;
QList<Block*> selected_blocks_;
int deferred_scroll_value_;
bool use_audio_time_units_;
QSplitter* view_splitter_;
int GetTrackY(const TrackReference& ref);
int GetTrackHeight(const TrackReference& ref);
void CenterOn(qreal scene_pos);
void AddGhost(TimelineViewGhostItem* ghost);
void UpdateViewTimebases();
void SetViewBeamCursor(const TimelineCoordinate& coord);
private slots:
void ViewMousePressed(TimelineViewMouseEvent* event);
void ViewMouseMoved(TimelineViewMouseEvent* event);
@@ -532,7 +281,7 @@ private slots:
void ViewDragDropped(TimelineViewMouseEvent* event);
void AddBlock(Block* block, TrackReference track);
void RemoveBlock(Block* block);
void RemoveBlock(const QList<Block*>& blocks);
void AddTrack(TrackOutput* track, Timeline::TrackType type);
void RemoveTrack(TrackOutput* track);
@@ -568,6 +317,8 @@ private slots:
void ViewTimestampChanged(int64_t ts);
void ToolChanged();
};
OLIVE_NAMESPACE_EXIT
@@ -0,0 +1,77 @@
/***
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 "timelinewidgetselections.h"
OLIVE_NAMESPACE_ENTER
void TimelineWidgetSelections::ShiftTime(const rational &diff)
{
for (auto it=this->begin(); it!=this->end(); it++) {
for (auto it2=it.value().begin(); it2!=it.value().end(); it2++) {
(*it2) += diff;
}
}
}
void TimelineWidgetSelections::ShiftTracks(Timeline::TrackType type, int diff)
{
TimelineWidgetSelections cached_selections;
{
// Take all selections of this track type
auto it = this->begin();
while (it != this->end()) {
if (it.key().type() == type) {
cached_selections.insert(it.key(), it.value());
it = this->erase(it);
} else {
it++;
}
}
}
// Then re-insert them with the diff applied
for (auto it=cached_selections.cbegin(); it!=cached_selections.cend(); it++) {
TrackReference ref(it.key().type(), it.key().index() + diff);
this->insert(ref, it.value());
}
}
void TimelineWidgetSelections::TrimIn(const rational &diff)
{
for (auto it=this->begin(); it!=this->end(); it++) {
for (auto it2=it.value().begin(); it2!=it.value().end(); it2++) {
(*it2).set_in((*it2).in() + diff);
}
}
}
void TimelineWidgetSelections::TrimOut(const rational &diff)
{
for (auto it=this->begin(); it!=this->end(); it++) {
for (auto it2=it.value().begin(); it2!=it.value().end(); it2++) {
(*it2).set_out((*it2).out() + diff);
}
}
}
OLIVE_NAMESPACE_EXIT
@@ -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/>.
***/
#ifndef TIMELINEWIDGETSELECTIONS_H
#define TIMELINEWIDGETSELECTIONS_H
#include <QHash>
#include "common/timerange.h"
#include "timeline/trackreference.h"
OLIVE_NAMESPACE_ENTER
class TimelineWidgetSelections : public QHash<TrackReference, TimeRangeList>
{
public:
TimelineWidgetSelections() = default;
void ShiftTime(const rational& diff);
void ShiftTracks(Timeline::TrackType type, int diff);
void TrimIn(const rational& diff);
void TrimOut(const rational& diff);
};
OLIVE_NAMESPACE_EXIT
#endif // TIMELINEWIDGETSELECTIONS_H
@@ -17,17 +17,30 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
widget/timelinewidget/tool/add.cpp
widget/timelinewidget/tool/add.h
widget/timelinewidget/tool/beam.cpp
widget/timelinewidget/tool/beam.h
widget/timelinewidget/tool/edit.cpp
widget/timelinewidget/tool/edit.h
widget/timelinewidget/tool/import.cpp
widget/timelinewidget/tool/import.h
widget/timelinewidget/tool/pointer.cpp
widget/timelinewidget/tool/pointer.h
widget/timelinewidget/tool/razor.cpp
widget/timelinewidget/tool/razor.h
widget/timelinewidget/tool/ripple.cpp
widget/timelinewidget/tool/ripple.h
widget/timelinewidget/tool/rolling.cpp
widget/timelinewidget/tool/rolling.h
widget/timelinewidget/tool/slide.cpp
widget/timelinewidget/tool/slide.h
widget/timelinewidget/tool/slip.cpp
widget/timelinewidget/tool/slip.h
widget/timelinewidget/tool/transition.cpp
widget/timelinewidget/tool/transition.h
widget/timelinewidget/tool/tool.cpp
widget/timelinewidget/tool/tool.h
widget/timelinewidget/tool/zoom.cpp
widget/timelinewidget/tool/zoom.h
PARENT_SCOPE
)
+11 -11
View File
@@ -20,6 +20,7 @@
#include "widget/timelinewidget/timelinewidget.h"
#include "add.h"
#include "core.h"
#include "node/factory.h"
#include "node/generator/solid/solid.h"
@@ -28,13 +29,13 @@
OLIVE_NAMESPACE_ENTER
TimelineWidget::AddTool::AddTool(TimelineWidget *parent) :
AddTool::AddTool(TimelineWidget *parent) :
BeamTool(parent),
ghost_(nullptr)
{
}
void TimelineWidget::AddTool::MousePress(TimelineViewMouseEvent *event)
void AddTool::MousePress(TimelineViewMouseEvent *event)
{
const TrackReference& track = event->GetTrack();
@@ -71,14 +72,13 @@ void TimelineWidget::AddTool::MousePress(TimelineViewMouseEvent *event)
ghost_->SetIn(drag_start_point_);
ghost_->SetOut(drag_start_point_);
ghost_->SetTrack(track);
ghost_->SetYCoords(parent()->GetTrackY(track), parent()->GetTrackHeight(track));
parent()->AddGhost(ghost_);
snap_points_.append(drag_start_point_);
}
}
void TimelineWidget::AddTool::MouseMove(TimelineViewMouseEvent *event)
void AddTool::MouseMove(TimelineViewMouseEvent *event)
{
if (!ghost_) {
return;
@@ -87,16 +87,16 @@ void TimelineWidget::AddTool::MouseMove(TimelineViewMouseEvent *event)
MouseMoveInternal(event->GetFrame(), event->GetModifiers() & Qt::AltModifier);
}
void TimelineWidget::AddTool::MouseRelease(TimelineViewMouseEvent *event)
void AddTool::MouseRelease(TimelineViewMouseEvent *event)
{
const TrackReference& track = ghost_->Track();
const TrackReference& track = ghost_->GetTrack();
if (ghost_) {
if (!ghost_->AdjustedLength().isNull()) {
if (!ghost_->GetAdjustedLength().isNull()) {
QUndoCommand* command = new QUndoCommand();
ClipBlock* clip = new ClipBlock();
clip->set_length_and_media_out(ghost_->AdjustedLength());
clip->set_length_and_media_out(ghost_->GetAdjustedLength());
clip->SetLabel(OLIVE_NAMESPACE::Tool::GetAddableObjectName(Core::instance()->GetSelectedAddableObject()));
NodeGraph* graph = static_cast<NodeGraph*>(parent()->GetConnectedNode()->parent());
@@ -156,14 +156,14 @@ void TimelineWidget::AddTool::MouseRelease(TimelineViewMouseEvent *event)
}
}
void TimelineWidget::AddTool::MouseMoveInternal(const rational &cursor_frame, bool outwards)
void AddTool::MouseMoveInternal(const rational &cursor_frame, bool outwards)
{
// Calculate movement
rational movement = cursor_frame - drag_start_point_;
// Validation: Ensure in point never goes below 0
if (movement < -ghost_->In() || (outwards && -movement < -ghost_->In())) {
movement = -ghost_->In();
if (movement < -ghost_->GetIn() || (outwards && -movement < -ghost_->GetIn())) {
movement = -ghost_->GetIn();
}
// Snap movement
+47
View File
@@ -0,0 +1,47 @@
/***
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 ADDTIMELINETOOL_H
#define ADDTIMELINETOOL_H
#include "beam.h"
OLIVE_NAMESPACE_ENTER
class AddTool : public BeamTool
{
public:
AddTool(TimelineWidget* parent);
virtual void MousePress(TimelineViewMouseEvent *event) override;
virtual void MouseMove(TimelineViewMouseEvent *event) override;
virtual void MouseRelease(TimelineViewMouseEvent *event) override;
protected:
void MouseMoveInternal(const rational& cursor_frame, bool outwards);
TimelineViewGhostItem* ghost_;
rational drag_start_point_;
};
OLIVE_NAMESPACE_EXIT
#endif // ADDTIMELINETOOL_H
+5 -4
View File
@@ -18,21 +18,22 @@
***/
#include "beam.h"
#include "widget/timelinewidget/timelinewidget.h"
OLIVE_NAMESPACE_ENTER
TimelineWidget::BeamTool::BeamTool(TimelineWidget *parent) :
Tool(parent)
BeamTool::BeamTool(TimelineWidget *parent) :
TimelineTool(parent)
{
}
void TimelineWidget::BeamTool::HoverMove(TimelineViewMouseEvent *event)
void BeamTool::HoverMove(TimelineViewMouseEvent *event)
{
parent()->SetViewBeamCursor(ValidatedCoordinate(event->GetCoordinates(true)));
}
TimelineCoordinate TimelineWidget::BeamTool::ValidatedCoordinate(TimelineCoordinate coord)
TimelineCoordinate BeamTool::ValidatedCoordinate(TimelineCoordinate coord)
{
if (Core::instance()->snapping()) {
rational movement;
+42
View File
@@ -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 BEAMTIMELINETOOL_H
#define BEAMTIMELINETOOL_H
#include "tool.h"
OLIVE_NAMESPACE_ENTER
class BeamTool : public TimelineTool
{
public:
BeamTool(TimelineWidget *parent);
virtual void HoverMove(TimelineViewMouseEvent *event) override;
protected:
TimelineCoordinate ValidatedCoordinate(TimelineCoordinate coord);
};
OLIVE_NAMESPACE_EXIT
#endif // BEAMTIMELINETOOL_H
+50 -7
View File
@@ -18,28 +18,71 @@
***/
#include "edit.h"
#include "widget/timelinewidget/timelinewidget.h"
OLIVE_NAMESPACE_ENTER
TimelineWidget::EditTool::EditTool(TimelineWidget* parent) :
EditTool::EditTool(TimelineWidget* parent) :
BeamTool(parent)
{
}
void TimelineWidget::EditTool::MousePress(TimelineViewMouseEvent *event)
void EditTool::MousePress(TimelineViewMouseEvent *event)
{
Q_UNUSED(event)
if (!(event->GetModifiers() & Qt::ShiftModifier)) {
parent()->DeselectAll();
}
}
void TimelineWidget::EditTool::MouseMove(TimelineViewMouseEvent *event)
void EditTool::MouseMove(TimelineViewMouseEvent *event)
{
Q_UNUSED(event)
if (dragging_) {
rational end_frame = event->GetFrame(true);
if (Core::instance()->snapping()) {
rational movement;
parent()->SnapPoint({end_frame}, &movement);
if (!movement.isNull()) {
end_frame += movement;
}
}
parent()->SetSelections(start_selections_);
parent()->AddSelection(TimeRange(start_coord_.GetFrame(), end_frame),
start_coord_.GetTrack());
} else {
start_selections_ = parent()->GetSelections();
dragging_ = true;
start_coord_ = event->GetCoordinates(true);
// Snap if we're snapping
if (Core::instance()->snapping()) {
rational movement;
parent()->SnapPoint({start_coord_.GetFrame()}, &movement);
if (!movement.isNull()) {
start_coord_.SetFrame(start_coord_.GetFrame() + movement);
}
}
dragging_ = true;
}
}
void TimelineWidget::EditTool::MouseRelease(TimelineViewMouseEvent *event)
void EditTool::MouseRelease(TimelineViewMouseEvent *event)
{
Q_UNUSED(event)
dragging_ = false;
}
void EditTool::MouseDoubleClick(TimelineViewMouseEvent *event)
{
TimelineViewBlockItem* item = parent()->GetItemAtScenePos(event->GetCoordinates());
if (item && !parent()->GetTrackFromReference(item->Track())->IsLocked()) {
parent()->AddSelection(item);
}
}
OLIVE_NAMESPACE_EXIT
+49
View File
@@ -0,0 +1,49 @@
/***
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 EDITTIMELINETOOL_H
#define EDITTIMELINETOOL_H
#include "beam.h"
#include "tool.h"
#include "widget/timelinewidget/timelinewidgetselections.h"
OLIVE_NAMESPACE_ENTER
class EditTool : public BeamTool
{
public:
EditTool(TimelineWidget* parent);
virtual void MousePress(TimelineViewMouseEvent *event) override;
virtual void MouseMove(TimelineViewMouseEvent *event) override;
virtual void MouseRelease(TimelineViewMouseEvent *event) override;
virtual void MouseDoubleClick(TimelineViewMouseEvent *event) override;
private:
TimelineWidgetSelections start_selections_;
TimelineCoordinate start_coord_;
};
OLIVE_NAMESPACE_EXIT
#endif // EDITTIMELINETOOL_H
+45 -27
View File
@@ -59,14 +59,14 @@ Timeline::TrackType TrackTypeFromStreamType(Stream::Type stream_type)
return Timeline::kTrackTypeNone;
}
TimelineWidget::ImportTool::ImportTool(TimelineWidget *parent) :
Tool(parent)
ImportTool::ImportTool(TimelineWidget *parent) :
TimelineTool(parent)
{
// Calculate width used for importing to give ghosts a slight lead-in so the ghosts aren't right on the cursor
import_pre_buffer_ = QFontMetricsWidth(parent->fontMetrics(), "HHHHHHHH");
}
void TimelineWidget::ImportTool::DragEnter(TimelineViewMouseEvent *event)
void ImportTool::DragEnter(TimelineViewMouseEvent *event)
{
QStringList mime_formats = event->GetMimeData()->formats();
@@ -118,7 +118,7 @@ void TimelineWidget::ImportTool::DragEnter(TimelineViewMouseEvent *event)
}
}
void TimelineWidget::ImportTool::DragMove(TimelineViewMouseEvent *event)
void ImportTool::DragMove(TimelineViewMouseEvent *event)
{
if (!dragged_footage_.isEmpty()) {
@@ -127,27 +127,24 @@ void TimelineWidget::ImportTool::DragMove(TimelineViewMouseEvent *event)
int track_movement = event->GetTrack().index() - drag_start_.GetTrack().index();
time_movement = ValidateTimeMovement(time_movement);
track_movement = ValidateTrackMovement(track_movement, parent()->ghost_items_);
track_movement = ValidateTrackMovement(track_movement, parent()->GetGhostItems());
// If snapping is enabled, check for snap points
if (Core::instance()->snapping()) {
parent()->SnapPoint(snap_points_, &time_movement);
time_movement = ValidateTimeMovement(time_movement);
track_movement = ValidateTrackMovement(track_movement, parent()->ghost_items_);
track_movement = ValidateTrackMovement(track_movement, parent()->GetGhostItems());
}
rational earliest_ghost = RATIONAL_MAX;
// Move ghosts to the mouse cursor
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) {
ghost->SetInAdjustment(time_movement);
ghost->SetOutAdjustment(time_movement);
ghost->SetTrackAdjustment(track_movement);
TrackReference adjusted_track = ghost->GetAdjustedTrack();
ghost->SetYCoords(parent()->GetTrackY(adjusted_track), parent()->GetTrackHeight(adjusted_track));
earliest_ghost = qMin(earliest_ghost, ghost->GetAdjustedIn());
}
@@ -171,7 +168,7 @@ void TimelineWidget::ImportTool::DragMove(TimelineViewMouseEvent *event)
}
}
void TimelineWidget::ImportTool::DragLeave(QDragLeaveEvent* event)
void ImportTool::DragLeave(QDragLeaveEvent* event)
{
if (!dragged_footage_.isEmpty()) {
parent()->ClearGhosts();
@@ -183,7 +180,7 @@ void TimelineWidget::ImportTool::DragLeave(QDragLeaveEvent* event)
}
}
void TimelineWidget::ImportTool::DragDrop(TimelineViewMouseEvent *event)
void ImportTool::DragDrop(TimelineViewMouseEvent *event)
{
if (!dragged_footage_.isEmpty()) {
DropGhosts(event->GetModifiers() & Qt::ControlModifier);
@@ -194,12 +191,12 @@ void TimelineWidget::ImportTool::DragDrop(TimelineViewMouseEvent *event)
}
}
void TimelineWidget::ImportTool::PlaceAt(const QList<Footage *> &footage, const rational &start, bool insert)
void ImportTool::PlaceAt(const QList<Footage *> &footage, const rational &start, bool insert)
{
PlaceAt(FootageToDraggedFootage(footage), start, insert);
}
void TimelineWidget::ImportTool::PlaceAt(const QList<DraggedFootage> &footage, const rational &start, bool insert)
void ImportTool::PlaceAt(const QList<DraggedFootage> &footage, const rational &start, bool insert)
{
dragged_footage_ = footage;
@@ -211,7 +208,7 @@ void TimelineWidget::ImportTool::PlaceAt(const QList<DraggedFootage> &footage, c
DropGhosts(insert);
}
void TimelineWidget::ImportTool::FootageToGhosts(rational ghost_start, const QList<DraggedFootage> &footage_list, const rational& dest_tb, const int& track_start)
void ImportTool::FootageToGhosts(rational ghost_start, const QList<DraggedFootage> &footage_list, const rational& dest_tb, const int& track_start)
{
foreach (const DraggedFootage& footage, footage_list) {
@@ -262,7 +259,7 @@ void TimelineWidget::ImportTool::FootageToGhosts(rational ghost_start, const QLi
// Increment track count for this track type
track_offsets[track_type]++;
ghost->setData(TimelineViewGhostItem::kAttachedFootage, QVariant::fromValue(stream));
ghost->SetData(TimelineViewGhostItem::kAttachedFootage, QVariant::fromValue(stream));
ghost->SetMode(Timeline::kMove);
footage_ghosts.append(ghost);
@@ -278,8 +275,8 @@ void TimelineWidget::ImportTool::FootageToGhosts(rational ghost_start, const QLi
ghost->SetIn(ghost_start);
ghost->SetOut(ghost_start + footage_duration);
snap_points_.append(ghost->In());
snap_points_.append(ghost->Out());
snap_points_.append(ghost->GetIn());
snap_points_.append(ghost->GetOut());
parent()->AddGhost(ghost);
}
@@ -290,7 +287,7 @@ void TimelineWidget::ImportTool::FootageToGhosts(rational ghost_start, const QLi
}
}
void TimelineWidget::ImportTool::PrepGhosts(const rational& frame, const int& track_index)
void ImportTool::PrepGhosts(const rational& frame, const int& track_index)
{
if (parent()->GetConnectedNode()) {
FootageToGhosts(frame,
@@ -300,7 +297,7 @@ void TimelineWidget::ImportTool::PrepGhosts(const rational& frame, const int& tr
}
}
void TimelineWidget::ImportTool::DropGhosts(bool insert)
void ImportTool::DropGhosts(bool insert)
{
QUndoCommand* command = new QUndoCommand();
@@ -398,21 +395,21 @@ void TimelineWidget::ImportTool::DropGhosts(bool insert)
if (dst_graph) {
QVector<Block*> block_items(parent()->ghost_items_.size());
QVector<Block*> block_items(parent()->GetGhostItems().size());
// Check if we're inserting
if (insert) {
InsertGapsAtGhostDestination(command);
}
for (int i=0;i<parent()->ghost_items_.size();i++) {
TimelineViewGhostItem* ghost = parent()->ghost_items_.at(i);
for (int i=0;i<parent()->GetGhostItems().size();i++) {
TimelineViewGhostItem* ghost = parent()->GetGhostItems().at(i);
StreamPtr footage_stream = ghost->data(TimelineViewGhostItem::kAttachedFootage).value<StreamPtr>();
StreamPtr footage_stream = ghost->GetData(TimelineViewGhostItem::kAttachedFootage).value<StreamPtr>();
ClipBlock* clip = new ClipBlock();
clip->set_media_in(ghost->MediaIn());
clip->set_length_and_media_out(ghost->Length());
clip->set_media_in(ghost->GetMediaIn());
clip->set_length_and_media_out(ghost->GetLength());
clip->SetLabel(footage_stream->footage()->name());
new NodeAddCommand(dst_graph, clip, command);
@@ -466,7 +463,7 @@ void TimelineWidget::ImportTool::DropGhosts(bool insert)
// Link any clips so far that share the same Footage with this one
for (int j=0;j<i;j++) {
StreamPtr footage_compare = parent()->ghost_items_.at(j)->data(TimelineViewGhostItem::kAttachedFootage).value<StreamPtr>();
StreamPtr footage_compare = parent()->GetGhostItems().at(j)->GetData(TimelineViewGhostItem::kAttachedFootage).value<StreamPtr>();
if (footage_compare->footage() == footage_stream->footage()) {
Block::Link(block_items.at(j), clip);
@@ -485,4 +482,25 @@ void TimelineWidget::ImportTool::DropGhosts(bool insert)
}
}
ImportTool::DraggedFootage ImportTool::FootageToDraggedFootage(Footage *f)
{
return DraggedFootage(f, f->get_enabled_stream_flags());
}
QList<ImportTool::DraggedFootage> ImportTool::FootageToDraggedFootage(QList<Footage *> footage)
{
QList<DraggedFootage> df;
foreach (Footage* f, footage) {
df.append(FootageToDraggedFootage(f));
}
return df;
}
QString ImportTool::tr(const char *s)
{
return QCoreApplication::translate("ImportTool", s);
}
OLIVE_NAMESPACE_EXIT
+91
View File
@@ -0,0 +1,91 @@
/***
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 IMPORTTIMELINETOOL_H
#define IMPORTTIMELINETOOL_H
#include "tool.h"
OLIVE_NAMESPACE_ENTER
class ImportTool : public TimelineTool
{
public:
ImportTool(TimelineWidget* parent);
virtual void DragEnter(TimelineViewMouseEvent *event) override;
virtual void DragMove(TimelineViewMouseEvent *event) override;
virtual void DragLeave(QDragLeaveEvent *event) override;
virtual void DragDrop(TimelineViewMouseEvent *event) override;
class DraggedFootage {
public:
DraggedFootage(Footage* f, quint64 streams) :
footage_(f),
streams_(streams)
{
}
Footage* footage() const {
return footage_;
}
const quint64& streams() const {
return streams_;
}
private:
Footage* footage_;
quint64 streams_;
};
void PlaceAt(const QList<Footage*> &footage, const rational& start, bool insert);
void PlaceAt(const QList<DraggedFootage> &footage, const rational& start, bool insert);
enum DropWithoutSequenceBehavior {
kDWSAsk,
kDWSAuto,
kDWSManual,
kDWSDisable
};
private:
static DraggedFootage FootageToDraggedFootage(Footage* f);
static QList<DraggedFootage> FootageToDraggedFootage(QList<Footage*> footage);
QString tr(const char* s);
void FootageToGhosts(rational ghost_start, const QList<DraggedFootage>& footage, const rational &dest_tb, const int &track_start);
void PrepGhosts(const rational &frame, const int &track_index);
void DropGhosts(bool insert);
QList<DraggedFootage> dragged_footage_;
int import_pre_buffer_;
};
OLIVE_NAMESPACE_EXIT
#endif // IMPORTTIMELINETOOL_H
+101 -79
View File
@@ -32,12 +32,13 @@
#include "core.h"
#include "node/block/gap/gap.h"
#include "node/block/transition/transition.h"
#include "pointer.h"
#include "widget/nodeview/nodeviewundo.h"
OLIVE_NAMESPACE_ENTER
TimelineWidget::PointerTool::PointerTool(TimelineWidget *parent) :
Tool(parent),
PointerTool::PointerTool(TimelineWidget *parent) :
TimelineTool(parent),
movement_allowed_(true),
trimming_allowed_(true),
track_movement_allowed_(true),
@@ -46,13 +47,12 @@ TimelineWidget::PointerTool::PointerTool(TimelineWidget *parent) :
{
}
void TimelineWidget::PointerTool::MousePress(TimelineViewMouseEvent *event)
void PointerTool::MousePress(TimelineViewMouseEvent *event)
{
// Determine if item clicked on is selectable
clicked_item_ = GetItemAtScenePos(event->GetCoordinates());
clicked_item_ = parent()->GetItemAtScenePos(event->GetCoordinates());
bool selectable_item = (clicked_item_
&& clicked_item_->flags() & QGraphicsItem::ItemIsSelectable
&& !parent()->GetTrackFromReference(clicked_item_->Track())->IsLocked());
if (selectable_item) {
@@ -76,14 +76,14 @@ void TimelineWidget::PointerTool::MousePress(TimelineViewMouseEvent *event)
}
// If this item is already selected, no further selection needs to be made
if (clicked_item_->isSelected()) {
if (parent()->IsBlockSelected(clicked_item_->block())) {
// Collect item deselections
QList<Block*> deselected_blocks;
// If shift is held, deselect it
if (event->GetModifiers() & Qt::ShiftModifier) {
clicked_item_->setSelected(false);
parent()->RemoveSelection(clicked_item_);
deselected_blocks.append(clicked_item_->block());
// If not holding alt, deselect all links as well
@@ -93,7 +93,7 @@ void TimelineWidget::PointerTool::MousePress(TimelineViewMouseEvent *event)
}
}
emit parent()->BlocksDeselected(deselected_blocks);
parent()->SignalDeselectedBlocks(deselected_blocks);
return;
}
@@ -110,7 +110,7 @@ void TimelineWidget::PointerTool::MousePress(TimelineViewMouseEvent *event)
QList<Block*> selected_blocks;
// Select this item
clicked_item_->setSelected(true);
parent()->AddSelection(clicked_item_);
selected_blocks.append(clicked_item_->block());
// If not holding alt, select all links as well
@@ -119,7 +119,7 @@ void TimelineWidget::PointerTool::MousePress(TimelineViewMouseEvent *event)
selected_blocks.append(clicked_item_->block()->linked_clips().toList());
}
emit parent()->BlocksSelected(selected_blocks);
parent()->SignalSelectedBlocks(selected_blocks);
} else if (event->GetButton() == Qt::LeftButton) {
@@ -131,7 +131,7 @@ void TimelineWidget::PointerTool::MousePress(TimelineViewMouseEvent *event)
}
}
void TimelineWidget::PointerTool::MouseMove(TimelineViewMouseEvent *event)
void PointerTool::MouseMove(TimelineViewMouseEvent *event)
{
if (rubberband_selecting_) {
// Process rubberband select
@@ -155,7 +155,7 @@ void TimelineWidget::PointerTool::MouseMove(TimelineViewMouseEvent *event)
}
if (dragging_ && !parent()->ghost_items_.isEmpty()) {
if (dragging_ && !parent()->GetGhostItems().isEmpty()) {
// We're already dragging AND we have ghosts to work with
ProcessDrag(event->GetCoordinates());
@@ -164,7 +164,7 @@ void TimelineWidget::PointerTool::MouseMove(TimelineViewMouseEvent *event)
}
}
void TimelineWidget::PointerTool::MouseRelease(TimelineViewMouseEvent *event)
void PointerTool::MouseRelease(TimelineViewMouseEvent *event)
{
if (rubberband_selecting_) {
// Finish rubberband select
@@ -175,7 +175,7 @@ void TimelineWidget::PointerTool::MouseRelease(TimelineViewMouseEvent *event)
if (dragging_) {
// If we were dragging, process the end of the drag
if (!parent()->ghost_items_.isEmpty()) {
if (!parent()->GetGhostItems().isEmpty()) {
FinishDrag(event);
}
@@ -187,11 +187,11 @@ void TimelineWidget::PointerTool::MouseRelease(TimelineViewMouseEvent *event)
}
}
void TimelineWidget::PointerTool::HoverMove(TimelineViewMouseEvent *event)
void PointerTool::HoverMove(TimelineViewMouseEvent *event)
{
if (trimming_allowed_) {
// No dragging, but we still want to process cursors
TimelineViewBlockItem* block_at_cursor = GetItemAtScenePos(event->GetCoordinates());
TimelineViewBlockItem* block_at_cursor = parent()->GetItemAtScenePos(event->GetCoordinates());
if (block_at_cursor) {
switch (IsCursorInTrimHandle(block_at_cursor, event->GetSceneX())) {
@@ -215,10 +215,10 @@ void TimelineWidget::PointerTool::HoverMove(TimelineViewMouseEvent *event)
void SetGhostToSlideMode(TimelineViewGhostItem* g)
{
g->SetCanMoveTracks(false);
g->setData(TimelineViewGhostItem::kGhostIsSliding, true);
g->SetData(TimelineViewGhostItem::kGhostIsSliding, true);
}
void TimelineWidget::PointerTool::InitiateDragInternal(TimelineViewBlockItem *clicked_item,
void PointerTool::InitiateDragInternal(TimelineViewBlockItem *clicked_item,
Timeline::MovementMode trim_mode,
bool dont_roll_trims,
bool allow_nongap_rolling,
@@ -427,16 +427,16 @@ void TimelineWidget::PointerTool::InitiateDragInternal(TimelineViewBlockItem *cl
// 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));
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);
ghost->SetData(TimelineViewGhostItem::kTrimShouldBeIgnored, true);
} else {
adjacent_ghost->setData(TimelineViewGhostItem::kTrimShouldBeIgnored, true);
adjacent_ghost->SetData(TimelineViewGhostItem::kTrimShouldBeIgnored, true);
}
}
}
@@ -444,7 +444,7 @@ void TimelineWidget::PointerTool::InitiateDragInternal(TimelineViewBlockItem *cl
}
}
void TimelineWidget::PointerTool::ProcessDrag(const TimelineCoordinate &mouse_pos)
void PointerTool::ProcessDrag(const TimelineCoordinate &mouse_pos)
{
// Calculate track movement
int track_movement = track_movement_allowed_
@@ -470,9 +470,9 @@ void TimelineWidget::PointerTool::ProcessDrag(const TimelineCoordinate &mouse_po
// Validate ghosts that are being moved (clips from other track types do NOT get moved)
{
QVector<TimelineViewGhostItem*> validate_track_ghosts = parent()->ghost_items_;
QVector<TimelineViewGhostItem*> validate_track_ghosts = parent()->GetGhostItems();
for (int i=0;i<validate_track_ghosts.size();i++) {
if (validate_track_ghosts.at(i)->Track().type() != drag_track_type_) {
if (validate_track_ghosts.at(i)->GetTrack().type() != drag_track_type_) {
validate_track_ghosts.removeAt(i);
i--;
}
@@ -481,8 +481,8 @@ void TimelineWidget::PointerTool::ProcessDrag(const TimelineCoordinate &mouse_po
}
// Perform movement
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
switch (ghost->mode()) {
foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) {
switch (ghost->GetMode()) {
case Timeline::kNone:
break;
case Timeline::kTrimIn:
@@ -498,11 +498,8 @@ void TimelineWidget::PointerTool::ProcessDrag(const TimelineCoordinate &mouse_po
// Track movement is only legal for moving, not for trimming
// Also, we only move the clips on the same track type that the drag started from
if (ghost->Track().type() == drag_track_type_) {
if (ghost->GetTrack().type() == drag_track_type_) {
ghost->SetTrackAdjustment(track_movement);
const TrackReference& track = ghost->GetAdjustedTrack();
ghost->SetYCoords(parent()->GetTrackY(track), parent()->GetTrackHeight(track));
}
break;
}
@@ -525,22 +522,22 @@ struct GhostBlockPair {
Block* block;
};
void TimelineWidget::PointerTool::FinishDrag(TimelineViewMouseEvent *event)
void 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_) {
foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) {
if (ghost->HasBeenAdjusted()) {
Block* b = Node::ValueToPtr<Block>(ghost->data(TimelineViewGhostItem::kAttachedBlock));
Block* b = Node::ValueToPtr<Block>(ghost->GetData(TimelineViewGhostItem::kAttachedBlock));
if (ghost->data(TimelineViewGhostItem::kGhostIsSliding).toBool()) {
if (ghost->GetData(TimelineViewGhostItem::kGhostIsSliding).toBool()) {
blocks_sliding.append({ghost, b});
} else if (ghost->mode() == Timeline::kMove) {
} else if (ghost->GetMode() == Timeline::kMove) {
blocks_moving.append({ghost, b});
} else if (Timeline::IsATrimMode(ghost->mode())) {
} else if (Timeline::IsATrimMode(ghost->GetMode())) {
blocks_trimming.append({ghost, b});
}
}
@@ -555,18 +552,34 @@ void TimelineWidget::PointerTool::FinishDrag(TimelineViewMouseEvent *event)
QUndoCommand* command = new QUndoCommand();
foreach (const GhostBlockPair& p, blocks_trimming) {
TimelineViewGhostItem* ghost = p.ghost;
new NodeGraphBeginOperationCommand(static_cast<NodeGraph*>(parent()->GetConnectedNode()->parent()), command);
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);
if (!blocks_trimming.isEmpty()) {
foreach (const GhostBlockPair& p, blocks_trimming) {
TimelineViewGhostItem* ghost = p.ghost;
c->SetTrimIsARollEdit(ghost->data(TimelineViewGhostItem::kTrimIsARollEdit).toBool());
if (!ghost->GetData(TimelineViewGhostItem::kTrimShouldBeIgnored).toBool()) {
// Must be an ordinary trim/roll
BlockTrimCommand* c = new BlockTrimCommand(parent()->GetTrackFromReference(ghost->GetAdjustedTrack()),
p.block,
ghost->GetAdjustedLength(),
ghost->GetMode(),
command);
c->SetTrimIsARollEdit(ghost->GetData(TimelineViewGhostItem::kTrimIsARollEdit).toBool());
}
}
if (blocks_moving.isEmpty() && blocks_sliding.isEmpty()) {
// Trim selections (deferring to moving/sliding blocks when necessary)
TimelineWidgetSelections new_sel = parent()->GetSelections();
TimelineViewGhostItem* reference_ghost = blocks_trimming.first().ghost;
if (reference_ghost->GetMode() == Timeline::kTrimIn) {
new_sel.TrimIn(reference_ghost->GetInAdjustment());
} else {
new_sel.TrimOut(reference_ghost->GetOutAdjustment());
}
new TimelineSetSelectionsCommand(parent(), new_sel, parent()->GetSelections(), command);
}
}
@@ -616,6 +629,12 @@ void TimelineWidget::PointerTool::FinishDrag(TimelineViewMouseEvent *event)
p.ghost->GetAdjustedIn(),
command);
}
// Adjust selections
TimelineWidgetSelections new_sel = parent()->GetSelections();
new_sel.ShiftTime(blocks_moving.first().ghost->GetInAdjustment());
new_sel.ShiftTracks(drag_track_type_, blocks_moving.first().ghost->GetTrackAdjustment());
new TimelineSetSelectionsCommand(parent(), new_sel, parent()->GetSelections(), command);
}
if (!blocks_sliding.isEmpty()) {
@@ -628,15 +647,15 @@ void TimelineWidget::PointerTool::FinishDrag(TimelineViewMouseEvent *event)
rational movement;
foreach (const GhostBlockPair& p, blocks_sliding) {
const TrackReference& track = p.ghost->Track();
const TrackReference& track = p.ghost->GetTrack();
switch (p.ghost->mode()) {
switch (p.ghost->GetMode()) {
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();
movement = p.ghost->GetInAdjustment();
QList<Block*>& blocks_on_this_track = slide_info[track];
bool inserted = false;
@@ -673,13 +692,20 @@ void TimelineWidget::PointerTool::FinishDrag(TimelineViewMouseEvent *event)
movement,
command);
}
// Adjust selections
TimelineWidgetSelections new_sel = parent()->GetSelections();
new_sel.ShiftTime(movement);
new TimelineSetSelectionsCommand(parent(), new_sel, parent()->GetSelections(), command);
}
}
new NodeGraphEndOperationCommand(static_cast<NodeGraph*>(parent()->GetConnectedNode()->parent()), command);
Core::instance()->undo_stack()->pushIfHasChildren(command);
}
Timeline::MovementMode TimelineWidget::PointerTool::IsCursorInTrimHandle(TimelineViewBlockItem *block, qreal cursor_x)
Timeline::MovementMode PointerTool::IsCursorInTrimHandle(TimelineViewBlockItem *block, qreal cursor_x)
{
double kTrimHandle = QFontMetricsWidth(parent()->fontMetrics(), "H");
@@ -697,7 +723,7 @@ Timeline::MovementMode TimelineWidget::PointerTool::IsCursorInTrimHandle(Timelin
}
}
void TimelineWidget::PointerTool::InitiateDrag(TimelineViewBlockItem* clicked_item,
void PointerTool::InitiateDrag(TimelineViewBlockItem* clicked_item,
Timeline::MovementMode trim_mode)
{
InitiateDragInternal(clicked_item, trim_mode, false, false, false);
@@ -705,20 +731,17 @@ void TimelineWidget::PointerTool::InitiateDrag(TimelineViewBlockItem* clicked_it
//#define HIDE_GAP_GHOSTS
TimelineViewGhostItem* TimelineWidget::PointerTool::AddGhostFromBlock(Block* block, const TrackReference& track, Timeline::MovementMode mode, bool check_if_exists)
TimelineViewGhostItem* 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) {
foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) {
if (Node::ValueToPtr<Block>(ghost->GetData(TimelineViewGhostItem::kAttachedBlock)) == block) {
return ghost;
}
}
}
TimelineViewGhostItem* ghost = TimelineViewGhostItem::FromBlock(block,
track,
parent()->GetTrackY(track),
parent()->GetTrackHeight(track));
TimelineViewGhostItem* ghost = TimelineViewGhostItem::FromBlock(block, track);
#ifdef HIDE_GAP_GHOSTS
if (block->type() == Block::kGap) {
@@ -731,14 +754,13 @@ TimelineViewGhostItem* TimelineWidget::PointerTool::AddGhostFromBlock(Block* blo
return ghost;
}
TimelineViewGhostItem* TimelineWidget::PointerTool::AddGhostFromNull(const rational &in, const rational &out, const TrackReference& track, Timeline::MovementMode mode)
TimelineViewGhostItem* PointerTool::AddGhostFromNull(const rational &in, const rational &out, const TrackReference& track, Timeline::MovementMode mode)
{
TimelineViewGhostItem* ghost = new TimelineViewGhostItem();
ghost->SetIn(in);
ghost->SetOut(out);
ghost->SetTrack(track);
ghost->SetYCoords(parent()->GetTrackY(track), parent()->GetTrackHeight(track));
#ifdef HIDE_GAP_GHOSTS
ghost->SetInvisible(true);
@@ -749,21 +771,21 @@ TimelineViewGhostItem* TimelineWidget::PointerTool::AddGhostFromNull(const ratio
return ghost;
}
void TimelineWidget::PointerTool::AddGhostInternal(TimelineViewGhostItem* ghost, Timeline::MovementMode mode)
void PointerTool::AddGhostInternal(TimelineViewGhostItem* ghost, Timeline::MovementMode mode)
{
ghost->SetMode(mode);
// Prepare snap points (optimizes snapping for later)
switch (mode) {
case Timeline::kMove:
snap_points_.append(ghost->In());
snap_points_.append(ghost->Out());
snap_points_.append(ghost->GetIn());
snap_points_.append(ghost->GetOut());
break;
case Timeline::kTrimIn:
snap_points_.append(ghost->In());
snap_points_.append(ghost->GetIn());
break;
case Timeline::kTrimOut:
snap_points_.append(ghost->Out());
snap_points_.append(ghost->GetOut());
break;
default:
break;
@@ -772,7 +794,7 @@ void TimelineWidget::PointerTool::AddGhostInternal(TimelineViewGhostItem* ghost,
parent()->AddGhost(ghost);
}
bool TimelineWidget::PointerTool::IsClipTrimmable(TimelineViewBlockItem* clip,
bool PointerTool::IsClipTrimmable(TimelineViewBlockItem* clip,
const QList<TimelineViewBlockItem*>& items,
const Timeline::MovementMode& mode)
{
@@ -788,7 +810,7 @@ bool TimelineWidget::PointerTool::IsClipTrimmable(TimelineViewBlockItem* clip,
return true;
}
bool TimelineWidget::PointerTool::AddMovingTransitionsToClipGhost(Block* block,
bool PointerTool::AddMovingTransitionsToClipGhost(Block* block,
const TrackReference& track,
Timeline::MovementMode movement,
const QList<TimelineViewBlockItem*>& selected_items)
@@ -838,41 +860,41 @@ bool TimelineWidget::PointerTool::AddMovingTransitionsToClipGhost(Block* block,
return ret;
}
rational TimelineWidget::PointerTool::ValidateInTrimming(rational movement)
rational PointerTool::ValidateInTrimming(rational movement)
{
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
if (ghost->mode() != Timeline::kTrimIn) {
foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) {
if (ghost->GetMode() != Timeline::kTrimIn) {
continue;
}
rational earliest_in = RATIONAL_MIN;
rational latest_in = ghost->Out();
rational latest_in = ghost->GetOut();
if (!ghost->CanHaveZeroLength()) {
latest_in -= parent()->timebase();
}
// Clamp adjusted value between the earliest and latest values
rational adjusted = ghost->In() + movement;
rational adjusted = ghost->GetIn() + movement;
rational clamped = clamp(adjusted, earliest_in, latest_in);
if (clamped != adjusted) {
movement = clamped - ghost->In();
movement = clamped - ghost->GetIn();
}
}
return movement;
}
rational TimelineWidget::PointerTool::ValidateOutTrimming(rational movement)
rational PointerTool::ValidateOutTrimming(rational movement)
{
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
if (ghost->mode() != Timeline::kTrimOut) {
foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) {
if (ghost->GetMode() != Timeline::kTrimOut) {
continue;
}
// Determine earliest and latest out points
rational earliest_out = ghost->In();
rational earliest_out = ghost->GetIn();
if (!ghost->CanHaveZeroLength()) {
earliest_out += parent()->timebase();
@@ -881,11 +903,11 @@ rational TimelineWidget::PointerTool::ValidateOutTrimming(rational movement)
rational latest_out = RATIONAL_MAX;
// Clamp adjusted value between the earliest and latest values
rational adjusted = ghost->Out() + movement;
rational adjusted = ghost->GetOut() + movement;
rational clamped = clamp(adjusted, earliest_out, latest_out);
if (clamped != adjusted) {
movement = clamped - ghost->Out();
movement = clamped - ghost->GetOut();
}
}
+127
View File
@@ -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 POINTERTIMELINETOOL_H
#define POINTERTIMELINETOOL_H
#include "tool.h"
OLIVE_NAMESPACE_ENTER
class PointerTool : public TimelineTool
{
public:
PointerTool(TimelineWidget* parent);
virtual void MousePress(TimelineViewMouseEvent *event) override;
virtual void MouseMove(TimelineViewMouseEvent *event) override;
virtual void MouseRelease(TimelineViewMouseEvent *event) override;
virtual void HoverMove(TimelineViewMouseEvent *event) override;
protected:
virtual void FinishDrag(TimelineViewMouseEvent *event);
virtual void InitiateDrag(TimelineViewBlockItem* clicked_item,
Timeline::MovementMode trim_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);
/**
* @brief Validates Ghosts that are getting their in points trimmed
*
* 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);
/**
* @brief Validates Ghosts that are getting their out points trimmed
*
* 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);
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_;
}
void SetMovementAllowed(bool e)
{
movement_allowed_ = e;
}
void SetTrimmingAllowed(bool e)
{
trimming_allowed_ = e;
}
void SetTrackMovementAllowed(bool e)
{
track_movement_allowed_ = e;
}
void SetGapTrimmingAllowed(bool e)
{
gap_trimming_allowed_ = e;
}
private:
Timeline::MovementMode IsCursorInTrimHandle(TimelineViewBlockItem* block, qreal cursor_x);
void AddGhostInternal(TimelineViewGhostItem* ghost, Timeline::MovementMode mode);
bool IsClipTrimmable(TimelineViewBlockItem* clip,
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 gap_trimming_allowed_;
bool rubberband_selecting_;
Timeline::TrackType drag_track_type_;
Timeline::MovementMode drag_movement_mode_;
TimelineViewBlockItem* clicked_item_;
};
OLIVE_NAMESPACE_EXIT
#endif // POINTERTIMELINETOOL_H
+5 -4
View File
@@ -18,23 +18,24 @@
***/
#include "razor.h"
#include "widget/timelinewidget/timelinewidget.h"
OLIVE_NAMESPACE_ENTER
TimelineWidget::RazorTool::RazorTool(TimelineWidget* parent) :
RazorTool::RazorTool(TimelineWidget* parent) :
BeamTool(parent)
{
}
void TimelineWidget::RazorTool::MousePress(TimelineViewMouseEvent *event)
void RazorTool::MousePress(TimelineViewMouseEvent *event)
{
split_tracks_.clear();
MouseMove(event);
}
void TimelineWidget::RazorTool::MouseMove(TimelineViewMouseEvent *event)
void RazorTool::MouseMove(TimelineViewMouseEvent *event)
{
if (!dragging_) {
drag_start_ = ValidatedCoordinate(event->GetCoordinates(true));
@@ -49,7 +50,7 @@ void TimelineWidget::RazorTool::MouseMove(TimelineViewMouseEvent *event)
}
}
void TimelineWidget::RazorTool::MouseRelease(TimelineViewMouseEvent *event)
void RazorTool::MouseRelease(TimelineViewMouseEvent *event)
{
Q_UNUSED(event)
+43
View File
@@ -0,0 +1,43 @@
/***
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 RAZORTIMELINETOOL_H
#define RAZORTIMELINETOOL_H
#include "beam.h"
OLIVE_NAMESPACE_ENTER
class RazorTool : public BeamTool
{
public:
RazorTool(TimelineWidget* parent);
virtual void MousePress(TimelineViewMouseEvent *event) override;
virtual void MouseMove(TimelineViewMouseEvent *event) override;
virtual void MouseRelease(TimelineViewMouseEvent *event) override;
private:
QVector<TrackReference> split_tracks_;
};
OLIVE_NAMESPACE_EXIT
#endif // RAZORTIMELINETOOL_H
+44 -30
View File
@@ -21,36 +21,37 @@
#include "widget/timelinewidget/timelinewidget.h"
#include "node/block/gap/gap.h"
#include "ripple.h"
#include "widget/nodeview/nodeviewundo.h"
OLIVE_NAMESPACE_ENTER
TimelineWidget::RippleTool::RippleTool(TimelineWidget* parent) :
RippleTool::RippleTool(TimelineWidget* parent) :
PointerTool(parent)
{
SetMovementAllowed(false);
SetGapTrimmingAllowed(true);
}
void TimelineWidget::RippleTool::InitiateDrag(TimelineViewBlockItem *clicked_item,
void RippleTool::InitiateDrag(TimelineViewBlockItem *clicked_item,
Timeline::MovementMode trim_mode)
{
InitiateDragInternal(clicked_item, trim_mode, true, true, false);
if (parent()->ghost_items_.isEmpty()) {
if (!parent()->HasGhosts()) {
return;
}
// Find the earliest ripple
rational earliest_ripple = RATIONAL_MAX;
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) {
rational ghost_ripple_point;
if (trim_mode == Timeline::kTrimIn) {
ghost_ripple_point = ghost->In();
ghost_ripple_point = ghost->GetIn();
} else {
ghost_ripple_point = ghost->Out();
ghost_ripple_point = ghost->GetOut();
}
earliest_ripple = qMin(earliest_ripple, ghost_ripple_point);
@@ -65,8 +66,8 @@ void TimelineWidget::RippleTool::InitiateDrag(TimelineViewBlockItem *clicked_ite
// Determine if we've already created a ghost on this track
bool ghost_on_this_track_exists = false;
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
if (parent()->GetTrackFromReference(ghost->Track()) == track) {
foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) {
if (parent()->GetTrackFromReference(ghost->GetTrack()) == track) {
ghost_on_this_track_exists = true;
break;
}
@@ -95,7 +96,7 @@ void TimelineWidget::RippleTool::InitiateDrag(TimelineViewBlockItem *clicked_ite
} 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));
ghost->SetData(TimelineViewGhostItem::kReferenceBlock, Node::PtrToValue(block_before_ripple));
}
}
}
@@ -103,34 +104,47 @@ void TimelineWidget::RippleTool::InitiateDrag(TimelineViewBlockItem *clicked_ite
}
}
void TimelineWidget::RippleTool::FinishDrag(TimelineViewMouseEvent *event)
void RippleTool::FinishDrag(TimelineViewMouseEvent *event)
{
Q_UNUSED(event)
QVector< QList<TrackListRippleToolCommand::RippleInfo> > info_list(Timeline::kTrackTypeCount);
if (parent()->HasGhosts()) {
QVector< QList<TrackListRippleToolCommand::RippleInfo> > info_list(Timeline::kTrackTypeCount);
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
TrackOutput* track = parent()->GetTrackFromReference(ghost->Track());
foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) {
TrackOutput* track = parent()->GetTrackFromReference(ghost->GetTrack());
TrackListRippleToolCommand::RippleInfo i = {Node::ValueToPtr<Block>(ghost->data(TimelineViewGhostItem::kAttachedBlock)),
Node::ValueToPtr<Block>(ghost->data(TimelineViewGhostItem::kReferenceBlock)),
track,
ghost->AdjustedLength(),
ghost->Length()};
TrackListRippleToolCommand::RippleInfo i = {Node::ValueToPtr<Block>(ghost->GetData(TimelineViewGhostItem::kAttachedBlock)),
Node::ValueToPtr<Block>(ghost->GetData(TimelineViewGhostItem::kReferenceBlock)),
track,
ghost->GetAdjustedLength(),
ghost->GetLength()};
info_list[track->track_type()].append(i);
info_list[track->track_type()].append(i);
}
QUndoCommand* command = new QUndoCommand();
if (!info_list.isEmpty()) {
for (int i=0;i<info_list.size();i++) {
new TrackListRippleToolCommand(parent()->GetConnectedNode()->track_list(static_cast<Timeline::TrackType>(i)),
info_list.at(i),
drag_movement_mode(),
command);
}
TimelineWidgetSelections new_sel = parent()->GetSelections();
TimelineViewGhostItem* reference_ghost = parent()->GetGhostItems().first();
if (drag_movement_mode() == Timeline::kTrimIn) {
new_sel.TrimOut(-reference_ghost->GetInAdjustment());
} else {
new_sel.TrimOut(reference_ghost->GetOutAdjustment());
}
new TimelineSetSelectionsCommand(parent(), new_sel, parent()->GetSelections(), command);
}
Core::instance()->undo_stack()->pushIfHasChildren(command);
}
QUndoCommand* command = new QUndoCommand();
for (int i=0;i<info_list.size();i++) {
new TrackListRippleToolCommand(parent()->GetConnectedNode()->track_list(static_cast<Timeline::TrackType>(i)),
info_list.at(i),
drag_movement_mode(),
command);
}
Core::instance()->undo_stack()->pushIfHasChildren(command);
}
OLIVE_NAMESPACE_EXIT
+41
View File
@@ -0,0 +1,41 @@
/***
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 RIPPLETIMELINETOOL_H
#define RIPPLETIMELINETOOL_H
#include "pointer.h"
OLIVE_NAMESPACE_ENTER
class RippleTool : public PointerTool
{
public:
RippleTool(TimelineWidget* parent);
protected:
virtual void FinishDrag(TimelineViewMouseEvent *event) override;
virtual void InitiateDrag(TimelineViewBlockItem* clicked_item,
Timeline::MovementMode trim_mode) override;
};
OLIVE_NAMESPACE_EXIT
#endif // RIPPLETIMELINETOOL_H
+3 -2
View File
@@ -21,18 +21,19 @@
#include "widget/timelinewidget/timelinewidget.h"
#include "node/block/gap/gap.h"
#include "rolling.h"
#include "widget/nodeview/nodeviewundo.h"
OLIVE_NAMESPACE_ENTER
TimelineWidget::RollingTool::RollingTool(TimelineWidget* parent) :
RollingTool::RollingTool(TimelineWidget* parent) :
PointerTool(parent)
{
SetMovementAllowed(false);
SetGapTrimmingAllowed(true);
}
void TimelineWidget::RollingTool::InitiateDrag(TimelineViewBlockItem *clicked_item,
void RollingTool::InitiateDrag(TimelineViewBlockItem *clicked_item,
Timeline::MovementMode trim_mode)
{
InitiateDragInternal(clicked_item, trim_mode, false, true, false);
+40
View File
@@ -0,0 +1,40 @@
/***
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 ROLLINGTIMELINETOOL_H
#define ROLLINGTIMELINETOOL_H
#include "pointer.h"
OLIVE_NAMESPACE_ENTER
class RollingTool : public PointerTool
{
public:
RollingTool(TimelineWidget* parent);
protected:
virtual void InitiateDrag(TimelineViewBlockItem* clicked_item,
Timeline::MovementMode trim_mode) override;
};
OLIVE_NAMESPACE_EXIT
#endif // ROLLINGTIMELINETOOL_H
+3 -2
View File
@@ -21,11 +21,12 @@
#include "widget/timelinewidget/timelinewidget.h"
#include "node/block/gap/gap.h"
#include "slide.h"
#include "widget/nodeview/nodeviewundo.h"
OLIVE_NAMESPACE_ENTER
TimelineWidget::SlideTool::SlideTool(TimelineWidget* parent) :
SlideTool::SlideTool(TimelineWidget* parent) :
PointerTool(parent)
{
SetTrimmingAllowed(false);
@@ -33,7 +34,7 @@ TimelineWidget::SlideTool::SlideTool(TimelineWidget* parent) :
SetGapTrimmingAllowed(true);
}
void TimelineWidget::SlideTool::InitiateDrag(TimelineViewBlockItem *clicked_item,
void SlideTool::InitiateDrag(TimelineViewBlockItem *clicked_item,
Timeline::MovementMode trim_mode)
{
InitiateDragInternal(clicked_item, trim_mode, false, true, true);
+41
View File
@@ -0,0 +1,41 @@
/***
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 SLIDETIMELINETOOL_H
#define SLIDETIMELINETOOL_H
#include "pointer.h"
OLIVE_NAMESPACE_ENTER
class SlideTool : public PointerTool
{
public:
SlideTool(TimelineWidget* parent);
protected:
virtual void InitiateDrag(TimelineViewBlockItem* clicked_item,
Timeline::MovementMode trim_mode) override;
};
OLIVE_NAMESPACE_EXIT
#endif // SLIDETIMELINETOOL_H
+10 -9
View File
@@ -24,30 +24,31 @@
#include "common/timecodefunctions.h"
#include "config/config.h"
#include "slip.h"
OLIVE_NAMESPACE_ENTER
TimelineWidget::SlipTool::SlipTool(TimelineWidget *parent) :
SlipTool::SlipTool(TimelineWidget *parent) :
PointerTool(parent)
{
SetTrimmingAllowed(false);
SetTrackMovementAllowed(false);
}
void TimelineWidget::SlipTool::ProcessDrag(const TimelineCoordinate &mouse_pos)
void SlipTool::ProcessDrag(const TimelineCoordinate &mouse_pos)
{
// Determine frame movement
rational time_movement = drag_start_.GetFrame() - mouse_pos.GetFrame();
// Validate slip (enforce all ghosts moving in legal ways)
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
if (ghost->MediaIn() + time_movement < 0) {
time_movement = -ghost->MediaIn();
foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) {
if (ghost->GetMediaIn() + time_movement < 0) {
time_movement = -ghost->GetMediaIn();
}
}
// Perform slip
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) {
ghost->SetMediaInAdjustment(time_movement);
}
@@ -62,15 +63,15 @@ void TimelineWidget::SlipTool::ProcessDrag(const TimelineCoordinate &mouse_pos)
parent());
}
void TimelineWidget::SlipTool::FinishDrag(TimelineViewMouseEvent *event)
void SlipTool::FinishDrag(TimelineViewMouseEvent *event)
{
Q_UNUSED(event)
QUndoCommand* command = new QUndoCommand();
// Find earliest point to ripple around
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
Block* b = Node::ValueToPtr<Block>(ghost->data(TimelineViewGhostItem::kAttachedBlock));
foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) {
Block* b = Node::ValueToPtr<Block>(ghost->GetData(TimelineViewGhostItem::kAttachedBlock));
new BlockSetMediaInCommand(b, ghost->GetAdjustedMediaIn(), command);
}
+40
View File
@@ -0,0 +1,40 @@
/***
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 SLIPTIMELINETOOL_H
#define SLIPTIMELINETOOL_H
#include "pointer.h"
OLIVE_NAMESPACE_ENTER
class SlipTool : public PointerTool
{
public:
SlipTool(TimelineWidget* parent);
protected:
virtual void ProcessDrag(const TimelineCoordinate &mouse_pos) override;
virtual void FinishDrag(TimelineViewMouseEvent *event) override;
};
OLIVE_NAMESPACE_EXIT
#endif // SLIPTIMELINETOOL_H
+17 -37
View File
@@ -25,22 +25,22 @@
OLIVE_NAMESPACE_ENTER
TimelineWidget::Tool::Tool(TimelineWidget *parent) :
TimelineTool::TimelineTool(TimelineWidget *parent) :
dragging_(false),
parent_(parent)
{
}
TimelineWidget::Tool::~Tool()
TimelineTool::~TimelineTool()
{
}
TimelineWidget *TimelineWidget::Tool::parent()
TimelineWidget *TimelineTool::parent()
{
return parent_;
}
Timeline::MovementMode TimelineWidget::Tool::FlipTrimMode(const Timeline::MovementMode &trim_mode)
Timeline::MovementMode TimelineTool::FlipTrimMode(const Timeline::MovementMode &trim_mode)
{
if (trim_mode == Timeline::kTrimIn) {
return Timeline::kTrimOut;
@@ -53,57 +53,37 @@ Timeline::MovementMode TimelineWidget::Tool::FlipTrimMode(const Timeline::Moveme
return trim_mode;
}
TimelineViewBlockItem *TimelineWidget::Tool::GetItemAtScenePos(const TimelineCoordinate& coord)
rational TimelineTool::ValidateTimeMovement(rational movement)
{
QMapIterator<Block*, TimelineViewBlockItem*> iterator(parent()->block_items_);
while (iterator.hasNext()) {
iterator.next();
Block* b = iterator.key();
TimelineViewBlockItem* item = iterator.value();
if (b->in() <= coord.GetFrame()
&& b->out() > coord.GetFrame()
&& item->Track() == coord.GetTrack()) {
return item;
}
}
return nullptr;
}
rational TimelineWidget::Tool::ValidateTimeMovement(rational movement)
{
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
if (ghost->mode() != Timeline::kMove) {
foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) {
if (ghost->GetMode() != Timeline::kMove) {
continue;
}
// Prevents any ghosts from going below 0:00:00 time
if (ghost->In() + movement < 0) {
movement = -ghost->In();
if (ghost->GetIn() + movement < 0) {
movement = -ghost->GetIn();
}
}
return movement;
}
int TimelineWidget::Tool::ValidateTrackMovement(int movement, const QVector<TimelineViewGhostItem*>& ghosts)
int TimelineTool::ValidateTrackMovement(int movement, const QVector<TimelineViewGhostItem*>& ghosts)
{
foreach (TimelineViewGhostItem* ghost, ghosts) {
if (ghost->mode() != Timeline::kMove) {
if (ghost->GetMode() != Timeline::kMove) {
continue;
}
if (!ghost->CanMoveTracks()) {
if (!ghost->GetCanMoveTracks()) {
return 0;
} else if (ghost->Track().index() + movement < 0) {
} else if (ghost->GetTrack().index() + movement < 0) {
// Prevents any ghosts from going to a non-existent negative track
movement = -ghost->Track().index();
movement = -ghost->GetTrack().index();
}
}
@@ -111,12 +91,12 @@ int TimelineWidget::Tool::ValidateTrackMovement(int movement, const QVector<Time
return movement;
}
void TimelineWidget::Tool::GetGhostData(rational *earliest_point, rational *latest_point)
void TimelineTool::GetGhostData(rational *earliest_point, rational *latest_point)
{
rational ep = RATIONAL_MAX;
rational lp = RATIONAL_MIN;
foreach (TimelineViewGhostItem* ghost, parent()->ghost_items_) {
foreach (TimelineViewGhostItem* ghost, parent()->GetGhostItems()) {
ep = qMin(ep, ghost->GetAdjustedIn());
lp = qMax(lp, ghost->GetAdjustedOut());
}
@@ -130,7 +110,7 @@ void TimelineWidget::Tool::GetGhostData(rational *earliest_point, rational *late
}
}
void TimelineWidget::Tool::InsertGapsAtGhostDestination(QUndoCommand *command)
void TimelineTool::InsertGapsAtGhostDestination(QUndoCommand *command)
{
rational earliest_point, latest_point;
+89
View File
@@ -0,0 +1,89 @@
/***
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 TIMELINETOOL_H
#define TIMELINETOOL_H
#include <QDragLeaveEvent>
#include "common/rational.h"
#include "widget/timelinewidget/view/timelineviewghostitem.h"
#include "widget/timelinewidget/view/timelineviewmouseevent.h"
OLIVE_NAMESPACE_ENTER
class TimelineWidget;
class TimelineTool
{
public:
TimelineTool(TimelineWidget* parent);
virtual ~TimelineTool();
virtual void MousePress(TimelineViewMouseEvent *){}
virtual void MouseMove(TimelineViewMouseEvent *){}
virtual void MouseRelease(TimelineViewMouseEvent *){}
virtual void MouseDoubleClick(TimelineViewMouseEvent *){}
virtual void HoverMove(TimelineViewMouseEvent *){}
virtual void DragEnter(TimelineViewMouseEvent *){}
virtual void DragMove(TimelineViewMouseEvent *){}
virtual void DragLeave(QDragLeaveEvent *){}
virtual void DragDrop(TimelineViewMouseEvent *){}
TimelineWidget* parent();
static Timeline::MovementMode FlipTrimMode(const Timeline::MovementMode& trim_mode);
protected:
/**
* @brief Validates Ghosts that are moving horizontally (time-based)
*
* 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);
/**
* @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);
void GetGhostData(rational *earliest_point, rational *latest_point);
void InsertGapsAtGhostDestination(QUndoCommand* command);
QList<rational> snap_points_;
bool dragging_;
TimelineCoordinate drag_start_;
private:
TimelineWidget* parent_;
};
OLIVE_NAMESPACE_EXIT
#endif // TIMELINETOOL_H
+19 -19
View File
@@ -23,16 +23,17 @@
#include "node/block/transition/crossdissolve/crossdissolvetransition.h"
#include "node/block/transition/transition.h"
#include "node/factory.h"
#include "transition.h"
#include "widget/nodeview/nodeviewundo.h"
OLIVE_NAMESPACE_ENTER
TimelineWidget::TransitionTool::TransitionTool(TimelineWidget *parent) :
TransitionTool::TransitionTool(TimelineWidget *parent) :
AddTool(parent)
{
}
void TimelineWidget::TransitionTool::MousePress(TimelineViewMouseEvent *event)
void TransitionTool::MousePress(TimelineViewMouseEvent *event)
{
const TrackReference& track = event->GetTrack();
TrackOutput* t = parent()->GetTrackFromReference(track);
@@ -77,15 +78,14 @@ void TimelineWidget::TransitionTool::MousePress(TimelineViewMouseEvent *event)
// Create ghost
ghost_ = new TimelineViewGhostItem();
ghost_->SetTrack(track);
ghost_->SetYCoords(parent()->GetTrackY(track), parent()->GetTrackHeight(track));
ghost_->SetIn(transition_start_point);
ghost_->SetOut(transition_start_point);
ghost_->SetMode(trim_mode);
ghost_->setData(TimelineViewGhostItem::kAttachedBlock, Node::PtrToValue(block_at_time));
ghost_->SetData(TimelineViewGhostItem::kAttachedBlock, Node::PtrToValue(block_at_time));
dual_transition_ = (other_block);
if (other_block)
ghost_->setData(TimelineViewGhostItem::kReferenceBlock, Node::PtrToValue(other_block));
ghost_->SetData(TimelineViewGhostItem::kReferenceBlock, Node::PtrToValue(other_block));
parent()->AddGhost(ghost_);
@@ -95,7 +95,7 @@ void TimelineWidget::TransitionTool::MousePress(TimelineViewMouseEvent *event)
drag_start_point_ = cursor_frame;
}
void TimelineWidget::TransitionTool::MouseMove(TimelineViewMouseEvent *event)
void TransitionTool::MouseMove(TimelineViewMouseEvent *event)
{
if (!ghost_) {
return;
@@ -104,12 +104,12 @@ void TimelineWidget::TransitionTool::MouseMove(TimelineViewMouseEvent *event)
MouseMoveInternal(event->GetFrame(), dual_transition_);
}
void TimelineWidget::TransitionTool::MouseRelease(TimelineViewMouseEvent *event)
void TransitionTool::MouseRelease(TimelineViewMouseEvent *event)
{
const TrackReference& track = ghost_->Track();
const TrackReference& track = ghost_->GetTrack();
if (ghost_) {
if (!ghost_->AdjustedLength().isNull()) {
if (!ghost_->GetAdjustedLength().isNull()) {
TransitionBlock* transition;
if (Core::instance()->GetSelectedTransition().isEmpty()) {
@@ -133,18 +133,18 @@ void TimelineWidget::TransitionTool::MouseRelease(TimelineViewMouseEvent *event)
command);
if (dual_transition_) {
transition->set_length_and_media_out(ghost_->AdjustedLength());
transition->set_media_in(-ghost_->AdjustedLength()/2);
transition->set_length_and_media_out(ghost_->GetAdjustedLength());
transition->set_media_in(-ghost_->GetAdjustedLength()/2);
// Block mouse is hovering over
Block* active_block = Node::ValueToPtr<Block>(ghost_->data(TimelineViewGhostItem::kAttachedBlock));
Block* active_block = Node::ValueToPtr<Block>(ghost_->GetData(TimelineViewGhostItem::kAttachedBlock));
// Block mouse is next to
Block* friend_block = Node::ValueToPtr<Block>(ghost_->data(TimelineViewGhostItem::kReferenceBlock));
Block* friend_block = Node::ValueToPtr<Block>(ghost_->GetData(TimelineViewGhostItem::kReferenceBlock));
// Use ghost mode to determine which block is which
Block* out_block = (ghost_->mode() == Timeline::kTrimIn) ? friend_block : active_block;
Block* in_block = (ghost_->mode() == Timeline::kTrimIn) ? active_block : friend_block;
Block* out_block = (ghost_->GetMode() == Timeline::kTrimIn) ? friend_block : active_block;
Block* in_block = (ghost_->GetMode() == Timeline::kTrimIn) ? active_block : friend_block;
// Connect block to transition
new NodeEdgeAddCommand(out_block->output(),
@@ -155,14 +155,14 @@ void TimelineWidget::TransitionTool::MouseRelease(TimelineViewMouseEvent *event)
transition->in_block_input(),
command);
} else {
Block* block_to_transition = Node::ValueToPtr<Block>(ghost_->data(TimelineViewGhostItem::kAttachedBlock));
Block* block_to_transition = Node::ValueToPtr<Block>(ghost_->GetData(TimelineViewGhostItem::kAttachedBlock));
NodeInput* transition_input_to_connect;
if (ghost_->mode() == Timeline::kTrimIn) {
transition->set_length_and_media_out(ghost_->AdjustedLength());
if (ghost_->GetMode() == Timeline::kTrimIn) {
transition->set_length_and_media_out(ghost_->GetAdjustedLength());
transition_input_to_connect = transition->in_block_input();
} else {
transition->set_length_and_media_out(ghost_->AdjustedLength());
transition->set_length_and_media_out(ghost_->GetAdjustedLength());
transition_input_to_connect = transition->out_block_input();
}
@@ -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 TRANSITIONTIMELINETOOL_H
#define TRANSITIONTIMELINETOOL_H
#include "add.h"
OLIVE_NAMESPACE_ENTER
class TransitionTool : public AddTool
{
public:
TransitionTool(TimelineWidget* parent);
virtual void MousePress(TimelineViewMouseEvent *event) override;
virtual void MouseMove(TimelineViewMouseEvent *event) override;
virtual void MouseRelease(TimelineViewMouseEvent *event) override;
private:
bool dual_transition_;
};
OLIVE_NAMESPACE_EXIT
#endif // TRANSITIONTIMELINETOOL_H
+14 -11
View File
@@ -19,20 +19,21 @@
***/
#include "widget/timelinewidget/timelinewidget.h"
#include "zoom.h"
OLIVE_NAMESPACE_ENTER
TimelineWidget::ZoomTool::ZoomTool(TimelineWidget *parent) :
Tool(parent)
ZoomTool::ZoomTool(TimelineWidget *parent) :
TimelineTool(parent)
{
}
void TimelineWidget::ZoomTool::MousePress(TimelineViewMouseEvent *event)
void ZoomTool::MousePress(TimelineViewMouseEvent *event)
{
Q_UNUSED(event)
}
void TimelineWidget::ZoomTool::MouseMove(TimelineViewMouseEvent *event)
void ZoomTool::MouseMove(TimelineViewMouseEvent *event)
{
Q_UNUSED(event)
@@ -45,15 +46,17 @@ void TimelineWidget::ZoomTool::MouseMove(TimelineViewMouseEvent *event)
}
}
void TimelineWidget::ZoomTool::MouseRelease(TimelineViewMouseEvent *event)
void ZoomTool::MouseRelease(TimelineViewMouseEvent *event)
{
int scroll_value;
if (dragging_) {
// Zoom into the rubberband selection
QRect screen_coords = parent()->rubberband_.geometry();
QRect screen_coords = parent()->GetRubberBandGeometry();
parent()->EndRubberBandSelect();
TimelineView* reference_view = parent()->views_.first()->view();
TimelineView* reference_view = parent()->GetFirstTimelineView();
QPointF scene_topleft = reference_view->mapToScene(reference_view->mapFrom(parent(), screen_coords.topLeft()));
QPointF scene_bottomright = reference_view->mapToScene(reference_view->mapFrom(parent(), screen_coords.bottomRight()));
@@ -64,10 +67,11 @@ void TimelineWidget::ZoomTool::MouseRelease(TimelineViewMouseEvent *event)
double scene_width = (scene_right - scene_left) / parent()->GetScale();
double new_scale = qMin(TimelineViewBase::kMaximumScale, static_cast<double>(reference_view->viewport()->width()) / scene_width);
parent()->deferred_scroll_value_ = qMax(0, qRound(scene_left / parent()->GetScale() * new_scale));
parent()->SetScale(new_scale);
scroll_value = qMax(0, qRound(scene_left / parent()->GetScale() * new_scale));
dragging_ = false;
} else {
// Simple zoom in/out at the cursor position
@@ -86,11 +90,10 @@ void TimelineWidget::ZoomTool::MouseRelease(TimelineViewMouseEvent *event)
// Adjust scroll location for new scale
double frame_x = event->GetFrame().toDouble() * scale;
parent()->deferred_scroll_value_ = qMax(0, qRound(frame_x - parent()->views_.first()->view()->viewport()->width()/2));
scroll_value = qMax(0, qRound(frame_x - parent()->GetFirstTimelineView()->viewport()->width()/2));
}
// (using a hacky singleShot so the scroll occurs after the scene and its scrollbars have updated)
QTimer::singleShot(0, parent(), &TimelineWidget::DeferredScrollAction);
parent()->QueueScroll(scroll_value);
}
OLIVE_NAMESPACE_EXIT
+41
View File
@@ -0,0 +1,41 @@
/***
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 ZOOMTIMELINETOOL_H
#define ZOOMTIMELINETOOL_H
#include "tool.h"
OLIVE_NAMESPACE_ENTER
class ZoomTool : public TimelineTool
{
public:
ZoomTool(TimelineWidget* parent);
virtual void MousePress(TimelineViewMouseEvent *event) override;
virtual void MouseMove(TimelineViewMouseEvent *event) override;
virtual void MouseRelease(TimelineViewMouseEvent *event) override;
};
OLIVE_NAMESPACE_EXIT
#endif // ZOOMTIMELINETOOL_H
@@ -68,10 +68,9 @@ TrackView::TrackView(Qt::Alignment vertical_alignment, QWidget *parent) :
void TrackView::ConnectTrackList(TrackList *list)
{
if (list_ != nullptr) {
foreach (TrackViewItem* item, items_) {
delete item;
foreach (TrackOutput* track, list_->GetTracks()) {
RemoveTrack(track);
}
items_.clear();
disconnect(list_, &TrackList::TrackHeightChanged, splitter_, &TrackViewSplitter::SetTrackHeight);
disconnect(list_, &TrackList::TrackAdded, this, &TrackView::InsertTrack);
@@ -82,11 +81,7 @@ void TrackView::ConnectTrackList(TrackList *list)
if (list_ != nullptr) {
foreach (TrackOutput* track, list_->GetTracks()) {
TrackViewItem* item = new TrackViewItem(track);
items_.append(item);
splitter_->Insert(track->Index(),
track->GetTrackHeightInPixels(),
item);
InsertTrack(track);
}
connect(list_, &TrackList::TrackHeightChanged, splitter_, &TrackViewSplitter::SetTrackHeight);
@@ -44,8 +44,6 @@ protected:
virtual void resizeEvent(QResizeEvent *e) override;
private:
QList<TrackViewItem*> items_;
TrackList* list_;
TrackViewSplitter* splitter_;
@@ -39,24 +39,26 @@ TrackViewItem::TrackViewItem(TrackOutput* track, QWidget *parent) :
stack_ = new QStackedWidget();
layout->addWidget(stack_);
label_ = new ClickableLabel(track_->GetTrackName());
connect(label_, SIGNAL(MouseDoubleClicked()), this, SLOT(LabelClicked()));
label_ = new ClickableLabel();
connect(label_, &ClickableLabel::MouseDoubleClicked, this, &TrackViewItem::LabelClicked);
connect(track_, &TrackOutput::LabelChanged, this, &TrackViewItem::UpdateLabel);
UpdateLabel();
stack_->addWidget(label_);
line_edit_ = new FocusableLineEdit();
connect(line_edit_, SIGNAL(Confirmed()), this, SLOT(LineEditConfirmed()));
connect(line_edit_, SIGNAL(Cancelled()), this, SLOT(LineEditCancelled()));
connect(line_edit_, &FocusableLineEdit::Confirmed, this, &TrackViewItem::LineEditConfirmed);
connect(line_edit_, &FocusableLineEdit::Cancelled, this, &TrackViewItem::LineEditCancelled);
stack_->addWidget(line_edit_);
mute_button_ = CreateMSLButton(tr("M"), Qt::red);
connect(mute_button_, SIGNAL(toggled(bool)), track_, SLOT(SetMuted(bool)));
connect(mute_button_, &QPushButton::toggled, track_, &TrackOutput::SetMuted);
layout->addWidget(mute_button_);
/*solo_button_ = CreateMSLButton(tr("S"), Qt::yellow);
layout->addWidget(solo_button_);*/
lock_button_ = CreateMSLButton(tr("L"), Qt::gray);
connect(lock_button_, SIGNAL(toggled(bool)), track_, SLOT(SetLocked(bool)));
connect(lock_button_, &QPushButton::toggled, track_, &TrackOutput::SetLocked);
layout->addWidget(lock_button_);
setMinimumHeight(mute_button_->height());
@@ -89,11 +91,8 @@ void TrackViewItem::LineEditConfirmed()
{
line_edit_->blockSignals(true);
QString line_edit_str = line_edit_->text();
if (!line_edit_str.isEmpty()) {
label_->setText(line_edit_str);
track_->SetTrackName(line_edit_str);
}
track_->SetLabel(line_edit_->text());
UpdateLabel();
stack_->setCurrentWidget(label_);
@@ -109,4 +108,13 @@ void TrackViewItem::LineEditCancelled()
line_edit_->blockSignals(false);
}
void TrackViewItem::UpdateLabel()
{
if (track_->GetLabel().isEmpty()) {
label_->setText(track_->GetDefaultTrackName(track_->track_type(), track_->Index()));
} else {
label_->setText(track_->GetLabel());
}
}
OLIVE_NAMESPACE_EXIT
@@ -59,6 +59,8 @@ private slots:
void LineEditCancelled();
void UpdateLabel();
};
OLIVE_NAMESPACE_EXIT
+28 -13
View File
@@ -25,6 +25,7 @@
#include "node/block/transition/transition.h"
#include "node/graph.h"
#include "widget/nodeview/nodeviewundo.h"
#include "widget/timelinewidget/timelinewidget.h"
OLIVE_NAMESPACE_ENTER
@@ -243,10 +244,6 @@ void TrackRippleRemoveAreaCommand::redo_internal()
foreach (Block* remove_block, removed_blocks_) {
track_->RippleRemoveBlock(remove_block);
BlockUnlinkAllCommand* unlink_command = new BlockUnlinkAllCommand(remove_block);
unlink_command->redo();
remove_block_commands_.append(unlink_command);
NodeRemoveWithExclusiveDeps* remove_command = new NodeRemoveWithExclusiveDeps(static_cast<NodeGraph*>(remove_block->parent()), remove_block);
remove_command->redo();
remove_block_commands_.append(remove_command);
@@ -295,7 +292,7 @@ void TrackRippleRemoveAreaCommand::undo_internal()
track_->RippleRemoveBlock(trim_in_);
trim_out_->set_length_and_media_out(trim_out_old_length_);
delete TakeNodeFromParentGraph(trim_in_);
TakeNodeFromParentGraph(trim_in_, &memory_manager_);
} else {
@@ -390,14 +387,14 @@ void TrackPlaceBlockCommand::undo_internal()
if (gap_ != nullptr) {
track_->RippleRemoveBlock(gap_);
delete TakeNodeFromParentGraph(gap_);
TakeNodeFromParentGraph(gap_, &memory_manager_);
}
} else {
TrackRippleRemoveAreaCommand::undo_internal();
}
for (;added_track_count_>0;added_track_count_--) {
timeline_->RemoveTrack();
timeline_->RemoveTrack(&memory_manager_);
}
}
@@ -926,7 +923,7 @@ void BlockTrimCommand::undo_internal()
if (we_created_adjacent_) {
// If we created a gap, just remove it straight up
track_->RippleRemoveBlock(adjacent_);
delete TakeNodeFromParentGraph(adjacent_);
TakeNodeFromParentGraph(adjacent_, &memory_manager_);
adjacent_ = nullptr;
we_created_adjacent_ = false;
} else if (adjacent_) {
@@ -1066,7 +1063,7 @@ void TrackReplaceBlockWithGapCommand::undo_internal()
// We made this gap, simply swap our gap back
track_->ReplaceBlock(our_gap_, block_);
delete TakeNodeFromParentGraph(our_gap_);
TakeNodeFromParentGraph(our_gap_, &memory_manager_);
our_gap_ = nullptr;
} else if (existing_gap_) {
@@ -1157,7 +1154,7 @@ void TrackSlideCommand::slide_internal(bool undo)
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_);
TakeNodeFromParentGraph(in_adjacent_, &memory_manager_);
we_created_in_adjacent_ = false;
in_adjacent_ = nullptr;
} else if (in_adjacent_->parent() == &memory_manager_) {
@@ -1172,7 +1169,7 @@ void TrackSlideCommand::slide_internal(bool undo)
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_);
TakeNodeFromParentGraph(out_adjacent_, &memory_manager_);
we_created_out_adjacent_ = false;
out_adjacent_ = nullptr;
} else if (out_adjacent_) {
@@ -1490,7 +1487,7 @@ void TrackListRippleToolCommand::undo_internal()
GapBlock* gap = working_data_.at(i).created_gap;
info.track->RippleRemoveBlock(gap);
delete TakeNodeFromParentGraph(gap);
TakeNodeFromParentGraph(gap, &memory_manager_);
}
}
}
@@ -1593,7 +1590,7 @@ void TrackListInsertGaps::undo_internal()
// Remove added gaps
foreach (GapBlock* gap, gaps_added_) {
TrackOutput::TrackFromBlock(gap)->RippleRemoveBlock(gap);
delete TakeNodeFromParentGraph(gap);
TakeNodeFromParentGraph(gap, &memory_manager_);
}
gaps_added_.clear();
@@ -1696,4 +1693,22 @@ void TransitionRemoveCommand::undo_internal()
track_->InvalidateCache(TimeRange(block_->in(), block_->out()), track_->block_input(), track_->block_input());
}
TimelineSetSelectionsCommand::TimelineSetSelectionsCommand(TimelineWidget *timeline, const TimelineWidgetSelections &now, const TimelineWidgetSelections &old, QUndoCommand *parent) :
QUndoCommand(parent),
timeline_(timeline),
old_(old),
now_(now)
{
}
void TimelineSetSelectionsCommand::redo()
{
timeline_->SetSelections(now_);
}
void TimelineSetSelectionsCommand::undo()
{
timeline_->SetSelections(old_);
}
OLIVE_NAMESPACE_EXIT
+20
View File
@@ -30,6 +30,7 @@
#include "node/output/track/tracklist.h"
#include "timeline/timelinepoints.h"
#include "undo/undocommand.h"
#include "widget/timelinewidget/timelinewidgetselections.h"
OLIVE_NAMESPACE_ENTER
@@ -589,6 +590,8 @@ private:
BlockSplitPreservingLinksCommand* split_command_;
QObject memory_manager_;
};
class TransitionRemoveCommand : public UndoCommand {
@@ -611,6 +614,23 @@ private:
};
class TimelineWidget;
class TimelineSetSelectionsCommand : public QUndoCommand {
public:
TimelineSetSelectionsCommand(TimelineWidget* timeline, const TimelineWidgetSelections& now, const TimelineWidgetSelections& old, QUndoCommand* parent = nullptr);
protected:
virtual void redo() override;
virtual void undo() override;
private:
TimelineWidget* timeline_;
TimelineWidgetSelections old_;
TimelineWidgetSelections now_;
};
OLIVE_NAMESPACE_EXIT
#endif // TIMELINEUNDOABLE_H
@@ -37,6 +37,8 @@ OLIVE_NAMESPACE_ENTER
TimelineView::TimelineView(Qt::Alignment vertical_alignment, QWidget *parent) :
TimelineViewBase(parent),
selections_(nullptr),
ghosts_(nullptr),
show_beam_cursor_(false),
connected_track_list_(nullptr)
{
@@ -46,7 +48,6 @@ TimelineView::TimelineView(Qt::Alignment vertical_alignment, QWidget *parent) :
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
setBackgroundRole(QPalette::Window);
setContextMenuPolicy(Qt::CustomContextMenu);
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
viewport()->setMouseTracking(true);
}
@@ -229,6 +230,43 @@ void TimelineView::drawForeground(QPainter *painter, const QRectF &rect)
{
TimelineViewBase::drawForeground(painter, rect);
// Draw selections
if (selections_ && !selections_->isEmpty()) {
painter->setPen(Qt::NoPen);
painter->setBrush(QColor(0, 0, 0, 64));
for (auto it=selections_->cbegin(); it!=selections_->cend(); it++) {
if (it.key().type() == connected_track_list_->type()) {
int track_index = it.key().index();
foreach (const TimeRange& range, it.value()) {
painter->drawRect(TimeToScene(range.in()),
GetTrackY(track_index),
TimeToScene(range.length()),
GetTrackHeight(track_index));
}
}
}
}
// Draw ghosts
if (ghosts_ && !ghosts_->isEmpty()) {
painter->setPen(QPen(Qt::yellow, 2));
painter->setBrush(Qt::NoBrush);
foreach (TimelineViewGhostItem* ghost, (*ghosts_)) {
if (ghost->GetTrack().type() == connected_track_list_->type()) {
int track_index = ghost->GetAdjustedTrack().index();
painter->drawRect(TimeToScene(ghost->GetAdjustedIn()),
GetTrackY(track_index),
TimeToScene(ghost->GetAdjustedLength()),
GetTrackHeight(track_index));
}
}
}
// Draw beam cursor
if (show_beam_cursor_
&& connected_track_list_
&& cursor_coord_.GetTrack().type() == connected_track_list_->type()) {
@@ -59,6 +59,16 @@ public:
void SetBeamCursor(const TimelineCoordinate& coord);
void SetSelectionList(QHash<TrackReference, TimeRangeList>* s)
{
selections_ = s;
}
void SetGhostList(QVector<TimelineViewGhostItem*>* ghosts)
{
ghosts_ = ghosts;
}
signals:
void MousePressed(TimelineViewMouseEvent* event);
void MouseMoved(TimelineViewMouseEvent* event);
@@ -108,6 +118,10 @@ private:
void UpdatePlayheadRect();
QHash<TrackReference, TimeRangeList>* selections_;
QVector<TimelineViewGhostItem*>* ghosts_;
bool show_beam_cursor_;
TimelineCoordinate cursor_coord_;
@@ -44,16 +44,25 @@ TimelineViewBase::TimelineViewBase(QWidget *parent) :
y_axis_enabled_(false),
y_scale_(1.0)
{
// Sets scene to our scene
setScene(&scene_);
// Set default scale
// Set default scale (ensures non-zero scale from beginning)
SetScale(1.0);
// Default to no default drag mode
SetDefaultDragMode(NoDrag);
connect(&scene_, SIGNAL(changed(const QList<QRectF>&)), this, SLOT(UpdateSceneRect()));
// Signal to update bounding rect when the scene changes
connect(&scene_, &QGraphicsScene::changed, this, &TimelineViewBase::UpdateSceneRect);
// Always enforce maximum scale
SetMaximumScale(kMaximumScale);
// Workaround for Qt drawing issues with the default MinimalViewportUpdate. While this might be
// slower (Qt documentation says it may actually be faster in some situations),
// MinimalViewportUpdate causes all sorts of graphical crud building up in the scene
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
}
void TimelineViewBase::TimebaseChangedEvent(const rational &)
@@ -42,11 +42,6 @@ TimelineViewBlockItem::TimelineViewBlockItem(Block *block, QGraphicsItem* parent
block_(block)
{
setBrush(Qt::white);
setCursor(Qt::DragMoveCursor);
setFlag(QGraphicsItem::ItemIsSelectable,
block_->type() == Block::kClip
|| block_->type() == Block::kGap
|| block_->type() == Block::kTransition);
UpdateRect();
}
@@ -24,18 +24,17 @@
OLIVE_NAMESPACE_ENTER
TimelineViewGhostItem::TimelineViewGhostItem(QGraphicsItem *parent) :
TimelineViewRect(parent),
TimelineViewGhostItem::TimelineViewGhostItem() :
track_adj_(0),
stream_(nullptr),
mode_(Timeline::kNone),
can_have_zero_length_(true),
can_move_tracks_(true)
can_move_tracks_(true),
invisible_(false)
{
SetInvisible(false);
}
TimelineViewGhostItem *TimelineViewGhostItem::FromBlock(Block *block, const TrackReference& track, int y, int height)
TimelineViewGhostItem *TimelineViewGhostItem::FromBlock(Block *block, const TrackReference& track)
{
TimelineViewGhostItem* ghost = new TimelineViewGhostItem();
@@ -43,8 +42,7 @@ TimelineViewGhostItem *TimelineViewGhostItem::FromBlock(Block *block, const Trac
ghost->SetOut(block->out());
ghost->SetMediaIn(block->media_in());
ghost->SetTrack(track);
ghost->SetYCoords(y, height);
ghost->setData(kAttachedBlock, Node::PtrToValue(block));
ghost->SetData(kAttachedBlock, Node::PtrToValue(block));
switch (block->type()) {
case Block::kClip:
@@ -66,7 +64,7 @@ bool TimelineViewGhostItem::CanHaveZeroLength() const
return can_have_zero_length_;
}
bool TimelineViewGhostItem::CanMoveTracks() const
bool TimelineViewGhostItem::GetCanMoveTracks() const
{
return can_move_tracks_;
}
@@ -76,7 +74,7 @@ void TimelineViewGhostItem::SetCanMoveTracks(bool e)
can_move_tracks_ = e;
}
void TimelineViewGhostItem::SetInvisible(bool invisible)
/*void TimelineViewGhostItem::SetInvisible(bool invisible)
{
setBrush(Qt::NoBrush);
@@ -85,29 +83,29 @@ void TimelineViewGhostItem::SetInvisible(bool invisible)
} else {
setPen(QPen(Qt::yellow, 2)); // FIXME: Make customizable via CSS
}
}
}*/
const rational &TimelineViewGhostItem::In() const
const rational &TimelineViewGhostItem::GetIn() const
{
return in_;
}
const rational &TimelineViewGhostItem::Out() const
const rational &TimelineViewGhostItem::GetOut() const
{
return out_;
}
const rational &TimelineViewGhostItem::MediaIn() const
const rational &TimelineViewGhostItem::GetMediaIn() const
{
return media_in_;
}
rational TimelineViewGhostItem::Length() const
rational TimelineViewGhostItem::GetLength() const
{
return out_ - in_;
}
rational TimelineViewGhostItem::AdjustedLength() const
rational TimelineViewGhostItem::GetAdjustedLength() const
{
return GetAdjustedOut() - GetAdjustedIn();
}
@@ -115,15 +113,11 @@ rational TimelineViewGhostItem::AdjustedLength() const
void TimelineViewGhostItem::SetIn(const rational &in)
{
in_ = in;
UpdateRect();
}
void TimelineViewGhostItem::SetOut(const rational &out)
{
out_ = out;
UpdateRect();
}
void TimelineViewGhostItem::SetMediaIn(const rational &media_in)
@@ -134,15 +128,11 @@ void TimelineViewGhostItem::SetMediaIn(const rational &media_in)
void TimelineViewGhostItem::SetInAdjustment(const rational &in_adj)
{
in_adj_ = in_adj;
UpdateRect();
}
void TimelineViewGhostItem::SetOutAdjustment(const rational &out_adj)
{
out_adj_ = out_adj;
UpdateRect();
}
void TimelineViewGhostItem::SetTrackAdjustment(const int &track_adj)
@@ -155,22 +145,22 @@ void TimelineViewGhostItem::SetMediaInAdjustment(const rational &media_in_adj)
media_in_adj_ = media_in_adj;
}
const rational &TimelineViewGhostItem::InAdjustment() const
const rational &TimelineViewGhostItem::GetInAdjustment() const
{
return in_adj_;
}
const rational &TimelineViewGhostItem::OutAdjustment() const
const rational &TimelineViewGhostItem::GetOutAdjustment() const
{
return out_adj_;
}
const rational &TimelineViewGhostItem::MediaInAdjustment() const
const rational &TimelineViewGhostItem::GetMediaInAdjustment() const
{
return media_in_adj_;
}
const int &TimelineViewGhostItem::TrackAdjustment() const
const int &TimelineViewGhostItem::GetTrackAdjustment() const
{
return track_adj_;
}
@@ -195,7 +185,7 @@ TrackReference TimelineViewGhostItem::GetAdjustedTrack() const
return TrackReference(track_.type(), track_.index() + track_adj_);
}
const Timeline::MovementMode &TimelineViewGhostItem::mode() const
const Timeline::MovementMode &TimelineViewGhostItem::GetMode() const
{
return mode_;
}
@@ -207,19 +197,10 @@ void TimelineViewGhostItem::SetMode(const Timeline::MovementMode &mode)
bool TimelineViewGhostItem::HasBeenAdjusted() const
{
return InAdjustment() != 0
|| OutAdjustment() != 0
|| MediaInAdjustment() != 0
|| TrackAdjustment() != 0;
}
void TimelineViewGhostItem::UpdateRect()
{
rational length = GetAdjustedOut() - GetAdjustedIn();
setRect(0, y_, TimeToScene(length), height_ - 1);
setPos(TimeToScene(GetAdjustedIn()), 0);
return GetInAdjustment() != 0
|| GetOutAdjustment() != 0
|| GetMediaInAdjustment() != 0
|| GetTrackAdjustment() != 0;
}
OLIVE_NAMESPACE_EXIT
@@ -32,7 +32,7 @@ OLIVE_NAMESPACE_ENTER
/**
* @brief A graphical representation of changes the user is making before they apply it
*/
class TimelineViewGhostItem : public TimelineViewRect
class TimelineViewGhostItem
{
public:
enum DataType {
@@ -44,23 +44,21 @@ public:
kTrimShouldBeIgnored
};
TimelineViewGhostItem(QGraphicsItem* parent = nullptr);
TimelineViewGhostItem();
static TimelineViewGhostItem* FromBlock(Block *block, const TrackReference &track, int y, int height);
static TimelineViewGhostItem* FromBlock(Block *block, const TrackReference &track);
bool CanHaveZeroLength() const;
bool CanMoveTracks() const;
bool GetCanMoveTracks() const;
void SetCanMoveTracks(bool e);
void SetInvisible(bool invisible);
const rational& GetIn() const;
const rational& GetOut() const;
const rational& GetMediaIn() const;
const rational& In() const;
const rational& Out() const;
const rational& MediaIn() const;
rational Length() const;
rational AdjustedLength() const;
rational GetLength() const;
rational GetAdjustedLength() const;
void SetIn(const rational& in);
void SetOut(const rational& out);
@@ -71,22 +69,50 @@ public:
void SetTrackAdjustment(const int& track_adj);
void SetMediaInAdjustment(const rational& media_in_adj);
const rational& InAdjustment() const;
const rational& OutAdjustment() const;
const rational& MediaInAdjustment() const;
const int& TrackAdjustment() const;
const rational& GetInAdjustment() const;
const rational& GetOutAdjustment() const;
const rational& GetMediaInAdjustment() const;
const int& GetTrackAdjustment() const;
rational GetAdjustedIn() const;
rational GetAdjustedOut() const;
rational GetAdjustedMediaIn() const;
TrackReference GetAdjustedTrack() const;
const Timeline::MovementMode& mode() const;
void SetMode(const Timeline::MovementMode& mode);
const Timeline::MovementMode& GetMode() const;
void SetMode(const Timeline::MovementMode& GetMode);
bool HasBeenAdjusted() const;
virtual void UpdateRect() override;
QVariant GetData(int key) const
{
return data_.value(key);
}
void SetData(int key, const QVariant& value)
{
data_.insert(key, value);
}
const TrackReference& GetTrack() const
{
return track_;
}
void SetTrack(const TrackReference& track)
{
track_ = track;
}
bool IsInvisible() const
{
return invisible_;
}
void SetInvisible(bool e)
{
invisible_ = e;
}
protected:
@@ -107,6 +133,13 @@ private:
bool can_have_zero_length_;
bool can_move_tracks_;
TrackReference track_;
QHash<int, QVariant> data_;
bool invisible_;
};
OLIVE_NAMESPACE_EXIT
+5 -1
View File
@@ -42,6 +42,8 @@ SeekableWidget::SeekableWidget(QWidget* parent) :
// Set width of playhead marker
playhead_width_ = QFontMetricsWidth(fm, "H");
setContextMenuPolicy(Qt::CustomContextMenu);
}
void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points)
@@ -78,7 +80,9 @@ const int &SeekableWidget::GetScroll() const
void SeekableWidget::mousePressEvent(QMouseEvent *event)
{
SeekToScreenPoint(event->pos().x());
if (event->button() == Qt::LeftButton) {
SeekToScreenPoint(event->pos().x());
}
}
void SeekableWidget::mouseMoveEvent(QMouseEvent *event)
+15
View File
@@ -27,6 +27,8 @@
#include "common/qtutils.h"
#include "config/config.h"
#include "core.h"
#include "widget/menu/menu.h"
#include "widget/menu/menushared.h"
OLIVE_NAMESPACE_ENTER
@@ -53,6 +55,9 @@ TimeRuler::TimeRuler(bool text_visible, bool cache_status_visible, QWidget* pare
// Force update if the default timecode display mode changes
connect(Core::instance(), &Core::TimecodeDisplayChanged, this, static_cast<void (TimeRuler::*)()>(&TimeRuler::update));
// Connect context menu
connect(this, &TimeRuler::customContextMenuRequested, this, &TimeRuler::ShowContextMenu);
}
void TimeRuler::SetPlaybackCache(PlaybackCache *cache)
@@ -297,6 +302,16 @@ int TimeRuler::CacheStatusHeight() const
return fontMetrics().height() / 4;
}
void TimeRuler::ShowContextMenu()
{
Menu m(this);
MenuShared::instance()->AddItemsForTimeRulerMenu(&m);
MenuShared::instance()->AboutToShowTimeRulerActions();
m.exec(QCursor::pos());
}
void TimeRuler::UpdateHeight()
{
int height = text_height();
+3
View File
@@ -64,6 +64,9 @@ private:
PlaybackCache* playback_cache_;
private slots:
void ShowContextMenu();
};
OLIVE_NAMESPACE_EXIT
+13 -9
View File
@@ -63,11 +63,13 @@ void AudioWaveformView::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
if (!playback_) {
return;
}
const AudioParams& params = playback_->GetParameters();
if (!playback_
|| playback_->GetPCMFilename().isEmpty()
|| !params.is_valid()) {
if (!params.is_valid()) {
return;
}
@@ -78,9 +80,9 @@ void AudioWaveformView::paintEvent(QPaintEvent *event)
cached_waveform_ = QPixmap(size());
cached_waveform_.fill(Qt::transparent);
QFile fs(playback_->GetPCMFilename());
QIODevice* fs = playback_->CreatePlaybackDevice();
if (fs.open(QFile::ReadOnly)) {
if (fs->open(QFile::ReadOnly)) {
QPainter wave_painter(&cached_waveform_);
@@ -89,13 +91,13 @@ void AudioWaveformView::paintEvent(QPaintEvent *event)
int drew = 0;
fs.seek(params.samples_to_bytes(ScreenToUnitRounded(0)));
fs->seek(params.samples_to_bytes(ScreenToUnitRounded(0)));
for (int x=0; x<width() && !fs.atEnd(); x++) {
for (int x=0; x<width() && !fs->atEnd(); x++) {
int samples_len = ScreenToUnitRounded(x+1) - ScreenToUnitRounded(x);
int max_read_size = params.samples_to_bytes(samples_len);
QByteArray read_buffer = fs.read(max_read_size);
QByteArray read_buffer = fs->read(max_read_size);
// Detect whether we've reached EOF and recalculate sample count if so
if (read_buffer.size() < max_read_size) {
@@ -117,9 +119,11 @@ void AudioWaveformView::paintEvent(QPaintEvent *event)
cached_scale_ = GetScale();
cached_scroll_ = GetScroll();
fs.close();
fs->close();
}
delete fs;
}
QPainter p(this);
+5
View File
@@ -57,6 +57,9 @@ void FootageViewerWidget::SetFootage(Footage *footage)
ConnectViewerNode(nullptr);
video_node_->SetFootage(nullptr);
audio_node_->SetFootage(nullptr);
NodeParam::DisconnectEdge(video_node_->output(), sequence_.viewer_output()->texture_input());
NodeParam::DisconnectEdge(audio_node_->output(), sequence_.viewer_output()->samples_input());
}
@@ -106,6 +109,8 @@ void FootageViewerWidget::SetFootage(Footage *footage)
ConnectViewerNode(sequence_.viewer_output(), footage_->project()->color_manager());
SetTimestamp(cached_timestamps_.value(footage_, 0));
} else {
SetTimestamp(0);
}
}
+197 -143
View File
@@ -115,7 +115,8 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
renderer_->SetPreviewGenerationEnabled(true);
// Ensures that seeking on the waveform view updates the time as expected
connect(waveform_view_, &AudioWaveformView::TimeChanged, this, &ViewerWidget::SetTimeAndSignal);
connect(waveform_view_, &AudioWaveformView::TimeChanged, this, &ViewerWidget::TimeChangedFromWaveform);
connect(waveform_view_, &AudioWaveformView::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu);
// Ensures renderer is updated if the global pixel format is changed
connect(PixelFormat::instance(), &PixelFormat::FormatChanged, this, &ViewerWidget::UpdateRendererVideoParameters);
@@ -143,7 +144,18 @@ void ViewerWidget::TimeChangedEvent(const int64_t &i)
}
controls_->SetTime(i);
waveform_view_->SetTime(i);
{
qint64 waveform_time;
if (waveform_view_->timebase() != this->timebase()) {
waveform_time = Timecode::rescale_timestamp(i, this->timebase(), waveform_view_->timebase());
} else {
waveform_time = i;
}
waveform_view_->SetTime(waveform_time);
}
if (GetConnectedNode() && last_time_ != i) {
rational time_set = Timecode::timestamp_to_time(i, timebase());
@@ -204,8 +216,8 @@ void ViewerWidget::ConnectNodeInternal(ViewerOutput *n)
UpdateStack();
waveform_view_->SetViewer(GetConnectedNode()->audio_playback_cache());
if (GetConnectedTimelinePoints()) {
waveform_view_->SetViewer(GetConnectedNode()->audio_playback_cache());
waveform_view_->ConnectTimelinePoints(GetConnectedTimelinePoints());
}
@@ -242,6 +254,9 @@ void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n)
waveform_view_->SetViewer(nullptr);
waveform_view_->ConnectTimelinePoints(nullptr);
// Queue an UpdateStack so that when it runs, the viewer node will be fully disconnected
QMetaObject::invokeMethod(this, "UpdateStack", Qt::QueuedConnection);
}
void ViewerWidget::ConnectedNodeChanged(ViewerOutput *n)
@@ -384,6 +399,13 @@ void ViewerWidget::DecodeCachedImage(RenderTicketPtr ticket, const QString &fn,
ticket->Finish(QVariant::fromValue(DecodeCachedImage(fn, time)));
}
bool ViewerWidget::ShouldForceWaveform() const
{
return GetConnectedNode()
&& !GetConnectedNode()->texture_input()->is_connected()
&& GetConnectedNode()->samples_input()->is_connected();
}
void ViewerWidget::UpdateTextureFromNode(const rational& time)
{
bool frame_exists_at_time = FrameExistsAtTime(time);
@@ -512,23 +534,24 @@ void ViewerWidget::PushScrubbedAudio()
{
if (!IsPlaying() && Config::Current()["AudioScrubbing"].toBool()) {
// Get audio src device from renderer
QString audio_fn = GetConnectedNode()->audio_playback_cache()->GetPCMFilename();
QFile audio_src(audio_fn);
AudioPlaybackCache::PlaybackDevice* audio_src = GetConnectedNode()->audio_playback_cache()->CreatePlaybackDevice();
if (audio_src.open(QFile::ReadOnly)) {
if (audio_src->open(QIODevice::ReadOnly)) {
const AudioParams& params = GetConnectedNode()->audio_playback_cache()->GetParameters();
// FIXME: Hardcoded scrubbing interval (20ms)
int size_of_sample = params.time_to_bytes(rational(20, 1000));
// Push audio
audio_src.seek(params.time_to_bytes(GetTime()));
QByteArray frame_audio = audio_src.read(size_of_sample);
audio_src->seek(params.time_to_bytes(GetTime()));
QByteArray frame_audio = audio_src->read(size_of_sample);
AudioManager::instance()->SetOutputParams(params);
AudioManager::instance()->PushToOutput(frame_audio);
audio_src.close();
audio_src->close();
}
delete audio_src;
}
}
@@ -628,13 +651,11 @@ void ViewerWidget::FinishPlayPreprocess()
{
int64_t playback_start_time = ruler()->GetTime();
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,
GetConnectedNode()->audio_playback_cache()->GetParameters().time_to_bytes(GetTime()),
playback_speed_);
}
AudioPlaybackCache* audio_cache = GetConnectedNode()->audio_playback_cache();
AudioManager::instance()->SetOutputParams(audio_cache->GetParameters());
AudioManager::instance()->StartOutput(audio_cache,
audio_cache->GetParameters().time_to_bytes(GetTime()),
playback_speed_);
playback_timer_.Start(playback_start_time, playback_speed_, timebase_dbl());
@@ -679,21 +700,22 @@ void ViewerWidget::UpdateStack()
{
rational new_tb;
if (GetConnectedNode()
&& !GetConnectedNode()->texture_input()->is_connected()
&& GetConnectedNode()->samples_input()->is_connected()) {
if (ShouldForceWaveform()) {
// If we have a node AND video is disconnected AND audio is connected, show waveform view
stack_->setCurrentWidget(waveform_view_);
new_tb = GetConnectedNode()->audio_params().time_base();
//new_tb = GetConnectedNode()->audio_params().time_base();
} else {
// Otherwise show regular display
stack_->setCurrentWidget(sizer_);
new_tb = GetConnectedNode()->video_params().time_base();
/*if (GetConnectedNode()) {
new_tb = GetConnectedNode()->video_params().time_base();
}*/
}
if (new_tb != timebase()) {
/*if (new_tb != timebase()) {
SetTimebase(new_tb);
}
}*/
}
void ViewerWidget::ContextMenuSetFullScreen(QAction *action)
@@ -784,139 +806,152 @@ void ViewerWidget::ShowContextMenu(const QPoint &pos)
{
Menu menu(static_cast<QWidget*>(sender()));
context_menu_widget_ = static_cast<ViewerDisplayWidget*>(sender());
context_menu_widget_ = dynamic_cast<ViewerDisplayWidget*>(sender());
// Color options
if (context_menu_widget_->color_manager() && color_menu_enabled_) {
{
Menu* ocio_display_menu = context_menu_widget_->GetDisplayMenu(&menu);
menu.addMenu(ocio_display_menu);
// ViewerDisplayWidget options
if (context_menu_widget_) {
// Color options
if (context_menu_widget_->color_manager() && color_menu_enabled_) {
{
Menu* ocio_display_menu = context_menu_widget_->GetDisplayMenu(&menu);
menu.addMenu(ocio_display_menu);
}
{
Menu* ocio_view_menu = context_menu_widget_->GetViewMenu(&menu);
menu.addMenu(ocio_view_menu);
}
{
Menu* ocio_look_menu = context_menu_widget_->GetLookMenu(&menu);
menu.addMenu(ocio_look_menu);
}
menu.addSeparator();
}
{
Menu* ocio_view_menu = context_menu_widget_->GetViewMenu(&menu);
menu.addMenu(ocio_view_menu);
// Viewer Zoom Level
Menu* zoom_menu = new Menu(tr("Zoom"), &menu);
menu.addMenu(zoom_menu);
int zoom_levels[] = {10, 25, 50, 75, 100, 150, 200, 400};
zoom_menu->addAction(tr("Fit"))->setData(0);
for (int i=0;i<8;i++) {
zoom_menu->addAction(tr("%1%").arg(zoom_levels[i]))->setData(zoom_levels[i]);
}
connect(zoom_menu, &QMenu::triggered, this, &ViewerWidget::SetZoomFromMenu);
}
{
Menu* ocio_look_menu = context_menu_widget_->GetLookMenu(&menu);
menu.addMenu(ocio_look_menu);
// Full Screen Menu
Menu* full_screen_menu = new Menu(tr("Full Screen"), &menu);
menu.addMenu(full_screen_menu);
for (int i=0;i<QGuiApplication::screens().size();i++) {
QScreen* s = QGuiApplication::screens().at(i);
QAction* a = full_screen_menu->addAction(tr("Screen %1: %2x%3").arg(QString::number(i),
QString::number(s->size().width()),
QString::number(s->size().height())));
a->setData(i);
}
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();
{
// Scopes
Menu* scopes_menu = new Menu(tr("Scopes"), &menu);
menu.addMenu(scopes_menu);
for (int i=0;i<ScopePanel::kTypeCount;i++) {
QAction* scope_action = scopes_menu->addAction(ScopePanel::TypeToName(static_cast<ScopePanel::Type>(i)));
scope_action->setData(i);
}
connect(scopes_menu, &Menu::triggered, this, &ViewerWidget::ContextMenuScopeTriggered);
}
menu.addSeparator();
{
Menu* cache_menu = new Menu(tr("Cache"), &menu);
menu.addMenu(cache_menu);
// Auto-cache
QAction* autocache_action = cache_menu->addAction(tr("Auto-Cache"));
autocache_action->setCheckable(true);
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();
{
// Safe Margins
Menu* safe_margin_menu = new Menu(tr("Safe Margins"), &menu);
menu.addMenu(safe_margin_menu);
QAction* safe_margin_off = safe_margin_menu->addAction(tr("Off"));
safe_margin_off->setCheckable(true);
safe_margin_off->setChecked(!context_menu_widget_->GetSafeMargin().is_enabled());
connect(safe_margin_off, &QAction::triggered, this, &ViewerWidget::ContextMenuDisableSafeMargins);
QAction* safe_margin_on = safe_margin_menu->addAction(tr("On"));
safe_margin_on->setCheckable(true);
safe_margin_on->setChecked(context_menu_widget_->GetSafeMargin().is_enabled() && !context_menu_widget_->GetSafeMargin().custom_ratio());
connect(safe_margin_on, &QAction::triggered, this, &ViewerWidget::ContextMenuSetSafeMargins);
QAction* safe_margin_custom = safe_margin_menu->addAction(tr("Custom Aspect"));
safe_margin_custom->setCheckable(true);
safe_margin_custom->setChecked(context_menu_widget_->GetSafeMargin().is_enabled() && context_menu_widget_->GetSafeMargin().custom_ratio());
connect(safe_margin_custom, &QAction::triggered, this, &ViewerWidget::ContextMenuSetCustomSafeMargins);
}
menu.addSeparator();
}
{
// Viewer Zoom Level
Menu* zoom_menu = new Menu(tr("Zoom"), &menu);
menu.addMenu(zoom_menu);
int zoom_levels[] = {10, 25, 50, 75, 100, 150, 200, 400};
zoom_menu->addAction(tr("Fit"))->setData(0);
for (int i=0;i<8;i++) {
zoom_menu->addAction(tr("%1%").arg(zoom_levels[i]))->setData(zoom_levels[i]);
}
connect(zoom_menu, &QMenu::triggered, this, &ViewerWidget::SetZoomFromMenu);
}
{
// Full Screen Menu
Menu* full_screen_menu = new Menu(tr("Full Screen"), &menu);
menu.addMenu(full_screen_menu);
for (int i=0;i<QGuiApplication::screens().size();i++) {
QScreen* s = QGuiApplication::screens().at(i);
QAction* a = full_screen_menu->addAction(tr("Screen %1: %2x%3").arg(QString::number(i),
QString::number(s->size().width()),
QString::number(s->size().height())));
a->setData(i);
}
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();
{
// Scopes
Menu* scopes_menu = new Menu(tr("Scopes"), &menu);
menu.addMenu(scopes_menu);
for (int i=0;i<ScopePanel::kTypeCount;i++) {
QAction* scope_action = scopes_menu->addAction(ScopePanel::TypeToName(static_cast<ScopePanel::Type>(i)));
scope_action->setData(i);
}
connect(scopes_menu, &Menu::triggered, this, &ViewerWidget::ContextMenuScopeTriggered);
}
menu.addSeparator();
{
Menu* cache_menu = new Menu(tr("Cache"), &menu);
menu.addMenu(cache_menu);
// Auto-cache
QAction* autocache_action = cache_menu->addAction(tr("Auto-Cache"));
autocache_action->setCheckable(true);
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();
{
// Safe Margins
Menu* safe_margin_menu = new Menu(tr("Safe Margins"), &menu);
menu.addMenu(safe_margin_menu);
QAction* safe_margin_off = safe_margin_menu->addAction(tr("Off"));
safe_margin_off->setCheckable(true);
safe_margin_off->setChecked(!context_menu_widget_->GetSafeMargin().is_enabled());
connect(safe_margin_off, &QAction::triggered, this, &ViewerWidget::ContextMenuDisableSafeMargins);
QAction* safe_margin_on = safe_margin_menu->addAction(tr("On"));
safe_margin_on->setCheckable(true);
safe_margin_on->setChecked(context_menu_widget_->GetSafeMargin().is_enabled() && !context_menu_widget_->GetSafeMargin().custom_ratio());
connect(safe_margin_on, &QAction::triggered, this, &ViewerWidget::ContextMenuSetSafeMargins);
QAction* safe_margin_custom = safe_margin_menu->addAction(tr("Custom Aspect"));
safe_margin_custom->setCheckable(true);
safe_margin_custom->setChecked(context_menu_widget_->GetSafeMargin().is_enabled() && context_menu_widget_->GetSafeMargin().custom_ratio());
connect(safe_margin_custom, &QAction::triggered, this, &ViewerWidget::ContextMenuSetCustomSafeMargins);
QAction* show_waveform_action = menu.addAction(tr("Show Audio Waveform"));
show_waveform_action->setCheckable(true);
show_waveform_action->setChecked(stack_->currentWidget() == waveform_view_);
show_waveform_action->setEnabled(!ShouldForceWaveform());
connect(show_waveform_action, &QAction::triggered, this, &ViewerWidget::ManualSwitchToWaveform);
}
menu.exec(static_cast<QWidget*>(sender())->mapToGlobal(pos));
@@ -1158,6 +1193,25 @@ void ViewerWidget::ViewerInvalidatedVideoRange(const TimeRange &range)
}
}
void ViewerWidget::ManualSwitchToWaveform(bool e)
{
if (e) {
stack_->setCurrentWidget(waveform_view_);
} else {
stack_->setCurrentWidget(sizer_);
}
}
void ViewerWidget::TimeChangedFromWaveform(qint64 t)
{
if (waveform_view_->timebase() != this->timebase()) {
// Transform time to our timebase
t = Timecode::rescale_timestamp(t, waveform_view_->timebase(), this->timebase());
}
SetTimeAndSignal(t);
}
void ViewerWidget::ViewerShiftedRange(const rational &from, const rational &to)
{
if (GetTime() >= qMin(from, to)) {
+6
View File
@@ -210,6 +210,8 @@ private:
void DecodeCachedImage(RenderTicketPtr ticket, const QString &fn, const rational& time) const;
bool ShouldForceWaveform() const;
QStackedWidget* stack_;
ViewerSizer* sizer_;
@@ -293,6 +295,10 @@ private slots:
void ViewerInvalidatedVideoRange(const OLIVE_NAMESPACE::TimeRange &range);
void ManualSwitchToWaveform(bool e);
void TimeChangedFromWaveform(qint64 t);
};
OLIVE_NAMESPACE_EXIT