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