Merge branch 'master' into otio
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
params_ = params;
|
||||
@@ -63,18 +59,19 @@ void AudioMonitor::SetParams(const AudioParams ¶ms)
|
||||
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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 ×tamp)
|
||||
@@ -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 ×tamp)
|
||||
{
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -226,8 +226,6 @@ public:
|
||||
const QString& id,
|
||||
const QString& key = QString());
|
||||
|
||||
static void SetBooleanAction(QAction* a, bool *boolean);
|
||||
|
||||
private:
|
||||
void Init();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ×tamp)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -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 ×tamp);
|
||||
|
||||
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
|
||||
|
||||
@@ -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) :
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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_) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
@@ -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&){}
|
||||
|
||||
@@ -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> ×)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -64,6 +64,9 @@ private:
|
||||
|
||||
PlaybackCache* playback_cache_;
|
||||
|
||||
private slots:
|
||||
void ShowContextMenu();
|
||||
|
||||
};
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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)) {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user