Merge branch 'nodeview-redux'
This commit is contained in:
@@ -197,11 +197,11 @@ void AudioVisualWaveform::Shift(const rational &from, const rational &to)
|
||||
int to_index = time_to_samples(to, rate_dbl);
|
||||
|
||||
if (from_index == to_index) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (from_index > data.size()) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (from_index > to_index) {
|
||||
@@ -226,7 +226,7 @@ void AudioVisualWaveform::Shift(const rational &from, const rational &to)
|
||||
memcpy(temp.data(), &data.data()[from_index], temp.size());
|
||||
memcpy(&data.data()[to_index], temp.data(), temp.size());
|
||||
|
||||
memset(reinterpret_cast<char*>(&data[from_index]), 0, distance * sizeof(SamplePerChannel));
|
||||
memset(&data.data()[from_index], 0, distance * sizeof(SamplePerChannel));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -396,7 +396,7 @@ bool FFmpegEncoder::WriteSubtitle(const SubtitleBlock *sub_block)
|
||||
subtitle.num_rects = 1;
|
||||
subtitle.rects = &rect_array;
|
||||
|
||||
subtitle.pts = Timecode::time_to_timestamp(sub_block->in(), av_get_time_base_q(), true);
|
||||
subtitle.pts = Timecode::time_to_timestamp(sub_block->in(), av_get_time_base_q(), Timecode::kFloor);
|
||||
subtitle.end_display_time = qRound64(sub_block->length().toDouble() * 1000);
|
||||
|
||||
QVector<uint8_t> out_buf(1024 * 1024);
|
||||
|
||||
@@ -36,6 +36,8 @@ set(OLIVE_SOURCES
|
||||
common/flipmodifiers.cpp
|
||||
common/flipmodifiers.h
|
||||
common/functiontimer.h
|
||||
common/jobtime.cpp
|
||||
common/jobtime.h
|
||||
common/lerp.h
|
||||
common/memorypool.h
|
||||
common/ocioutils.cpp
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
#include "jobtime.h"
|
||||
|
||||
#include <QMutex>
|
||||
|
||||
namespace olive {
|
||||
|
||||
uint64_t job_time_index = 0;
|
||||
QMutex job_time_mutex;
|
||||
|
||||
JobTime::JobTime()
|
||||
{
|
||||
Acquire();
|
||||
}
|
||||
|
||||
void JobTime::Acquire()
|
||||
{
|
||||
job_time_mutex.lock();
|
||||
|
||||
value_ = job_time_index;
|
||||
job_time_index++;
|
||||
|
||||
job_time_mutex.unlock();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, const olive::JobTime& r)
|
||||
{
|
||||
return debug.space() << r.value();
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
#ifndef JOBTIME_H
|
||||
#define JOBTIME_H
|
||||
|
||||
#include <QDebug>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace olive {
|
||||
|
||||
class JobTime
|
||||
{
|
||||
public:
|
||||
JobTime();
|
||||
|
||||
void Acquire();
|
||||
|
||||
uint64_t value() const
|
||||
{
|
||||
return value_;
|
||||
}
|
||||
|
||||
bool operator==(const JobTime &rhs) const
|
||||
{
|
||||
return value_ == rhs.value_;
|
||||
}
|
||||
|
||||
bool operator!=(const JobTime &rhs) const
|
||||
{
|
||||
return value_ != rhs.value_;
|
||||
}
|
||||
|
||||
bool operator<(const JobTime &rhs) const
|
||||
{
|
||||
return value_ < rhs.value_;
|
||||
}
|
||||
|
||||
bool operator>(const JobTime &rhs) const
|
||||
{
|
||||
return value_ > rhs.value_;
|
||||
}
|
||||
|
||||
bool operator<=(const JobTime &rhs) const
|
||||
{
|
||||
return value_ <= rhs.value_;
|
||||
}
|
||||
|
||||
bool operator>=(const JobTime &rhs) const
|
||||
{
|
||||
return value_ >= rhs.value_;
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t value_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, const olive::JobTime& r);
|
||||
|
||||
Q_DECLARE_METATYPE(olive::JobTime)
|
||||
|
||||
#endif // JOBTIME_H
|
||||
@@ -239,7 +239,7 @@ rational Timecode::timecode_to_time(const QString &timecode, const rational &tim
|
||||
return timestamp_to_time(timestamp, timebase);
|
||||
}
|
||||
|
||||
rational Timecode::snap_time_to_timebase(const rational &time, const rational &timebase, bool floor)
|
||||
rational Timecode::snap_time_to_timebase(const rational &time, const rational &timebase, Rounding floor)
|
||||
{
|
||||
// Just convert to a timestamp in timebase units and back
|
||||
int64_t timestamp = time_to_timestamp(time, timebase, floor);
|
||||
@@ -275,19 +275,23 @@ QString Timecode::TimeToString(int64_t ms)
|
||||
.arg(ss, 2, 10, QChar('0'));
|
||||
}
|
||||
|
||||
int64_t Timecode::time_to_timestamp(const rational &time, const rational &timebase, bool floor)
|
||||
int64_t Timecode::time_to_timestamp(const rational &time, const rational &timebase, Rounding floor)
|
||||
{
|
||||
return time_to_timestamp(time.toDouble(), timebase, floor);
|
||||
}
|
||||
|
||||
int64_t Timecode::time_to_timestamp(const double &time, const rational &timebase, bool floor)
|
||||
int64_t Timecode::time_to_timestamp(const double &time, const rational &timebase, Rounding floor)
|
||||
{
|
||||
double d = time * timebase.flipped().toDouble();
|
||||
|
||||
if (floor) {
|
||||
return qFloor(d);
|
||||
} else {
|
||||
switch (floor) {
|
||||
case kRound:
|
||||
default:
|
||||
return qRound64(d);
|
||||
case kFloor:
|
||||
return qFloor(d);
|
||||
case kCeil:
|
||||
return qCeil(d);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,12 @@ public:
|
||||
kMilliseconds
|
||||
};
|
||||
|
||||
enum Rounding {
|
||||
kCeil,
|
||||
kFloor,
|
||||
kRound
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Convert a timestamp (according to a rational timebase) to a user-friendly string representation
|
||||
*/
|
||||
@@ -55,10 +61,10 @@ public:
|
||||
static int64_t timecode_to_timestamp(const QString& timecode, const rational& timebase, const Display& display, bool *ok = nullptr);
|
||||
static rational timecode_to_time(const QString& timecode, const rational& timebase, const Display& display, bool *ok = nullptr);
|
||||
|
||||
static rational snap_time_to_timebase(const rational& time, const rational& timebase, bool floor = false);
|
||||
static rational snap_time_to_timebase(const rational& time, const rational& timebase, Rounding floor = kRound);
|
||||
|
||||
static int64_t time_to_timestamp(const rational& time, const rational& timebase, bool floor = false);
|
||||
static int64_t time_to_timestamp(const double& time, const rational& timebase, bool floor = false);
|
||||
static int64_t time_to_timestamp(const rational& time, const rational& timebase, Rounding floor = kRound);
|
||||
static int64_t time_to_timestamp(const double& time, const rational& timebase, Rounding floor = kRound);
|
||||
|
||||
static int64_t rescale_timestamp(const int64_t& ts, const rational& source, const rational& dest);
|
||||
static int64_t rescale_timestamp_ceil(const int64_t& ts, const rational& source, const rational& dest);
|
||||
|
||||
+73
-24
@@ -199,30 +199,7 @@ void TimeRangeList::insert(TimeRange range_to_add)
|
||||
|
||||
void TimeRangeList::remove(const TimeRange &remove)
|
||||
{
|
||||
int sz = this->size();
|
||||
|
||||
for (int i=0;i<sz;i++) {
|
||||
TimeRange& compare = array_[i];
|
||||
|
||||
if (remove.Contains(compare)) {
|
||||
// This element is entirely encompassed in this range, remove it
|
||||
array_.removeAt(i);
|
||||
i--;
|
||||
sz--;
|
||||
} else if (compare.Contains(remove, false, false)) {
|
||||
// The remove range is within this element, only choice is to split the element into two
|
||||
TimeRange new_range(remove.out(), compare.out());
|
||||
compare.set_out(remove.in());
|
||||
insert(new_range);
|
||||
break;
|
||||
} else if (compare.in() < remove.in() && compare.out() > remove.in()) {
|
||||
// This element's out point overlaps the range's in, we'll trim it
|
||||
compare.set_out(remove.in());
|
||||
} else if (compare.in() < remove.out() && compare.out() > remove.out()) {
|
||||
// This element's in point overlaps the range's out, we'll trim it
|
||||
compare.set_in(remove.out());
|
||||
}
|
||||
}
|
||||
util_remove(&array_, remove);
|
||||
}
|
||||
|
||||
bool TimeRangeList::contains(const TimeRange &range, bool in_inclusive, bool out_inclusive) const
|
||||
@@ -296,6 +273,78 @@ uint qHash(const TimeRange &r, uint seed)
|
||||
return qHash(r.in(), seed) ^ qHash(r.out(), seed);
|
||||
}
|
||||
|
||||
TimeRangeListFrameIterator::TimeRangeListFrameIterator() :
|
||||
TimeRangeListFrameIterator(TimeRangeList(), rational::NaN)
|
||||
{
|
||||
}
|
||||
|
||||
TimeRangeListFrameIterator::TimeRangeListFrameIterator(const TimeRangeList &list, const rational &timebase) :
|
||||
list_(list),
|
||||
timebase_(timebase),
|
||||
index_(-1),
|
||||
size_(-1)
|
||||
{
|
||||
UpdateIndexIfNecessary();
|
||||
}
|
||||
|
||||
bool TimeRangeListFrameIterator::GetNext(rational *out)
|
||||
{
|
||||
if (!HasNext()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Output current value
|
||||
*out = current_;
|
||||
|
||||
// Determine next value by adding timebase
|
||||
current_ += timebase_;
|
||||
|
||||
// If this time is outside the current range, jump to the next one
|
||||
UpdateIndexIfNecessary();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TimeRangeListFrameIterator::HasNext() const
|
||||
{
|
||||
return index_ < list_.size();
|
||||
}
|
||||
|
||||
int TimeRangeListFrameIterator::size()
|
||||
{
|
||||
if (size_ == -1) {
|
||||
// Size isn't calculated automatically for optimization, so we'll calculate it now
|
||||
size_ = 0;
|
||||
|
||||
foreach (const TimeRange &range, list_) {
|
||||
rational start = Timecode::snap_time_to_timebase(range.in(), timebase_, Timecode::kCeil);
|
||||
rational end = Timecode::snap_time_to_timebase(range.out(), timebase_, Timecode::kFloor);
|
||||
|
||||
if (end == range.out()) {
|
||||
end -= timebase_;
|
||||
}
|
||||
|
||||
int64_t start_ts = Timecode::time_to_timestamp(start, timebase_);
|
||||
int64_t end_ts = Timecode::time_to_timestamp(end, timebase_);
|
||||
|
||||
size_ += 1 + (end_ts - start_ts);
|
||||
}
|
||||
}
|
||||
|
||||
return size_;
|
||||
}
|
||||
|
||||
void TimeRangeListFrameIterator::UpdateIndexIfNecessary()
|
||||
{
|
||||
while (index_ < list_.size() && (index_ == -1 || current_ >= list_.at(index_).out())) {
|
||||
index_++;
|
||||
|
||||
if (index_ < list_.size()) {
|
||||
current_ = Timecode::snap_time_to_timebase(list_.at(index_).in(), timebase_, Timecode::kCeil);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, const olive::TimeRange &r)
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#define TIMERANGE_H
|
||||
|
||||
#include "rational.h"
|
||||
#include "timecodefunctions.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -80,6 +81,36 @@ public:
|
||||
|
||||
void remove(const TimeRange& remove);
|
||||
|
||||
template <typename T>
|
||||
static void util_remove(QVector<T> *list, const TimeRange &remove)
|
||||
{
|
||||
int sz = list->size();
|
||||
|
||||
for (int i=0;i<sz;i++) {
|
||||
T& compare = (*list)[i];
|
||||
|
||||
if (remove.Contains(compare)) {
|
||||
// This element is entirely encompassed in this range, remove it
|
||||
list->removeAt(i);
|
||||
i--;
|
||||
sz--;
|
||||
} else if (compare.Contains(remove, false, false)) {
|
||||
// The remove range is within this element, only choice is to split the element into two
|
||||
T new_range = compare;
|
||||
new_range.set_in(remove.out());
|
||||
compare.set_out(remove.in());
|
||||
list->append(new_range);
|
||||
break;
|
||||
} else if (compare.in() < remove.in() && compare.out() > remove.in()) {
|
||||
// This element's out point overlaps the range's in, we'll trim it
|
||||
compare.set_out(remove.in());
|
||||
} else if (compare.in() < remove.out() && compare.out() > remove.out()) {
|
||||
// This element's in point overlaps the range's out, we'll trim it
|
||||
compare.set_in(remove.out());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool contains(const TimeRange& range, bool in_inclusive = true, bool out_inclusive = true) const;
|
||||
|
||||
bool isEmpty() const
|
||||
@@ -127,16 +158,69 @@ public:
|
||||
return array_.last();
|
||||
}
|
||||
|
||||
const TimeRange& at(int index) const
|
||||
{
|
||||
return array_.at(index);
|
||||
}
|
||||
|
||||
const QVector<TimeRange>& internal_array() const
|
||||
{
|
||||
return array_;
|
||||
}
|
||||
|
||||
bool operator==(const TimeRangeList &rhs) const
|
||||
{
|
||||
return array_ == rhs.array_;
|
||||
}
|
||||
|
||||
private:
|
||||
QVector<TimeRange> array_;
|
||||
|
||||
};
|
||||
|
||||
class TimeRangeListFrameIterator
|
||||
{
|
||||
public:
|
||||
TimeRangeListFrameIterator();
|
||||
TimeRangeListFrameIterator(const TimeRangeList &list, const rational &timebase);
|
||||
|
||||
bool GetNext(rational *out);
|
||||
|
||||
bool HasNext() const;
|
||||
|
||||
QVector<rational> ToVector() const
|
||||
{
|
||||
TimeRangeListFrameIterator copy(list_, timebase_);
|
||||
QVector<rational> times;
|
||||
rational r;
|
||||
while (copy.GetNext(&r)) {
|
||||
times.append(r);
|
||||
}
|
||||
return times;
|
||||
}
|
||||
|
||||
int size();
|
||||
|
||||
void reset()
|
||||
{
|
||||
*this = TimeRangeListFrameIterator();
|
||||
}
|
||||
|
||||
private:
|
||||
void UpdateIndexIfNecessary();
|
||||
|
||||
TimeRangeList list_;
|
||||
|
||||
rational timebase_;
|
||||
|
||||
rational current_;
|
||||
|
||||
int index_;
|
||||
|
||||
int size_;
|
||||
|
||||
};
|
||||
|
||||
uint qHash(const TimeRange& r, uint seed = 0);
|
||||
|
||||
}
|
||||
|
||||
+4
-2
@@ -75,14 +75,15 @@
|
||||
namespace olive {
|
||||
|
||||
Core* Core::instance_ = nullptr;
|
||||
const uint Core::kProjectVersion = 210122;
|
||||
const uint Core::kProjectVersion = 210528;
|
||||
|
||||
Core::Core(const CoreParams& params) :
|
||||
main_window_(nullptr),
|
||||
tool_(Tool::kPointer),
|
||||
addable_object_(Tool::kAddableEmpty),
|
||||
snapping_(true),
|
||||
core_params_(params)
|
||||
core_params_(params),
|
||||
effects_slider_is_being_dragged_(false)
|
||||
{
|
||||
// Store reference to this object, making the assumption that Core will only ever be made in
|
||||
// main(). This will obviously break if not.
|
||||
@@ -421,6 +422,7 @@ void Core::CreateNewSequence()
|
||||
|
||||
command->add_child(new NodeAddCommand(active_project, new_sequence));
|
||||
command->add_child(new FolderAddChild(GetSelectedFolderInActiveProject(), new_sequence));
|
||||
command->add_child(new NodeSetPositionCommand(new_sequence, new_sequence, QPointF(0, 0), false));
|
||||
|
||||
// Create and connect default nodes to new sequence
|
||||
new_sequence->add_default_nodes(command);
|
||||
|
||||
@@ -304,6 +304,10 @@ public:
|
||||
|
||||
void OpenNodeInViewer(ViewerOutput* viewer);
|
||||
|
||||
bool EffectsSliderIsBeingDragged() const {return effects_slider_is_being_dragged_;}
|
||||
|
||||
void SetEffectsSliderIsBeingDragged(bool e) {effects_slider_is_being_dragged_ = e;}
|
||||
|
||||
static const uint kProjectVersion;
|
||||
|
||||
public slots:
|
||||
@@ -571,6 +575,11 @@ private:
|
||||
*/
|
||||
QVector<QUuid> autorecovered_projects_;
|
||||
|
||||
/**
|
||||
* @brief An effects slider somewhere is being dragged
|
||||
*/
|
||||
bool effects_slider_is_being_dragged_;
|
||||
|
||||
private slots:
|
||||
void SaveAutorecovery();
|
||||
|
||||
|
||||
@@ -115,6 +115,7 @@ private:
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "core.h"
|
||||
#include "node/output/track/track.h"
|
||||
#include "transition/transition.h"
|
||||
#include "widget/slider/floatslider.h"
|
||||
@@ -29,6 +30,8 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
#define super Node
|
||||
|
||||
const QString Block::kLengthInput = QStringLiteral("length_in");
|
||||
const QString Block::kMediaInInput = QStringLiteral("media_in_in");
|
||||
const QString Block::kEnabledInput = QStringLiteral("enabled_in");
|
||||
@@ -47,7 +50,6 @@ Block::Block() :
|
||||
SetInputProperty(kLengthInput, QStringLiteral("min"), QVariant::fromValue(rational(0, 1)));
|
||||
SetInputProperty(kLengthInput, QStringLiteral("view"), RationalSlider::kTime);
|
||||
SetInputProperty(kLengthInput, QStringLiteral("viewlock"), true);
|
||||
IgnoreInvalidationsFrom(kLengthInput);
|
||||
IgnoreHashingFrom(kLengthInput);
|
||||
|
||||
AddInput(kMediaInInput, NodeValue::kRational, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable));
|
||||
@@ -221,7 +223,7 @@ void Block::set_length_internal(const rational &length)
|
||||
|
||||
void Block::Retranslate()
|
||||
{
|
||||
Node::Retranslate();
|
||||
super::Retranslate();
|
||||
|
||||
SetInputName(kLengthInput, tr("Length"));
|
||||
SetInputName(kMediaInInput, tr("Media In"));
|
||||
@@ -235,4 +237,24 @@ void Block::Hash(const QString &, QCryptographicHash &, const rational &, const
|
||||
// A block does nothing by default, so we hash nothing
|
||||
}
|
||||
|
||||
void Block::InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options)
|
||||
{
|
||||
TimeRange r;
|
||||
|
||||
if (from == kLengthInput) {
|
||||
// We must intercept the signal here
|
||||
r = TimeRange(qMin(length(), last_length_), RATIONAL_MAX);
|
||||
|
||||
if (!Core::instance()->EffectsSliderIsBeingDragged()) {
|
||||
last_length_ = length();
|
||||
}
|
||||
|
||||
options.insert(QStringLiteral("lengthevent"), true);
|
||||
} else {
|
||||
r = range;
|
||||
}
|
||||
|
||||
super::InvalidateCache(r, from, element, options);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -155,6 +155,8 @@ public:
|
||||
|
||||
virtual void Hash(const QString& output, QCryptographicHash &hash, const rational &time, const VideoParams& video_params) const override;
|
||||
|
||||
virtual void InvalidateCache(const TimeRange& range, const QString& from, int element = -1, InvalidateCacheOptions options = InvalidateCacheOptions()) override;
|
||||
|
||||
static const QString kLengthInput;
|
||||
static const QString kMediaInInput;
|
||||
static const QString kEnabledInput;
|
||||
@@ -193,6 +195,8 @@ private:
|
||||
|
||||
QVector<Block*> block_links_;
|
||||
|
||||
rational last_length_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ QString ClipBlock::Description() const
|
||||
return tr("A time-based node that represents a media source.");
|
||||
}
|
||||
|
||||
void ClipBlock::InvalidateCache(const TimeRange& range, const QString& from, int element, qint64 job_time)
|
||||
void ClipBlock::InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options)
|
||||
{
|
||||
Q_UNUSED(element)
|
||||
|
||||
@@ -63,10 +63,10 @@ void ClipBlock::InvalidateCache(const TimeRange& range, const QString& from, int
|
||||
rational start = MediaToSequenceTime(range.in());
|
||||
rational end = MediaToSequenceTime(range.out());
|
||||
|
||||
super::InvalidateCache(TimeRange(start, end), from, element, job_time);
|
||||
super::InvalidateCache(TimeRange(start, end), from, element, options);
|
||||
} else {
|
||||
// Otherwise, pass signal along normally
|
||||
super::InvalidateCache(range, from, element, job_time);
|
||||
super::InvalidateCache(range, from, element, options);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ public:
|
||||
virtual QString id() const override;
|
||||
virtual QString Description() const override;
|
||||
|
||||
virtual void InvalidateCache(const TimeRange& range, const QString& from, int element, qint64 job_time) override;
|
||||
virtual void InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options) override;
|
||||
|
||||
virtual TimeRange InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const override;
|
||||
|
||||
|
||||
@@ -44,6 +44,45 @@ void NodeGraph::Clear()
|
||||
}
|
||||
}
|
||||
|
||||
qreal NodeGraph::GetNodeContextHeight(Node *context)
|
||||
{
|
||||
const PositionMap &map = position_map_.value(context);
|
||||
|
||||
qreal top = 0, bottom = 0;
|
||||
|
||||
foreach (const QPointF &pt, map) {
|
||||
top = qMin(pt.y(), top);
|
||||
bottom = qMax(pt.y(), bottom);
|
||||
}
|
||||
|
||||
return bottom - top;
|
||||
}
|
||||
|
||||
int NodeGraph::GetNumberOfContextsNodeIsIn(Node *node) const
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for (auto it=position_map_.cbegin(); it!=position_map_.cend(); it++) {
|
||||
if (it.value().contains(node)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
bool NodeGraph::NodeOutputsToContext(Node *node) const
|
||||
{
|
||||
for (auto it=position_map_.cbegin(); it!=position_map_.cend(); it++) {
|
||||
const PositionMap &pm = it.value();
|
||||
if (pm.contains(node) && node->OutputsTo(it.key(), true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void NodeGraph::childEvent(QChildEvent *event)
|
||||
{
|
||||
super::childEvent(event);
|
||||
@@ -75,6 +114,18 @@ void NodeGraph::childEvent(QChildEvent *event)
|
||||
emit NodeRemoved(node);
|
||||
emit node->RemovedFromGraph(this);
|
||||
|
||||
for (auto it=position_map_.begin(); it!=position_map_.end(); it++) {
|
||||
PositionMap &map = it.value();
|
||||
for (auto jt=map.begin(); jt!=map.end(); ) {
|
||||
if (jt.key() == node) {
|
||||
jt = map.erase(jt);
|
||||
emit NodePositionRemoved(node, it.key());
|
||||
} else {
|
||||
jt++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,55 @@ public:
|
||||
return default_nodes_;
|
||||
}
|
||||
|
||||
bool NodeMapContainsNode(Node* node, Node* context) const
|
||||
{
|
||||
return position_map_.value(context).contains(node);
|
||||
}
|
||||
|
||||
QPointF GetNodePosition(Node* node, Node* context)
|
||||
{
|
||||
return position_map_.value(context).value(node);
|
||||
}
|
||||
|
||||
void SetNodePosition(Node* node, Node* context, const QPointF& pos)
|
||||
{
|
||||
position_map_[context].insert(node, pos);
|
||||
emit NodePositionAdded(node, context, pos);
|
||||
}
|
||||
|
||||
void RemoveNodePosition(Node* node, Node* context)
|
||||
{
|
||||
PositionMap& map = position_map_[context];
|
||||
map.remove(node);
|
||||
if (map.isEmpty()) {
|
||||
position_map_.remove(context);
|
||||
}
|
||||
emit NodePositionRemoved(node, context);
|
||||
}
|
||||
|
||||
bool ContextContainsNode(Node *node, Node *context)
|
||||
{
|
||||
return position_map_[context].contains(node);
|
||||
}
|
||||
|
||||
qreal GetNodeContextHeight(Node *context);
|
||||
|
||||
using PositionMap = QMap<Node*, QPointF>;
|
||||
|
||||
const PositionMap &GetNodesForContext(Node *context)
|
||||
{
|
||||
return position_map_[context];
|
||||
}
|
||||
|
||||
const QMap<Node *, PositionMap> &GetPositionMap() const
|
||||
{
|
||||
return position_map_;
|
||||
}
|
||||
|
||||
int GetNumberOfContextsNodeIsIn(Node *node) const;
|
||||
|
||||
bool NodeOutputsToContext(Node *node) const;
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief Signal emitted when a Node is added to the graph
|
||||
@@ -80,6 +129,10 @@ signals:
|
||||
|
||||
void ValueChanged(const NodeInput& input);
|
||||
|
||||
void NodePositionAdded(Node *node, Node *relative, const QPointF &position);
|
||||
|
||||
void NodePositionRemoved(Node *node, Node *relative);
|
||||
|
||||
protected:
|
||||
void AddDefaultNode(Node* n)
|
||||
{
|
||||
@@ -93,6 +146,10 @@ private:
|
||||
|
||||
QVector<Node*> default_nodes_;
|
||||
|
||||
QMap<Node *, PositionMap> position_map_;
|
||||
|
||||
PositionMap root_position_map_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
+167
-85
@@ -46,7 +46,6 @@ const QString Node::kDefaultOutput = QStringLiteral("output");
|
||||
Node::Node(bool create_default_output) :
|
||||
can_be_deleted_(true),
|
||||
override_color_(-1),
|
||||
last_change_time_(0),
|
||||
folder_(nullptr),
|
||||
operation_stack_(0),
|
||||
cache_result_(false)
|
||||
@@ -91,20 +90,6 @@ void Node::Load(QXmlStreamReader *reader, XMLNodeData& xml_node_data, uint versi
|
||||
LoadInput(reader, xml_node_data, cancelled);
|
||||
} else if (reader->name() == QStringLiteral("ptr")) {
|
||||
xml_node_data.node_ptrs.insert(reader->readElementText().toULongLong(), this);
|
||||
} else if (reader->name() == QStringLiteral("pos")) {
|
||||
QPointF p;
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("x")) {
|
||||
p.setX(reader->readElementText().toDouble());
|
||||
} else if (reader->name() == QStringLiteral("y")) {
|
||||
p.setY(reader->readElementText().toDouble());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
SetPosition(p);
|
||||
} else if (reader->name() == QStringLiteral("label")) {
|
||||
SetLabel(reader->readElementText());
|
||||
} else if (reader->name() == QStringLiteral("color")) {
|
||||
@@ -166,11 +151,6 @@ void Node::Save(QXmlStreamWriter *writer) const
|
||||
{
|
||||
writer->writeTextElement(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(this)));
|
||||
|
||||
writer->writeStartElement(QStringLiteral("pos"));
|
||||
writer->writeTextElement(QStringLiteral("x"), QString::number(GetPosition().x()));
|
||||
writer->writeTextElement(QStringLiteral("y"), QString::number(GetPosition().y()));
|
||||
writer->writeEndElement(); // pos
|
||||
|
||||
writer->writeTextElement(QStringLiteral("label"), GetLabel());
|
||||
writer->writeTextElement(QStringLiteral("color"), QString::number(override_color_));
|
||||
|
||||
@@ -282,9 +262,6 @@ void Node::ConnectEdge(const NodeOutput &output, const NodeInput &input)
|
||||
input.node()->input_connections_[input] = output;
|
||||
output.node()->output_connections_.push_back(std::pair<NodeOutput, NodeInput>({output, input}));
|
||||
|
||||
// Update change times
|
||||
input.node()->UpdateLastChangedTime();
|
||||
|
||||
// Call internal events
|
||||
input.node()->InputConnectedEvent(input.input(), input.element(), output);
|
||||
output.node()->OutputConnectedEvent(output.output(), input);
|
||||
@@ -314,9 +291,6 @@ void Node::DisconnectEdge(const NodeOutput &output, const NodeInput &input)
|
||||
OutputConnections& outputs = output.node()->output_connections_;
|
||||
outputs.erase(std::find(outputs.begin(), outputs.end(), std::pair<NodeOutput, NodeInput>({output, input})));
|
||||
|
||||
// Update change times
|
||||
input.node()->UpdateLastChangedTime();
|
||||
|
||||
// Call internal events
|
||||
input.node()->InputDisconnectedEvent(input.input(), input.element(), output);
|
||||
output.node()->OutputDisconnectedEvent(output.output(), input);
|
||||
@@ -953,7 +927,7 @@ void Node::InputArrayResize(const QString &id, int size, bool undoable)
|
||||
if (undoable) {
|
||||
Core::instance()->undo_stack()->push(c);
|
||||
} else {
|
||||
c->redo();
|
||||
c->redo_now();
|
||||
delete c;
|
||||
}
|
||||
}
|
||||
@@ -1045,12 +1019,12 @@ NodeValueTable Node::Value(const QString& output, NodeValueDatabase &value) cons
|
||||
return value.Merge();
|
||||
}
|
||||
|
||||
void Node::InvalidateCache(const TimeRange &range, const QString &from, int element, qint64 job_time)
|
||||
void Node::InvalidateCache(const TimeRange &range, const QString &from, int element, InvalidateCacheOptions options)
|
||||
{
|
||||
Q_UNUSED(from)
|
||||
Q_UNUSED(element)
|
||||
|
||||
SendInvalidateCache(range, job_time);
|
||||
SendInvalidateCache(range, options);
|
||||
}
|
||||
|
||||
void Node::BeginOperation()
|
||||
@@ -1134,7 +1108,7 @@ void Node::CopyDependencyGraph(const QVector<Node *> &src, const QVector<Node *>
|
||||
}
|
||||
}
|
||||
|
||||
Node *Node::CopyNodeAndDependencyGraphMinusItemsInternal(QMap<const Node*, Node*>& created, const Node *node, MultiUndoCommand *command)
|
||||
Node *Node::CopyNodeAndDependencyGraphMinusItemsInternal(QMap<Node*, Node*>& created, Node *node, MultiUndoCommand *command)
|
||||
{
|
||||
// Make a new node of the same type
|
||||
Node* copy = node->copy();
|
||||
@@ -1171,17 +1145,26 @@ Node *Node::CopyNodeAndDependencyGraphMinusItemsInternal(QMap<const Node*, Node*
|
||||
NodeInput(copy, input.input(), input.element())));
|
||||
}
|
||||
|
||||
if (node->parent()->GetPositionMap().contains(node)) {
|
||||
// This node is a context, copy the context
|
||||
const NodeGraph::PositionMap &map = node->parent()->GetPositionMap().value(node);
|
||||
for (auto it=map.cbegin(); it!=map.cend(); it++) {
|
||||
// Add either the copy (if it exists) or the original node to the context
|
||||
command->add_child(new NodeSetPositionCommand(created.value(it.key(), it.key()), copy, it.value(), false));
|
||||
}
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
Node *Node::CopyNodeAndDependencyGraphMinusItems(const Node *node, MultiUndoCommand *command)
|
||||
Node *Node::CopyNodeAndDependencyGraphMinusItems(Node *node, MultiUndoCommand *command)
|
||||
{
|
||||
QMap<const Node*, Node*> created;
|
||||
QMap<Node*, Node*> created;
|
||||
|
||||
return CopyNodeAndDependencyGraphMinusItemsInternal(created, node, command);
|
||||
}
|
||||
|
||||
Node *Node::CopyNodeInGraph(const Node *node, MultiUndoCommand *command)
|
||||
Node *Node::CopyNodeInGraph(Node *node, MultiUndoCommand *command)
|
||||
{
|
||||
Node* copy;
|
||||
|
||||
@@ -1194,19 +1177,28 @@ Node *Node::CopyNodeInGraph(const Node *node, MultiUndoCommand *command)
|
||||
copy));
|
||||
|
||||
command->add_child(new NodeCopyInputsCommand(node, copy, true));
|
||||
|
||||
if (node->parent()->GetPositionMap().contains(node)) {
|
||||
// This node is a context, copy the context
|
||||
const NodeGraph::PositionMap &map = node->parent()->GetPositionMap().value(node);
|
||||
for (auto it=map.cbegin(); it!=map.cend(); it++) {
|
||||
// Add to the context
|
||||
command->add_child(new NodeSetPositionCommand(it.key(), copy, it.value(), false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
void Node::SendInvalidateCache(const TimeRange &range, qint64 job_time)
|
||||
void Node::SendInvalidateCache(const TimeRange &range, const InvalidateCacheOptions &options)
|
||||
{
|
||||
if (GetOperationStack() == 0) {
|
||||
for (const OutputConnection& conn : output_connections_) {
|
||||
// Send clear cache signal to the Node
|
||||
const NodeInput& in = conn.second;
|
||||
|
||||
in.node()->InvalidateCache(range, in.input(), in.element(), job_time);
|
||||
in.node()->InvalidateCache(range, in.input(), in.element(), options);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1482,7 +1474,6 @@ void Node::CopyInputs(const Node *source, Node *destination, bool include_connec
|
||||
CopyInput(source, destination, input, include_connections, true);
|
||||
}
|
||||
|
||||
destination->SetPosition(source->GetPosition());
|
||||
destination->SetLabel(source->GetLabel());
|
||||
destination->SetOverrideColor(source->GetOverrideColor());
|
||||
}
|
||||
@@ -1642,15 +1633,28 @@ void Node::GenerateFrame(FramePtr frame, const GenerateJob &job) const
|
||||
Q_UNUSED(job)
|
||||
}
|
||||
|
||||
bool Node::OutputsTo(Node *n, bool recursively) const
|
||||
bool Node::OutputsTo(Node *n, bool recursively, const OutputConnections &ignore_edges, const OutputConnection &added_edge) const
|
||||
{
|
||||
for (const OutputConnection& conn : output_connections_) {
|
||||
if (std::find(ignore_edges.cbegin(), ignore_edges.cend(), conn) != ignore_edges.cend()) {
|
||||
// If this edge is in the "ignore edges" list, skip it
|
||||
continue;
|
||||
}
|
||||
|
||||
Node* connected = conn.second.node();
|
||||
|
||||
if (connected == n) {
|
||||
return true;
|
||||
} else if (recursively && connected->OutputsTo(n, recursively)) {
|
||||
} else if (recursively && connected->OutputsTo(n, recursively, ignore_edges, added_edge)) {
|
||||
return true;
|
||||
} else if (added_edge.first.node() == this) {
|
||||
Node *proposed_connected = added_edge.second.node();
|
||||
|
||||
if (proposed_connected == n) {
|
||||
return true;
|
||||
} else if (recursively && proposed_connected->OutputsTo(n, recursively, ignore_edges, added_edge)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1717,7 +1721,7 @@ bool Node::InputsFrom(const QString &id, bool recursively) const
|
||||
return false;
|
||||
}
|
||||
|
||||
int Node::GetRoutesTo(Node *n) const
|
||||
int Node::GetNumberOfRoutesTo(Node *n) const
|
||||
{
|
||||
bool outputs_directly = false;
|
||||
int routes = 0;
|
||||
@@ -1728,7 +1732,7 @@ int Node::GetRoutesTo(Node *n) const
|
||||
if (connected_node == n) {
|
||||
outputs_directly = true;
|
||||
} else {
|
||||
routes += connected_node->GetRoutesTo(n);
|
||||
routes += connected_node->GetNumberOfRoutesTo(n);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1831,33 +1835,8 @@ QVariant Node::PtrToValue(void *ptr)
|
||||
return reinterpret_cast<quintptr>(ptr);
|
||||
}
|
||||
|
||||
const QPointF &Node::GetPosition() const
|
||||
{
|
||||
return position_;
|
||||
}
|
||||
|
||||
void Node::SetPosition(const QPointF &pos, bool move_dependencies_relatively_too)
|
||||
{
|
||||
QPointF old_pos = position_;
|
||||
|
||||
position_ = pos;
|
||||
|
||||
emit PositionChanged(position_);
|
||||
|
||||
if (move_dependencies_relatively_too) {
|
||||
QPointF difference = pos - old_pos;
|
||||
|
||||
for (auto it=input_connections_.cbegin(); it!=input_connections_.cend(); it++) {
|
||||
Node* c = it->second.node();
|
||||
c->SetPosition(c->GetPosition() + difference, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Node::ParameterValueChanged(const QString& input, int element, const TimeRange& range)
|
||||
{
|
||||
UpdateLastChangedTime();
|
||||
|
||||
InputValueChangedEvent(input, element);
|
||||
|
||||
emit ValueChanged(NodeInput(this, input, element), range);
|
||||
@@ -2049,11 +2028,6 @@ void Node::SaveImmediate(QXmlStreamWriter *writer, const QString& input, int ele
|
||||
}
|
||||
}
|
||||
|
||||
void Node::UpdateLastChangedTime()
|
||||
{
|
||||
last_change_time_ = QDateTime::currentMSecsSinceEpoch();
|
||||
}
|
||||
|
||||
TimeRange Node::GetRangeAffectedByKeyframe(NodeKeyframe *key) const
|
||||
{
|
||||
const NodeKeyframeTrack& key_track = GetTrackFromKeyframe(key);
|
||||
@@ -2302,36 +2276,144 @@ void NodeSetPositionAndShiftSurroundingsCommand::redo()
|
||||
{
|
||||
if (commands_.isEmpty()) {
|
||||
// Move first node
|
||||
NodeSetPositionCommand* set_pos_command = new NodeSetPositionCommand(node_, position_, move_dependencies_);
|
||||
set_pos_command->redo();
|
||||
NodeSetPositionCommand* set_pos_command = new NodeSetPositionCommand(node_, relative_, position_, move_dependencies_);
|
||||
set_pos_command->redo_now();
|
||||
commands_.append(set_pos_command);
|
||||
|
||||
// Get bounding rect
|
||||
QRectF bounding_rect(position_.x() - 0.5, position_.y() - 0.5, 1, 1);
|
||||
qreal bounding_rect_sz = 1.0;
|
||||
qreal bounding_rect_half_sz = bounding_rect_sz * 0.5;
|
||||
QRectF bounding_rect(position_.x() - bounding_rect_half_sz, position_.y() - bounding_rect_half_sz, bounding_rect_sz, bounding_rect_sz);
|
||||
|
||||
// Start moving other nodes
|
||||
foreach (Node* surrounding, node_->parent()->nodes()) {
|
||||
if (bounding_rect.contains(surrounding->GetPosition()) && surrounding != node_) {
|
||||
QPointF new_pos = surrounding->GetPosition();
|
||||
if (surrounding != node_) {
|
||||
QPointF surrounding_position = node_->parent()->GetNodePosition(surrounding, relative_);
|
||||
if (bounding_rect.contains(surrounding_position)) {
|
||||
QPointF new_pos = surrounding_position;
|
||||
|
||||
qreal move_rate = 0.50;
|
||||
qreal move_rate = 0.50;
|
||||
|
||||
if (surrounding->GetPosition().y() < position_.y()) {
|
||||
move_rate = -move_rate;
|
||||
if (surrounding_position.y() < position_.y()) {
|
||||
move_rate = -move_rate;
|
||||
}
|
||||
|
||||
new_pos.setY(new_pos.y() + move_rate);
|
||||
|
||||
auto sur_command = new NodeSetPositionAndShiftSurroundingsCommand(surrounding, relative_, new_pos, true);
|
||||
sur_command->redo();
|
||||
commands_.append(sur_command);
|
||||
}
|
||||
|
||||
new_pos.setY(new_pos.y() + move_rate);
|
||||
|
||||
auto sur_command = new NodeSetPositionAndShiftSurroundingsCommand(surrounding, new_pos, true);
|
||||
sur_command->redo();
|
||||
commands_.append(sur_command);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i=0; i<commands_.size(); i++) {
|
||||
commands_.at(i)->redo();
|
||||
commands_.at(i)->redo_now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeSetPositionCommand::redo()
|
||||
{
|
||||
graph_ = node_->parent();
|
||||
if (!(added_ = !graph_->NodeMapContainsNode(node_, relevant_))) {
|
||||
old_pos_ = graph_->GetNodePosition(node_, relevant_);
|
||||
}
|
||||
graph_->SetNodePosition(node_, relevant_, pos_);
|
||||
}
|
||||
|
||||
void NodeSetPositionCommand::undo()
|
||||
{
|
||||
if (added_) {
|
||||
graph_->RemoveNodePosition(node_, relevant_);
|
||||
} else {
|
||||
graph_->SetNodePosition(node_, relevant_, old_pos_);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeSetPositionAsChildCommand::redo()
|
||||
{
|
||||
if (!sub_command_) {
|
||||
// Calculate position of node
|
||||
NodeGraph *graph = parent_->parent();
|
||||
QPointF pos = graph->GetNodePosition(parent_, relative_);
|
||||
|
||||
// This is a dependency, so we'll place it one X before
|
||||
pos.setX(pos.x() - 1);
|
||||
|
||||
// The Y will be calculated using the index and child count
|
||||
pos.setY(pos.y() - (double(child_count_)*0.5) + this_index_ + 0.5);
|
||||
|
||||
sub_command_ = new MultiUndoCommand();
|
||||
if (shift_surroundings_) {
|
||||
sub_command_->add_child(new NodeSetPositionAndShiftSurroundingsCommand(node_, relative_, pos, true));
|
||||
} else {
|
||||
sub_command_->add_child(new NodeSetPositionCommand(node_, relative_, pos, true));
|
||||
}
|
||||
}
|
||||
|
||||
sub_command_->redo();
|
||||
}
|
||||
|
||||
void NodeSetPositionToOffsetOfAnotherNodeCommand::redo()
|
||||
{
|
||||
NodeGraph *graph = node_->parent();
|
||||
old_pos_ = graph->GetNodePosition(node_, relative_);
|
||||
graph->SetNodePosition(node_, relative_, graph->GetNodePosition(other_node_, relative_) + offset_);
|
||||
}
|
||||
|
||||
void NodeSetPositionToOffsetOfAnotherNodeCommand::undo()
|
||||
{
|
||||
NodeGraph *graph = node_->parent();
|
||||
graph->SetNodePosition(node_, relative_, old_pos_);
|
||||
}
|
||||
|
||||
void NodeRemovePositionFromContextCommand::redo()
|
||||
{
|
||||
NodeGraph *graph = node_->parent();
|
||||
|
||||
contained_ = graph->ContextContainsNode(node_, context_);
|
||||
|
||||
if (contained_) {
|
||||
old_pos_ = graph->GetNodePosition(node_, context_);
|
||||
graph->RemoveNodePosition(node_, context_);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeRemovePositionFromContextCommand::undo()
|
||||
{
|
||||
if (contained_) {
|
||||
NodeGraph *graph = node_->parent();
|
||||
graph->SetNodePosition(node_, context_, old_pos_);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeRemovePositionFromAllContextsCommand::redo()
|
||||
{
|
||||
NodeGraph *graph = node_->parent();
|
||||
|
||||
if (points_.empty()) {
|
||||
// No points yet, let's see what points we should remove
|
||||
auto map = graph->GetPositionMap();
|
||||
for (auto it=map.cbegin(); it!=map.cend(); it++) {
|
||||
if (it.value().contains(node_)) {
|
||||
points_.insert({it.key(), it.value().value(node_)});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it=points_.cbegin(); it!=points_.cend(); it++) {
|
||||
graph->RemoveNodePosition(node_, it->first);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeRemovePositionFromAllContextsCommand::undo()
|
||||
{
|
||||
NodeGraph *graph = node_->parent();
|
||||
|
||||
for (auto it=points_.crbegin(); it!=points_.crend(); it++) {
|
||||
graph->SetNodePosition(node_, it->first, it->second);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+125
-81
@@ -23,6 +23,7 @@
|
||||
|
||||
#include <map>
|
||||
#include <QCryptographicHash>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QPainter>
|
||||
#include <QPointF>
|
||||
@@ -557,7 +558,8 @@ public:
|
||||
* Whether to keep traversing down outputs to find this node (TRUE) or stick to immediate outputs
|
||||
* (FALSE).
|
||||
*/
|
||||
bool OutputsTo(Node* n, bool recursively) const;
|
||||
bool OutputsTo(Node* n, bool recursively, const OutputConnections &ignore_edges = OutputConnections(), const OutputConnection &added_edge = OutputConnection()) const;
|
||||
|
||||
/**
|
||||
* @brief Same as OutputsTo(Node*), but for a node ID rather than a specific instance.
|
||||
*/
|
||||
@@ -581,7 +583,7 @@ public:
|
||||
/**
|
||||
* @brief Determines how many paths go from this node out to another node
|
||||
*/
|
||||
int GetRoutesTo(Node* n) const;
|
||||
int GetNumberOfRoutesTo(Node* n) const;
|
||||
|
||||
/**
|
||||
* @brief Severs all input and output connections
|
||||
@@ -621,6 +623,8 @@ public:
|
||||
*/
|
||||
static T* ValueToPtr(const QVariant& ptr);
|
||||
|
||||
using InvalidateCacheOptions = QHash<QString, QVariant>;
|
||||
|
||||
/**
|
||||
* @brief Signal all dependent Nodes that anything cached between start_range and end_range is now invalid and
|
||||
* requires re-rendering
|
||||
@@ -630,16 +634,11 @@ public:
|
||||
* the DAG. Even if the time needs to be transformed somehow (e.g. converting media time to sequence time), you can
|
||||
* call this function with transformed time and relay the signal that way.
|
||||
*/
|
||||
virtual void InvalidateCache(const TimeRange& range, const QString& from, int element, qint64 job_time);
|
||||
virtual void InvalidateCache(const TimeRange& range, const QString& from, int element = -1, InvalidateCacheOptions options = InvalidateCacheOptions());
|
||||
|
||||
void InvalidateCache(const TimeRange& range, const QString& from, int element = -1)
|
||||
void InvalidateCache(const TimeRange& range, const NodeInput& from, const InvalidateCacheOptions &options = InvalidateCacheOptions())
|
||||
{
|
||||
InvalidateCache(range, from, element, last_change_time_);
|
||||
}
|
||||
|
||||
void InvalidateCache(const TimeRange& range, const NodeInput& from)
|
||||
{
|
||||
InvalidateCache(range, from.input(), from.element());
|
||||
InvalidateCache(range, from.input(), from.element(), options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -689,9 +688,9 @@ public:
|
||||
static QVector<Node*> CopyDependencyGraph(const QVector<Node*>& nodes, MultiUndoCommand *command);
|
||||
static void CopyDependencyGraph(const QVector<Node*>& src, const QVector<Node*>& dst, MultiUndoCommand *command);
|
||||
|
||||
static Node* CopyNodeAndDependencyGraphMinusItems(const Node* node, MultiUndoCommand* command);
|
||||
static Node* CopyNodeAndDependencyGraphMinusItems(Node* node, MultiUndoCommand* command);
|
||||
|
||||
static Node* CopyNodeInGraph(const Node* node, MultiUndoCommand* command);
|
||||
static Node* CopyNodeInGraph(Node *node, MultiUndoCommand* command);
|
||||
|
||||
/**
|
||||
* @brief Return whether this Node can be deleted or not
|
||||
@@ -718,10 +717,6 @@ public:
|
||||
*/
|
||||
virtual NodeValueTable Value(const QString &output, NodeValueDatabase& value) const;
|
||||
|
||||
const QPointF& GetPosition() const;
|
||||
|
||||
void SetPosition(const QPointF& pos, bool move_dependencies_relatively_too = false);
|
||||
|
||||
virtual bool HasGizmos() const;
|
||||
|
||||
virtual void DrawGizmos(NodeValueDatabase& db, QPainter* p);
|
||||
@@ -889,7 +884,7 @@ protected:
|
||||
SetInputProperty(id, QStringLiteral("combo_str"), strings);
|
||||
}
|
||||
|
||||
void SendInvalidateCache(const TimeRange &range, qint64 job_time);
|
||||
void SendInvalidateCache(const TimeRange &range, const InvalidateCacheOptions &options);
|
||||
|
||||
/**
|
||||
* @brief Don't send cache invalidation signals if `input` is connected or disconnected
|
||||
@@ -1014,6 +1009,7 @@ private:
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override
|
||||
{
|
||||
node_->InputArrayInsert(input_, index_, false);
|
||||
@@ -1040,6 +1036,9 @@ private:
|
||||
size_(size)
|
||||
{}
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override
|
||||
{
|
||||
old_size_ = node_->InputArraySize(input_);
|
||||
@@ -1072,8 +1071,6 @@ private:
|
||||
node_->ArrayResizeInternal(input_, old_size_);
|
||||
}
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
private:
|
||||
Node* node_;
|
||||
QString input_;
|
||||
@@ -1130,7 +1127,7 @@ private:
|
||||
|
||||
void ArrayResizeInternal(const QString& id, int size);
|
||||
|
||||
static Node *CopyNodeAndDependencyGraphMinusItemsInternal(QMap<const Node*, Node*>& created, const Node *node, MultiUndoCommand *command);
|
||||
static Node *CopyNodeAndDependencyGraphMinusItemsInternal(QMap<Node *, Node *> &created, Node *node, MultiUndoCommand *command);
|
||||
|
||||
/**
|
||||
* @brief Immediates aren't deleted, so the actual array size may be larger than ArraySize()
|
||||
@@ -1157,8 +1154,6 @@ private:
|
||||
|
||||
void SaveImmediate(QXmlStreamWriter *writer, const QString &input, int element) const;
|
||||
|
||||
void UpdateLastChangedTime();
|
||||
|
||||
/**
|
||||
* @brief Intelligently determine how what time range is affected by a keyframe
|
||||
*/
|
||||
@@ -1180,11 +1175,6 @@ private:
|
||||
*/
|
||||
bool can_be_deleted_;
|
||||
|
||||
/**
|
||||
* @brief UI position for NodeViews
|
||||
*/
|
||||
QPointF position_;
|
||||
|
||||
/**
|
||||
* @brief Custom user label for node
|
||||
*/
|
||||
@@ -1213,8 +1203,6 @@ private:
|
||||
|
||||
OutputConnections output_connections_;
|
||||
|
||||
qint64 last_change_time_;
|
||||
|
||||
QString tooltip_;
|
||||
|
||||
Folder* folder_;
|
||||
@@ -1312,44 +1300,41 @@ using NodePtr = std::shared_ptr<Node>;
|
||||
class NodeSetPositionCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
NodeSetPositionCommand(Node* node, const QPointF& position, bool move_dependencies_relatively) :
|
||||
node_(node),
|
||||
new_pos_(position),
|
||||
move_deps_(move_dependencies_relatively)
|
||||
NodeSetPositionCommand(Node* node, Node* relevant, const QPointF& pos, bool move_dependencies_relatively)
|
||||
{
|
||||
node_ = node;
|
||||
relevant_ = relevant;
|
||||
pos_ = pos;
|
||||
move_deps_ = move_dependencies_relatively;
|
||||
}
|
||||
|
||||
virtual Project * GetRelevantProject() const override
|
||||
virtual Project* GetRelevantProject() const override
|
||||
{
|
||||
return node_->project();
|
||||
}
|
||||
|
||||
virtual void redo() override
|
||||
{
|
||||
old_pos_ = node_->GetPosition();
|
||||
node_->SetPosition(new_pos_, move_deps_);
|
||||
}
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override
|
||||
{
|
||||
node_->SetPosition(old_pos_, move_deps_);
|
||||
}
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
Node* node_;
|
||||
|
||||
QPointF new_pos_;
|
||||
Node* relevant_;
|
||||
QPointF pos_;
|
||||
QPointF old_pos_;
|
||||
|
||||
bool added_;
|
||||
bool move_deps_;
|
||||
NodeGraph *graph_;
|
||||
|
||||
};
|
||||
|
||||
class NodeSetPositionAndShiftSurroundingsCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
NodeSetPositionAndShiftSurroundingsCommand(Node* node, const QPointF& pos, bool move_dependencies_relatively) :
|
||||
NodeSetPositionAndShiftSurroundingsCommand(Node* node, Node *relative, const QPointF& pos, bool move_dependencies_relatively) :
|
||||
node_(node),
|
||||
relative_(relative),
|
||||
position_(pos),
|
||||
move_dependencies_(move_dependencies_relatively)
|
||||
{}
|
||||
@@ -1364,18 +1349,21 @@ public:
|
||||
return node_->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override
|
||||
{
|
||||
for (int i=commands_.size()-1; i>=0; i--) {
|
||||
commands_.at(i)->undo();
|
||||
commands_.at(i)->undo_now();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Node* node_;
|
||||
|
||||
Node *relative_;
|
||||
|
||||
QPointF position_;
|
||||
|
||||
bool move_dependencies_;
|
||||
@@ -1387,9 +1375,10 @@ private:
|
||||
class NodeSetPositionAsChildCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
NodeSetPositionAsChildCommand(Node* node, Node* parent, int this_index, int child_count, bool shift_surroundings) :
|
||||
NodeSetPositionAsChildCommand(Node* node, Node* parent, Node *relative, double this_index, int child_count, bool shift_surroundings) :
|
||||
node_(node),
|
||||
parent_(parent),
|
||||
relative_(relative),
|
||||
this_index_(this_index),
|
||||
child_count_(child_count),
|
||||
shift_surroundings_(shift_surroundings),
|
||||
@@ -1407,27 +1396,8 @@ public:
|
||||
return node_->project();
|
||||
}
|
||||
|
||||
virtual void redo() override
|
||||
{
|
||||
if (!sub_command_) {
|
||||
// Calculate position of node
|
||||
QPointF pos = parent_->GetPosition();
|
||||
|
||||
// This is a dependency, so we'll place it one X before
|
||||
pos.setX(pos.x() - 1);
|
||||
|
||||
// The Y will be calculated using the index and child count
|
||||
pos.setY(pos.y() - (double(child_count_)*0.5) + this_index_ + 0.5);
|
||||
|
||||
if (shift_surroundings_) {
|
||||
sub_command_ = new NodeSetPositionAndShiftSurroundingsCommand(node_, pos, true);
|
||||
} else {
|
||||
sub_command_ = new NodeSetPositionCommand(node_, pos, true);
|
||||
}
|
||||
}
|
||||
|
||||
sub_command_->redo();
|
||||
}
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override
|
||||
{
|
||||
@@ -1437,22 +1407,44 @@ public:
|
||||
private:
|
||||
Node* node_;
|
||||
Node* parent_;
|
||||
Node *relative_;
|
||||
|
||||
int this_index_;
|
||||
double this_index_;
|
||||
int child_count_;
|
||||
|
||||
bool shift_surroundings_;
|
||||
|
||||
UndoCommand* sub_command_;
|
||||
MultiUndoCommand* sub_command_;
|
||||
|
||||
};
|
||||
|
||||
class NodePositionCloseChildGapCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
NodePositionCloseChildGapCommand(Node *parent, void *relative, int remove_index, int child_count, bool shift_surroundings);
|
||||
|
||||
virtual Project * GetRelevantProject() const override
|
||||
{
|
||||
return parent_->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
Node *parent_;
|
||||
|
||||
};
|
||||
|
||||
class NodeSetPositionToOffsetOfAnotherNodeCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
NodeSetPositionToOffsetOfAnotherNodeCommand(Node* node, Node* other_node, const QPointF& offset) :
|
||||
NodeSetPositionToOffsetOfAnotherNodeCommand(Node* node, Node* other_node, Node *relative, const QPointF& offset) :
|
||||
node_(node),
|
||||
other_node_(other_node),
|
||||
relative_(relative),
|
||||
offset_(offset)
|
||||
{}
|
||||
|
||||
@@ -1461,20 +1453,72 @@ public:
|
||||
return node_->project();
|
||||
}
|
||||
|
||||
virtual void redo() override
|
||||
{
|
||||
node_->SetPosition(other_node_->GetPosition() + offset_);
|
||||
}
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override
|
||||
{
|
||||
node_->SetPosition(other_node_->GetPosition() - offset_);
|
||||
}
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
Node* node_;
|
||||
Node* other_node_;
|
||||
Node *relative_;
|
||||
QPointF offset_;
|
||||
QPointF old_pos_;
|
||||
|
||||
};
|
||||
|
||||
class NodeRemovePositionFromContextCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
NodeRemovePositionFromContextCommand(Node *node, Node *context) :
|
||||
node_(node),
|
||||
context_(context)
|
||||
{
|
||||
}
|
||||
|
||||
virtual Project * GetRelevantProject() const override
|
||||
{
|
||||
return node_->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
Node *node_;
|
||||
|
||||
Node *context_;
|
||||
|
||||
QPointF old_pos_;
|
||||
|
||||
bool contained_;
|
||||
|
||||
};
|
||||
|
||||
class NodeRemovePositionFromAllContextsCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
NodeRemovePositionFromAllContextsCommand(Node *node) :
|
||||
node_(node)
|
||||
{
|
||||
}
|
||||
|
||||
virtual Project * GetRelevantProject() const override
|
||||
{
|
||||
return node_->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
Node *node_;
|
||||
|
||||
std::map<Node *, QPointF> points_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -40,9 +40,6 @@ const QString Track::kMutedInput = QStringLiteral("muted_in");
|
||||
|
||||
Track::Track() :
|
||||
track_type_(Track::kNone),
|
||||
track_length_(0),
|
||||
midop_track_length_(0),
|
||||
preop_track_length_(0),
|
||||
index_(-1),
|
||||
locked_(false)
|
||||
{
|
||||
@@ -280,11 +277,8 @@ void Track::InputDisconnectedEvent(const QString &input, int element, const Node
|
||||
// Update lengths
|
||||
if (next) {
|
||||
UpdateInOutFrom(blocks_.indexOf(next));
|
||||
} else if (blocks_.isEmpty()) {
|
||||
SetLengthInternal(0);
|
||||
} else {
|
||||
SetLengthInternal(blocks_.last()->out());
|
||||
}
|
||||
emit TrackLengthChanged();
|
||||
|
||||
disconnect(b, &Block::LengthChanged, this, &Track::BlockLengthChanged);
|
||||
|
||||
@@ -431,7 +425,7 @@ QVector<Block *> Track::BlocksAtTimeRange(const TimeRange &range) const
|
||||
return list;
|
||||
}
|
||||
|
||||
void Track::InvalidateCache(const TimeRange& range, const QString& from, int element, qint64 job_time)
|
||||
void Track::InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options)
|
||||
{
|
||||
if (GetOperationStack() != 0) {
|
||||
return;
|
||||
@@ -443,7 +437,8 @@ void Track::InvalidateCache(const TimeRange& range, const QString& from, int ele
|
||||
|
||||
if (from == kBlockInput
|
||||
&& element >= 0
|
||||
&& (b = dynamic_cast<const Block*>(GetConnectedOutput(from, element).node()))) {
|
||||
&& (b = dynamic_cast<const Block*>(GetConnectedOutput(from, element).node()))
|
||||
&& !options.value(QStringLiteral("lengthevent")).toBool()) {
|
||||
// Limit the range signal to the corresponding block
|
||||
if (range.out() <= b->in() || range.in() >= b->out()) {
|
||||
return;
|
||||
@@ -451,11 +446,14 @@ void Track::InvalidateCache(const TimeRange& range, const QString& from, int ele
|
||||
|
||||
limited = TimeRange(qMax(range.in(), b->in()), qMin(range.out(), b->out()));
|
||||
} else {
|
||||
limited = TimeRange(qMax(range.in(), rational(0)), qMin(range.out(), qMax(preop_track_length_, track_length())));
|
||||
preop_track_length_ = track_length_;
|
||||
limited = range;
|
||||
}
|
||||
|
||||
Node::InvalidateCache(limited, from, element, job_time);
|
||||
// NOTE: For now, I figure we drop this key, but we may find in the future that it's advantageous
|
||||
// to keep it
|
||||
options.remove(QStringLiteral("lengthevent"));
|
||||
|
||||
Node::InvalidateCache(limited, from, element, options);
|
||||
}
|
||||
|
||||
void Track::InsertBlockBefore(Block* block, Block* after)
|
||||
@@ -520,7 +518,7 @@ void Track::AppendBlock(Block *block)
|
||||
EndOperation();
|
||||
|
||||
// Invalidate area that block was added to
|
||||
Node::InvalidateCache(TimeRange(block->in(), track_length()), kBlockInput);
|
||||
Node::InvalidateCache(TimeRange(block->in(), block->out()), kBlockInput);
|
||||
}
|
||||
|
||||
void Track::RippleRemoveBlock(Block *block)
|
||||
@@ -552,13 +550,17 @@ void Track::ReplaceBlock(Block *old, Block *replace)
|
||||
if (old->length() == replace->length()) {
|
||||
Node::InvalidateCache(TimeRange(replace->in(), replace->out()), kBlockInput);
|
||||
} else {
|
||||
Node::InvalidateCache(TimeRange(replace->in(), RATIONAL_MAX), kBlockInput);
|
||||
Node::InvalidateCache(TimeRange(replace->in(), track_length()), kBlockInput);
|
||||
}
|
||||
}
|
||||
|
||||
const rational &Track::track_length() const
|
||||
rational Track::track_length() const
|
||||
{
|
||||
return track_length_;
|
||||
if (blocks_.isEmpty()) {
|
||||
return 0;
|
||||
} else {
|
||||
return blocks_.last()->out();
|
||||
}
|
||||
}
|
||||
|
||||
QString Track::GetDefaultTrackName(Track::Type type, int index)
|
||||
@@ -600,15 +602,6 @@ void Track::Hash(const QString &output, QCryptographicHash &hash, const rational
|
||||
}
|
||||
}
|
||||
|
||||
void Track::EndOperation()
|
||||
{
|
||||
super::EndOperation();
|
||||
|
||||
if (track_length_ != midop_track_length_) {
|
||||
SetLengthInternal(midop_track_length_);
|
||||
}
|
||||
}
|
||||
|
||||
void Track::SetMuted(bool e)
|
||||
{
|
||||
SetStandardValue(kMutedInput, e);
|
||||
@@ -640,7 +633,7 @@ void Track::UpdateInOutFrom(int index)
|
||||
emit BlocksRefreshed();
|
||||
|
||||
// Update track length
|
||||
SetLengthInternal(last_out);
|
||||
emit TrackLengthChanged();
|
||||
}
|
||||
|
||||
int Track::GetArrayIndexFromBlock(Block *block) const
|
||||
@@ -658,48 +651,12 @@ int Track::GetCacheIndexFromArrayIndex(int index) const
|
||||
return block_array_indexes_.indexOf(index);
|
||||
}
|
||||
|
||||
void Track::SetLengthInternal(const rational &r, bool invalidate)
|
||||
{
|
||||
// Hold track length until operation stack is empty
|
||||
midop_track_length_ = r;
|
||||
|
||||
if (GetOperationStack() == 0 && track_length_ != r) {
|
||||
TimeRange invalidate_range(track_length_, r);
|
||||
track_length_ = r;
|
||||
preop_track_length_ = qMax(preop_track_length_, track_length_);
|
||||
emit TrackLengthChanged();
|
||||
|
||||
if (invalidate) {
|
||||
Node::InvalidateCache(invalidate_range, kBlockInput);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Track::BlockLengthChanged()
|
||||
{
|
||||
// Assumes sender is a Block
|
||||
Block* b = static_cast<Block*>(sender());
|
||||
|
||||
rational old_out = b->out();
|
||||
|
||||
UpdateInOutFrom(blocks_.indexOf(b));
|
||||
|
||||
rational new_out = b->out();
|
||||
|
||||
TimeRange invalidate_region(qMin(old_out, new_out), track_length());
|
||||
|
||||
// The cache won't start while dragging, so we store up our invalidations if it's held down
|
||||
// and release them once the mouse is no longer pressed
|
||||
if (qApp->mouseButtons() & Qt::LeftButton) {
|
||||
block_length_pending_invalidations_.insert(invalidate_region);
|
||||
} else if (!block_length_pending_invalidations_.isEmpty()) {
|
||||
foreach (const TimeRange& r, block_length_pending_invalidations_) {
|
||||
Node::InvalidateCache(r, kBlockInput);
|
||||
}
|
||||
block_length_pending_invalidations_.clear();
|
||||
}
|
||||
|
||||
Node::InvalidateCache(invalidate_region, kBlockInput);
|
||||
}
|
||||
|
||||
uint qHash(const Track::Reference &r, uint seed)
|
||||
|
||||
@@ -286,7 +286,7 @@ public:
|
||||
return blocks_;
|
||||
}
|
||||
|
||||
virtual void InvalidateCache(const TimeRange& range, const QString& from, int element, qint64 job_time) override;
|
||||
virtual void InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options) override;
|
||||
|
||||
/**
|
||||
* @brief Adds Block `block` at the very beginning of the Sequence before all other clips
|
||||
@@ -330,7 +330,7 @@ public:
|
||||
*/
|
||||
void ReplaceBlock(Block* old, Block* replace);
|
||||
|
||||
const rational& track_length() const;
|
||||
rational track_length() const;
|
||||
|
||||
static QString GetDefaultTrackName(Track::Type type, int index);
|
||||
|
||||
@@ -345,8 +345,6 @@ public:
|
||||
return waveform_;
|
||||
}
|
||||
|
||||
virtual void EndOperation() override;
|
||||
|
||||
static const double kTrackHeightDefault;
|
||||
static const double kTrackHeightMinimum;
|
||||
static const double kTrackHeightInterval;
|
||||
@@ -420,8 +418,6 @@ private:
|
||||
|
||||
int GetCacheIndexFromArrayIndex(int index) const;
|
||||
|
||||
void SetLengthInternal(const rational& r, bool invalidate = true);
|
||||
|
||||
TimeRangeList block_length_pending_invalidations_;
|
||||
|
||||
QVector<Block*> blocks_;
|
||||
@@ -429,12 +425,6 @@ private:
|
||||
|
||||
Track::Type track_type_;
|
||||
|
||||
rational track_length_;
|
||||
|
||||
rational midop_track_length_;
|
||||
|
||||
rational preop_track_length_;
|
||||
|
||||
double track_height_;
|
||||
|
||||
int index_;
|
||||
|
||||
@@ -222,7 +222,7 @@ void ViewerOutput::ShiftCache(const rational &from, const rational &to)
|
||||
ShiftAudioCache(from, to);
|
||||
}
|
||||
|
||||
void ViewerOutput::InvalidateCache(const TimeRange& range, const QString& from, int element, qint64 job_time)
|
||||
void ViewerOutput::InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options)
|
||||
{
|
||||
Q_UNUSED(element)
|
||||
|
||||
@@ -233,16 +233,16 @@ void ViewerOutput::InvalidateCache(const TimeRange& range, const QString& from,
|
||||
|
||||
if (invalidated_range.in() != invalidated_range.out()) {
|
||||
if (from == kTextureInput || from == kVideoParamsInput) {
|
||||
video_frame_cache_.Invalidate(invalidated_range, job_time);
|
||||
video_frame_cache_.Invalidate(invalidated_range);
|
||||
} else {
|
||||
audio_playback_cache_.Invalidate(invalidated_range, job_time);
|
||||
audio_playback_cache_.Invalidate(invalidated_range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VerifyLength();
|
||||
|
||||
super::InvalidateCache(range, from, element, job_time);
|
||||
super::InvalidateCache(range, from, element, options);
|
||||
}
|
||||
|
||||
QVector<QString> ViewerOutput::inputs_for_output(const QString &output) const
|
||||
@@ -300,14 +300,8 @@ void ViewerOutput::Retranslate()
|
||||
void ViewerOutput::VerifyLength()
|
||||
{
|
||||
video_length_ = VerifyLengthInternal(Track::kVideo);
|
||||
if (video_cache_enabled_) {
|
||||
video_frame_cache_.SetLength(video_length_);
|
||||
}
|
||||
|
||||
audio_length_ = VerifyLengthInternal(Track::kAudio);
|
||||
if (audio_cache_enabled_) {
|
||||
audio_playback_cache_.SetLength(audio_length_);
|
||||
}
|
||||
|
||||
rational subtitle_length = VerifyLengthInternal(Track::kSubtitle);
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ public:
|
||||
void ShiftAudioCache(const rational& from, const rational& to);
|
||||
void ShiftCache(const rational& from, const rational& to);
|
||||
|
||||
virtual void InvalidateCache(const TimeRange& range, const QString& from, int element, qint64 job_time) override;
|
||||
virtual void InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options) override;
|
||||
|
||||
virtual QVector<QString> inputs_for_output(const QString& output) const override;
|
||||
|
||||
@@ -154,6 +154,9 @@ public:
|
||||
|
||||
virtual NodeOutput GetConnectedSampleOutput();
|
||||
|
||||
void SetViewerVideoCacheEnabled(bool e) { video_cache_enabled_ = e; }
|
||||
void SetViewerAudioCacheEnabled(bool e) { audio_cache_enabled_ = e; }
|
||||
|
||||
static const QString kVideoParamsInput;
|
||||
static const QString kAudioParamsInput;
|
||||
|
||||
@@ -202,9 +205,6 @@ protected:
|
||||
|
||||
int AddStream(Track::Type type, const QVariant &value);
|
||||
|
||||
void SetViewerVideoCacheEnabled(bool e) { video_cache_enabled_ = e; }
|
||||
void SetViewerAudioCacheEnabled(bool e) { audio_cache_enabled_ = e; }
|
||||
|
||||
private:
|
||||
rational last_length_;
|
||||
rational video_length_;
|
||||
|
||||
@@ -138,18 +138,17 @@ void FolderAddChild::redo()
|
||||
Node::ConnectEdge(child_, NodeInput(folder_, Folder::kChildInput, array_index));
|
||||
|
||||
if (autoposition_) {
|
||||
old_position_ = child_->GetPosition();
|
||||
if (!position_command_) {
|
||||
position_command_ = new NodeSetPositionAsChildCommand(child_, folder_, array_index, array_index+1, true);
|
||||
position_command_ = new NodeSetPositionAsChildCommand(child_, folder_, folder_->project()->root(), array_index, array_index+1, true);
|
||||
}
|
||||
position_command_->redo();
|
||||
position_command_->redo_now();
|
||||
}
|
||||
}
|
||||
|
||||
void FolderAddChild::undo()
|
||||
{
|
||||
if (position_command_) {
|
||||
position_command_->undo();
|
||||
position_command_->undo_now();
|
||||
}
|
||||
|
||||
Node::DisconnectEdge(child_, NodeInput(folder_, Folder::kChildInput, folder_->InputArraySize(Folder::kChildInput)-1));
|
||||
|
||||
@@ -135,6 +135,7 @@ public:
|
||||
return folder_->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override
|
||||
@@ -209,6 +210,7 @@ public:
|
||||
|
||||
virtual Project * GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override;
|
||||
@@ -220,8 +222,6 @@ private:
|
||||
|
||||
bool autoposition_;
|
||||
|
||||
QPointF old_position_;
|
||||
|
||||
NodeSetPositionAsChildCommand* position_command_;
|
||||
|
||||
};
|
||||
|
||||
@@ -39,26 +39,27 @@ Project::Project() :
|
||||
// Generate UUID for this project
|
||||
RegenerateUuid();
|
||||
|
||||
// Folder root for project
|
||||
root_ = new Folder();
|
||||
root_->setParent(this);
|
||||
root_->SetLabel(tr("Root"));
|
||||
root_->SetCanBeDeleted(false);
|
||||
SetNodePosition(root_, root_, QPointF(0, 0));
|
||||
|
||||
// Adds a color manager "node" to this project so that it synchronizes
|
||||
color_manager_ = new ColorManager();
|
||||
color_manager_->setParent(this);
|
||||
color_manager_->SetPosition(QPointF(1, 0));
|
||||
SetNodePosition(color_manager_, root_, QPointF(1, 0));
|
||||
color_manager_->SetCanBeDeleted(false);
|
||||
AddDefaultNode(color_manager_);
|
||||
|
||||
// Same with project settings
|
||||
settings_ = new ProjectSettingsNode();
|
||||
settings_->setParent(this);
|
||||
settings_->SetPosition(QPointF(2, 0));
|
||||
SetNodePosition(settings_, root_, QPointF(2, 0));
|
||||
settings_->SetCanBeDeleted(false);
|
||||
AddDefaultNode(settings_);
|
||||
|
||||
// Folder root for project
|
||||
root_ = new Folder();
|
||||
root_->setParent(this);
|
||||
root_->SetLabel(tr("Root"));
|
||||
root_->SetCanBeDeleted(false);
|
||||
|
||||
connect(color_manager(), &ColorManager::ValueChanged,
|
||||
this, &Project::ColorManagerValueChanged);
|
||||
}
|
||||
@@ -135,6 +136,71 @@ void Project::Load(QXmlStreamReader *reader, MainWindowLayoutInfo* layout, uint
|
||||
}
|
||||
}
|
||||
|
||||
} else if (reader->name() == QStringLiteral("positions")) {
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
|
||||
if (reader->name() == QStringLiteral("context")) {
|
||||
|
||||
quintptr context_ptr = 0;
|
||||
XMLAttributeLoop(reader, attr) {
|
||||
if (attr.name() == QStringLiteral("ptr")) {
|
||||
context_ptr = attr.value().toULongLong();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Node *context = xml_node_data.node_ptrs.value(context_ptr);
|
||||
|
||||
if (!context) {
|
||||
qWarning() << "Failed to find pointer for context";
|
||||
reader->skipCurrentElement();
|
||||
} else {
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("node")) {
|
||||
quintptr node_ptr = 0;
|
||||
XMLAttributeLoop(reader, attr) {
|
||||
if (attr.name() == QStringLiteral("ptr")) {
|
||||
node_ptr = attr.value().toULongLong();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Node *node = xml_node_data.node_ptrs.value(node_ptr);
|
||||
|
||||
if (!node) {
|
||||
qWarning() << "Failed to find pointer for node position";
|
||||
reader->skipCurrentElement();
|
||||
} else {
|
||||
QPointF pos;
|
||||
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("x")) {
|
||||
pos.setX(reader->readElementText().toDouble());
|
||||
} else if (reader->name() == QStringLiteral("y")) {
|
||||
pos.setY(reader->readElementText().toDouble());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
SetNodePosition(node, context, pos);
|
||||
}
|
||||
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
reader->skipCurrentElement();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Skip this
|
||||
@@ -176,6 +242,32 @@ void Project::Save(QXmlStreamWriter *writer) const
|
||||
|
||||
writer->writeEndElement(); // nodes
|
||||
|
||||
writer->writeStartElement(QStringLiteral("positions"));
|
||||
|
||||
for (auto it=GetPositionMap().cbegin(); it!=GetPositionMap().cend(); it++) {
|
||||
writer->writeStartElement(QStringLiteral("context"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(it.key())));
|
||||
|
||||
const PositionMap &map = it.value();
|
||||
|
||||
for (auto jt=map.cbegin(); jt!=map.cend(); jt++) {
|
||||
writer->writeStartElement(QStringLiteral("node"));
|
||||
|
||||
writer->writeAttribute(QStringLiteral("ptr"), QString::number(reinterpret_cast<quintptr>(jt.key())));
|
||||
|
||||
const QPointF &pos = jt.value();
|
||||
writer->writeTextElement(QStringLiteral("x"), QString::number(pos.x()));
|
||||
writer->writeTextElement(QStringLiteral("y"), QString::number(pos.y()));
|
||||
|
||||
writer->writeEndElement(); // node
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // context
|
||||
}
|
||||
|
||||
writer->writeEndElement(); // positions
|
||||
|
||||
// Save main window project layout
|
||||
MainWindowLayoutInfo main_window_info = Core::instance()->main_window()->SaveLayout();
|
||||
main_window_info.toXml(writer);
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include "panel/timeline/timeline.h"
|
||||
#include "ui/icons/icons.h"
|
||||
#include "widget/timelinewidget/undo/timelineundogeneral.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -63,8 +64,8 @@ void Sequence::add_default_nodes(MultiUndoCommand* command)
|
||||
command->add_child(video_track_command);
|
||||
command->add_child(audio_track_command);
|
||||
} else {
|
||||
video_track_command->redo();
|
||||
audio_track_command->redo();
|
||||
video_track_command->redo_now();
|
||||
audio_track_command->redo_now();
|
||||
delete video_track_command;
|
||||
delete audio_track_command;
|
||||
}
|
||||
|
||||
+20
-1
@@ -20,20 +20,39 @@
|
||||
|
||||
#include "node.h"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace olive {
|
||||
|
||||
NodePanel::NodePanel(QWidget *parent) :
|
||||
PanelWidget(QStringLiteral("NodePanel"), parent)
|
||||
{
|
||||
QWidget *outer_widget = new QWidget(this);
|
||||
|
||||
QVBoxLayout *outer_layout = new QVBoxLayout(outer_widget);
|
||||
outer_layout->setMargin(0);
|
||||
|
||||
NodeViewToolBar *toolbar = new NodeViewToolBar();
|
||||
outer_layout->addWidget(toolbar);
|
||||
|
||||
// Create NodeView widget
|
||||
node_view_ = new NodeView(this);
|
||||
outer_layout->addWidget(node_view_);
|
||||
|
||||
// Connect toolbar to NodeView
|
||||
connect(toolbar, &NodeViewToolBar::MiniMapEnabledToggled, node_view_, &NodeView::SetMiniMapEnabled);
|
||||
connect(toolbar, &NodeViewToolBar::AddNodeClicked, node_view_, &NodeView::ShowAddMenu);
|
||||
|
||||
// Set defaults
|
||||
toolbar->SetMiniMapEnabled(true);
|
||||
node_view_->SetMiniMapEnabled(true);
|
||||
|
||||
// Connect node view signals to this panel
|
||||
connect(node_view_, &NodeView::NodesSelected, this, &NodePanel::NodesSelected);
|
||||
connect(node_view_, &NodeView::NodesDeselected, this, &NodePanel::NodesDeselected);
|
||||
|
||||
// Set it as the main widget of this panel
|
||||
SetWidgetWithPadding(node_view_);
|
||||
SetWidgetWithPadding(outer_widget);
|
||||
|
||||
// Set strings
|
||||
Retranslate();
|
||||
|
||||
+12
-20
@@ -22,6 +22,7 @@
|
||||
#define NODEPANEL_H
|
||||
|
||||
#include "widget/nodeview/nodeview.h"
|
||||
#include "widget/nodeview/nodeviewtoolbar.h"
|
||||
#include "widget/panel/panel.h"
|
||||
|
||||
namespace olive {
|
||||
@@ -40,9 +41,14 @@ public:
|
||||
return node_view_->GetGraph();
|
||||
}
|
||||
|
||||
void SetGraph(NodeGraph *graph)
|
||||
void SetGraph(NodeGraph *graph, const QVector<Node*> &nodes)
|
||||
{
|
||||
node_view_->SetGraph(graph);
|
||||
node_view_->SetGraph(graph, nodes);
|
||||
}
|
||||
|
||||
void ClearGraph()
|
||||
{
|
||||
node_view_->ClearGraph();
|
||||
}
|
||||
|
||||
virtual void SelectAll() override
|
||||
@@ -96,28 +102,14 @@ public:
|
||||
}
|
||||
|
||||
public slots:
|
||||
void Select(const QVector<Node*>& nodes)
|
||||
void Select(const QVector<Node*>& nodes, bool center_view_on_item)
|
||||
{
|
||||
node_view_->Select(nodes);
|
||||
node_view_->Select(nodes, center_view_on_item);
|
||||
}
|
||||
|
||||
void SelectWithDependencies(const QVector<Node*>& nodes)
|
||||
void SelectWithDependencies(const QVector<Node*>& nodes, bool center_view_on_item)
|
||||
{
|
||||
node_view_->SelectWithDependencies(nodes);
|
||||
}
|
||||
|
||||
void SelectBlocks(const QVector<Block*>& blocks)
|
||||
{
|
||||
QVector<Node*> nodes(blocks.size());
|
||||
memcpy(nodes.data(), blocks.constData(), blocks.size() * sizeof(Block*));
|
||||
node_view_->SelectWithDependencies(nodes);
|
||||
}
|
||||
|
||||
void DeselectBlocks(const QVector<Block*>& nodes)
|
||||
{
|
||||
Q_UNUSED(nodes)
|
||||
qDebug() << "Stub";
|
||||
//node_view_->DeselectBlocks(nodes);
|
||||
node_view_->SelectWithDependencies(nodes, center_view_on_item);
|
||||
}
|
||||
|
||||
signals:
|
||||
|
||||
@@ -28,8 +28,7 @@ PanelManager* PanelManager::instance_ = nullptr;
|
||||
|
||||
PanelManager::PanelManager(QObject *parent) :
|
||||
QObject(parent),
|
||||
locked_(false),
|
||||
last_focused_panel_(nullptr)
|
||||
locked_(false)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -46,11 +45,11 @@ const QList<PanelWidget *> &PanelManager::panels()
|
||||
return focus_history_;
|
||||
}
|
||||
|
||||
PanelWidget *PanelManager::CurrentlyFocused() const
|
||||
PanelWidget *PanelManager::CurrentlyFocused(bool enable_hover) const
|
||||
{
|
||||
// If hover focus is enabled, find the currently hovered panel and return it (if no panel is hovered, resort to
|
||||
// default behavior)
|
||||
if (Config::Current()["HoverFocus"].toBool()) {
|
||||
if (enable_hover && Config::Current()[QStringLiteral("HoverFocus")].toBool()) {
|
||||
PanelWidget* hovered = CurrentlyHovered();
|
||||
|
||||
if (hovered != nullptr) {
|
||||
@@ -111,7 +110,7 @@ void PanelManager::FocusChanged(QWidget *old, QWidget *now)
|
||||
|
||||
if (panel_cast_test) {
|
||||
|
||||
if (last_focused_panel_ != panel_cast_test) {
|
||||
if (focus_history_.first() != panel_cast_test) {
|
||||
// If so, bump this to the top of the focus history
|
||||
int panel_index = focus_history_.indexOf(panel_cast_test);
|
||||
|
||||
@@ -130,7 +129,6 @@ void PanelManager::FocusChanged(QWidget *old, QWidget *now)
|
||||
focus_history_.move(panel_index, 0);
|
||||
}
|
||||
|
||||
last_focused_panel_ = panel_cast_test;
|
||||
emit FocusedPanelChanged(panel_cast_test);
|
||||
}
|
||||
|
||||
@@ -158,10 +156,6 @@ void PanelManager::PanelDestroyed()
|
||||
PanelWidget* panel = static_cast<PanelWidget*>(sender());
|
||||
|
||||
focus_history_.removeOne(panel);
|
||||
|
||||
if (last_focused_panel_ == panel) {
|
||||
last_focused_panel_ = focus_history_.isEmpty() ? nullptr : focus_history_.first();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -65,14 +65,12 @@ public:
|
||||
/**
|
||||
* @brief Return the currently focused widget, or nullptr if nothing is focused
|
||||
*
|
||||
* This result == CurrentlyFocused() if HoverFocus is true
|
||||
* This result == CurrentlyFocused() if HoverFocus is true and panel is hovered
|
||||
*/
|
||||
PanelWidget* CurrentlyFocused() const;
|
||||
PanelWidget* CurrentlyFocused(bool enable_hover = true) const;
|
||||
|
||||
/**
|
||||
* @brief Return the widget that the mouse is currently hovering over, or nullptr if nothing is hovered over
|
||||
*
|
||||
* This result == CurrentlyFocused() if HoverFocus is true
|
||||
*/
|
||||
PanelWidget* CurrentlyHovered() const;
|
||||
|
||||
@@ -155,13 +153,6 @@ private:
|
||||
*/
|
||||
static PanelManager* instance_;
|
||||
|
||||
/**
|
||||
* @brief The last panel that was focused
|
||||
*
|
||||
* Stored to prevent emitting FocusedPanelChanged() multiple times for the same panel
|
||||
*/
|
||||
PanelWidget* last_focused_panel_;
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* @brief Processing if a panel gets deleted
|
||||
@@ -195,6 +186,12 @@ T *PanelManager::CreatePanel(QWidget *parent)
|
||||
// Connect destroy signal so we can remove it from focus history
|
||||
connect(panel, &PanelWidget::destroyed, this, &PanelManager::PanelDestroyed, Qt::DirectConnection);
|
||||
|
||||
if (focus_history_.size() == 1) {
|
||||
// This is the first panel, focus it
|
||||
panel->SetBorderVisible(true);
|
||||
emit FocusedPanelChanged(panel);
|
||||
}
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ ProjectPanel::ProjectPanel(QWidget *parent) :
|
||||
explorer_ = new ProjectExplorer(this);
|
||||
layout->addWidget(explorer_);
|
||||
connect(explorer_, &ProjectExplorer::DoubleClickedItem, this, &ProjectPanel::ItemDoubleClickSlot);
|
||||
connect(explorer_, &ProjectExplorer::SelectionChanged, this, &ProjectPanel::SelectionChanged);
|
||||
|
||||
// Set toolbar's view to the explorer's view
|
||||
toolbar->SetView(explorer_->view_type());
|
||||
|
||||
@@ -66,6 +66,8 @@ public slots:
|
||||
signals:
|
||||
void ProjectNameChanged();
|
||||
|
||||
void SelectionChanged(const QVector<Node *> &selected);
|
||||
|
||||
private:
|
||||
virtual void Retranslate() override;
|
||||
|
||||
|
||||
@@ -33,13 +33,7 @@ TimelinePanel::TimelinePanel(QWidget *parent) :
|
||||
|
||||
Retranslate();
|
||||
|
||||
connect(tw, &TimelineWidget::BlocksSelected, this, &TimelinePanel::BlocksSelected);
|
||||
connect(tw, &TimelineWidget::BlocksDeselected, this, &TimelinePanel::BlocksDeselected);
|
||||
}
|
||||
|
||||
void TimelinePanel::Clear()
|
||||
{
|
||||
static_cast<TimelineWidget*>(GetTimeBasedWidget())->Clear();
|
||||
connect(tw, &TimelineWidget::BlockSelectionChanged, this, &TimelinePanel::BlockSelectionChanged);
|
||||
}
|
||||
|
||||
void TimelinePanel::SplitAtPlayhead()
|
||||
|
||||
@@ -35,8 +35,6 @@ class TimelinePanel : public TimeBasedPanel
|
||||
public:
|
||||
TimelinePanel(QWidget* parent);
|
||||
|
||||
void Clear();
|
||||
|
||||
void SplitAtPlayhead();
|
||||
|
||||
QByteArray SaveSplitterState() const;
|
||||
@@ -89,13 +87,16 @@ public:
|
||||
|
||||
void OverwriteFootageAtPlayhead(const QVector<ViewerOutput *> &footage);
|
||||
|
||||
const QVector<Block*>& GetSelectedBlocks() const
|
||||
{
|
||||
return static_cast<TimelineWidget*>(GetTimeBasedWidget())->GetSelectedBlocks();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void Retranslate() override;
|
||||
|
||||
signals:
|
||||
void BlocksSelected(const QVector<Block*>& selected_blocks);
|
||||
|
||||
void BlocksDeselected(const QVector<Block*>& deselected_blocks);
|
||||
void BlockSelectionChanged(const QVector<Block*>& selected_blocks);
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -46,6 +46,8 @@ set(OLIVE_SOURCES
|
||||
render/rendercache.h
|
||||
render/rendererthreadwrapper.cpp
|
||||
render/rendererthreadwrapper.h
|
||||
render/renderjobtracker.cpp
|
||||
render/renderjobtracker.h
|
||||
render/rendermanager.cpp
|
||||
render/rendermanager.h
|
||||
render/rendermodes.h
|
||||
|
||||
@@ -54,13 +54,6 @@ const QVector<uint64_t> AudioParams::kSupportedChannelLayouts = {
|
||||
|
||||
const AudioParams::Format AudioParams::kInternalFormat = AudioParams::kFormatFloat32;
|
||||
|
||||
qint64 AudioParams::time_to_bytes(const double &time) const
|
||||
{
|
||||
Q_ASSERT(is_valid());
|
||||
|
||||
return qint64(time_to_samples(time)) * channel_count() * bytes_per_sample_per_channel();
|
||||
}
|
||||
|
||||
bool AudioParams::operator==(const AudioParams &other) const
|
||||
{
|
||||
return (format() == other.format()
|
||||
@@ -94,11 +87,28 @@ QAudioFormat::SampleType AudioParams::GetQtSampleType(AudioParams::Format format
|
||||
return QAudioFormat::Unknown;
|
||||
}
|
||||
|
||||
qint64 AudioParams::time_to_bytes(const double &time) const
|
||||
{
|
||||
return time_to_bytes_per_channel(time) * channel_count();
|
||||
}
|
||||
|
||||
qint64 AudioParams::time_to_bytes(const rational &time) const
|
||||
{
|
||||
return time_to_bytes(time.toDouble());
|
||||
}
|
||||
|
||||
qint64 AudioParams::time_to_bytes_per_channel(const double &time) const
|
||||
{
|
||||
Q_ASSERT(is_valid());
|
||||
|
||||
return qint64(time_to_samples(time)) * bytes_per_sample_per_channel();
|
||||
}
|
||||
|
||||
qint64 AudioParams::time_to_bytes_per_channel(const rational &time) const
|
||||
{
|
||||
return time_to_bytes_per_channel(time.toDouble());
|
||||
}
|
||||
|
||||
qint64 AudioParams::time_to_samples(const double &time) const
|
||||
{
|
||||
Q_ASSERT(is_valid());
|
||||
@@ -139,6 +149,13 @@ rational AudioParams::bytes_to_time(const qint64 &bytes) const
|
||||
return samples_to_time(bytes_to_samples(bytes));
|
||||
}
|
||||
|
||||
rational AudioParams::bytes_per_channel_to_time(const qint64 &bytes) const
|
||||
{
|
||||
Q_ASSERT(is_valid());
|
||||
|
||||
return samples_to_time(bytes_to_samples(bytes * channel_count()));
|
||||
}
|
||||
|
||||
int AudioParams::channel_count() const
|
||||
{
|
||||
return channel_count_;
|
||||
|
||||
@@ -161,12 +161,15 @@ public:
|
||||
|
||||
qint64 time_to_bytes(const double& time) const;
|
||||
qint64 time_to_bytes(const rational& time) const;
|
||||
qint64 time_to_bytes_per_channel(const double& time) const;
|
||||
qint64 time_to_bytes_per_channel(const rational& time) const;
|
||||
qint64 time_to_samples(const double& time) const;
|
||||
qint64 time_to_samples(const rational& time) const;
|
||||
qint64 samples_to_bytes(const qint64& samples) const;
|
||||
rational samples_to_time(const qint64& samples) const;
|
||||
qint64 bytes_to_samples(const qint64 &bytes) const;
|
||||
rational bytes_to_time(const qint64 &bytes) const;
|
||||
rational bytes_per_channel_to_time(const qint64 &bytes) const;
|
||||
int channel_count() const;
|
||||
int bytes_per_sample_per_channel() const;
|
||||
int bits_per_sample() const;
|
||||
|
||||
+158
-146
@@ -22,13 +22,14 @@
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QRandomGenerator>
|
||||
#include <QUuid>
|
||||
|
||||
#include "common/filefunctions.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
const qint64 AudioPlaybackCache::kDefaultSegmentSize = 5242880;
|
||||
const qint64 AudioPlaybackCache::kDefaultSegmentSizePerChannel = 10 * 1024 * 1024;
|
||||
|
||||
AudioPlaybackCache::AudioPlaybackCache(QObject* parent) :
|
||||
PlaybackCache(parent)
|
||||
@@ -56,79 +57,81 @@ void AudioPlaybackCache::SetParameters(const AudioParams ¶ms)
|
||||
emit ParametersChanged();
|
||||
}
|
||||
|
||||
void AudioPlaybackCache::WritePCM(const TimeRange &range, SampleBufferPtr samples, const AudioVisualWaveform *waveform, const qint64 &job_time)
|
||||
void AudioPlaybackCache::WritePCM(const TimeRange &range, const TimeRangeList &valid_ranges, SampleBufferPtr samples, const AudioVisualWaveform *waveform)
|
||||
{
|
||||
QList<TimeRange> valid_ranges = GetValidRanges(range, job_time);
|
||||
if (valid_ranges.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure if we have enough segments to write this data, creating more if not
|
||||
qint64 length_diff = params_.time_to_bytes(range.out()) - playlist_.GetLength();
|
||||
qint64 length_diff = params_.time_to_bytes_per_channel(range.out()) - playlist_.GetLength();
|
||||
while (length_diff > 0) {
|
||||
qint64 seg_sz = qMin(kDefaultSegmentSize, length_diff);
|
||||
qint64 seg_sz = qMin(kDefaultSegmentSizePerChannel, length_diff);
|
||||
playlist_.push_back(CreateSegment(seg_sz, playlist_.GetLength()));
|
||||
length_diff -= seg_sz;
|
||||
}
|
||||
|
||||
// Convert to packed data, which is what we store on disk so it can be played back easily
|
||||
QByteArray a;
|
||||
if (samples) {
|
||||
a = samples->toPackedData();
|
||||
}
|
||||
|
||||
// Keep track of validated ranges so we can signal them all at once at the end
|
||||
TimeRangeList ranges_we_validated;
|
||||
|
||||
// Calculate buffer size per channel
|
||||
qint64 buffer_size_per_channel = samples->sample_count() * params_.bytes_per_sample_per_channel();
|
||||
|
||||
// Write each valid range to the segments
|
||||
foreach (const TimeRange& r, valid_ranges) {
|
||||
rational this_segment_in = 0;
|
||||
|
||||
// Write PCM to playlist
|
||||
for (auto it=playlist_.begin(); it!=playlist_.end(); it++) {
|
||||
rational this_segment_out = this_segment_in + params_.bytes_to_time((*it).size());
|
||||
rational this_segment_out = this_segment_in + params_.bytes_per_channel_to_time((*it).size());
|
||||
|
||||
if (r.in() < this_segment_out) {
|
||||
// We'll write at least something to this segment
|
||||
QFile seg_file((*it).filename());
|
||||
bool succeeded = true;
|
||||
|
||||
if (seg_file.open(QFile::ReadWrite)) {
|
||||
// Calculate how much to write
|
||||
rational this_write_in_point = qMax(r.in(), this_segment_in);
|
||||
rational this_write_out_point = qMin(r.out(), this_segment_out);
|
||||
// Calculate how much to write
|
||||
rational this_write_in_point = qMax(r.in(), this_segment_in);
|
||||
rational this_write_out_point = qMin(r.out(), this_segment_out);
|
||||
|
||||
// Calculate what the byte offsets are going to be in this segment file
|
||||
rational in_point_relative = this_write_in_point - this_segment_in;
|
||||
qint64 dst_offset = params_.time_to_bytes(in_point_relative);
|
||||
for (int i=0; i<(*it).channels(); i++) {
|
||||
QFile seg_file((*it).filename(i));
|
||||
|
||||
// Calculate where to retrieve data from in the source buffer
|
||||
qint64 src_offset = params_.time_to_bytes(this_write_in_point - range.in());
|
||||
if (seg_file.open(QFile::ReadWrite)) {
|
||||
// Calculate what the byte offsets are going to be in this segment file
|
||||
rational in_point_relative = this_write_in_point - this_segment_in;
|
||||
qint64 dst_offset = params_.time_to_bytes_per_channel(in_point_relative);
|
||||
|
||||
// Determine how many bytes need to be written
|
||||
qint64 total_write_length = params_.time_to_bytes(this_write_out_point - this_write_in_point);
|
||||
// Calculate where to retrieve data from in the source buffer
|
||||
qint64 src_offset = params_.time_to_bytes_per_channel(this_write_in_point - range.in());
|
||||
|
||||
// Determine how many bytes we actually have in the source buffer
|
||||
qint64 possible_write_length = qMin(qMax(qint64(0), a.size() - src_offset), total_write_length);
|
||||
// Determine how many bytes need to be written
|
||||
qint64 total_write_length = params_.time_to_bytes_per_channel(this_write_out_point - this_write_in_point);
|
||||
|
||||
// Seek to our start offset
|
||||
seg_file.seek(dst_offset);
|
||||
// Retrieve data buffer
|
||||
const char *a = reinterpret_cast<const char*>(samples->data(i));
|
||||
|
||||
// If we have source bytes to write, write them here
|
||||
if (possible_write_length > 0) {
|
||||
seg_file.write(a.data() + src_offset, possible_write_length);
|
||||
// Determine how many bytes we actually have in the source buffer
|
||||
qint64 possible_write_length = qMin(qMax(qint64(0), buffer_size_per_channel - src_offset), total_write_length);
|
||||
|
||||
// Seek to our start offset
|
||||
seg_file.seek(dst_offset);
|
||||
|
||||
// If we have source bytes to write, write them here
|
||||
if (possible_write_length > 0) {
|
||||
seg_file.write(a + src_offset, possible_write_length);
|
||||
}
|
||||
|
||||
if (possible_write_length < total_write_length) {
|
||||
// Fill remaining space with silence
|
||||
QByteArray s(total_write_length - possible_write_length, 0x00);
|
||||
seg_file.write(s);
|
||||
}
|
||||
|
||||
seg_file.close();
|
||||
} else {
|
||||
qWarning() << "Failed to write PCM data to" << seg_file.fileName();
|
||||
succeeded = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (possible_write_length < total_write_length) {
|
||||
// Fill remaining space with silence
|
||||
QByteArray s(total_write_length - possible_write_length, 0x00);
|
||||
seg_file.write(s);
|
||||
}
|
||||
|
||||
seg_file.close();
|
||||
|
||||
if (succeeded) {
|
||||
ranges_we_validated.insert(TimeRange(this_write_in_point, this_write_out_point));
|
||||
} else {
|
||||
qWarning() << "Failed to write PCM data to" << seg_file.fileName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,23 +157,22 @@ void AudioPlaybackCache::WritePCM(const TimeRange &range, SampleBufferPtr sample
|
||||
}
|
||||
}
|
||||
|
||||
void AudioPlaybackCache::WriteSilence(const TimeRange &range, qint64 job_time)
|
||||
void AudioPlaybackCache::WriteSilence(const TimeRange &range)
|
||||
{
|
||||
// WritePCM will automatically fill non-existent bytes with silence, so we just have to send
|
||||
// it an empty sample buffer
|
||||
WritePCM(range, nullptr, nullptr, job_time);
|
||||
WritePCM(range, {range}, nullptr, nullptr);
|
||||
}
|
||||
|
||||
void AudioPlaybackCache::ShiftEvent(const rational &from_in_time, const rational &to_in_time)
|
||||
{
|
||||
if (from_in_time == to_in_time || from_in_time >= GetLength()) {
|
||||
// Nothing to be done
|
||||
qint64 to = params_.time_to_bytes_per_channel(to_in_time);
|
||||
qint64 from = params_.time_to_bytes_per_channel(from_in_time);
|
||||
|
||||
if (from >= playlist_.GetLength()) {
|
||||
return;
|
||||
}
|
||||
|
||||
qint64 to = params_.time_to_bytes(to_in_time);
|
||||
qint64 from = params_.time_to_bytes(from_in_time);
|
||||
|
||||
int to_seg_index = playlist_.GetIndexOfPosition(to);
|
||||
int from_seg_index = playlist_.GetIndexOfPosition(from);
|
||||
|
||||
@@ -202,7 +204,7 @@ void AudioPlaybackCache::ShiftEvent(const rational &from_in_time, const rational
|
||||
qint64 time_to_insert = to - from;
|
||||
|
||||
while (time_to_insert) {
|
||||
qint64 new_seg_sz = qMin(kDefaultSegmentSize, time_to_insert);
|
||||
qint64 new_seg_sz = qMin(kDefaultSegmentSizePerChannel, time_to_insert);
|
||||
|
||||
// Set offset to 0 for now and fill it in later
|
||||
playlist_.insert(insert_index, CreateSegment(new_seg_sz, 0));
|
||||
@@ -262,56 +264,45 @@ void AudioPlaybackCache::ShiftEvent(const rational &from_in_time, const rational
|
||||
}
|
||||
}
|
||||
|
||||
void AudioPlaybackCache::LengthChangedEvent(const rational& old, const rational& newlen)
|
||||
{
|
||||
Q_UNUSED(old)
|
||||
|
||||
if (!params_.is_valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
qint64 new_len_in_bytes = params_.time_to_bytes(newlen);
|
||||
|
||||
while (new_len_in_bytes < playlist_.GetLength()) {
|
||||
Segment& last_seg = playlist_.back();
|
||||
|
||||
if (playlist_.GetLength() - last_seg.size() < new_len_in_bytes) {
|
||||
// Truncate this segment rather than removing it
|
||||
qint64 diff = playlist_.GetLength() - new_len_in_bytes;
|
||||
|
||||
TrimSegmentOut(&last_seg, last_seg.size() - diff);
|
||||
} else {
|
||||
// Remove last segment
|
||||
RemoveSegmentFromArray(playlist_.size() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AudioPlaybackCache::Segment AudioPlaybackCache::CloneSegment(const AudioPlaybackCache::Segment &s) const
|
||||
{
|
||||
Segment new_seg = s;
|
||||
|
||||
// Copy data to a new file
|
||||
QString new_filename = GenerateSegmentFilename();
|
||||
QFile::copy(s.filename(), new_filename);
|
||||
new_seg.set_channels(s.channels());
|
||||
|
||||
new_seg.set_filename(new_filename);
|
||||
// Copy data to a new file
|
||||
for (int i=0; i<s.channels(); i++) {
|
||||
QString new_filename = GenerateSegmentFilename();
|
||||
QFile::copy(s.filename(i), new_filename);
|
||||
|
||||
new_seg.set_filename(i, new_filename);
|
||||
}
|
||||
|
||||
return new_seg;
|
||||
}
|
||||
|
||||
AudioPlaybackCache::Segment AudioPlaybackCache::CreateSegment(const qint64 &size, const qint64& offset) const
|
||||
{
|
||||
Segment s(size, GenerateSegmentFilename());
|
||||
Segment s(size);
|
||||
|
||||
s.set_channels(params_.channel_count());
|
||||
|
||||
for (int i=0; i<params_.channel_count(); i++) {
|
||||
// Generate random unused filename for this segment
|
||||
QString fn = GenerateSegmentFilename();
|
||||
|
||||
// Set it for this segment/channel
|
||||
s.set_filename(i, fn);
|
||||
|
||||
// Create empty file
|
||||
QFile f(fn);
|
||||
if (f.open(QFile::WriteOnly)) {
|
||||
f.close();
|
||||
}
|
||||
}
|
||||
|
||||
s.set_offset(offset);
|
||||
|
||||
// Create empty file
|
||||
QFile f(s.filename());
|
||||
if (f.open(QFile::WriteOnly)) {
|
||||
f.close();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -320,7 +311,7 @@ QString AudioPlaybackCache::GenerateSegmentFilename() const
|
||||
QString new_seg_filename;
|
||||
|
||||
do {
|
||||
uint32_t r = std::rand();
|
||||
uint32_t r = QRandomGenerator::global()->generate();
|
||||
new_seg_filename = QDir(GetCacheDirectory()).filePath(QStringLiteral("%1.pcm").arg(r));
|
||||
} while (QFileInfo::exists(new_seg_filename));
|
||||
|
||||
@@ -330,21 +321,23 @@ QString AudioPlaybackCache::GenerateSegmentFilename() const
|
||||
void AudioPlaybackCache::TrimSegmentIn(AudioPlaybackCache::Segment *s, qint64 new_length)
|
||||
{
|
||||
// Read filename
|
||||
QFile f(s->filename());
|
||||
if (f.open(QFile::ReadWrite)) {
|
||||
// Read segment into memory, according to the size we acknowledge
|
||||
QByteArray data = f.read(s->size());
|
||||
for (int i=0; i<s->channels(); i++) {
|
||||
QFile f(s->filename(i));
|
||||
if (f.open(QFile::ReadWrite)) {
|
||||
// Read segment into memory, according to the size we acknowledge
|
||||
QByteArray data = f.read(s->size());
|
||||
|
||||
// Trim to new length
|
||||
data = data.right(new_length);
|
||||
// Trim to new length
|
||||
data = data.right(new_length);
|
||||
|
||||
// Seek to start and write
|
||||
f.seek(0);
|
||||
// Seek to start and write
|
||||
f.seek(0);
|
||||
|
||||
// Write trimmed data
|
||||
f.write(data);
|
||||
// Write trimmed data
|
||||
f.write(data);
|
||||
|
||||
f.close();
|
||||
f.close();
|
||||
}
|
||||
}
|
||||
|
||||
s->set_size(new_length);
|
||||
@@ -358,14 +351,19 @@ void AudioPlaybackCache::TrimSegmentOut(AudioPlaybackCache::Segment *s, qint64 n
|
||||
|
||||
void AudioPlaybackCache::RemoveSegmentFromArray(int index)
|
||||
{
|
||||
QFile::remove(playlist_.at(index).filename());
|
||||
const Segment &s = playlist_.at(index);
|
||||
for (int i=0; i<s.channels(); i++) {
|
||||
QFile::remove(s.filename(i));
|
||||
}
|
||||
playlist_.removeAt(index);
|
||||
}
|
||||
|
||||
void AudioPlaybackCache::ClearPlaylist()
|
||||
{
|
||||
foreach (const Segment& s, playlist_) {
|
||||
QFile::remove(s.filename());
|
||||
for (int i=0; i<s.channels(); i++) {
|
||||
QFile::remove(s.filename(i));
|
||||
}
|
||||
}
|
||||
playlist_.clear();
|
||||
}
|
||||
@@ -391,37 +389,22 @@ void AudioPlaybackCache::UpdateOffsetsFrom(int index)
|
||||
}
|
||||
}
|
||||
|
||||
QList<TimeRange> AudioPlaybackCache::GetValidRanges(const TimeRange& range, const qint64& job_time)
|
||||
{
|
||||
QList<TimeRange> valid_ranges;
|
||||
|
||||
for (int i=jobs_.size()-1;i>=0;i--) {
|
||||
const JobIdentifier& job = jobs_.at(i);
|
||||
|
||||
if (job_time >= job.job_time && job.range.OverlapsWith(range)) {
|
||||
valid_ranges.append(job.range.Intersected(range));
|
||||
}
|
||||
}
|
||||
|
||||
return valid_ranges;
|
||||
}
|
||||
|
||||
AudioPlaybackCache::PlaybackDevice *AudioPlaybackCache::CreatePlaybackDevice(QObject* parent) const
|
||||
{
|
||||
return new PlaybackDevice(playlist_, parent);
|
||||
return new PlaybackDevice(playlist_, params_.bytes_per_sample_per_channel(), parent);
|
||||
}
|
||||
|
||||
AudioPlaybackCache::Segment::Segment(qint64 size, const QString &filename)
|
||||
AudioPlaybackCache::Segment::Segment(qint64 size)
|
||||
{
|
||||
size_ = size;
|
||||
filename_ = filename;
|
||||
}
|
||||
|
||||
AudioPlaybackCache::PlaybackDevice::PlaybackDevice(const AudioPlaybackCache::Playlist &playlist, QObject *parent) :
|
||||
AudioPlaybackCache::PlaybackDevice::PlaybackDevice(const AudioPlaybackCache::Playlist &playlist, int sample_sz, QObject *parent) :
|
||||
QIODevice(parent),
|
||||
playlist_(playlist),
|
||||
current_segment_(0),
|
||||
segment_read_index_(0)
|
||||
segment_read_index_(0),
|
||||
sample_size_(sample_sz)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -458,37 +441,66 @@ qint64 AudioPlaybackCache::PlaybackDevice::readData(char *data, qint64 maxSize)
|
||||
&& current_segment_ < playlist_.size()) {
|
||||
const Segment& cs = playlist_.at(current_segment_);
|
||||
qint64 current_segment_sz = cs.size();
|
||||
QFile segment_file(cs.filename());
|
||||
|
||||
if (segment_file.open(QFile::ReadOnly)) {
|
||||
// Seek to our stored index of this segment
|
||||
segment_file.seek(segment_read_index_);
|
||||
QVector<QFile*> segment_files(cs.channels());
|
||||
segment_files.fill(nullptr);
|
||||
|
||||
// Determine how many bytes to read
|
||||
qint64 this_read_length = qMin(current_segment_sz - segment_read_index_,
|
||||
maxSize - read_size);
|
||||
bool all_files_opened = true;
|
||||
|
||||
// Read those bytes
|
||||
segment_file.read(data + read_size, this_read_length);
|
||||
// Open all file handles
|
||||
for (int i=0; i<cs.channels(); i++) {
|
||||
QFile *f = new QFile(cs.filename(i));
|
||||
segment_files[i] = f;
|
||||
|
||||
// Close the file
|
||||
segment_file.close();
|
||||
if (f->open(QFile::ReadOnly)) {
|
||||
// Seek to our stored index of this segment
|
||||
f->seek(segment_read_index_);
|
||||
} else {
|
||||
all_files_opened = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add to the read index
|
||||
segment_read_index_ += this_read_length;
|
||||
// If all file handles opened successfully, time to interleave and send them out
|
||||
if (all_files_opened) {
|
||||
// Determine how many bytes to read
|
||||
qint64 this_read_length = qMin((current_segment_sz - segment_read_index_) * cs.channels(), maxSize - read_size);
|
||||
|
||||
// Add to the read size
|
||||
read_size += this_read_length;
|
||||
qint64 target = read_size + this_read_length;
|
||||
|
||||
// If we've reached the end of this segment, tick the counter over to the next segment
|
||||
if (segment_read_index_ == current_segment_sz) {
|
||||
// Jump to the next file
|
||||
segment_read_index_ = 0;
|
||||
current_segment_++;
|
||||
while (read_size < target) {
|
||||
for (int i=0; i<cs.channels(); i++) {
|
||||
QFile *segment_file = segment_files.at(i);
|
||||
|
||||
// Read those bytes
|
||||
segment_file->read(data + read_size, sample_size_);
|
||||
|
||||
// Add to the read size
|
||||
read_size += sample_size_;
|
||||
}
|
||||
|
||||
// Add to the read index
|
||||
segment_read_index_ += sample_size_;
|
||||
|
||||
// If we've reached the end of this segment, tick the counter over to the next segment
|
||||
if (segment_read_index_ == current_segment_sz) {
|
||||
// Jump to the next file
|
||||
segment_read_index_ = 0;
|
||||
current_segment_++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close and delete file handles
|
||||
for (int i=0; i<cs.channels(); i++) {
|
||||
QFile *f = segment_files.at(i);
|
||||
|
||||
if (f) {
|
||||
if (f->isOpen()) {
|
||||
f->close();
|
||||
}
|
||||
delete f;
|
||||
}
|
||||
} else {
|
||||
qWarning() << "Failed to read data from segment";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,17 +66,14 @@ public:
|
||||
|
||||
void SetParameters(const AudioParams& params);
|
||||
|
||||
void WritePCM(const TimeRange &range, SampleBufferPtr samples, const AudioVisualWaveform *waveform, const qint64& job_time);
|
||||
void WritePCM(const TimeRange &range, const TimeRangeList &valid_ranges, SampleBufferPtr samples, const AudioVisualWaveform *waveform);
|
||||
|
||||
void WriteSilence(const TimeRange &range, qint64 job_time);
|
||||
|
||||
QList<TimeRange> GetValidRanges(const TimeRange &range, const qint64 &job_time);
|
||||
void WriteSilence(const TimeRange &range);
|
||||
|
||||
class Segment
|
||||
{
|
||||
public:
|
||||
Segment() = default;
|
||||
Segment(qint64 size, const QString& filename);
|
||||
Segment(qint64 size = 0);
|
||||
|
||||
qint64 size() const
|
||||
{
|
||||
@@ -98,14 +95,24 @@ public:
|
||||
offset_ = o;
|
||||
}
|
||||
|
||||
const QString& filename() const
|
||||
int channels() const
|
||||
{
|
||||
return filename_;
|
||||
return filenames_.size();
|
||||
}
|
||||
|
||||
void set_filename(const QString& filename)
|
||||
void set_channels(int index)
|
||||
{
|
||||
filename_ = filename;
|
||||
filenames_.resize(index);
|
||||
}
|
||||
|
||||
const QString& filename(int index) const
|
||||
{
|
||||
return filenames_.at(index);
|
||||
}
|
||||
|
||||
void set_filename(int index, const QString& filename)
|
||||
{
|
||||
filenames_[index] = filename;
|
||||
}
|
||||
|
||||
qint64 end() const
|
||||
@@ -114,7 +121,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
QString filename_;
|
||||
QVector<QString> filenames_;
|
||||
|
||||
qint64 size_;
|
||||
|
||||
@@ -136,7 +143,7 @@ public:
|
||||
class PlaybackDevice : public QIODevice
|
||||
{
|
||||
public:
|
||||
PlaybackDevice(const Playlist& playlist, QObject* parent = nullptr);
|
||||
PlaybackDevice(const Playlist& playlist, int sample_sz, QObject* parent = nullptr);
|
||||
|
||||
virtual ~PlaybackDevice() override;
|
||||
|
||||
@@ -169,6 +176,8 @@ public:
|
||||
|
||||
qint64 segment_read_index_;
|
||||
|
||||
int sample_size_;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -193,10 +202,8 @@ signals:
|
||||
protected:
|
||||
virtual void ShiftEvent(const rational& from, const rational& to) override;
|
||||
|
||||
virtual void LengthChangedEvent(const rational& old, const rational& newlen) override;
|
||||
|
||||
private:
|
||||
static const qint64 kDefaultSegmentSize;
|
||||
static const qint64 kDefaultSegmentSizePerChannel;
|
||||
|
||||
Segment CloneSegment(const Segment& s) const;
|
||||
|
||||
|
||||
+56
-142
@@ -32,7 +32,6 @@
|
||||
|
||||
#include "codec/frame.h"
|
||||
#include "common/filefunctions.h"
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "render/diskmanager.h"
|
||||
|
||||
namespace olive {
|
||||
@@ -52,24 +51,32 @@ FrameHashCache::FrameHashCache(QObject *parent) :
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray FrameHashCache::GetHash(const rational &time)
|
||||
QByteArray FrameHashCache::GetHash(const int64_t &time)
|
||||
{
|
||||
return time_hash_map_.value(time);
|
||||
if (time < GetMapSize()) {
|
||||
return time_hash_map_.at(time);
|
||||
} else {
|
||||
return QByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
void FrameHashCache::SetHash(const rational &time, const QByteArray &hash, const qint64& job_time, bool frame_exists)
|
||||
QByteArray FrameHashCache::GetHash(const rational &time)
|
||||
{
|
||||
for (int i=jobs_.size()-1; i>=0; i--) {
|
||||
const JobIdentifier& job = jobs_.at(i);
|
||||
return GetHash(ToTimestamp(time));
|
||||
}
|
||||
|
||||
if (job.range.Contains(time)
|
||||
&& job_time < job.job_time) {
|
||||
// Hash here has changed since this frame started rendering, discard it
|
||||
return;
|
||||
}
|
||||
void FrameHashCache::SetHash(const rational &time, const QByteArray &hash, bool frame_exists)
|
||||
{
|
||||
int64_t ts = ToTimestamp(time);
|
||||
if (ts >= GetMapSize()) {
|
||||
// Disabled: bizarrely causes the whole app to hang indefinitely when used
|
||||
// Reserve an extra minute to cut down on the amount of reallocations to make
|
||||
//time_hash_map_.reserve(ts + timebase_.flipped().toDouble() * 60);
|
||||
|
||||
// Add enough entries to insert this hash
|
||||
time_hash_map_.resize(ts + 1);
|
||||
}
|
||||
|
||||
time_hash_map_.insert(time, hash);
|
||||
time_hash_map_[ts] = hash;
|
||||
|
||||
TimeRange validated_range;
|
||||
if (frame_exists) {
|
||||
@@ -85,11 +92,11 @@ void FrameHashCache::SetTimebase(const rational &tb)
|
||||
|
||||
void FrameHashCache::ValidateFramesWithHash(const QByteArray &hash)
|
||||
{
|
||||
const TimeRangeList& invalidated_ranges = GetInvalidatedRanges();
|
||||
auto invalidated_ranges = GetInvalidatedRanges(ToTime(GetMapSize()));
|
||||
|
||||
for (auto iterator=time_hash_map_.begin();iterator!=time_hash_map_.end();iterator++) {
|
||||
if (iterator.value() == hash) {
|
||||
TimeRange frame_range(iterator.key(), iterator.key() + timebase_);
|
||||
for (int64_t i=0; i<GetMapSize(); i++) {
|
||||
if (time_hash_map_[i] == hash) {
|
||||
TimeRange frame_range(ToTime(i), ToTime(i+1));
|
||||
|
||||
if (invalidated_ranges.contains(frame_range)) {
|
||||
Validate(frame_range);
|
||||
@@ -98,85 +105,6 @@ void FrameHashCache::ValidateFramesWithHash(const QByteArray &hash)
|
||||
}
|
||||
}
|
||||
|
||||
QList<rational> FrameHashCache::GetFramesWithHash(const QByteArray &hash)
|
||||
{
|
||||
QList<rational> times;
|
||||
|
||||
for (auto iterator=time_hash_map_.begin();iterator!=time_hash_map_.end();iterator++) {
|
||||
if (iterator.value() == hash) {
|
||||
times.append(iterator.key());
|
||||
}
|
||||
}
|
||||
|
||||
return times;
|
||||
}
|
||||
|
||||
QList<rational> FrameHashCache::TakeFramesWithHash(const QByteArray &hash)
|
||||
{
|
||||
TimeRangeList range_to_invalidate;
|
||||
QList<rational> times;
|
||||
|
||||
auto iterator = time_hash_map_.begin();
|
||||
|
||||
while (iterator != time_hash_map_.end()) {
|
||||
if (iterator.value() == hash) {
|
||||
times.append(iterator.key());
|
||||
range_to_invalidate.insert(TimeRange(iterator.key(), iterator.key() + timebase_));
|
||||
|
||||
iterator = time_hash_map_.erase(iterator);
|
||||
} else {
|
||||
iterator++;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const TimeRange& r, range_to_invalidate) {
|
||||
// We apply a 0 job time because the graph hasn't changed to get here, so any renderer should
|
||||
// be up to date already
|
||||
Invalidate(r, 0);
|
||||
}
|
||||
|
||||
return times;
|
||||
}
|
||||
|
||||
QMap<rational, QByteArray> FrameHashCache::time_hash_map()
|
||||
{
|
||||
return time_hash_map_;
|
||||
}
|
||||
|
||||
QVector<rational> FrameHashCache::GetFrameListFromTimeRange(TimeRangeList range_list, const rational &timebase)
|
||||
{
|
||||
// If timebase is null, this will be an infinite loop
|
||||
Q_ASSERT(!timebase.isNull());
|
||||
|
||||
QVector<rational> times;
|
||||
|
||||
foreach (const TimeRange &range, range_list) {
|
||||
rational frame = Timecode::snap_time_to_timebase(range.in(), timebase, true);
|
||||
|
||||
while (frame < range.out()) {
|
||||
times.append(frame);
|
||||
frame += timebase;
|
||||
}
|
||||
}
|
||||
|
||||
return times;
|
||||
}
|
||||
|
||||
QVector<rational> FrameHashCache::GetFrameListFromTimeRange(const TimeRangeList &range)
|
||||
{
|
||||
return GetFrameListFromTimeRange(range, timebase_);
|
||||
}
|
||||
|
||||
QVector<rational> FrameHashCache::GetInvalidatedFrames()
|
||||
{
|
||||
return GetFrameListFromTimeRange(GetInvalidatedRanges());
|
||||
}
|
||||
|
||||
QVector<rational> FrameHashCache::GetInvalidatedFrames(const TimeRange &intersecting)
|
||||
{
|
||||
return GetFrameListFromTimeRange(GetInvalidatedRanges().Intersects(intersecting));
|
||||
}
|
||||
|
||||
bool FrameHashCache::SaveCacheFrame(const QByteArray& hash,
|
||||
char* data,
|
||||
const VideoParams& vparam,
|
||||
@@ -313,21 +241,6 @@ FramePtr FrameHashCache::LoadCacheFrame(const QString &fn)
|
||||
return frame;
|
||||
}
|
||||
|
||||
void FrameHashCache::LengthChangedEvent(const rational &old, const rational &newlen)
|
||||
{
|
||||
if (newlen < old) {
|
||||
auto i = time_hash_map_.begin();
|
||||
|
||||
while (i != time_hash_map_.end()) {
|
||||
if (i.key() >= newlen) {
|
||||
i = time_hash_map_.erase(i);
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct HashTimePair {
|
||||
rational time;
|
||||
QByteArray hash;
|
||||
@@ -335,51 +248,52 @@ struct HashTimePair {
|
||||
|
||||
void FrameHashCache::ShiftEvent(const rational &from, const rational &to)
|
||||
{
|
||||
auto i = time_hash_map_.begin();
|
||||
|
||||
// POSITIVE if moving forward ->
|
||||
// NEGATIVE if moving backward <-
|
||||
rational diff = to - from;
|
||||
bool diff_is_negative = (diff < 0);
|
||||
|
||||
QList<HashTimePair> shifted_times;
|
||||
int64_t to_ts = ToTimestamp(to);
|
||||
int64_t from_ts = ToTimestamp(from);
|
||||
|
||||
while (i != time_hash_map_.end()) {
|
||||
if (diff_is_negative && i.key() >= to && i.key() < from) {
|
||||
|
||||
// This time will be removed in the shift so we just discard it
|
||||
i = time_hash_map_.erase(i);
|
||||
|
||||
} else if (i.key() >= from) {
|
||||
|
||||
// This time is after the from time and must be shifted
|
||||
shifted_times.append({i.key() + diff, i.value()});
|
||||
i = time_hash_map_.erase(i);
|
||||
|
||||
} else {
|
||||
|
||||
// Do nothing
|
||||
i++;
|
||||
|
||||
}
|
||||
if (from_ts >= GetMapSize()) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (const HashTimePair& p, shifted_times) {
|
||||
time_hash_map_.insert(p.time, p.hash);
|
||||
if (diff_is_negative) {
|
||||
// We're moving the frames starting at `from` backwards to where `to` is
|
||||
if (to_ts < GetMapSize()) {
|
||||
time_hash_map_.erase(time_hash_map_.begin() + to_ts, time_hash_map_.begin() + from_ts);
|
||||
}
|
||||
} else {
|
||||
// We're moving the frames starting at `from` forwards to where `to` is
|
||||
if (from_ts < GetMapSize()) {
|
||||
time_hash_map_.insert(time_hash_map_.begin() + from_ts, to_ts - from_ts, QByteArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FrameHashCache::InvalidateEvent(const TimeRange &range)
|
||||
{
|
||||
if (!timebase_.isNull()) {
|
||||
QVector<rational> invalid_frames = GetFrameListFromTimeRange({range});
|
||||
|
||||
foreach (const rational& r, invalid_frames) {
|
||||
time_hash_map_.remove(r);
|
||||
int64_t start = ToTimestamp(range.in(), Timecode::kCeil);
|
||||
int64_t end = ToTimestamp(range.out(), Timecode::kCeil);
|
||||
for (int64_t i=start; i<GetMapSize() && i<end; i++) {
|
||||
time_hash_map_[i].clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rational FrameHashCache::ToTime(const int64_t &ts) const
|
||||
{
|
||||
return Timecode::timestamp_to_time(ts, timebase_);
|
||||
}
|
||||
|
||||
int64_t FrameHashCache::ToTimestamp(const rational &ts, Timecode::Rounding rounding) const
|
||||
{
|
||||
return Timecode::time_to_timestamp(ts, timebase_, rounding);
|
||||
}
|
||||
|
||||
void FrameHashCache::HashDeleted(const QString& s, const QByteArray &hash)
|
||||
{
|
||||
QString cache_dir = GetCacheDirectory();
|
||||
@@ -388,16 +302,16 @@ void FrameHashCache::HashDeleted(const QString& s, const QByteArray &hash)
|
||||
}
|
||||
|
||||
TimeRangeList ranges_to_invalidate;
|
||||
for (auto i=time_hash_map_.constBegin(); i!=time_hash_map_.constEnd(); i++) {
|
||||
if (i.value() == hash) {
|
||||
ranges_to_invalidate.insert(TimeRange(i.key(), i.key() + timebase_));
|
||||
for (int64_t i=0; i<GetMapSize(); i++) {
|
||||
if (time_hash_map_.at(i) == hash) {
|
||||
ranges_to_invalidate.insert(TimeRange(ToTime(i), ToTime(i+1)));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const TimeRange& range, ranges_to_invalidate) {
|
||||
// We set job time to 0 because the nodes haven't changed and any render job should be up
|
||||
// to date
|
||||
Invalidate(range, 0);
|
||||
Invalidate(range);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-22
@@ -24,6 +24,7 @@
|
||||
#include <QMutex>
|
||||
|
||||
#include "common/rational.h"
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "common/timerange.h"
|
||||
#include "codec/frame.h"
|
||||
#include "render/playbackcache.h"
|
||||
@@ -37,24 +38,18 @@ class FrameHashCache : public PlaybackCache
|
||||
public:
|
||||
FrameHashCache(QObject* parent = nullptr);
|
||||
|
||||
QByteArray GetHash(const int64_t& time);
|
||||
QByteArray GetHash(const rational& time);
|
||||
|
||||
const rational &GetTimebase() const
|
||||
{
|
||||
return timebase_;
|
||||
}
|
||||
|
||||
void SetTimebase(const rational& tb);
|
||||
|
||||
void ValidateFramesWithHash(const QByteArray& hash);
|
||||
|
||||
/**
|
||||
* @brief Returns a list of frames that use a particular hash
|
||||
*/
|
||||
QList<rational> GetFramesWithHash(const QByteArray& hash);
|
||||
|
||||
/**
|
||||
* @brief Same as FramesWithHash() but also removes these frames from the map
|
||||
*/
|
||||
QList<rational> TakeFramesWithHash(const QByteArray& hash);
|
||||
|
||||
QMap<rational, QByteArray> time_hash_map();
|
||||
|
||||
/**
|
||||
* @brief Return the path of the cached image at this time
|
||||
*/
|
||||
@@ -70,23 +65,23 @@ public:
|
||||
FramePtr LoadCacheFrame(const QByteArray& hash) const;
|
||||
static FramePtr LoadCacheFrame(const QString& fn);
|
||||
|
||||
static QVector<rational> GetFrameListFromTimeRange(TimeRangeList range_list, const rational& timebase);
|
||||
QVector<rational> GetFrameListFromTimeRange(const TimeRangeList &range);
|
||||
QVector<rational> GetInvalidatedFrames();
|
||||
QVector<rational> GetInvalidatedFrames(const TimeRange& intersecting);
|
||||
|
||||
public slots:
|
||||
void SetHash(const olive::rational& time, const QByteArray& hash, const qint64 &job_time, bool frame_exists);
|
||||
void SetHash(const olive::rational &time, const QByteArray& hash, bool frame_exists);
|
||||
|
||||
protected:
|
||||
virtual void LengthChangedEvent(const rational& old, const rational& newlen) override;
|
||||
|
||||
virtual void ShiftEvent(const rational& from, const rational& to) override;
|
||||
|
||||
virtual void InvalidateEvent(const TimeRange& range) override;
|
||||
|
||||
private:
|
||||
QMap<rational, QByteArray> time_hash_map_;
|
||||
rational ToTime(const int64_t &ts) const;
|
||||
int64_t ToTimestamp(const rational &ts, Timecode::Rounding rounding = Timecode::kRound) const;
|
||||
|
||||
int64_t GetMapSize() const
|
||||
{
|
||||
return int64_t(time_hash_map_.size());
|
||||
}
|
||||
|
||||
std::vector<QByteArray> time_hash_map_;
|
||||
|
||||
rational timebase_;
|
||||
|
||||
|
||||
@@ -27,64 +27,25 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
void PlaybackCache::Invalidate(const TimeRange &r, qint64 job_time)
|
||||
void PlaybackCache::Invalidate(const TimeRange &r, bool signal)
|
||||
{
|
||||
if (r.in() == r.out()) {
|
||||
qWarning() << "Tried to invalidate zero-length range";
|
||||
return;
|
||||
}
|
||||
|
||||
invalidated_.insert(r);
|
||||
|
||||
RemoveRangeFromJobs(r);
|
||||
jobs_.append({r, job_time});
|
||||
validated_.remove(r);
|
||||
|
||||
InvalidateEvent(r);
|
||||
|
||||
emit Invalidated(r);
|
||||
if (signal) {
|
||||
emit Invalidated(r);
|
||||
}
|
||||
}
|
||||
|
||||
void PlaybackCache::InvalidateAll()
|
||||
{
|
||||
if (length_.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Invalidate(TimeRange(0, length_), 0);
|
||||
}
|
||||
|
||||
void PlaybackCache::SetLength(const rational &r)
|
||||
{
|
||||
if (length_ == r) {
|
||||
// Same length - do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
LengthChangedEvent(length_, r);
|
||||
|
||||
TimeRange range_diff(length_, r);
|
||||
|
||||
if (r.isNull()) {
|
||||
invalidated_.clear();
|
||||
jobs_.clear();
|
||||
} else if (r > length_) {
|
||||
// If new length is greater, simply extend the invalidated range for now
|
||||
invalidated_.insert(range_diff);
|
||||
jobs_.append({range_diff, 0});
|
||||
} else {
|
||||
// If new length is smaller, removed hashes
|
||||
invalidated_.remove(range_diff);
|
||||
RemoveRangeFromJobs(range_diff);
|
||||
}
|
||||
|
||||
rational old_length = length_;
|
||||
length_ = r;
|
||||
|
||||
if (r > old_length) {
|
||||
emit Invalidated(range_diff);
|
||||
} else {
|
||||
emit Validated(range_diff);
|
||||
}
|
||||
Invalidate(TimeRange(0, RATIONAL_MAX));
|
||||
}
|
||||
|
||||
void PlaybackCache::Shift(rational from, rational to)
|
||||
@@ -93,57 +54,33 @@ void PlaybackCache::Shift(rational from, rational to)
|
||||
return;
|
||||
}
|
||||
|
||||
if (from > length_) {
|
||||
if (to > from) {
|
||||
// No-op
|
||||
return;
|
||||
} else if (to >= length_) {
|
||||
// No-op
|
||||
return;
|
||||
} else {
|
||||
from = length_;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "FIXME: 0 job time may cause cache desyncs";
|
||||
|
||||
// An region between `from` and `to` will be inserted or spliced out
|
||||
TimeRangeList ranges_to_shift = invalidated_.Intersects(TimeRange(from, RATIONAL_MAX));
|
||||
TimeRangeList ranges_to_shift = validated_.Intersects(TimeRange(from, RATIONAL_MAX));
|
||||
|
||||
// Remove everything from the minimum point
|
||||
TimeRange remove_range = TimeRange(qMin(from, to), RATIONAL_MAX);
|
||||
RemoveRangeFromJobs(remove_range);
|
||||
Validate(remove_range);
|
||||
Invalidate(remove_range, false);
|
||||
|
||||
// Shift invalidated ranges
|
||||
// (`diff` is POSITIVE when moving forward -> and NEGATIVE when moving backward <-)
|
||||
rational diff = to - from;
|
||||
foreach (const TimeRange& r, ranges_to_shift) {
|
||||
Invalidate(r + diff, 0);
|
||||
Validate(r + diff, false);
|
||||
}
|
||||
|
||||
ShiftEvent(from, to);
|
||||
|
||||
length_ += diff;
|
||||
|
||||
if (diff > 0) {
|
||||
// If shifting forward, add this section to the invalidated region
|
||||
Invalidate(TimeRange(from, to), 0);
|
||||
}
|
||||
|
||||
// Emit signals
|
||||
emit Shifted(from, to);
|
||||
}
|
||||
|
||||
void PlaybackCache::Validate(const TimeRange &r)
|
||||
void PlaybackCache::Validate(const TimeRange &r, bool signal)
|
||||
{
|
||||
invalidated_.remove(r);
|
||||
validated_.insert(r);
|
||||
|
||||
emit Validated(r);
|
||||
}
|
||||
|
||||
void PlaybackCache::LengthChangedEvent(const rational &, const rational &)
|
||||
{
|
||||
if (signal) {
|
||||
emit Validated(r);
|
||||
}
|
||||
}
|
||||
|
||||
void PlaybackCache::InvalidateEvent(const TimeRange &)
|
||||
@@ -165,29 +102,27 @@ Project *PlaybackCache::GetProject() const
|
||||
return viewer->project();
|
||||
}
|
||||
|
||||
void PlaybackCache::RemoveRangeFromJobs(const TimeRange &remove)
|
||||
TimeRangeList PlaybackCache::GetInvalidatedRanges(TimeRange intersecting)
|
||||
{
|
||||
// Code shamelessly copied from TimeRangeList::RemoveTimeRange
|
||||
for (int i=0;i<jobs_.size();i++) {
|
||||
JobIdentifier& job = jobs_[i];
|
||||
TimeRange& compare = job.range;
|
||||
TimeRangeList invalidated;
|
||||
|
||||
if (remove.Contains(compare)) {
|
||||
// This element is entirely encompassed in this range, remove it
|
||||
jobs_.removeAt(i);
|
||||
i--;
|
||||
} else if (compare.Contains(remove, false, false)) {
|
||||
// The remove range is within this element, only choice is to split the element into two
|
||||
jobs_.append({TimeRange(remove.out(), compare.out()), job.job_time});
|
||||
compare.set_out(remove.in());
|
||||
} else if (compare.in() < remove.in() && compare.out() > remove.in()) {
|
||||
// This element's out point overlaps the range's in, we'll trim it
|
||||
compare.set_out(remove.in());
|
||||
} else if (compare.in() < remove.out() && compare.out() > remove.out()) {
|
||||
// This element's in point overlaps the range's out, we'll trim it
|
||||
compare.set_in(remove.out());
|
||||
}
|
||||
// Prevent TimeRange from being below 0, some other behavior in Olive relies on this behavior
|
||||
// and it seemed reasonable to have safety code in here
|
||||
intersecting.set_out(qMax(rational(0), intersecting.out()));
|
||||
intersecting.set_in(qMax(rational(0), intersecting.in()));
|
||||
|
||||
invalidated.insert(intersecting);
|
||||
|
||||
foreach (const TimeRange &range, validated_) {
|
||||
invalidated.remove(range);
|
||||
}
|
||||
|
||||
return invalidated;
|
||||
}
|
||||
|
||||
bool PlaybackCache::HasInvalidatedRanges(const TimeRange &intersecting)
|
||||
{
|
||||
return !validated_.contains(intersecting);
|
||||
}
|
||||
|
||||
QString PlaybackCache::GetCacheDirectory() const
|
||||
@@ -201,4 +136,9 @@ QString PlaybackCache::GetCacheDirectory() const
|
||||
}
|
||||
}
|
||||
|
||||
ViewerOutput *PlaybackCache::viewer_parent() const
|
||||
{
|
||||
return dynamic_cast<ViewerOutput*>(parent());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+15
-37
@@ -24,51 +24,44 @@
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
|
||||
#include "common/jobtime.h"
|
||||
#include "common/timerange.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class Project;
|
||||
class ViewerOutput;
|
||||
|
||||
class PlaybackCache : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
PlaybackCache(QObject* parent = nullptr) :
|
||||
QObject(parent),
|
||||
length_(0)
|
||||
QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
const rational& GetLength()
|
||||
TimeRangeList GetInvalidatedRanges(TimeRange intersecting);
|
||||
TimeRangeList GetInvalidatedRanges(const rational &length)
|
||||
{
|
||||
return length_;
|
||||
return GetInvalidatedRanges(TimeRange(0, length));
|
||||
}
|
||||
|
||||
bool IsFullyValidated()
|
||||
bool HasInvalidatedRanges(const TimeRange &intersecting);
|
||||
bool HasInvalidatedRanges(const rational &length)
|
||||
{
|
||||
return invalidated_.isEmpty();
|
||||
}
|
||||
|
||||
const TimeRangeList& GetInvalidatedRanges()
|
||||
{
|
||||
return invalidated_;
|
||||
}
|
||||
|
||||
bool HasInvalidatedRanges()
|
||||
{
|
||||
return !invalidated_.isEmpty();
|
||||
return HasInvalidatedRanges(TimeRange(0, length));
|
||||
}
|
||||
|
||||
QString GetCacheDirectory() const;
|
||||
|
||||
ViewerOutput *viewer_parent() const;
|
||||
|
||||
void Invalidate(const TimeRange& r, bool signal = true);
|
||||
|
||||
public slots:
|
||||
void Invalidate(const TimeRange& r, qint64 job_time);
|
||||
|
||||
void InvalidateAll();
|
||||
|
||||
void SetLength(const rational& r);
|
||||
|
||||
void Shift(rational from, rational to);
|
||||
|
||||
signals:
|
||||
@@ -78,12 +71,8 @@ signals:
|
||||
|
||||
void Shifted(const olive::rational& from, const olive::rational& to);
|
||||
|
||||
void LengthChanged(const olive::rational& r);
|
||||
|
||||
protected:
|
||||
void Validate(const TimeRange& r);
|
||||
|
||||
virtual void LengthChangedEvent(const rational& old, const rational& newlen);
|
||||
void Validate(const TimeRange& r, bool signal = true);
|
||||
|
||||
virtual void InvalidateEvent(const TimeRange& range);
|
||||
|
||||
@@ -91,19 +80,8 @@ protected:
|
||||
|
||||
Project* GetProject() const;
|
||||
|
||||
struct JobIdentifier {
|
||||
TimeRange range;
|
||||
qint64 job_time;
|
||||
};
|
||||
|
||||
QList<JobIdentifier> jobs_;
|
||||
|
||||
private:
|
||||
void RemoveRangeFromJobs(const TimeRange& remove);
|
||||
|
||||
TimeRangeList invalidated_;
|
||||
|
||||
rational length_;
|
||||
TimeRangeList validated_;
|
||||
|
||||
};
|
||||
|
||||
|
||||
+157
-115
@@ -4,6 +4,7 @@
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
|
||||
#include "codec/conformmanager.h"
|
||||
#include "core.h"
|
||||
#include "node/project/project.h"
|
||||
#include "render/rendermanager.h"
|
||||
#include "render/renderprocessor.h"
|
||||
@@ -14,10 +15,7 @@ PreviewAutoCacher::PreviewAutoCacher() :
|
||||
viewer_node_(nullptr),
|
||||
has_changed_(false),
|
||||
use_custom_range_(false),
|
||||
single_frame_render_(nullptr),
|
||||
last_update_time_(0),
|
||||
ignore_next_mouse_button_(false),
|
||||
last_conform_task_(0)
|
||||
single_frame_render_(nullptr)
|
||||
{
|
||||
paused_ = !Config::Current()[QStringLiteral("AutoCacheEnabled")].toBool(),
|
||||
|
||||
@@ -63,16 +61,20 @@ void PreviewAutoCacher::SetPaused(bool paused)
|
||||
paused_ = paused;
|
||||
}
|
||||
|
||||
void GenerateHashesInternal(ViewerOutput *viewer, FrameHashCache* cache, const QVector<rational> ×, qint64 job_time)
|
||||
QVector<PreviewAutoCacher::HashData> PreviewAutoCacher::GenerateHashes(ViewerOutput *viewer, FrameHashCache* cache, const QVector<rational> ×)
|
||||
{
|
||||
std::vector<QByteArray> existing_hashes;
|
||||
QVector<HashData> hash_data(times.size());
|
||||
|
||||
QVector<QByteArray> existing_hashes;
|
||||
|
||||
for (int i=0; i<times.size(); i++) {
|
||||
const rational &time = times.at(i);
|
||||
|
||||
foreach (const rational& time, times) {
|
||||
// See if hash already exists in disk cache
|
||||
QByteArray hash = RenderManager::Hash(viewer->GetConnectedTextureOutput(), viewer->GetVideoParams(), time);
|
||||
|
||||
// Check memory list since disk checking is slow
|
||||
bool hash_exists = (std::find(existing_hashes.begin(), existing_hashes.end(), hash) != existing_hashes.end());
|
||||
bool hash_exists = existing_hashes.contains(hash);
|
||||
|
||||
if (!hash_exists) {
|
||||
hash_exists = QFileInfo::exists(cache->CachePathName(hash));
|
||||
@@ -83,40 +85,10 @@ void GenerateHashesInternal(ViewerOutput *viewer, FrameHashCache* cache, const Q
|
||||
}
|
||||
|
||||
// Set hash in FrameHashCache's thread rather than in ours to prevent race conditions
|
||||
QMetaObject::invokeMethod(cache, "SetHash", Qt::QueuedConnection,
|
||||
OLIVE_NS_ARG(rational, time),
|
||||
Q_ARG(QByteArray, hash),
|
||||
Q_ARG(qint64, job_time),
|
||||
Q_ARG(bool, hash_exists));
|
||||
}
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::GenerateHashes(ViewerOutput *viewer, FrameHashCache* cache, const QVector<rational> ×, qint64 job_time)
|
||||
{
|
||||
// Ensure number of threads doesn't exceed idealThreadCount for maximum concurrency
|
||||
int hashes_per_thread = times.size() / qMax(1, QThread::idealThreadCount()-1);
|
||||
|
||||
// Somewhat arbitrary (it felt right) number used to determine when the overhead of sending this
|
||||
// to threads will exceed the benefit of multithreading
|
||||
static const int kMinimumHashesPerThread = 500;
|
||||
if (hashes_per_thread < kMinimumHashesPerThread) {
|
||||
hashes_per_thread = kMinimumHashesPerThread;
|
||||
hash_data[i] = {time, hash, hash_exists};
|
||||
}
|
||||
|
||||
// Queue threaded tasks for each
|
||||
if (hashes_per_thread >= times.size()) {
|
||||
// Don't bother queuing in other thread, just run
|
||||
GenerateHashesInternal(viewer, cache, times, job_time);
|
||||
} else {
|
||||
QVector<QFuture<void> > threads;
|
||||
for (int i=0; i<times.size(); i+=hashes_per_thread) {
|
||||
threads.append(QtConcurrent::run(GenerateHashesInternal, viewer, cache, times.mid(i, i == times.size() - 1 ? -1 : hashes_per_thread), job_time));
|
||||
}
|
||||
|
||||
for (int i=0; i<threads.size(); i++) {
|
||||
threads[i].waitForFinished();
|
||||
}
|
||||
}
|
||||
return hash_data;
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::VideoInvalidated(const TimeRange &range)
|
||||
@@ -124,10 +96,9 @@ void PreviewAutoCacher::VideoInvalidated(const TimeRange &range)
|
||||
ClearVideoQueue();
|
||||
|
||||
// Hash these frames since that should be relatively quick.
|
||||
if (ignore_next_mouse_button_ || !(qApp->mouseButtons() & Qt::LeftButton)) {
|
||||
ignore_next_mouse_button_ = false;
|
||||
|
||||
if (!Core::instance()->EffectsSliderIsBeingDragged()) {
|
||||
invalidated_video_.insert(range);
|
||||
video_job_tracker_.insert(range, graph_changed_time_);
|
||||
|
||||
TryRender();
|
||||
}
|
||||
@@ -135,7 +106,9 @@ void PreviewAutoCacher::VideoInvalidated(const TimeRange &range)
|
||||
|
||||
void PreviewAutoCacher::AudioInvalidated(const TimeRange &range)
|
||||
{
|
||||
// ClearAudioQueue();
|
||||
// ClearAudioQueue();
|
||||
|
||||
audio_job_tracker_.insert(range, graph_changed_time_);
|
||||
|
||||
// Start jobs to re-render the audio at this range, split into 2 second chunks
|
||||
invalidated_audio_.insert(range);
|
||||
@@ -145,19 +118,33 @@ void PreviewAutoCacher::AudioInvalidated(const TimeRange &range)
|
||||
|
||||
void PreviewAutoCacher::HashesProcessed()
|
||||
{
|
||||
QFutureWatcher<void>* watcher = static_cast<QFutureWatcher<void>*>(sender());
|
||||
QFutureWatcher< QVector<HashData> >* watcher = static_cast<QFutureWatcher<QVector<HashData> >*>(sender());
|
||||
|
||||
if (hash_tasks_.contains(watcher)) {
|
||||
hash_tasks_.removeOne(watcher);
|
||||
|
||||
// Restart delayed requeue timer
|
||||
delayed_requeue_timer_.stop();
|
||||
delayed_requeue_timer_.start();
|
||||
// Set all hashes we received
|
||||
JobTime job_time = watcher->property("job").value<JobTime>();
|
||||
auto hashes = watcher->result();
|
||||
foreach (auto hash, hashes) {
|
||||
if (video_job_tracker_.isCurrent(hash.time, job_time)) {
|
||||
viewer_node_->video_frame_cache()->SetHash(hash.time, hash.hash, hash.exists);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hash_iterator_.HasNext()) {
|
||||
// Restart delayed requeue timer
|
||||
delayed_requeue_timer_.stop();
|
||||
delayed_requeue_timer_.start();
|
||||
}
|
||||
}
|
||||
|
||||
// The cacher might be waiting for this job to finish
|
||||
if (!graph_update_queue_.isEmpty()) {
|
||||
TryRender();
|
||||
} else if (hash_iterator_.HasNext()) {
|
||||
// Launch next hashes
|
||||
QueueNextHashTask();
|
||||
}
|
||||
|
||||
delete watcher;
|
||||
@@ -170,20 +157,23 @@ void PreviewAutoCacher::AudioRendered()
|
||||
if (audio_tasks_.contains(watcher)) {
|
||||
if (watcher->HasResult()) {
|
||||
const TimeRange &range = audio_tasks_.value(watcher);
|
||||
JobTime watcher_job_time = watcher->property("job").value<JobTime>();
|
||||
|
||||
TimeRangeList valid_ranges = audio_job_tracker_.getCurrentSubRanges(range, watcher_job_time);
|
||||
|
||||
AudioVisualWaveform waveform = watcher->GetTicket()->property("waveform").value<AudioVisualWaveform>();
|
||||
|
||||
viewer_node_->audio_playback_cache()->WritePCM(range,
|
||||
valid_ranges,
|
||||
watcher->Get().value<SampleBufferPtr>(),
|
||||
&waveform,
|
||||
watcher->GetTicket()->GetJobTime());
|
||||
&waveform);
|
||||
|
||||
bool pcm_is_usable = true;
|
||||
|
||||
if (watcher->GetTicket()->property("incomplete").toBool()) {
|
||||
if (last_conform_task_ > watcher->GetTicket()->GetJobTime()) {
|
||||
if (last_conform_task_ > watcher_job_time) {
|
||||
// Requeue now
|
||||
viewer_node_->audio_playback_cache()->Invalidate(range, QDateTime::currentMSecsSinceEpoch());
|
||||
viewer_node_->audio_playback_cache()->Invalidate(range);
|
||||
pcm_is_usable = false;
|
||||
} else {
|
||||
// Wait for conform
|
||||
@@ -205,19 +195,15 @@ void PreviewAutoCacher::AudioRendered()
|
||||
}
|
||||
}
|
||||
|
||||
if (track) {
|
||||
QList<TimeRange> valid_ranges = viewer_node_->audio_playback_cache()->GetValidRanges(waveform_info.range,
|
||||
watcher->GetTicket()->GetJobTime());
|
||||
if (!valid_ranges.isEmpty()) {
|
||||
// Generate visual waveform in this background thread
|
||||
track->waveform().set_channel_count(viewer_node_->GetAudioParams().channel_count());
|
||||
if (track && !valid_ranges.isEmpty()) {
|
||||
// Generate visual waveform in this background thread
|
||||
track->waveform().set_channel_count(viewer_node_->GetAudioParams().channel_count());
|
||||
|
||||
foreach (const TimeRange& r, valid_ranges) {
|
||||
track->waveform().OverwriteSums(waveform_info.waveform, r.in(), r.in() - waveform_info.range.in(), r.length());
|
||||
}
|
||||
|
||||
emit track->PreviewChanged();
|
||||
foreach (const TimeRange& r, valid_ranges) {
|
||||
track->waveform().OverwriteSums(waveform_info.waveform, r.in(), r.in() - waveform_info.range.in(), r.length());
|
||||
}
|
||||
|
||||
emit track->PreviewChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -227,7 +213,9 @@ void PreviewAutoCacher::AudioRendered()
|
||||
}
|
||||
|
||||
// The cacher might be waiting for this job to finish
|
||||
if (!graph_update_queue_.isEmpty()) {
|
||||
if (graph_update_queue_.isEmpty()) {
|
||||
QueueNextAudioTask();
|
||||
} else {
|
||||
TryRender();
|
||||
}
|
||||
|
||||
@@ -246,6 +234,7 @@ void PreviewAutoCacher::VideoRendered()
|
||||
if (!hash.isEmpty() && VideoParams::FormatIsFloat(viewer_node_->GetVideoParams().format())) {
|
||||
FramePtr frame = watcher->Get().value<FramePtr>();
|
||||
RenderTicketWatcher* w = new RenderTicketWatcher();
|
||||
w->setProperty("job", QVariant::fromValue(last_update_time_));
|
||||
w->setProperty("frame", QVariant::fromValue(frame));
|
||||
video_download_tasks_.insert(w, hash);
|
||||
connect(w, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::VideoDownloaded);
|
||||
@@ -274,6 +263,8 @@ void PreviewAutoCacher::VideoRendered()
|
||||
TryRender();
|
||||
}
|
||||
|
||||
QueueNextFrameInRange(1);
|
||||
|
||||
delete watcher;
|
||||
}
|
||||
|
||||
@@ -394,9 +385,14 @@ void PreviewAutoCacher::InsertIntoCopyMap(Node *node, Node *copy)
|
||||
Node::CopyInputs(node, copy, false);
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::UpdateGraphChangeValue()
|
||||
{
|
||||
graph_changed_time_.Acquire();
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::UpdateLastSyncedValue()
|
||||
{
|
||||
last_update_time_ = QDateTime::currentMSecsSinceEpoch();
|
||||
last_update_time_.Acquire();
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::CancelQueuedSingleFrameRender()
|
||||
@@ -442,11 +438,14 @@ void PreviewAutoCacher::ClearVideoQueue(bool hard)
|
||||
|
||||
has_changed_ = true;
|
||||
use_custom_range_ = false;
|
||||
queued_frame_iterator_.reset();
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::ClearAudioQueue(bool hard)
|
||||
{
|
||||
ClearQueueInternal(audio_tasks_, hard, &PreviewAutoCacher::AudioRendered);
|
||||
|
||||
audio_iterator_.clear();
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::ClearVideoDownloadQueue(bool hard)
|
||||
@@ -457,26 +456,31 @@ void PreviewAutoCacher::ClearVideoDownloadQueue(bool hard)
|
||||
void PreviewAutoCacher::NodeAdded(Node *node)
|
||||
{
|
||||
graph_update_queue_.append({QueuedJob::kNodeAdded, node, NodeInput(), NodeOutput()});
|
||||
UpdateGraphChangeValue();
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::NodeRemoved(Node *node)
|
||||
{
|
||||
graph_update_queue_.append({QueuedJob::kNodeRemoved, node, NodeInput(), NodeOutput()});
|
||||
UpdateGraphChangeValue();
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::EdgeAdded(const NodeOutput &output, const NodeInput &input)
|
||||
{
|
||||
graph_update_queue_.append({QueuedJob::kEdgeAdded, nullptr, input, output});
|
||||
UpdateGraphChangeValue();
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::EdgeRemoved(const NodeOutput &output, const NodeInput &input)
|
||||
{
|
||||
graph_update_queue_.append({QueuedJob::kEdgeRemoved, nullptr, input, output});
|
||||
UpdateGraphChangeValue();
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::ValueChanged(const NodeInput &input)
|
||||
{
|
||||
graph_update_queue_.append({QueuedJob::kValueChanged, nullptr, input, NodeOutput()});
|
||||
UpdateGraphChangeValue();
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::TryRender()
|
||||
@@ -493,30 +497,20 @@ void PreviewAutoCacher::TryRender()
|
||||
|
||||
// If we're here, we must be able to render
|
||||
if (!invalidated_video_.isEmpty()) {
|
||||
QVector<rational> frames = viewer_node_->video_frame_cache()->GetFrameListFromTimeRange(invalidated_video_);
|
||||
hash_iterator_ = TimeRangeListFrameIterator(invalidated_video_, viewer_node_->video_frame_cache()->GetTimebase());
|
||||
|
||||
QFutureWatcher<void>* watcher = new QFutureWatcher<void>();
|
||||
hash_tasks_.append(watcher);
|
||||
connect(watcher, &QFutureWatcher<void>::finished, this, &PreviewAutoCacher::HashesProcessed);
|
||||
watcher->setFuture(QtConcurrent::run(&PreviewAutoCacher::GenerateHashes,
|
||||
copied_viewer_node_,
|
||||
viewer_node_->video_frame_cache(),
|
||||
frames,
|
||||
last_update_time_));
|
||||
for (int i=0; i<QThread::idealThreadCount(); i++) {
|
||||
QueueNextHashTask();
|
||||
}
|
||||
|
||||
invalidated_video_.clear();
|
||||
}
|
||||
|
||||
if (!invalidated_audio_.isEmpty()) {
|
||||
foreach (const TimeRange& range, invalidated_audio_) {
|
||||
std::list<TimeRange> chunks = range.Split(30);
|
||||
audio_iterator_ = invalidated_audio_;
|
||||
|
||||
foreach (const TimeRange& r, chunks) {
|
||||
RenderTicketWatcher* watcher = new RenderTicketWatcher();
|
||||
connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::AudioRendered);
|
||||
audio_tasks_.insert(watcher, r);
|
||||
watcher->SetTicket(RenderManager::instance()->RenderAudio(copied_viewer_node_, r, RenderMode::kOffline, true));
|
||||
}
|
||||
for (int i=0; i<QThread::idealThreadCount(); i++) {
|
||||
QueueNextAudioTask();
|
||||
}
|
||||
|
||||
invalidated_audio_.clear();
|
||||
@@ -547,6 +541,7 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(const QByteArray &hash, cons
|
||||
{
|
||||
RenderTicketWatcher* watcher = new RenderTicketWatcher();
|
||||
watcher->setProperty("hash", hash);
|
||||
watcher->setProperty("job", QVariant::fromValue(last_update_time_));
|
||||
connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::VideoRendered);
|
||||
video_tasks_.insert(watcher, hash);
|
||||
watcher->SetTicket(RenderManager::instance()->RenderFrame(copied_viewer_node_,
|
||||
@@ -564,7 +559,7 @@ void PreviewAutoCacher::RequeueFrames()
|
||||
delayed_requeue_timer_.stop();
|
||||
|
||||
if (viewer_node_
|
||||
&& viewer_node_->video_frame_cache()->HasInvalidatedRanges()
|
||||
&& viewer_node_->video_frame_cache()->HasInvalidatedRanges(viewer_node_->GetVideoLength())
|
||||
&& hash_tasks_.isEmpty()
|
||||
&& has_changed_
|
||||
&& VideoParams::FormatIsFloat(viewer_node_->GetVideoParams().format())
|
||||
@@ -578,30 +573,10 @@ void PreviewAutoCacher::RequeueFrames()
|
||||
using_range = cache_range_;
|
||||
}
|
||||
|
||||
QVector<rational> invalidated_ranges = viewer_node_->video_frame_cache()->GetInvalidatedFrames(using_range);
|
||||
TimeRangeList invalidated = viewer_node_->video_frame_cache()->GetInvalidatedRanges(using_range);
|
||||
queued_frame_iterator_ = TimeRangeListFrameIterator(invalidated, viewer_node_->video_frame_cache()->GetTimebase());
|
||||
|
||||
foreach (const rational& t, invalidated_ranges) {
|
||||
const QByteArray& hash = viewer_node_->video_frame_cache()->GetHash(t);
|
||||
|
||||
RenderTicketWatcher* render_task = video_tasks_.key(hash);
|
||||
|
||||
if (t >= using_range.in()
|
||||
&& t < using_range.out()) {
|
||||
// We want this hash, if we're not already rendering, start render now
|
||||
if (!render_task && !video_download_tasks_.key(hash)) {
|
||||
// Don't render any hash more than once
|
||||
RenderFrame(hash, t, false, false);
|
||||
}
|
||||
} else if (render_task) {
|
||||
// Cancel this frame unless it's already started
|
||||
QMutexLocker locker(render_task->GetTicket()->lock());
|
||||
|
||||
if (!render_task->GetTicket()->IsRunning(false)) {
|
||||
video_tasks_.remove(render_task);
|
||||
delete render_task;
|
||||
}
|
||||
}
|
||||
}
|
||||
QueueNextFrameInRange(RenderManager::GetNumberOfIdealConcurrentJobs());
|
||||
|
||||
has_changed_ = false;
|
||||
}
|
||||
@@ -609,21 +584,16 @@ void PreviewAutoCacher::RequeueFrames()
|
||||
|
||||
void PreviewAutoCacher::ConformFinished()
|
||||
{
|
||||
last_conform_task_ = QDateTime::currentMSecsSinceEpoch();
|
||||
last_conform_task_.Acquire();
|
||||
|
||||
if (viewer_node_) {
|
||||
foreach (const TimeRange &range, audio_needing_conform_) {
|
||||
viewer_node_->audio_playback_cache()->Invalidate(range, QDateTime::currentMSecsSinceEpoch());
|
||||
viewer_node_->audio_playback_cache()->Invalidate(range);
|
||||
}
|
||||
audio_needing_conform_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::IgnoreNextMouseButton()
|
||||
{
|
||||
ignore_next_mouse_button_ = true;
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::ForceCacheRange(const TimeRange &range)
|
||||
{
|
||||
has_changed_ = true;
|
||||
@@ -671,6 +641,8 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node)
|
||||
copy_map_.clear();
|
||||
copied_viewer_node_ = nullptr;
|
||||
graph_update_queue_.clear();
|
||||
video_job_tracker_.clear();
|
||||
audio_job_tracker_.clear();
|
||||
|
||||
// Disconnect signals for future node additions/deletions
|
||||
NodeGraph* graph = viewer_node_->parent();
|
||||
@@ -709,6 +681,8 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node)
|
||||
|
||||
// Find copied viewer node
|
||||
copied_viewer_node_ = static_cast<ViewerOutput*>(copy_map_.value(viewer_node_));
|
||||
copied_viewer_node_->SetViewerVideoCacheEnabled(false);
|
||||
copied_viewer_node_->SetViewerAudioCacheEnabled(false);
|
||||
copied_color_manager_ = static_cast<ColorManager*>(copy_map_.value(viewer_node_->project()->color_manager()));
|
||||
|
||||
// Add all connections
|
||||
@@ -718,6 +692,8 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure graph change value is just before the sync value
|
||||
UpdateGraphChangeValue();
|
||||
UpdateLastSyncedValue();
|
||||
|
||||
// Connect signals for future node additions/deletions
|
||||
@@ -728,8 +704,10 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node)
|
||||
connect(graph, &NodeGraph::ValueChanged, this, &PreviewAutoCacher::ValueChanged);
|
||||
|
||||
// Copy invalidated ranges - used to determine which frames need hashing
|
||||
invalidated_video_ = viewer_node_->video_frame_cache()->GetInvalidatedRanges();
|
||||
invalidated_audio_ = viewer_node_->audio_playback_cache()->GetInvalidatedRanges();
|
||||
invalidated_video_ = viewer_node_->video_frame_cache()->GetInvalidatedRanges(viewer_node_->GetVideoLength());
|
||||
video_job_tracker_.insert(invalidated_video_, graph_changed_time_);
|
||||
invalidated_audio_ = viewer_node_->audio_playback_cache()->GetInvalidatedRanges(viewer_node_->GetAudioLength());
|
||||
audio_job_tracker_.insert(invalidated_audio_, graph_changed_time_);
|
||||
|
||||
connect(viewer_node_->video_frame_cache(),
|
||||
&PlaybackCache::Invalidated,
|
||||
@@ -775,6 +753,70 @@ void PreviewAutoCacher::ClearQueueRemoveEventInternal(QVector<RenderTicketWatche
|
||||
Q_UNUSED(it)
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::QueueNextFrameInRange(int max)
|
||||
{
|
||||
rational t;
|
||||
while (max && queued_frame_iterator_.GetNext(&t)) {
|
||||
const QByteArray& hash = viewer_node_->video_frame_cache()->GetHash(t);
|
||||
|
||||
RenderTicketWatcher* render_task = video_tasks_.key(hash);
|
||||
|
||||
// We want this hash, if we're not already rendering, start render now
|
||||
if (!render_task && !video_download_tasks_.key(hash)) {
|
||||
// Don't render any hash more than once
|
||||
RenderFrame(hash, t, false, false);
|
||||
|
||||
max--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::QueueNextHashTask()
|
||||
{
|
||||
// Magic number: dunno what the best number for this is yet
|
||||
static const int kMaxFrames = 1000;
|
||||
|
||||
QVector<rational> times(kMaxFrames);
|
||||
for (int i=0; i<kMaxFrames; i++) {
|
||||
rational r;
|
||||
if (hash_iterator_.GetNext(&r)) {
|
||||
times[i] = r;
|
||||
} else {
|
||||
times.resize(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QFutureWatcher< QVector<HashData> >* watcher = new QFutureWatcher< QVector<HashData> >();
|
||||
watcher->setProperty("job", QVariant::fromValue(last_update_time_));
|
||||
hash_tasks_.append(watcher);
|
||||
connect(watcher, &QFutureWatcher< QVector<HashData> >::finished, this, &PreviewAutoCacher::HashesProcessed);
|
||||
watcher->setFuture(QtConcurrent::run(PreviewAutoCacher::GenerateHashes,
|
||||
copied_viewer_node_,
|
||||
viewer_node_->video_frame_cache(),
|
||||
times));
|
||||
}
|
||||
|
||||
void PreviewAutoCacher::QueueNextAudioTask()
|
||||
{
|
||||
if (!audio_iterator_.isEmpty()) {
|
||||
// Copy first range in list
|
||||
TimeRange r = audio_iterator_.first();
|
||||
|
||||
// Limit to 30 seconds (FIXME: Hardcoded)
|
||||
r.set_out(qMin(r.out(), r.in() + 30));
|
||||
|
||||
// Start job
|
||||
RenderTicketWatcher* watcher = new RenderTicketWatcher();
|
||||
watcher->setProperty("job", QVariant::fromValue(last_update_time_));
|
||||
connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::AudioRendered);
|
||||
audio_tasks_.insert(watcher, r);
|
||||
watcher->SetTicket(RenderManager::instance()->RenderAudio(copied_viewer_node_, r, RenderMode::kOffline, true));
|
||||
|
||||
audio_iterator_.remove(r);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename Func>
|
||||
void PreviewAutoCacher::ClearQueueInternal(T& list, bool hard, Func member)
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "node/node.h"
|
||||
#include "node/output/viewer/viewer.h"
|
||||
#include "node/project/project.h"
|
||||
#include "render/renderjobtracker.h"
|
||||
#include "threading/threadticketwatcher.h"
|
||||
|
||||
namespace olive {
|
||||
@@ -33,16 +34,6 @@ public:
|
||||
*/
|
||||
void SetViewerNode(ViewerOutput *viewer_node);
|
||||
|
||||
/**
|
||||
* @brief If the mouse is held during the next cache invalidation, cache anyway
|
||||
*
|
||||
* By default, PreviewAutoCacher ignores invalidations that occur while the mouse is held down,
|
||||
* assuming that if the mouse is held, the user is dragging something. If you know the mouse will
|
||||
* be held during a certain action and want PreviewAutoCacher to cache anyway, call this before
|
||||
* the cache invalidates.
|
||||
*/
|
||||
void IgnoreNextMouseButton();
|
||||
|
||||
/**
|
||||
* @brief Returns whether the auto-cache is currently paused or not
|
||||
*/
|
||||
@@ -80,8 +71,6 @@ public:
|
||||
void ClearVideoDownloadQueue(bool wait = false);
|
||||
|
||||
private:
|
||||
static void GenerateHashes(ViewerOutput *viewer, FrameHashCache *cache, const QVector<rational>& times, qint64 job_time);
|
||||
|
||||
void TryRender();
|
||||
|
||||
RenderTicketWatcher *RenderFrame(const QByteArray& hash, const rational &time, bool prioritize, bool texture_only);
|
||||
@@ -104,6 +93,7 @@ private:
|
||||
|
||||
void InsertIntoCopyMap(Node* node, Node* copy);
|
||||
|
||||
void UpdateGraphChangeValue();
|
||||
void UpdateLastSyncedValue();
|
||||
|
||||
void CancelQueuedSingleFrameRender();
|
||||
@@ -115,6 +105,18 @@ private:
|
||||
void ClearQueueRemoveEventInternal(QMap<RenderTicketWatcher*, TimeRange>::iterator it);
|
||||
void ClearQueueRemoveEventInternal(QVector<RenderTicketWatcher*>::iterator it);
|
||||
|
||||
void QueueNextFrameInRange(int max);
|
||||
void QueueNextHashTask();
|
||||
void QueueNextAudioTask();
|
||||
|
||||
struct HashData {
|
||||
rational time;
|
||||
QByteArray hash;
|
||||
bool exists;
|
||||
};
|
||||
|
||||
static QVector<HashData> GenerateHashes(ViewerOutput *viewer, FrameHashCache* cache, const QVector<rational> ×);
|
||||
|
||||
class QueuedJob {
|
||||
public:
|
||||
enum Type {
|
||||
@@ -155,21 +157,27 @@ private:
|
||||
|
||||
RenderTicketPtr single_frame_render_;
|
||||
|
||||
QList<QFutureWatcher<void>*> hash_tasks_;
|
||||
QList<QFutureWatcher< QVector<HashData> >*> hash_tasks_;
|
||||
QMap<RenderTicketWatcher*, TimeRange> audio_tasks_;
|
||||
QMap<RenderTicketWatcher*, QByteArray> video_tasks_;
|
||||
QMap<RenderTicketWatcher*, QByteArray> video_download_tasks_;
|
||||
QMap<RenderTicketWatcher*, QVector<RenderTicketPtr> > video_immediate_passthroughs_;
|
||||
|
||||
qint64 last_update_time_;
|
||||
|
||||
bool ignore_next_mouse_button_;
|
||||
JobTime graph_changed_time_;
|
||||
JobTime last_update_time_;
|
||||
|
||||
QTimer delayed_requeue_timer_;
|
||||
|
||||
TimeRangeList audio_needing_conform_;
|
||||
|
||||
qint64 last_conform_task_;
|
||||
JobTime last_conform_task_;
|
||||
|
||||
RenderJobTracker video_job_tracker_;
|
||||
RenderJobTracker audio_job_tracker_;
|
||||
|
||||
TimeRangeListFrameIterator queued_frame_iterator_;
|
||||
TimeRangeListFrameIterator hash_iterator_;
|
||||
TimeRangeList audio_iterator_;
|
||||
|
||||
private slots:
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "renderjobtracker.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
void RenderJobTracker::insert(const TimeRange &range, JobTime job_time)
|
||||
{
|
||||
// First remove any ranges with this (code copied
|
||||
TimeRangeList::util_remove(&jobs_, range);
|
||||
|
||||
// Now append the job
|
||||
TimeRangeWithJob job(range, job_time);
|
||||
jobs_.append(job);
|
||||
}
|
||||
|
||||
void RenderJobTracker::insert(const TimeRangeList &ranges, JobTime job_time)
|
||||
{
|
||||
foreach (const TimeRange &r, ranges) {
|
||||
insert(r, job_time);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderJobTracker::clear()
|
||||
{
|
||||
jobs_.clear();
|
||||
}
|
||||
|
||||
bool RenderJobTracker::isCurrent(const rational &time, JobTime job_time) const
|
||||
{
|
||||
for (auto it=jobs_.crbegin(); it!=jobs_.crend(); it++) {
|
||||
if (it->Contains(time)) {
|
||||
return job_time >= it->GetJobTime();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
TimeRangeList RenderJobTracker::getCurrentSubRanges(const TimeRange &range, const JobTime &job_time) const
|
||||
{
|
||||
TimeRangeList current_ranges;
|
||||
|
||||
for (auto it=jobs_.crbegin(); it!=jobs_.crend(); it++) {
|
||||
if (job_time >= it->GetJobTime() && it->OverlapsWith(range)) {
|
||||
current_ranges.insert(it->Intersected(range));
|
||||
}
|
||||
}
|
||||
|
||||
return current_ranges;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef RENDERJOBTRACKER_H
|
||||
#define RENDERJOBTRACKER_H
|
||||
|
||||
#include "common/jobtime.h"
|
||||
#include "common/timerange.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class RenderJobTracker
|
||||
{
|
||||
public:
|
||||
RenderJobTracker() = default;
|
||||
|
||||
void insert(const TimeRange &range, JobTime job_time);
|
||||
void insert(const TimeRangeList &ranges, JobTime job_time);
|
||||
|
||||
void clear();
|
||||
|
||||
bool isCurrent(const rational &time, JobTime job_time) const;
|
||||
|
||||
TimeRangeList getCurrentSubRanges(const TimeRange &range, const JobTime &job_time) const;
|
||||
|
||||
private:
|
||||
class TimeRangeWithJob : public TimeRange
|
||||
{
|
||||
public:
|
||||
TimeRangeWithJob() = default;
|
||||
TimeRangeWithJob(const TimeRange &range, const JobTime &job_time)
|
||||
{
|
||||
set_range(range.in(), range.out());
|
||||
job_time_ = job_time;
|
||||
}
|
||||
|
||||
JobTime GetJobTime() const {return job_time_;}
|
||||
void SetJobTime(JobTime jt) {job_time_ = jt;}
|
||||
|
||||
private:
|
||||
JobTime job_time_;
|
||||
|
||||
};
|
||||
|
||||
QVector<TimeRangeWithJob> jobs_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // RENDERJOBTRACKER_H
|
||||
@@ -123,6 +123,11 @@ public:
|
||||
return backend_;
|
||||
}
|
||||
|
||||
static int GetNumberOfIdealConcurrentJobs()
|
||||
{
|
||||
return QThread::idealThreadCount();
|
||||
}
|
||||
|
||||
signals:
|
||||
|
||||
private:
|
||||
|
||||
@@ -147,9 +147,8 @@ bool ExportTask::Run()
|
||||
return success;
|
||||
}
|
||||
|
||||
void ExportTask::FrameDownloaded(FramePtr f, const QByteArray &hash, const QVector<rational> ×, qint64 job_time)
|
||||
void ExportTask::FrameDownloaded(FramePtr f, const QByteArray &hash, const QVector<rational> ×)
|
||||
{
|
||||
Q_UNUSED(job_time)
|
||||
Q_UNUSED(hash)
|
||||
|
||||
foreach (const rational& t, times) {
|
||||
@@ -179,10 +178,8 @@ void ExportTask::FrameDownloaded(FramePtr f, const QByteArray &hash, const QVect
|
||||
}
|
||||
}
|
||||
|
||||
void ExportTask::AudioDownloaded(const TimeRange &range, SampleBufferPtr samples, qint64 job_time)
|
||||
void ExportTask::AudioDownloaded(const TimeRange &range, SampleBufferPtr samples)
|
||||
{
|
||||
Q_UNUSED(job_time)
|
||||
|
||||
TimeRange adjusted_range = range;
|
||||
|
||||
if (params_.has_custom_range()) {
|
||||
|
||||
@@ -38,9 +38,9 @@ public:
|
||||
protected:
|
||||
virtual bool Run() override;
|
||||
|
||||
virtual void FrameDownloaded(FramePtr frame, const QByteArray& hash, const QVector<rational>& times, qint64 job_time) override;
|
||||
virtual void FrameDownloaded(FramePtr frame, const QByteArray& hash, const QVector<rational>& times) override;
|
||||
|
||||
virtual void AudioDownloaded(const TimeRange& range, SampleBufferPtr samples, qint64 job_time) override;
|
||||
virtual void AudioDownloaded(const TimeRange& range, SampleBufferPtr samples) override;
|
||||
|
||||
virtual void EncodeSubtitle(const SubtitleBlock *sub) override;
|
||||
|
||||
|
||||
@@ -57,13 +57,18 @@ PreCacheTask::~PreCacheTask()
|
||||
bool PreCacheTask::Run()
|
||||
{
|
||||
// Get list of invalidated ranges
|
||||
TimeRangeList video_range = viewer()->video_frame_cache()->GetInvalidatedRanges();
|
||||
TimeRange intersection;
|
||||
|
||||
// If we're caching only in-out, limit the range to that
|
||||
if (footage_->GetTimelinePoints()->workarea()->enabled()) {
|
||||
video_range = video_range.Intersects(footage_->GetTimelinePoints()->workarea()->range());
|
||||
// If we're caching only in-out, limit the range to that
|
||||
intersection = footage_->GetTimelinePoints()->workarea()->range();
|
||||
} else {
|
||||
// Otherwise use full length
|
||||
intersection = TimeRange(0, footage_->GetVideoLength());
|
||||
}
|
||||
|
||||
TimeRangeList video_range = viewer()->video_frame_cache()->GetInvalidatedRanges(intersection);
|
||||
|
||||
Render(project_->color_manager(),
|
||||
video_range,
|
||||
TimeRangeList(),
|
||||
@@ -74,7 +79,7 @@ bool PreCacheTask::Run()
|
||||
return true;
|
||||
}
|
||||
|
||||
void PreCacheTask::FrameDownloaded(FramePtr frame, const QByteArray &hash, const QVector<rational> ×, qint64 job_time)
|
||||
void PreCacheTask::FrameDownloaded(FramePtr frame, const QByteArray &hash, const QVector<rational> ×)
|
||||
{
|
||||
// Do nothing. Pre-cache essentially just creates more frames in the cache, it doesn't need to do
|
||||
// anything else.
|
||||
@@ -82,16 +87,14 @@ void PreCacheTask::FrameDownloaded(FramePtr frame, const QByteArray &hash, const
|
||||
Q_UNUSED(frame)
|
||||
Q_UNUSED(hash)
|
||||
Q_UNUSED(times)
|
||||
Q_UNUSED(job_time)
|
||||
}
|
||||
|
||||
void PreCacheTask::AudioDownloaded(const TimeRange &range, SampleBufferPtr samples, qint64 job_time)
|
||||
void PreCacheTask::AudioDownloaded(const TimeRange &range, SampleBufferPtr samples)
|
||||
{
|
||||
// Pre-cache doesn't cache any audio
|
||||
|
||||
Q_UNUSED(range)
|
||||
Q_UNUSED(samples)
|
||||
Q_UNUSED(job_time)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,9 +38,9 @@ public:
|
||||
protected:
|
||||
virtual bool Run() override;
|
||||
|
||||
virtual void FrameDownloaded(FramePtr frame, const QByteArray& hash, const QVector<rational>& times, qint64 job_time) override;
|
||||
virtual void FrameDownloaded(FramePtr frame, const QByteArray& hash, const QVector<rational>& times) override;
|
||||
|
||||
virtual void AudioDownloaded(const TimeRange& range, SampleBufferPtr samples, qint64 job_time) override;
|
||||
virtual void AudioDownloaded(const TimeRange& range, SampleBufferPtr samples) override;
|
||||
|
||||
private:
|
||||
Project* project_;
|
||||
|
||||
@@ -58,7 +58,7 @@ bool ProjectLoadTask::Run()
|
||||
// Project is newer than we support
|
||||
SetError(tr("This project is newer than this version of Olive and cannot be opened."));
|
||||
return false;
|
||||
} else if (project_version < 210122) { // Change this if we drop support for a project version
|
||||
} else if (project_version < 210528) { // Change this if we drop support for a project version
|
||||
// Project is older than we support
|
||||
SetError(tr("This project is from a version of Olive that is no longer supported in this version."));
|
||||
return false;
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <opentimelineio/serializableCollection.h>
|
||||
#include <opentimelineio/timeline.h>
|
||||
#include <opentimelineio/transition.h>
|
||||
#include <QApplication>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "node/block/clip/clip.h"
|
||||
@@ -36,7 +37,7 @@
|
||||
#include "node/project/folder/folder.h"
|
||||
#include "node/project/footage/footage.h"
|
||||
#include "node/project/sequence/sequence.h"
|
||||
#include "widget/timelinewidget/timelineundo.h"
|
||||
#include "widget/timelinewidget/undo/timelineundogeneral.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -86,7 +87,7 @@ bool LoadOTIOTask::Run()
|
||||
Sequence* sequence = new Sequence();
|
||||
sequence->SetLabel(QString::fromStdString(timeline->name()));
|
||||
sequence->setParent(project_);
|
||||
FolderAddChild(project_->root(), sequence).redo();
|
||||
FolderAddChild(project_->root(), sequence).redo_now();
|
||||
|
||||
// FIXME: As far as I know, OTIO doesn't store video/audio parameters?
|
||||
sequence->set_default_parameters();
|
||||
@@ -110,7 +111,7 @@ bool LoadOTIOTask::Run()
|
||||
|
||||
// Create track
|
||||
TimelineAddTrackCommand t(sequence->track_list(type));
|
||||
t.redo();
|
||||
t.redo_now();
|
||||
track = t.track();
|
||||
} else {
|
||||
qWarning() << "Found unknown track type:" << otio_track->kind().c_str();
|
||||
|
||||
@@ -55,8 +55,6 @@ bool RenderTask::Render(ColorManager* manager,
|
||||
double total_length = 0;
|
||||
|
||||
// Store real time before any rendering takes place
|
||||
qint64 job_time = QDateTime::currentMSecsSinceEpoch();
|
||||
|
||||
// Queue audio jobs
|
||||
foreach (const TimeRange& range, audio_range) {
|
||||
// Don't count audio progress, since it's generally a lot faster than video and is weighted at
|
||||
@@ -84,16 +82,19 @@ bool RenderTask::Render(ColorManager* manager,
|
||||
|
||||
if (!video_range.isEmpty()) {
|
||||
// Get list of discrete frames from range
|
||||
QVector<rational> times = FrameHashCache::GetFrameListFromTimeRange(video_range, video_params().frame_rate_as_time_base());
|
||||
QVector<QByteArray> hashes(times.size());
|
||||
TimeRangeListFrameIterator iterator(video_range, video_params().frame_rate_as_time_base());
|
||||
QVector<rational> times(iterator.size());
|
||||
QVector<QByteArray> hashes(iterator.size());
|
||||
|
||||
// Generate hashes
|
||||
for (int i=0; i<times.size(); i++) {
|
||||
rational r;
|
||||
for (int i=0; iterator.GetNext(&r); i++) {
|
||||
if (IsCancelled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
hashes[i] = RenderManager::instance()->Hash(viewer()->GetConnectedTextureOutput(), video_params_, times.at(i));
|
||||
times[i] = r;
|
||||
hashes[i] = RenderManager::instance()->Hash(viewer()->GetConnectedTextureOutput(), video_params_, r);
|
||||
}
|
||||
|
||||
// Filter out duplicates
|
||||
@@ -190,9 +191,7 @@ bool RenderTask::Render(ColorManager* manager,
|
||||
|
||||
TimeRange range = watcher->property("range").value<TimeRange>();
|
||||
|
||||
AudioDownloaded(range,
|
||||
watcher->Get().value<SampleBufferPtr>(),
|
||||
job_time);
|
||||
AudioDownloaded(range, watcher->Get().value<SampleBufferPtr>());
|
||||
|
||||
// Don't count audio progress, since it's generally a lot faster than video and is weighted at
|
||||
// 50%, which makes the progress bar look weird to the uninitiated
|
||||
@@ -214,7 +213,7 @@ bool RenderTask::Render(ColorManager* manager,
|
||||
|
||||
// Assume single-step video or video download ticket
|
||||
QByteArray rendered_hash = watcher->property("hash").toByteArray();
|
||||
FrameDownloaded(watcher->Get().value<FramePtr>(), rendered_hash, time_map.value(rendered_hash), job_time);
|
||||
FrameDownloaded(watcher->Get().value<FramePtr>(), rendered_hash, time_map.value(rendered_hash));
|
||||
|
||||
if (native_progress_signalling_) {
|
||||
double progress_to_add = 1.0;
|
||||
|
||||
@@ -51,9 +51,9 @@ protected:
|
||||
|
||||
virtual void DownloadFrame(QThread* thread, FramePtr frame, const QByteArray &hash);
|
||||
|
||||
virtual void FrameDownloaded(FramePtr frame, const QByteArray& hash, const QVector<rational>& times, qint64 job_time) = 0;
|
||||
virtual void FrameDownloaded(FramePtr frame, const QByteArray& hash, const QVector<rational>& times) = 0;
|
||||
|
||||
virtual void AudioDownloaded(const TimeRange& range, SampleBufferPtr samples, qint64 job_time) = 0;
|
||||
virtual void AudioDownloaded(const TimeRange& range, SampleBufferPtr samples) = 0;
|
||||
|
||||
virtual void EncodeSubtitle(const SubtitleBlock *subtitle);
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ RenderTicket::RenderTicket() :
|
||||
has_result_(false),
|
||||
finish_count_(0)
|
||||
{
|
||||
SetJobTime();
|
||||
}
|
||||
|
||||
void RenderTicket::WaitForFinished(QMutex *mutex)
|
||||
|
||||
@@ -38,16 +38,6 @@ class RenderTicket : public QObject
|
||||
public:
|
||||
RenderTicket();
|
||||
|
||||
qint64 GetJobTime() const
|
||||
{
|
||||
return job_time_;
|
||||
}
|
||||
|
||||
void SetJobTime()
|
||||
{
|
||||
job_time_ = QDateTime::currentMSecsSinceEpoch();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the ticket's current state
|
||||
*
|
||||
@@ -137,8 +127,6 @@ private:
|
||||
|
||||
QWaitCondition wait_;
|
||||
|
||||
qint64 job_time_;
|
||||
|
||||
};
|
||||
|
||||
using RenderTicketPtr = std::shared_ptr<RenderTicket>;
|
||||
|
||||
@@ -24,23 +24,39 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
MultiUndoCommand::MultiUndoCommand() :
|
||||
done_(false)
|
||||
{
|
||||
}
|
||||
|
||||
void MultiUndoCommand::redo()
|
||||
{
|
||||
for (auto it=children_.cbegin(); it!=children_.cend(); it++) {
|
||||
(*it)->redo_and_set_modified();
|
||||
if (!done_) {
|
||||
for (auto it=children_.cbegin(); it!=children_.cend(); it++) {
|
||||
(*it)->redo_and_set_modified();
|
||||
}
|
||||
done_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void MultiUndoCommand::undo()
|
||||
{
|
||||
for (auto it=children_.crbegin(); it!=children_.crend(); it++) {
|
||||
(*it)->undo_and_set_modified();
|
||||
if (done_) {
|
||||
for (auto it=children_.crbegin(); it!=children_.crend(); it++) {
|
||||
(*it)->undo_and_set_modified();
|
||||
}
|
||||
done_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
UndoCommand::UndoCommand()
|
||||
{
|
||||
prepared_ = false;
|
||||
}
|
||||
|
||||
void UndoCommand::redo_and_set_modified()
|
||||
{
|
||||
redo();
|
||||
redo_now();
|
||||
|
||||
project_ = GetRelevantProject();
|
||||
if (project_) {
|
||||
@@ -51,11 +67,26 @@ void UndoCommand::redo_and_set_modified()
|
||||
|
||||
void UndoCommand::undo_and_set_modified()
|
||||
{
|
||||
undo();
|
||||
undo_now();
|
||||
|
||||
if (project_) {
|
||||
project_->set_modified(modified_);
|
||||
}
|
||||
}
|
||||
|
||||
void UndoCommand::redo_now()
|
||||
{
|
||||
if (!prepared_) {
|
||||
prepare();
|
||||
prepared_ = true;
|
||||
}
|
||||
|
||||
redo();
|
||||
}
|
||||
|
||||
void UndoCommand::undo_now()
|
||||
{
|
||||
undo();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+16
-5
@@ -34,17 +34,19 @@ class Project;
|
||||
class UndoCommand
|
||||
{
|
||||
public:
|
||||
UndoCommand() = default;
|
||||
UndoCommand();
|
||||
|
||||
virtual ~UndoCommand(){}
|
||||
|
||||
DISABLE_COPY_MOVE(UndoCommand)
|
||||
|
||||
virtual void redo() = 0;
|
||||
virtual void undo() = 0;
|
||||
bool has_prepared() const {return prepared_;}
|
||||
void set_prepared(bool e) {prepared_ = true;}
|
||||
|
||||
void redo_now();
|
||||
void undo_now();
|
||||
|
||||
void redo_and_set_modified();
|
||||
|
||||
void undo_and_set_modified();
|
||||
|
||||
virtual Project* GetRelevantProject() const = 0;
|
||||
@@ -59,6 +61,11 @@ public:
|
||||
name_ = name;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void prepare(){}
|
||||
virtual void redo() = 0;
|
||||
virtual void undo() = 0;
|
||||
|
||||
private:
|
||||
bool modified_;
|
||||
|
||||
@@ -66,12 +73,14 @@ private:
|
||||
|
||||
Project* project_;
|
||||
|
||||
bool prepared_;
|
||||
|
||||
};
|
||||
|
||||
class MultiUndoCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
MultiUndoCommand() = default;
|
||||
MultiUndoCommand();
|
||||
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
@@ -99,6 +108,8 @@ public:
|
||||
private:
|
||||
std::vector<UndoCommand*> children_;
|
||||
|
||||
bool done_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ public:
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
@@ -51,6 +52,7 @@ public:
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
|
||||
@@ -30,8 +30,10 @@
|
||||
|
||||
namespace olive {
|
||||
|
||||
#define super TimeBasedWidget
|
||||
|
||||
NodeParamView::NodeParamView(QWidget *parent) :
|
||||
TimeBasedWidget(true, false, parent),
|
||||
super(true, false, parent),
|
||||
last_scroll_val_(0),
|
||||
focused_node_(nullptr)
|
||||
{
|
||||
@@ -194,7 +196,7 @@ void NodeParamView::DeselectNodes(const QVector<Node *> &nodes)
|
||||
|
||||
void NodeParamView::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
super::resizeEvent(event);
|
||||
|
||||
vertical_scrollbar_->setPageStep(vertical_scrollbar_->height());
|
||||
|
||||
@@ -203,14 +205,14 @@ void NodeParamView::resizeEvent(QResizeEvent *event)
|
||||
|
||||
void NodeParamView::ScaleChangedEvent(const double &scale)
|
||||
{
|
||||
TimeBasedWidget::ScaleChangedEvent(scale);
|
||||
super::ScaleChangedEvent(scale);
|
||||
|
||||
keyframe_view_->SetScale(scale);
|
||||
}
|
||||
|
||||
void NodeParamView::TimebaseChangedEvent(const rational &timebase)
|
||||
{
|
||||
TimeBasedWidget::TimebaseChangedEvent(timebase);
|
||||
super::TimebaseChangedEvent(timebase);
|
||||
|
||||
keyframe_view_->SetTimebase(timebase);
|
||||
|
||||
@@ -223,7 +225,7 @@ void NodeParamView::TimebaseChangedEvent(const rational &timebase)
|
||||
|
||||
void NodeParamView::TimeChangedEvent(const int64_t ×tamp)
|
||||
{
|
||||
TimeBasedWidget::TimeChangedEvent(timestamp);
|
||||
super::TimeChangedEvent(timestamp);
|
||||
|
||||
keyframe_view_->SetTime(timestamp);
|
||||
|
||||
|
||||
@@ -62,13 +62,15 @@ NodeParamViewItem::NodeParamViewItem(Node *node, QWidget *parent) :
|
||||
|
||||
this->setWidget(body_);
|
||||
|
||||
// Use dummy QWidget to retain width when not expanded (QDockWidget seems to ignore the titlebar
|
||||
// size hints and will shrink as small as possible if the body is hidden)
|
||||
hidden_body_ = new QWidget(this);
|
||||
|
||||
connect(node_, &Node::LabelChanged, this, &NodeParamViewItem::Retranslate);
|
||||
|
||||
setBackgroundRole(QPalette::Base);
|
||||
setAutoFillBackground(true);
|
||||
|
||||
setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
|
||||
|
||||
setFocusPolicy(Qt::ClickFocus);
|
||||
|
||||
Retranslate();
|
||||
@@ -88,7 +90,7 @@ void NodeParamViewItem::SetTime(const rational &time)
|
||||
|
||||
void NodeParamViewItem::SetTimebase(const rational& timebase)
|
||||
{
|
||||
body_->SetTimebase(timebase);
|
||||
body_->SetTimebase(timebase);
|
||||
}
|
||||
|
||||
Node *NodeParamViewItem::GetNode() const
|
||||
@@ -140,7 +142,7 @@ void NodeParamViewItem::Retranslate()
|
||||
|
||||
void NodeParamViewItem::SetExpanded(bool e)
|
||||
{
|
||||
body_->setVisible(e);
|
||||
setWidget(e ? body_ : hidden_body_);
|
||||
title_bar_->SetExpanded(e);
|
||||
|
||||
emit ExpandedChanged(e);
|
||||
@@ -537,10 +539,10 @@ void NodeParamViewItemBody::ToggleArrayExpanded()
|
||||
}
|
||||
}
|
||||
|
||||
void NodeParamViewItemBody::SetTimebase(const rational& timebase)
|
||||
void NodeParamViewItemBody::SetTimebase(const rational& timebase)
|
||||
{
|
||||
foreach (const InputUI& ui_obj, input_ui_map_) {
|
||||
ui_obj.widget_bridge->SetTimebase(timebase);
|
||||
ui_obj.widget_bridge->SetTimebase(timebase);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -216,6 +216,8 @@ private:
|
||||
|
||||
NodeParamViewItemBody* body_;
|
||||
|
||||
QWidget *hidden_body_;
|
||||
|
||||
Node* node_;
|
||||
|
||||
rational time_;
|
||||
|
||||
@@ -35,6 +35,7 @@ public:
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
@@ -51,6 +52,7 @@ public:
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
@@ -70,6 +72,7 @@ public:
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
@@ -90,6 +93,7 @@ public:
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
@@ -109,6 +113,7 @@ public:
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
@@ -128,6 +133,7 @@ public:
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
@@ -146,6 +152,7 @@ public:
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override;
|
||||
|
||||
@@ -62,6 +62,11 @@ void NodeParamViewWidgetBridge::SetTime(const rational &time)
|
||||
}
|
||||
}
|
||||
|
||||
int GetSliderCount(NodeValue::Type type)
|
||||
{
|
||||
return NodeValue::get_number_of_keyframe_tracks(type);
|
||||
}
|
||||
|
||||
void NodeParamViewWidgetBridge::CreateWidgets()
|
||||
{
|
||||
if (input_.IsArray() && input_.element() == -1) {
|
||||
@@ -73,7 +78,8 @@ void NodeParamViewWidgetBridge::CreateWidgets()
|
||||
} else {
|
||||
|
||||
// We assume the first data type is the "primary" type
|
||||
switch (input_.GetDataType()) {
|
||||
NodeValue::Type t = input_.GetDataType();
|
||||
switch (t) {
|
||||
// None of these inputs have applicable UI widgets
|
||||
case NodeValue::kNone:
|
||||
case NodeValue::kTexture:
|
||||
@@ -89,29 +95,17 @@ void NodeParamViewWidgetBridge::CreateWidgets()
|
||||
CreateSliders<IntegerSlider>(1);
|
||||
break;
|
||||
}
|
||||
case NodeValue::kFloat:
|
||||
{
|
||||
CreateSliders<FloatSlider>(1);
|
||||
break;
|
||||
}
|
||||
case NodeValue::kRational:
|
||||
{
|
||||
CreateSliders<RationalSlider>(1);
|
||||
break;
|
||||
}
|
||||
case NodeValue::kFloat:
|
||||
case NodeValue::kVec2:
|
||||
{
|
||||
CreateSliders<FloatSlider>(2);
|
||||
break;
|
||||
}
|
||||
case NodeValue::kVec3:
|
||||
{
|
||||
CreateSliders<FloatSlider>(3);
|
||||
break;
|
||||
}
|
||||
case NodeValue::kVec4:
|
||||
{
|
||||
CreateSliders<FloatSlider>(4);
|
||||
CreateSliders<FloatSlider>(GetSliderCount(t));
|
||||
break;
|
||||
}
|
||||
case NodeValue::kCombo:
|
||||
@@ -410,6 +404,7 @@ void NodeParamViewWidgetBridge::CreateSliders(int count)
|
||||
T* fs = new T();
|
||||
fs->SliderBase::SetDefaultValue(input_.GetSplitDefaultValueForTrack(i));
|
||||
fs->SetLadderElementCount(2);
|
||||
fs->SetIsEffectsSlider(true);
|
||||
widgets_.append(fs);
|
||||
connect(fs, &T::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
|
||||
}
|
||||
|
||||
@@ -23,8 +23,12 @@ set(OLIVE_SOURCES
|
||||
widget/nodeview/nodeviewedge.h
|
||||
widget/nodeview/nodeviewitem.cpp
|
||||
widget/nodeview/nodeviewitem.h
|
||||
widget/nodeview/nodeviewminimap.cpp
|
||||
widget/nodeview/nodeviewminimap.h
|
||||
widget/nodeview/nodeviewscene.cpp
|
||||
widget/nodeview/nodeviewscene.h
|
||||
widget/nodeview/nodeviewtoolbar.cpp
|
||||
widget/nodeview/nodeviewtoolbar.h
|
||||
widget/nodeview/nodeviewundo.cpp
|
||||
widget/nodeview/nodeviewundo.h
|
||||
PARENT_SCOPE
|
||||
|
||||
+861
-134
File diff suppressed because it is too large
Load Diff
+108
-16
@@ -27,8 +27,10 @@
|
||||
#include "node/graph.h"
|
||||
#include "node/nodecopypaste.h"
|
||||
#include "nodeviewedge.h"
|
||||
#include "nodeviewminimap.h"
|
||||
#include "nodeviewscene.h"
|
||||
#include "widget/handmovableview/handmovableview.h"
|
||||
#include "widget/menu/menu.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -51,10 +53,9 @@ public:
|
||||
return graph_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the graph to view
|
||||
*/
|
||||
void SetGraph(NodeGraph* graph);
|
||||
void SetGraph(NodeGraph *graph, const QVector<Node *> &nodes);
|
||||
|
||||
void ClearGraph();
|
||||
|
||||
/**
|
||||
* @brief Delete selected nodes from graph (user-friendly/undoable)
|
||||
@@ -64,8 +65,8 @@ public:
|
||||
void SelectAll();
|
||||
void DeselectAll();
|
||||
|
||||
void Select(QVector<Node *> nodes);
|
||||
void SelectWithDependencies(QVector<Node *> nodes);
|
||||
void Select(QVector<Node *> nodes, bool center_view_on_item);
|
||||
void SelectWithDependencies(QVector<Node *> nodes, bool center_view_on_item);
|
||||
|
||||
void CopySelected(bool cut);
|
||||
void Paste();
|
||||
@@ -78,6 +79,19 @@ public:
|
||||
|
||||
void ZoomOut();
|
||||
|
||||
public slots:
|
||||
void SetMiniMapEnabled(bool e)
|
||||
{
|
||||
minimap_->setVisible(e);
|
||||
}
|
||||
|
||||
void ShowAddMenu()
|
||||
{
|
||||
Menu *m = CreateAddMenu(nullptr);
|
||||
m->exec(QCursor::pos());
|
||||
delete m;
|
||||
}
|
||||
|
||||
signals:
|
||||
void NodesSelected(const QVector<Node*>& nodes);
|
||||
|
||||
@@ -90,8 +104,14 @@ protected:
|
||||
virtual void mouseMoveEvent(QMouseEvent *event) override;
|
||||
virtual void mouseReleaseEvent(QMouseEvent* event) override;
|
||||
|
||||
virtual void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
virtual void ZoomIntoCursorPosition(QWheelEvent *event, double multiplier, const QPointF &cursor_pos) override;
|
||||
|
||||
virtual bool event(QEvent *event) override;
|
||||
|
||||
virtual bool eventFilter(QObject *object, QEvent *event) override;
|
||||
|
||||
private:
|
||||
void AttachNodesToCursor(const QVector<Node *> &nodes);
|
||||
|
||||
@@ -108,17 +128,32 @@ private:
|
||||
|
||||
void ZoomFromKeyboard(double multiplier);
|
||||
|
||||
bool DetermineIfNodeIsFloatingInContext(Node *node, Node *context, Node *source, const Node::OutputConnections &removed_edges, const Node::OutputConnection &added_edge);
|
||||
void UpdateContextsFromEdgeRemove(MultiUndoCommand *command, const Node::OutputConnections &remove_edges);
|
||||
void UpdateContextsFromEdgeAdd(MultiUndoCommand *command, const Node::OutputConnection &added_edge, const Node::OutputConnections &removed_edges = Node::OutputConnections());
|
||||
void RecursivelyAddNodeToContext(MultiUndoCommand *command, Node *node, Node *context);
|
||||
void RecursivelyRemoveFloatingNodeFromContext(MultiUndoCommand *command, Node *node, Node *context, Node *source, const Node::OutputConnections &removed_edges, const Node::OutputConnection &added_edge, bool prevent_removing);
|
||||
|
||||
QPointF GetEstimatedPositionForContext(NodeViewItem *item, Node *context) const;
|
||||
|
||||
Menu *CreateAddMenu(Menu *parent);
|
||||
|
||||
void CreateNewEdge(NodeViewItem *output_item);
|
||||
|
||||
NodeViewItem *UpdateNodeItem(Node *node, bool ignore_own_context = false);
|
||||
|
||||
class NodeViewAttachNodesToCursor : public UndoCommand
|
||||
{
|
||||
public:
|
||||
NodeViewAttachNodesToCursor(NodeView* view, const QVector<Node*>& nodes);
|
||||
|
||||
virtual Project * GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override;
|
||||
|
||||
virtual Project * GetRelevantProject() const override;
|
||||
|
||||
private:
|
||||
NodeView* view_;
|
||||
|
||||
@@ -126,6 +161,8 @@ private:
|
||||
|
||||
};
|
||||
|
||||
NodeViewMiniMap *minimap_;
|
||||
|
||||
NodeGraph* graph_;
|
||||
|
||||
struct AttachedItem {
|
||||
@@ -133,6 +170,33 @@ private:
|
||||
QPointF original_pos;
|
||||
};
|
||||
|
||||
class NodeViewItemPreventRemovingCommand : public UndoCommand
|
||||
{
|
||||
public:
|
||||
NodeViewItemPreventRemovingCommand(NodeView *view, Node *node, bool prevent_removing) :
|
||||
view_(view),
|
||||
node_(node),
|
||||
new_prevent_removing_(prevent_removing)
|
||||
{}
|
||||
|
||||
virtual Project * GetRelevantProject() const override
|
||||
{
|
||||
return node_->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
NodeView *view_;
|
||||
Node *node_;
|
||||
bool new_prevent_removing_;
|
||||
bool old_prevent_removing_;
|
||||
|
||||
};
|
||||
|
||||
QList<AttachedItem> attached_items_;
|
||||
|
||||
NodeViewEdge* drop_edge_;
|
||||
@@ -147,21 +211,34 @@ private:
|
||||
|
||||
NodeViewScene scene_;
|
||||
|
||||
QVector<Node*> selected_nodes_;
|
||||
MultiUndoCommand* paste_command_;
|
||||
|
||||
QVector<Block*> selected_blocks_;
|
||||
QVector<Node*> selected_nodes_;
|
||||
|
||||
enum FilterMode {
|
||||
kFilterShowAll,
|
||||
kFilterShowSelectedBlocks
|
||||
kFilterShowSelective
|
||||
};
|
||||
|
||||
struct Position {
|
||||
Node *node;
|
||||
QPointF original_item_pos;
|
||||
};
|
||||
|
||||
QMap<NodeViewItem *, Position> positions_;
|
||||
|
||||
FilterMode filter_mode_;
|
||||
|
||||
QVector<Node*> filter_nodes_;
|
||||
QVector<Node*> last_set_filter_nodes_;
|
||||
QMap<Node*, QPointF> context_offsets_;
|
||||
|
||||
double scale_;
|
||||
|
||||
bool create_edge_already_exists_;
|
||||
|
||||
bool queue_reposition_contexts_;
|
||||
|
||||
static const double kMinimumScale;
|
||||
|
||||
private slots:
|
||||
@@ -185,11 +262,6 @@ private slots:
|
||||
*/
|
||||
void ContextMenuSetDirection(QAction* action);
|
||||
|
||||
/**
|
||||
* @brief Receiver for auto-position descendents menu action
|
||||
*/
|
||||
void AutoPositionDescendents();
|
||||
|
||||
/**
|
||||
* @brief Receiver for the user changing the filter
|
||||
*/
|
||||
@@ -200,6 +272,26 @@ private slots:
|
||||
*/
|
||||
void OpenSelectedNodeInViewer();
|
||||
|
||||
//void AddNode(Node *node);
|
||||
void RemoveNode(Node *node);
|
||||
void AddEdge(const NodeOutput& output, const NodeInput& input);
|
||||
void RemoveEdge(const NodeOutput& output, const NodeInput& input);
|
||||
|
||||
void AddNodePosition(Node *node, Node *relative);
|
||||
void RemoveNodePosition(Node *node, Node *relative);
|
||||
|
||||
void UpdateSceneBoundingRect();
|
||||
|
||||
void CenterOnItemsBoundingRect();
|
||||
|
||||
void RepositionMiniMap();
|
||||
|
||||
void UpdateViewportOnMiniMap();
|
||||
|
||||
void MoveToScenePoint(const QPointF &pos);
|
||||
|
||||
void RepositionContexts();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -80,80 +80,11 @@ void NodeViewEdge::SetHighlighted(bool e)
|
||||
|
||||
void NodeViewEdge::SetPoints(const QPointF &start, const QPointF &end, bool input_is_expanded)
|
||||
{
|
||||
QPainterPath path;
|
||||
path.moveTo(start);
|
||||
cached_start_ = start;
|
||||
cached_end_ = end;
|
||||
cached_input_is_expanded_ = input_is_expanded;
|
||||
|
||||
double angle = qAtan2(end.y() - start.y(), end.x() - start.x());
|
||||
|
||||
if (curved_) {
|
||||
|
||||
double half_x = lerp(start.x(), end.x(), 0.5);
|
||||
double half_y = lerp(start.y(), end.y(), 0.5);
|
||||
|
||||
QPointF cp1, cp2;
|
||||
|
||||
if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal) {
|
||||
cp1 = QPointF(half_x, start.y());
|
||||
} else {
|
||||
cp1 = QPointF(start.x(), half_y);
|
||||
}
|
||||
|
||||
if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal || input_is_expanded) {
|
||||
cp2 = QPointF(half_x, end.y());
|
||||
} else {
|
||||
cp2 = QPointF(end.x(), half_y);
|
||||
}
|
||||
|
||||
path.cubicTo(cp1, cp2, end);
|
||||
|
||||
if (!qFuzzyCompare(start.x(), end.x())) {
|
||||
double continue_x = end.x() - qCos(angle)*arrow_size_;
|
||||
|
||||
double x1, x2, x3, x4, y1, y2, y3, y4;
|
||||
if (start.x() < end.x()) {
|
||||
x1 = start.x();
|
||||
x2 = cp1.x();
|
||||
x3 = cp2.x();
|
||||
x4 = end.x();
|
||||
y1 = start.y();
|
||||
y2 = cp1.y();
|
||||
y3 = cp2.y();
|
||||
y4 = end.y();
|
||||
} else {
|
||||
x1 = end.x();
|
||||
x2 = cp2.x();
|
||||
x3 = cp1.x();
|
||||
x4 = start.x();
|
||||
y1 = end.y();
|
||||
y2 = cp2.y();
|
||||
y3 = cp1.y();
|
||||
y4 = start.y();
|
||||
}
|
||||
|
||||
double t = Bezier::CubicXtoT(continue_x, x1, x2, x3, x4);
|
||||
double y = Bezier::CubicTtoY(y1, y2, y3, y4, t);
|
||||
|
||||
angle = qAtan2(end.y() - y, end.x() - continue_x);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
path.lineTo(end);
|
||||
|
||||
}
|
||||
|
||||
setPath(path);
|
||||
|
||||
const double arrow_angle = 150.0 * 3.141592 / 180.0;
|
||||
QVector<QPointF> arrow_points(4);
|
||||
arrow_points[0] = end;
|
||||
arrow_points[1] = end + QPointF(qCos(angle + arrow_angle) * arrow_size_, qSin(angle + arrow_angle) * arrow_size_);
|
||||
arrow_points[2] = end + QPointF(qCos(angle - arrow_angle) * arrow_size_, qSin(angle - arrow_angle) * arrow_size_);
|
||||
arrow_points[3] = end;
|
||||
|
||||
arrow_ = QPolygonF(arrow_points);
|
||||
arrow_bounding_rect_ = arrow_.boundingRect();
|
||||
arrow_bounding_rect_.adjust(-arrow_size_, -arrow_size_, arrow_size_, arrow_size_);
|
||||
UpdateCurve();
|
||||
}
|
||||
|
||||
void NodeViewEdge::SetFlowDirection(NodeViewCommon::FlowDirection dir)
|
||||
@@ -169,7 +100,7 @@ void NodeViewEdge::SetCurved(bool e)
|
||||
{
|
||||
curved_ = e;
|
||||
|
||||
update();
|
||||
UpdateCurve();
|
||||
}
|
||||
|
||||
void NodeViewEdge::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *)
|
||||
@@ -219,4 +150,81 @@ void NodeViewEdge::Init()
|
||||
arrow_size_ = QFontMetrics(QFont()).height() / 2;
|
||||
}
|
||||
|
||||
void NodeViewEdge::UpdateCurve()
|
||||
{
|
||||
const QPointF &start = cached_start_;
|
||||
const QPointF &end = cached_end_;
|
||||
const bool input_is_expanded = cached_input_is_expanded_;
|
||||
|
||||
QPainterPath path;
|
||||
path.moveTo(start);
|
||||
|
||||
double angle = qAtan2(end.y() - start.y(), end.x() - start.x());
|
||||
|
||||
if (curved_) {
|
||||
|
||||
double half_x = lerp(start.x(), end.x(), 0.5);
|
||||
double half_y = lerp(start.y(), end.y(), 0.5);
|
||||
|
||||
QPointF cp1, cp2;
|
||||
|
||||
if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal) {
|
||||
cp1 = QPointF(half_x, start.y());
|
||||
} else {
|
||||
cp1 = QPointF(start.x(), half_y);
|
||||
}
|
||||
|
||||
if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal || input_is_expanded) {
|
||||
cp2 = QPointF(half_x, end.y());
|
||||
} else {
|
||||
cp2 = QPointF(end.x(), half_y);
|
||||
}
|
||||
|
||||
path.cubicTo(cp1, cp2, end);
|
||||
|
||||
if (!qFuzzyCompare(start.x(), end.x())) {
|
||||
double continue_x = end.x() - qCos(angle)*arrow_size_;
|
||||
|
||||
double x1 = start.x();
|
||||
double x2 = cp1.x();
|
||||
double x3 = cp2.x();
|
||||
double x4 = end.x();
|
||||
double y1 = start.y();
|
||||
double y2 = cp1.y();
|
||||
double y3 = cp2.y();
|
||||
double y4 = end.y();
|
||||
|
||||
if (start.x() >= end.x()) {
|
||||
std::swap(x1, x4);
|
||||
std::swap(x2, x3);
|
||||
std::swap(y1, y4);
|
||||
std::swap(y2, y3);
|
||||
}
|
||||
|
||||
double t = Bezier::CubicXtoT(continue_x, x1, x2, x3, x4);
|
||||
double y = Bezier::CubicTtoY(y1, y2, y3, y4, t);
|
||||
|
||||
angle = qAtan2(end.y() - y, end.x() - continue_x);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
path.lineTo(end);
|
||||
|
||||
}
|
||||
|
||||
setPath(path);
|
||||
|
||||
const double arrow_angle = 150.0 * M_PI / 180.0;
|
||||
QVector<QPointF> arrow_points(4);
|
||||
arrow_points[0] = end;
|
||||
arrow_points[1] = end + QPointF(qCos(angle + arrow_angle) * arrow_size_, qSin(angle + arrow_angle) * arrow_size_);
|
||||
arrow_points[2] = end + QPointF(qCos(angle - arrow_angle) * arrow_size_, qSin(angle - arrow_angle) * arrow_size_);
|
||||
arrow_points[3] = end;
|
||||
|
||||
arrow_ = QPolygonF(arrow_points);
|
||||
arrow_bounding_rect_ = arrow_.boundingRect();
|
||||
arrow_bounding_rect_.adjust(-arrow_size_, -arrow_size_, arrow_size_, arrow_size_);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -122,6 +122,8 @@ protected:
|
||||
private:
|
||||
void Init();
|
||||
|
||||
void UpdateCurve();
|
||||
|
||||
NodeOutput output_;
|
||||
|
||||
NodeInput input_;
|
||||
@@ -148,6 +150,10 @@ private:
|
||||
|
||||
QRectF arrow_bounding_rect_;
|
||||
|
||||
QPointF cached_start_;
|
||||
QPointF cached_end_;
|
||||
bool cached_input_is_expanded_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -45,7 +45,8 @@ NodeViewItem::NodeViewItem(QGraphicsItem *parent) :
|
||||
expanded_(false),
|
||||
hide_titlebar_(false),
|
||||
highlighted_index_(-1),
|
||||
flow_dir_(NodeViewCommon::kLeftToRight)
|
||||
flow_dir_(NodeViewCommon::kLeftToRight),
|
||||
prevent_removing_(false)
|
||||
{
|
||||
// Set flags for this widget
|
||||
setFlag(QGraphicsItem::ItemIsMovable);
|
||||
@@ -64,57 +65,20 @@ NodeViewItem::NodeViewItem(QGraphicsItem *parent) :
|
||||
|
||||
title_bar_rect_ = QRectF(-widget_width/2, -widget_height/2, widget_width, widget_height);
|
||||
setRect(title_bar_rect_);
|
||||
|
||||
output_triangle_.resize(3);
|
||||
}
|
||||
|
||||
QPointF NodeViewItem::GetNodePosition() const
|
||||
{
|
||||
QPointF node_pos;
|
||||
|
||||
qreal adjusted_x = pos().x() / DefaultItemHorizontalPadding();
|
||||
qreal adjusted_y = pos().y() / DefaultItemVerticalPadding();
|
||||
|
||||
switch (flow_dir_) {
|
||||
case NodeViewCommon::kLeftToRight:
|
||||
node_pos.setX(adjusted_x);
|
||||
node_pos.setY(adjusted_y);
|
||||
break;
|
||||
case NodeViewCommon::kRightToLeft:
|
||||
node_pos.setX(-adjusted_x);
|
||||
node_pos.setY(adjusted_y);
|
||||
break;
|
||||
case NodeViewCommon::kTopToBottom:
|
||||
node_pos.setX(adjusted_y);
|
||||
node_pos.setY(adjusted_x);
|
||||
break;
|
||||
case NodeViewCommon::kBottomToTop:
|
||||
node_pos.setX(-adjusted_y);
|
||||
node_pos.setY(adjusted_x);
|
||||
break;
|
||||
}
|
||||
|
||||
return node_pos;
|
||||
return ScreenToNodePoint(pos(), flow_dir_);
|
||||
}
|
||||
|
||||
void NodeViewItem::SetNodePosition(const QPointF &pos)
|
||||
{
|
||||
switch (flow_dir_) {
|
||||
case NodeViewCommon::kLeftToRight:
|
||||
setPos(pos.x() * DefaultItemHorizontalPadding(),
|
||||
pos.y() * DefaultItemVerticalPadding());
|
||||
break;
|
||||
case NodeViewCommon::kRightToLeft:
|
||||
setPos(-pos.x() * DefaultItemHorizontalPadding(),
|
||||
pos.y() * DefaultItemVerticalPadding());
|
||||
break;
|
||||
case NodeViewCommon::kTopToBottom:
|
||||
setPos(pos.y() * DefaultItemHorizontalPadding(),
|
||||
pos.x() * DefaultItemVerticalPadding());
|
||||
break;
|
||||
case NodeViewCommon::kBottomToTop:
|
||||
setPos(pos.y() * DefaultItemHorizontalPadding(),
|
||||
-pos.x() * DefaultItemVerticalPadding());
|
||||
break;
|
||||
}
|
||||
cached_node_pos_ = pos;
|
||||
|
||||
UpdateNodePosition();
|
||||
}
|
||||
|
||||
int NodeViewItem::DefaultTextPadding()
|
||||
@@ -129,7 +93,7 @@ int NodeViewItem::DefaultItemHeight()
|
||||
|
||||
int NodeViewItem::DefaultItemWidth()
|
||||
{
|
||||
return QtUtils::QFontMetricsWidth(QFontMetrics(QFont()), "HHHHHHHHHHHH");;
|
||||
return QtUtils::QFontMetricsWidth(QFontMetrics(QFont()), "HHHHHHHHHHHHHHHH");;
|
||||
}
|
||||
|
||||
int NodeViewItem::DefaultItemBorder()
|
||||
@@ -137,24 +101,88 @@ int NodeViewItem::DefaultItemBorder()
|
||||
return QFontMetrics(QFont()).height() / 12;
|
||||
}
|
||||
|
||||
qreal NodeViewItem::DefaultItemHorizontalPadding() const
|
||||
QPointF NodeViewItem::NodeToScreenPoint(QPointF p, NodeViewCommon::FlowDirection direction)
|
||||
{
|
||||
if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal) {
|
||||
switch (direction) {
|
||||
case NodeViewCommon::kLeftToRight:
|
||||
// NodeGraphs are always left-to-right internally, no need to translate
|
||||
break;
|
||||
case NodeViewCommon::kRightToLeft:
|
||||
// Invert X value
|
||||
p.setX(-p.x());
|
||||
break;
|
||||
case NodeViewCommon::kTopToBottom:
|
||||
// Swap X/Y
|
||||
p = QPointF(p.y(), p.x());
|
||||
break;
|
||||
case NodeViewCommon::kBottomToTop:
|
||||
// Swap X/Y and invert Y
|
||||
p = QPointF(p.y(), -p.x());
|
||||
break;
|
||||
}
|
||||
|
||||
// Multiply by item sizes for this direction
|
||||
p.setX(p.x() * DefaultItemHorizontalPadding(direction));
|
||||
p.setY(p.y() * DefaultItemVerticalPadding(direction));
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
QPointF NodeViewItem::ScreenToNodePoint(QPointF p, NodeViewCommon::FlowDirection direction)
|
||||
{
|
||||
// Divide by item sizes for this direction
|
||||
p.setX(p.x() / DefaultItemHorizontalPadding(direction));
|
||||
p.setY(p.y() / DefaultItemVerticalPadding(direction));
|
||||
|
||||
switch (direction) {
|
||||
case NodeViewCommon::kLeftToRight:
|
||||
// NodeGraphs are always left-to-right internally, no need to translate
|
||||
break;
|
||||
case NodeViewCommon::kRightToLeft:
|
||||
// Invert X value
|
||||
p.setX(-p.x());
|
||||
break;
|
||||
case NodeViewCommon::kTopToBottom:
|
||||
// Swap X/Y
|
||||
p = QPointF(p.y(), p.x());
|
||||
break;
|
||||
case NodeViewCommon::kBottomToTop:
|
||||
// Swap X/Y and invert Y
|
||||
p = QPointF(-p.y(), p.x());
|
||||
break;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
qreal NodeViewItem::DefaultItemHorizontalPadding(NodeViewCommon::FlowDirection dir)
|
||||
{
|
||||
if (NodeViewCommon::GetFlowOrientation(dir) == Qt::Horizontal) {
|
||||
return DefaultItemWidth() * 1.5;
|
||||
} else {
|
||||
return DefaultItemWidth() * 1.25;
|
||||
}
|
||||
}
|
||||
|
||||
qreal NodeViewItem::DefaultItemVerticalPadding() const
|
||||
qreal NodeViewItem::DefaultItemVerticalPadding(NodeViewCommon::FlowDirection dir)
|
||||
{
|
||||
if (NodeViewCommon::GetFlowOrientation(flow_dir_) == Qt::Horizontal) {
|
||||
if (NodeViewCommon::GetFlowOrientation(dir) == Qt::Horizontal) {
|
||||
return DefaultItemHeight() * 1.5;
|
||||
} else {
|
||||
return DefaultItemHeight() * 2.0;
|
||||
}
|
||||
}
|
||||
|
||||
qreal NodeViewItem::DefaultItemHorizontalPadding() const
|
||||
{
|
||||
return DefaultItemHorizontalPadding(flow_dir_);
|
||||
}
|
||||
|
||||
qreal NodeViewItem::DefaultItemVerticalPadding() const
|
||||
{
|
||||
return DefaultItemVerticalPadding(flow_dir_);
|
||||
}
|
||||
|
||||
void NodeViewItem::AddEdge(NodeViewEdge *edge)
|
||||
{
|
||||
edges_.append(edge);
|
||||
@@ -192,8 +220,6 @@ void NodeViewItem::SetNode(Node *n)
|
||||
node_inputs_.append(input);
|
||||
}
|
||||
}
|
||||
|
||||
SetNodePosition(node_->GetPosition());
|
||||
}
|
||||
|
||||
update();
|
||||
@@ -317,6 +343,41 @@ void NodeViewItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti
|
||||
painter->setBrush(Qt::NoBrush);
|
||||
|
||||
painter->drawRect(rect());
|
||||
|
||||
// Draw output triangle
|
||||
painter->setPen(Qt::NoPen);
|
||||
painter->setBrush(app_pal.color(QPalette::Text));
|
||||
int triangle_sz = title_bar_rect_.height() / 2;
|
||||
int triangle_sz_half = triangle_sz / 2;
|
||||
|
||||
switch (flow_dir_) {
|
||||
case NodeViewCommon::kLeftToRight:
|
||||
// Triangle pointing right
|
||||
output_triangle_[0] = QPointF(rect().right(), rect().center().y() - triangle_sz_half);
|
||||
output_triangle_[1] = QPointF(rect().right() + triangle_sz_half, rect().center().y());
|
||||
output_triangle_[2] = QPointF(rect().right(), rect().center().y() + triangle_sz_half);
|
||||
break;
|
||||
case NodeViewCommon::kTopToBottom:
|
||||
// Triangle pointing down
|
||||
output_triangle_[0] = QPointF(rect().center().x() - triangle_sz_half, rect().bottom());
|
||||
output_triangle_[1] = QPointF(rect().center().x(), rect().bottom() + triangle_sz_half);
|
||||
output_triangle_[2] = QPointF(rect().center().x() + triangle_sz_half, rect().bottom());
|
||||
break;
|
||||
case NodeViewCommon::kBottomToTop:
|
||||
// Triangle pointing up
|
||||
output_triangle_[0] = QPointF(rect().center().x() - triangle_sz_half, rect().top());
|
||||
output_triangle_[1] = QPointF(rect().center().x(), rect().top() - triangle_sz_half);
|
||||
output_triangle_[2] = QPointF(rect().center().x() + triangle_sz_half, rect().top());
|
||||
break;
|
||||
case NodeViewCommon::kRightToLeft:
|
||||
// Triangle pointing left
|
||||
output_triangle_[0] = QPointF(rect().left(), rect().center().y() - triangle_sz_half);
|
||||
output_triangle_[1] = QPointF(rect().left() - triangle_sz_half, rect().center().y());
|
||||
output_triangle_[2] = QPointF(rect().left(), rect().center().y() + triangle_sz_half);
|
||||
break;
|
||||
}
|
||||
|
||||
painter->drawPolygon(output_triangle_);
|
||||
}
|
||||
|
||||
void NodeViewItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
@@ -352,10 +413,6 @@ void NodeViewItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
|
||||
QVariant NodeViewItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
|
||||
{
|
||||
if (change == ItemPositionHasChanged && node_) {
|
||||
node_->blockSignals(true);
|
||||
node_->SetPosition(GetNodePosition());
|
||||
node_->blockSignals(false);
|
||||
|
||||
ReadjustAllEdges();
|
||||
}
|
||||
|
||||
@@ -472,6 +529,8 @@ QPointF NodeViewItem::GetOutputPoint(const QString& output) const
|
||||
void NodeViewItem::SetFlowDirection(NodeViewCommon::FlowDirection dir)
|
||||
{
|
||||
flow_dir_ = dir;
|
||||
|
||||
UpdateNodePosition();
|
||||
}
|
||||
|
||||
QPointF NodeViewItem::GetInputPointInternal(int index, const QPointF& source_pos) const
|
||||
@@ -496,4 +555,9 @@ QPointF NodeViewItem::GetInputPointInternal(int index, const QPointF& source_pos
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewItem::UpdateNodePosition()
|
||||
{
|
||||
setPos(NodeToScreenPoint(cached_node_pos_, flow_dir_));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -95,8 +95,12 @@ public:
|
||||
|
||||
static int DefaultItemBorder();
|
||||
|
||||
qreal DefaultItemHorizontalPadding() const;
|
||||
static QPointF NodeToScreenPoint(QPointF p, NodeViewCommon::FlowDirection direction);
|
||||
static QPointF ScreenToNodePoint(QPointF p, NodeViewCommon::FlowDirection direction);
|
||||
|
||||
static qreal DefaultItemHorizontalPadding(NodeViewCommon::FlowDirection dir);
|
||||
static qreal DefaultItemVerticalPadding(NodeViewCommon::FlowDirection dir);
|
||||
qreal DefaultItemHorizontalPadding() const;
|
||||
qreal DefaultItemVerticalPadding() const;
|
||||
|
||||
void AddEdge(NodeViewEdge* edge);
|
||||
@@ -111,6 +115,21 @@ public:
|
||||
|
||||
void SetHighlightedIndex(int index);
|
||||
|
||||
void SetPreventRemoving(bool e)
|
||||
{
|
||||
prevent_removing_ = e;
|
||||
}
|
||||
|
||||
bool GetPreventRemoving() const
|
||||
{
|
||||
return prevent_removing_;
|
||||
}
|
||||
|
||||
const QPolygonF &GetOutputTriangle() const
|
||||
{
|
||||
return output_triangle_;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
|
||||
|
||||
@@ -136,6 +155,11 @@ private:
|
||||
*/
|
||||
QPointF GetInputPointInternal(int index, const QPointF &source_pos) const;
|
||||
|
||||
/**
|
||||
* @brief Internal update function when logical position changes
|
||||
*/
|
||||
void UpdateNodePosition();
|
||||
|
||||
/**
|
||||
* @brief Reference to attached Node
|
||||
*/
|
||||
@@ -167,6 +191,12 @@ private:
|
||||
|
||||
QVector<NodeViewEdge*> edges_;
|
||||
|
||||
QPointF cached_node_pos_;
|
||||
|
||||
bool prevent_removing_;
|
||||
|
||||
QPolygonF output_triangle_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "nodeviewminimap.h"
|
||||
|
||||
#include <QMouseEvent>
|
||||
|
||||
namespace olive {
|
||||
|
||||
#define super QGraphicsView
|
||||
|
||||
NodeViewMiniMap::NodeViewMiniMap(NodeViewScene *scene, QWidget *parent) :
|
||||
super(parent),
|
||||
resizing_(false)
|
||||
{
|
||||
connect(scene, &QGraphicsScene::sceneRectChanged, this, &NodeViewMiniMap::SceneChanged);
|
||||
setScene(scene);
|
||||
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
setViewportUpdateMode(FullViewportUpdate);
|
||||
setFrameShape(QFrame::Panel);
|
||||
setFrameShadow(QFrame::Plain);
|
||||
|
||||
QMetaObject::invokeMethod(this, &NodeViewMiniMap::SetDefaultSize, Qt::QueuedConnection);
|
||||
|
||||
resize_triangle_sz_ = fontMetrics().height() / 2;
|
||||
}
|
||||
|
||||
void NodeViewMiniMap::SetViewportRect(const QPolygonF &rect)
|
||||
{
|
||||
viewport_rect_ = rect;
|
||||
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void NodeViewMiniMap::drawForeground(QPainter *painter, const QRectF &rect)
|
||||
{
|
||||
super::drawForeground(painter, rect);
|
||||
|
||||
QColor viewport_color = palette().text().color();
|
||||
|
||||
// Draw resize triangle
|
||||
painter->save();
|
||||
painter->resetTransform();
|
||||
|
||||
QPointF triangle[3] = {QPointF(0, 0), QPointF(resize_triangle_sz_, 0), QPointF(0, resize_triangle_sz_)};
|
||||
painter->setBrush(viewport_color);
|
||||
painter->setPen(viewport_color);
|
||||
painter->drawPolygon(triangle, 3);
|
||||
|
||||
painter->restore();
|
||||
|
||||
// Draw viewport rectangle
|
||||
viewport_color.setAlphaF(0.25);
|
||||
painter->setBrush(viewport_color);
|
||||
|
||||
painter->drawPolygon(viewport_rect_);
|
||||
}
|
||||
|
||||
void NodeViewMiniMap::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
super::resizeEvent(event);
|
||||
|
||||
emit Resized();
|
||||
|
||||
SceneChanged(sceneRect());
|
||||
}
|
||||
|
||||
void NodeViewMiniMap::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
if (event->pos().x() <= resize_triangle_sz_ && event->pos().y() <= resize_triangle_sz_) {
|
||||
// Resizing!
|
||||
resizing_ = true;
|
||||
resize_anchor_ = QCursor::pos();
|
||||
} else {
|
||||
EmitMoveSignal(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewMiniMap::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->buttons() & Qt::LeftButton) {
|
||||
if (resizing_) {
|
||||
QPointF movement = QCursor::pos() - resize_anchor_;
|
||||
resize(QSize(width() - movement.x(), height() - movement.y()));
|
||||
resize_anchor_ = QCursor::pos();
|
||||
} else {
|
||||
EmitMoveSignal(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewMiniMap::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
resizing_ = false;
|
||||
}
|
||||
|
||||
void NodeViewMiniMap::SceneChanged(const QRectF &bounding)
|
||||
{
|
||||
double x_scale = double(this->width()) / bounding.width();
|
||||
double y_scale = double(this->height()) / bounding.height();
|
||||
|
||||
double min_scale = qMin(x_scale, y_scale);
|
||||
|
||||
QTransform transform;
|
||||
transform.scale(min_scale, min_scale);
|
||||
|
||||
setTransform(transform);
|
||||
}
|
||||
|
||||
void NodeViewMiniMap::SetDefaultSize()
|
||||
{
|
||||
if (parentWidget()) {
|
||||
resize(parentWidget()->width()/4, parentWidget()->height()/4);
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewMiniMap::EmitMoveSignal(QMouseEvent *event)
|
||||
{
|
||||
emit MoveToScenePoint(mapToScene(event->pos()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef NODEVIEWMINIMAP_H
|
||||
#define NODEVIEWMINIMAP_H
|
||||
|
||||
#include <QGraphicsView>
|
||||
|
||||
#include "nodeviewscene.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
class NodeViewMiniMap : public QGraphicsView
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
NodeViewMiniMap(NodeViewScene *scene, QWidget *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void SetViewportRect(const QPolygonF &rect);
|
||||
|
||||
signals:
|
||||
void Resized();
|
||||
|
||||
void MoveToScenePoint(const QPointF &pos);
|
||||
|
||||
protected:
|
||||
virtual void drawForeground(QPainter *painter, const QRectF &rect) override;
|
||||
|
||||
virtual void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
virtual void mousePressEvent(QMouseEvent *event) override;
|
||||
virtual void mouseMoveEvent(QMouseEvent *event) override;
|
||||
virtual void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
virtual void mouseDoubleClickEvent(QMouseEvent *event) override{}
|
||||
|
||||
private slots:
|
||||
void SceneChanged(const QRectF &bounding);
|
||||
|
||||
void SetDefaultSize();
|
||||
|
||||
private:
|
||||
void EmitMoveSignal(QMouseEvent *event);
|
||||
|
||||
int resize_triangle_sz_;
|
||||
|
||||
QPolygonF viewport_rect_;
|
||||
|
||||
bool resizing_;
|
||||
|
||||
QPoint resize_anchor_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // NODEVIEWMINIMAP_H
|
||||
@@ -43,9 +43,6 @@ void NodeViewScene::SetFlowDirection(NodeViewCommon::FlowDirection direction)
|
||||
QHash<Node*, NodeViewItem*>::const_iterator i;
|
||||
for (i=item_map_.constBegin(); i!=item_map_.constEnd(); i++) {
|
||||
i.value()->SetFlowDirection(direction_);
|
||||
|
||||
// Update position too
|
||||
i.value()->SetNodePosition(i.key()->GetPosition());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +65,10 @@ void NodeViewScene::clear()
|
||||
// deleted. Calling this function appears to update the internal cache and prevent this.
|
||||
selectedItems();
|
||||
|
||||
qDeleteAll(item_map_);
|
||||
for (auto it=item_map_.cbegin(); it!=item_map_.cend(); it++) {
|
||||
DisconnectNode(it.key());
|
||||
delete it.value();
|
||||
}
|
||||
item_map_.clear();
|
||||
|
||||
qDeleteAll(edges_);
|
||||
@@ -150,7 +150,7 @@ QVector<NodeViewEdge *> NodeViewScene::GetSelectedEdges() const
|
||||
return edges;
|
||||
}
|
||||
|
||||
void NodeViewScene::AddNode(Node* node)
|
||||
NodeViewItem* NodeViewScene::AddNode(Node* node)
|
||||
{
|
||||
NodeViewItem* item = new NodeViewItem();
|
||||
|
||||
@@ -160,32 +160,38 @@ void NodeViewScene::AddNode(Node* node)
|
||||
addItem(item);
|
||||
item_map_.insert(node, item);
|
||||
|
||||
connect(node, &Node::PositionChanged, this, &NodeViewScene::NodePositionChanged);
|
||||
connect(node, &Node::LabelChanged, this, &NodeViewScene::NodeAppearanceChanged);
|
||||
connect(node, &Node::ColorChanged, this, &NodeViewScene::NodeAppearanceChanged);
|
||||
ConnectNode(node);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void NodeViewScene::RemoveNode(Node *node)
|
||||
{
|
||||
disconnect(node, &Node::ColorChanged, this, &NodeViewScene::NodeAppearanceChanged);
|
||||
disconnect(node, &Node::LabelChanged, this, &NodeViewScene::NodeAppearanceChanged);
|
||||
disconnect(node, &Node::PositionChanged, this, &NodeViewScene::NodePositionChanged);
|
||||
DisconnectNode(node);
|
||||
|
||||
delete item_map_.take(node);
|
||||
}
|
||||
|
||||
void NodeViewScene::AddEdge(const NodeOutput &output, const NodeInput &input)
|
||||
NodeViewEdge* NodeViewScene::AddEdge(const NodeOutput &output, const NodeInput &input)
|
||||
{
|
||||
AddEdgeInternal(output, input, NodeToUIObject(output.node()), NodeToUIObject(input.node()));
|
||||
NodeViewEdge *edge = EdgeToUIObject(output, input);
|
||||
|
||||
if (!edge) {
|
||||
edge = AddEdgeInternal(output, input, NodeToUIObject(output.node()), NodeToUIObject(input.node()));
|
||||
}
|
||||
|
||||
return edge;
|
||||
}
|
||||
|
||||
void NodeViewScene::RemoveEdge(const NodeOutput &output, const NodeInput &input)
|
||||
{
|
||||
NodeViewEdge* edge = EdgeToUIObject(output, input);
|
||||
edge->from_item()->RemoveEdge(edge);
|
||||
edge->to_item()->RemoveEdge(edge);
|
||||
edges_.removeOne(edge);
|
||||
delete edge;
|
||||
if (edge) {
|
||||
edge->from_item()->RemoveEdge(edge);
|
||||
edge->to_item()->RemoveEdge(edge);
|
||||
edges_.removeOne(edge);
|
||||
delete edge;
|
||||
}
|
||||
}
|
||||
|
||||
int NodeViewScene::DetermineWeight(Node *n)
|
||||
@@ -195,7 +201,7 @@ int NodeViewScene::DetermineWeight(Node *n)
|
||||
int weight = 0;
|
||||
|
||||
foreach (Node* i, inputs) {
|
||||
if (i->GetRoutesTo(n) == 1) {
|
||||
if (i->GetNumberOfRoutesTo(n) == 1) {
|
||||
weight += DetermineWeight(i);
|
||||
}
|
||||
}
|
||||
@@ -203,7 +209,7 @@ int NodeViewScene::DetermineWeight(Node *n)
|
||||
return qMax(1, weight);
|
||||
}
|
||||
|
||||
void NodeViewScene::AddEdgeInternal(const NodeOutput& output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to)
|
||||
NodeViewEdge* NodeViewScene::AddEdgeInternal(const NodeOutput& output, const NodeInput& input, NodeViewItem *from, NodeViewItem *to)
|
||||
{
|
||||
NodeViewEdge* edge_ui = new NodeViewEdge(output, input, from, to);
|
||||
|
||||
@@ -215,6 +221,20 @@ void NodeViewScene::AddEdgeInternal(const NodeOutput& output, const NodeInput& i
|
||||
|
||||
addItem(edge_ui);
|
||||
edges_.append(edge_ui);
|
||||
|
||||
return edge_ui;
|
||||
}
|
||||
|
||||
void NodeViewScene::ConnectNode(Node *n)
|
||||
{
|
||||
connect(n, &Node::LabelChanged, this, &NodeViewScene::NodeAppearanceChanged);
|
||||
connect(n, &Node::ColorChanged, this, &NodeViewScene::NodeAppearanceChanged);
|
||||
}
|
||||
|
||||
void NodeViewScene::DisconnectNode(Node *n)
|
||||
{
|
||||
disconnect(n, &Node::ColorChanged, this, &NodeViewScene::NodeAppearanceChanged);
|
||||
disconnect(n, &Node::LabelChanged, this, &NodeViewScene::NodeAppearanceChanged);
|
||||
}
|
||||
|
||||
Qt::Orientation NodeViewScene::GetFlowOrientation() const
|
||||
@@ -227,39 +247,6 @@ NodeViewCommon::FlowDirection NodeViewScene::GetFlowDirection() const
|
||||
return direction_;
|
||||
}
|
||||
|
||||
void NodeViewScene::ReorganizeFrom(Node* n)
|
||||
{
|
||||
QVector<Node*> immediates = n->GetImmediateDependencies();
|
||||
|
||||
if (immediates.isEmpty()) {
|
||||
// Nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
QPointF parent_pos = n->GetPosition();
|
||||
|
||||
int weight_count = DetermineWeight(n);
|
||||
|
||||
qreal child_x = parent_pos.x() - 1.0;
|
||||
qreal children_height = weight_count-1;
|
||||
qreal children_y = parent_pos.y() - children_height * 0.5;
|
||||
|
||||
int weight_counter = 0;
|
||||
|
||||
foreach (Node* i, immediates) {
|
||||
if (i->GetRoutesTo(n) == 1) {
|
||||
int weight = DetermineWeight(i);
|
||||
|
||||
i->SetPosition(QPointF(child_x,
|
||||
children_y + weight_counter + (weight - 1) * 0.5));
|
||||
|
||||
weight_counter += weight;
|
||||
|
||||
ReorganizeFrom(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewScene::SetEdgesAreCurved(bool curved)
|
||||
{
|
||||
if (curved_edges_ != curved) {
|
||||
@@ -271,12 +258,6 @@ void NodeViewScene::SetEdgesAreCurved(bool curved)
|
||||
}
|
||||
}
|
||||
|
||||
void NodeViewScene::NodePositionChanged(const QPointF &pos)
|
||||
{
|
||||
// Update node's internal position
|
||||
item_map_.value(static_cast<Node*>(sender()))->SetNodePosition(pos);
|
||||
}
|
||||
|
||||
void NodeViewScene::NodeAppearanceChanged()
|
||||
{
|
||||
// Force item to update
|
||||
|
||||
@@ -79,8 +79,6 @@ public:
|
||||
return curved_edges_;
|
||||
}
|
||||
|
||||
void ReorganizeFrom(Node* n);
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief Slot when a Node is added to a graph (SetGraph() connects this)
|
||||
@@ -88,7 +86,7 @@ public slots:
|
||||
* This should NEVER be called directly, only connected to a NodeGraph. To add a Node to the NodeGraph
|
||||
* use NodeGraph::AddNode().
|
||||
*/
|
||||
void AddNode(Node* node);
|
||||
NodeViewItem *AddNode(Node* node);
|
||||
|
||||
/**
|
||||
* @brief Slot when a Node is removed from a graph (SetGraph() connects this)
|
||||
@@ -98,7 +96,7 @@ public slots:
|
||||
*/
|
||||
void RemoveNode(Node* node);
|
||||
|
||||
void AddEdge(const NodeOutput& output, const NodeInput& input);
|
||||
NodeViewEdge *AddEdge(const NodeOutput& output, const NodeInput& input);
|
||||
void RemoveEdge(const NodeOutput& output, const NodeInput& input);
|
||||
|
||||
/**
|
||||
@@ -109,7 +107,11 @@ public slots:
|
||||
private:
|
||||
static int DetermineWeight(Node* n);
|
||||
|
||||
void AddEdgeInternal(const NodeOutput &output, const NodeInput &input, NodeViewItem* from, NodeViewItem* to);
|
||||
NodeViewEdge* AddEdgeInternal(const NodeOutput &output, const NodeInput &input, NodeViewItem* from, NodeViewItem* to);
|
||||
|
||||
void ConnectNode(Node *n);
|
||||
|
||||
void DisconnectNode(Node *n);
|
||||
|
||||
QHash<Node*, NodeViewItem*> item_map_;
|
||||
|
||||
@@ -122,11 +124,6 @@ private:
|
||||
bool curved_edges_;
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* @brief Receiver for whenever a node position changes
|
||||
*/
|
||||
void NodePositionChanged(const QPointF& pos);
|
||||
|
||||
/**
|
||||
* @brief Receiver for when a node's label has changed
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#include "nodeviewtoolbar.h"
|
||||
|
||||
#include <QEvent>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
#include "ui/icons/icons.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
#define super QWidget
|
||||
|
||||
NodeViewToolBar::NodeViewToolBar(QWidget *parent) :
|
||||
QWidget(parent)
|
||||
{
|
||||
QHBoxLayout *layout = new QHBoxLayout(this);
|
||||
layout->setMargin(0);
|
||||
|
||||
add_node_btn_ = new QPushButton();
|
||||
connect(add_node_btn_, &QPushButton::clicked, this, &NodeViewToolBar::AddNodeClicked);
|
||||
layout->addWidget(add_node_btn_);
|
||||
|
||||
minimap_btn_ = new QPushButton();
|
||||
minimap_btn_->setCheckable(true);
|
||||
connect(minimap_btn_, &QPushButton::clicked, this, &NodeViewToolBar::MiniMapEnabledToggled);
|
||||
layout->addWidget(minimap_btn_);
|
||||
|
||||
layout->addStretch();
|
||||
|
||||
Retranslate();
|
||||
UpdateIcons();
|
||||
}
|
||||
|
||||
void NodeViewToolBar::changeEvent(QEvent *e)
|
||||
{
|
||||
if (e->type() == QEvent::LanguageChange) {
|
||||
Retranslate();
|
||||
} else if (e->type() == QEvent::StyleChange) {
|
||||
UpdateIcons();
|
||||
}
|
||||
super::changeEvent(e);
|
||||
}
|
||||
|
||||
void NodeViewToolBar::Retranslate()
|
||||
{
|
||||
add_node_btn_->setToolTip(tr("Add Node"));
|
||||
minimap_btn_->setText(tr("Mini-Map"));
|
||||
minimap_btn_->setToolTip(tr("Toggle Mini-Map"));
|
||||
}
|
||||
|
||||
void NodeViewToolBar::UpdateIcons()
|
||||
{
|
||||
add_node_btn_->setIcon(icon::Add);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef NODEVIEWTOOLBAR_H
|
||||
#define NODEVIEWTOOLBAR_H
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QWidget>
|
||||
|
||||
namespace olive {
|
||||
|
||||
class NodeViewToolBar : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
NodeViewToolBar(QWidget *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void SetMiniMapEnabled(bool e)
|
||||
{
|
||||
minimap_btn_->setChecked(e);
|
||||
}
|
||||
|
||||
signals:
|
||||
void AddNodeClicked();
|
||||
|
||||
void MiniMapEnabledToggled(bool e);
|
||||
|
||||
protected:
|
||||
virtual void changeEvent(QEvent *e) override;
|
||||
|
||||
private:
|
||||
void Retranslate();
|
||||
|
||||
void UpdateIcons();
|
||||
|
||||
QPushButton *add_node_btn_;
|
||||
|
||||
QPushButton *minimap_btn_;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // NODEVIEWTOOLBAR_H
|
||||
@@ -21,7 +21,6 @@
|
||||
#include "nodeviewundo.h"
|
||||
|
||||
#include "node/project/sequence/sequence.h"
|
||||
#include "widget/timelinewidget/timelineundo.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -44,7 +43,7 @@ void NodeEdgeAddCommand::redo()
|
||||
remove_command_ = new NodeEdgeRemoveCommand(input_.GetConnectedOutput(), input_);
|
||||
}
|
||||
|
||||
remove_command_->redo();
|
||||
remove_command_->redo_now();
|
||||
}
|
||||
|
||||
Node::ConnectEdge(output_, input_);
|
||||
@@ -55,7 +54,7 @@ void NodeEdgeAddCommand::undo()
|
||||
Node::DisconnectEdge(output_, input_);
|
||||
|
||||
if (remove_command_) {
|
||||
remove_command_->undo();
|
||||
remove_command_->undo_now();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +124,7 @@ void NodeCopyInputsCommand::redo()
|
||||
Node::CopyInputs(src_, dest_, include_connections_);
|
||||
}
|
||||
|
||||
void NodeRemoveAndDisconnectCommand::prep()
|
||||
void NodeRemoveAndDisconnectCommand::prepare()
|
||||
{
|
||||
command_ = new MultiUndoCommand();
|
||||
|
||||
@@ -142,6 +141,8 @@ void NodeRemoveAndDisconnectCommand::prep()
|
||||
for (const Node::OutputConnection& conn : node_->output_connections()) {
|
||||
command_->add_child(new NodeEdgeRemoveCommand(conn.first, conn.second));
|
||||
}
|
||||
|
||||
command_->add_child(new NodeRemovePositionFromAllContextsCommand(node_));
|
||||
}
|
||||
|
||||
void NodeRenameCommand::AddNode(Node *node, const QString &new_name)
|
||||
|
||||
@@ -39,6 +39,7 @@ public:
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
@@ -61,6 +62,7 @@ public:
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
@@ -80,6 +82,7 @@ public:
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
@@ -95,8 +98,7 @@ public:
|
||||
NodeRemoveAndDisconnectCommand(Node* node) :
|
||||
node_(node),
|
||||
graph_(nullptr),
|
||||
command_(nullptr),
|
||||
prepped_(false)
|
||||
command_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -110,13 +112,11 @@ public:
|
||||
return dynamic_cast<Project*>(graph_);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void prepare() override;
|
||||
|
||||
virtual void redo() override
|
||||
{
|
||||
if (!prepped_) {
|
||||
prep();
|
||||
prepped_ = true;
|
||||
}
|
||||
|
||||
command_->redo();
|
||||
|
||||
graph_ = node_->parent();
|
||||
@@ -132,8 +132,6 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void prep();
|
||||
|
||||
QObject memory_manager_;
|
||||
|
||||
Node* node_;
|
||||
@@ -141,16 +139,13 @@ private:
|
||||
|
||||
MultiUndoCommand* command_;
|
||||
|
||||
bool prepped_;
|
||||
|
||||
};
|
||||
|
||||
class NodeRemoveWithExclusiveDependenciesAndDisconnect : public UndoCommand {
|
||||
public:
|
||||
NodeRemoveWithExclusiveDependenciesAndDisconnect(Node* node) :
|
||||
node_(node),
|
||||
command_(nullptr),
|
||||
prepped_(false)
|
||||
command_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -168,23 +163,8 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
virtual void redo() override
|
||||
{
|
||||
if (!prepped_) {
|
||||
prep();
|
||||
prepped_ = true;
|
||||
}
|
||||
|
||||
command_->redo();
|
||||
}
|
||||
|
||||
virtual void undo() override
|
||||
{
|
||||
command_->undo();
|
||||
}
|
||||
|
||||
private:
|
||||
void prep()
|
||||
protected:
|
||||
virtual void prepare() override
|
||||
{
|
||||
command_ = new MultiUndoCommand();
|
||||
|
||||
@@ -197,9 +177,19 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
virtual void redo() override
|
||||
{
|
||||
command_->redo();
|
||||
}
|
||||
|
||||
virtual void undo() override
|
||||
{
|
||||
command_->undo();
|
||||
}
|
||||
|
||||
private:
|
||||
Node* node_;
|
||||
MultiUndoCommand* command_;
|
||||
bool prepped_;
|
||||
|
||||
};
|
||||
|
||||
@@ -209,12 +199,13 @@ public:
|
||||
Node* dest,
|
||||
bool include_connections);
|
||||
|
||||
virtual Project* GetRelevantProject() const override {return nullptr;}
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override {}
|
||||
|
||||
virtual Project* GetRelevantProject() const override {return nullptr;}
|
||||
|
||||
private:
|
||||
const Node* src_;
|
||||
|
||||
@@ -238,6 +229,7 @@ public:
|
||||
return a_->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override
|
||||
{
|
||||
if (link_) {
|
||||
@@ -278,6 +270,7 @@ public:
|
||||
return node_->project();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void redo() override
|
||||
{
|
||||
unlinked_ = node_->links();
|
||||
@@ -334,12 +327,13 @@ public:
|
||||
|
||||
void AddNode(Node* node, const QString& new_name);
|
||||
|
||||
virtual Project * GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
|
||||
virtual void undo() override;
|
||||
|
||||
virtual Project * GetRelevantProject() const override;
|
||||
|
||||
private:
|
||||
QVector<Node*> nodes_;
|
||||
|
||||
|
||||
@@ -134,6 +134,7 @@ void ProjectExplorer::AddView(QAbstractItemView *view)
|
||||
view->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
connect(view, &QAbstractItemView::clicked, this, &ProjectExplorer::ItemClickedSlot);
|
||||
connect(view, &QAbstractItemView::doubleClicked, this, &ProjectExplorer::ItemDoubleClickedSlot);
|
||||
connect(view->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ProjectExplorer::ViewSelectionChanged);
|
||||
connect(view, SIGNAL(DoubleClickedEmptyArea()), this, SLOT(ViewEmptyAreaDoubleClickedSlot()));
|
||||
stacked_widget_->addWidget(view);
|
||||
}
|
||||
@@ -509,6 +510,28 @@ void ProjectExplorer::ContextMenuStartProxy(QAction *a)
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectExplorer::ViewSelectionChanged()
|
||||
{
|
||||
QItemSelectionModel *model = static_cast<QItemSelectionModel *>(sender());
|
||||
|
||||
QModelIndexList selection = model->selectedIndexes();
|
||||
|
||||
QVector<Node *> nodes;
|
||||
|
||||
foreach (const QModelIndex &index, selection) {
|
||||
Node *sel = static_cast<Node*>(sort_model_.mapToSource(index).internalPointer());
|
||||
if (!nodes.contains(sel)) {
|
||||
nodes.append(sel);
|
||||
}
|
||||
}
|
||||
|
||||
if (nodes.isEmpty()) {
|
||||
nodes.append(get_root());
|
||||
}
|
||||
|
||||
emit SelectionChanged(nodes);
|
||||
}
|
||||
|
||||
Project *ProjectExplorer::project() const
|
||||
{
|
||||
return model_.project();
|
||||
|
||||
@@ -100,6 +100,8 @@ signals:
|
||||
*/
|
||||
void DoubleClickedItem(Node* item);
|
||||
|
||||
void SelectionChanged(const QVector<Node *> &selected);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Get all the blocks that solely rely on an input node
|
||||
@@ -185,6 +187,8 @@ private slots:
|
||||
|
||||
void ContextMenuStartProxy(QAction* a);
|
||||
|
||||
void ViewSelectionChanged();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include "common/qtutils.h"
|
||||
#include "config/config.h"
|
||||
#include "core.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -34,7 +35,8 @@ NumericSliderBase::NumericSliderBase(QWidget *parent) :
|
||||
has_max_(false),
|
||||
dragged_diff_(0),
|
||||
drag_multiplier_(1.0),
|
||||
setting_drag_value_(false)
|
||||
setting_drag_value_(false),
|
||||
is_effects_slider_(false)
|
||||
{
|
||||
// Numeric sliders are draggable, so we have a cursor that indicates that
|
||||
setCursor(Qt::SizeHorCursor);
|
||||
@@ -60,6 +62,10 @@ void NumericSliderBase::LabelPressed()
|
||||
|
||||
connect(drag_ladder_, &SliderLadder::DraggedByValue, this, &NumericSliderBase::LadderDragged);
|
||||
connect(drag_ladder_, &SliderLadder::Released, this, &NumericSliderBase::LadderReleased);
|
||||
|
||||
if (is_effects_slider_) {
|
||||
Core::instance()->SetEffectsSliderIsBeingDragged(true);
|
||||
}
|
||||
}
|
||||
|
||||
void NumericSliderBase::LadderDragged(int value, double multiplier)
|
||||
@@ -90,6 +96,10 @@ void NumericSliderBase::LadderDragged(int value, double multiplier)
|
||||
|
||||
void NumericSliderBase::LadderReleased()
|
||||
{
|
||||
if (is_effects_slider_) {
|
||||
Core::instance()->SetEffectsSliderIsBeingDragged(false);
|
||||
}
|
||||
|
||||
drag_ladder_->deleteLater();
|
||||
drag_ladder_ = nullptr;
|
||||
dragged_diff_ = 0;
|
||||
|
||||
@@ -42,6 +42,8 @@ public:
|
||||
|
||||
bool IsDragging() const;
|
||||
|
||||
void SetIsEffectsSlider(bool e) {is_effects_slider_ = e;}
|
||||
|
||||
protected:
|
||||
const QVariant& GetOffset() const
|
||||
{
|
||||
@@ -87,6 +89,8 @@ private:
|
||||
|
||||
bool setting_drag_value_;
|
||||
|
||||
bool is_effects_slider_;
|
||||
|
||||
private slots:
|
||||
void LabelPressed();
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
#include "config/config.h"
|
||||
#include "core.h"
|
||||
#include "node/project/sequence/sequence.h"
|
||||
#include "widget/timelinewidget/timelineundo.h"
|
||||
#include "widget/timelinewidget/undo/timelineundoworkarea.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
|
||||
@@ -148,6 +148,7 @@ private:
|
||||
|
||||
virtual Project* GetRelevantProject() const override;
|
||||
|
||||
protected:
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
|
||||
@@ -16,14 +16,13 @@
|
||||
|
||||
add_subdirectory(trackview)
|
||||
add_subdirectory(tool)
|
||||
add_subdirectory(undo)
|
||||
add_subdirectory(view)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
widget/timelinewidget/timelineandtrackview.cpp
|
||||
widget/timelinewidget/timelineandtrackview.h
|
||||
widget/timelinewidget/timelineundo.cpp
|
||||
widget/timelinewidget/timelineundo.h
|
||||
widget/timelinewidget/timelinewidget.cpp
|
||||
widget/timelinewidget/timelinewidget.h
|
||||
widget/timelinewidget/timelinewidgetselections.cpp
|
||||
|
||||
@@ -1,375 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2021 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "timelineundo.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
BlockTrimCommand::BlockTrimCommand(Track *track, Block* block, rational new_length, Timeline::MovementMode mode) :
|
||||
prepped_(false),
|
||||
track_(track),
|
||||
block_(block),
|
||||
new_length_(new_length),
|
||||
mode_(mode),
|
||||
deleted_adjacent_command_(nullptr),
|
||||
trim_is_a_roll_edit_(false)
|
||||
{
|
||||
}
|
||||
|
||||
void BlockTrimCommand::redo()
|
||||
{
|
||||
if (!prepped_) {
|
||||
prep();
|
||||
prepped_ = true;
|
||||
}
|
||||
|
||||
if (doing_nothing_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Begin an operation since we'll be doing a lot
|
||||
track_->BeginOperation();
|
||||
|
||||
// Determine how much time to invalidate
|
||||
TimeRange invalidate_range;
|
||||
|
||||
if (mode_ == Timeline::kTrimIn) {
|
||||
invalidate_range = TimeRange(block_->in(), block_->in() + trim_diff_);
|
||||
block_->set_length_and_media_in(new_length_);
|
||||
} else {
|
||||
invalidate_range = TimeRange(block_->out(), block_->out() - trim_diff_);
|
||||
block_->set_length_and_media_out(new_length_);
|
||||
}
|
||||
|
||||
if (needs_adjacent_) {
|
||||
if (we_created_adjacent_) {
|
||||
// Add adjacent and insert it
|
||||
adjacent_->setParent(track_->parent());
|
||||
|
||||
if (mode_ == Timeline::kTrimIn) {
|
||||
track_->InsertBlockBefore(adjacent_, block_);
|
||||
} else {
|
||||
track_->InsertBlockAfter(adjacent_, block_);
|
||||
}
|
||||
} else if (we_removed_adjacent_) {
|
||||
track_->RippleRemoveBlock(adjacent_);
|
||||
|
||||
// It no longer inputs/outputs anything, remove it
|
||||
if (remove_block_from_graph_ && NodeCanBeRemoved(adjacent_)) {
|
||||
if (!deleted_adjacent_command_) {
|
||||
deleted_adjacent_command_ = CreateAndRunRemoveCommand(adjacent_);
|
||||
} else {
|
||||
deleted_adjacent_command_->redo();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rational adjacent_length = adjacent_->length() + trim_diff_;
|
||||
|
||||
if (mode_ == Timeline::kTrimIn) {
|
||||
adjacent_->set_length_and_media_out(adjacent_length);
|
||||
} else {
|
||||
adjacent_->set_length_and_media_in(adjacent_length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
track_->EndOperation();
|
||||
|
||||
if (dynamic_cast<TransitionBlock*>(block_)) {
|
||||
// Whole transition needs to be invalidated
|
||||
invalidate_range = block_->range();
|
||||
}
|
||||
|
||||
track_->Node::InvalidateCache(invalidate_range, Track::kBlockInput);
|
||||
}
|
||||
|
||||
void BlockTrimCommand::undo()
|
||||
{
|
||||
if (doing_nothing_) {
|
||||
return;
|
||||
}
|
||||
|
||||
track_->BeginOperation();
|
||||
|
||||
// Will be POSITIVE if trimming shorter and NEGATIVE if trimming longer
|
||||
if (needs_adjacent_) {
|
||||
if (we_created_adjacent_) {
|
||||
// Adjacent is ours, just delete it
|
||||
track_->RippleRemoveBlock(adjacent_);
|
||||
adjacent_->setParent(&memory_manager_);
|
||||
} else {
|
||||
if (we_removed_adjacent_) {
|
||||
if (deleted_adjacent_command_) {
|
||||
// We deleted adjacent, restore it now
|
||||
deleted_adjacent_command_->undo();
|
||||
}
|
||||
|
||||
if (mode_ == Timeline::kTrimIn) {
|
||||
track_->InsertBlockBefore(adjacent_, block_);
|
||||
} else {
|
||||
track_->InsertBlockAfter(adjacent_, block_);
|
||||
}
|
||||
} else {
|
||||
rational adjacent_length = adjacent_->length() - trim_diff_;
|
||||
|
||||
if (mode_ == Timeline::kTrimIn) {
|
||||
adjacent_->set_length_and_media_out(adjacent_length);
|
||||
} else {
|
||||
adjacent_->set_length_and_media_in(adjacent_length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TimeRange invalidate_range;
|
||||
|
||||
if (mode_ == Timeline::kTrimIn) {
|
||||
block_->set_length_and_media_in(old_length_);
|
||||
|
||||
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 (dynamic_cast<TransitionBlock*>(block_)) {
|
||||
// Whole transition needs to be invalidated
|
||||
invalidate_range = block_->range();
|
||||
}
|
||||
|
||||
track_->EndOperation();
|
||||
|
||||
track_->Node::InvalidateCache(invalidate_range, Track::kBlockInput);
|
||||
}
|
||||
|
||||
void BlockTrimCommand::prep()
|
||||
{
|
||||
// Store old length
|
||||
old_length_ = block_->length();
|
||||
|
||||
// Determine if the length isn't changing, in which case we set a flag to do nothing
|
||||
if ((doing_nothing_ = (old_length_ == new_length_))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Will be POSITIVE if trimming shorter and NEGATIVE if trimming longer
|
||||
trim_diff_ = old_length_ - new_length_;
|
||||
|
||||
// Retrieve our adjacent block (or nullptr if none)
|
||||
if (mode_ == Timeline::kTrimIn) {
|
||||
adjacent_ = block_->previous();
|
||||
} else {
|
||||
adjacent_ = block_->next();
|
||||
}
|
||||
|
||||
// Ignore when trimming the out with no adjacent, because the user must have trimmed the end
|
||||
// of the last block in the track, so we don't need to do anything elses
|
||||
needs_adjacent_ = (mode_ == Timeline::kTrimIn || adjacent_);
|
||||
|
||||
if (needs_adjacent_) {
|
||||
// If we're trimming shorter, we need an adjacent, so check if we have a viable one.
|
||||
we_created_adjacent_ = (trim_diff_ > 0 && (!adjacent_ || (!dynamic_cast<GapBlock*>(adjacent_) && !trim_is_a_roll_edit_)));
|
||||
|
||||
if (we_created_adjacent_) {
|
||||
// We shortened but don't have a viable adjacent to lengthen, so we create one
|
||||
adjacent_ = new GapBlock();
|
||||
adjacent_->set_length_and_media_out(trim_diff_);
|
||||
} else {
|
||||
// Determine if we're removing the adjacent
|
||||
rational adjacent_length = adjacent_->length() + trim_diff_;
|
||||
we_removed_adjacent_ = adjacent_length.isNull();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TrackReplaceBlockWithGapCommand::redo()
|
||||
{
|
||||
// Determine if this block is connected to any transitions that should also be removed by this operation
|
||||
if (transition_remove_commands_.isEmpty()) {
|
||||
CreateRemoveTransitionCommandIfNecessary(false);
|
||||
CreateRemoveTransitionCommandIfNecessary(true);
|
||||
}
|
||||
for (auto it=transition_remove_commands_.cbegin(); it!=transition_remove_commands_.cend(); it++) {
|
||||
(*it)->redo();
|
||||
}
|
||||
|
||||
if (block_->next()) {
|
||||
track_->BeginOperation();
|
||||
|
||||
// Invalidate the range inhabited by this block
|
||||
TimeRange invalidate_range(block_->in(), block_->out());
|
||||
|
||||
// Block has a next, which means it's NOT at the end of the sequence and thus requires a gap
|
||||
rational new_gap_length = block_->length();
|
||||
|
||||
Block* previous = block_->previous();
|
||||
Block* next = block_->next();
|
||||
|
||||
bool previous_is_a_gap = dynamic_cast<GapBlock*>(previous);
|
||||
bool next_is_a_gap = dynamic_cast<GapBlock*>(next);
|
||||
|
||||
if (previous_is_a_gap && next_is_a_gap) {
|
||||
// Clip is preceded and followed by a gap, so we'll merge the two
|
||||
existing_gap_ = static_cast<GapBlock*>(previous);
|
||||
|
||||
existing_merged_gap_ = static_cast<GapBlock*>(next);
|
||||
new_gap_length += existing_merged_gap_->length();
|
||||
track_->RippleRemoveBlock(existing_merged_gap_);
|
||||
existing_merged_gap_->setParent(&memory_manager_);
|
||||
} else if (previous_is_a_gap) {
|
||||
// Extend this gap to fill space left by block
|
||||
existing_gap_ = static_cast<GapBlock*>(previous);
|
||||
} else if (next_is_a_gap) {
|
||||
// Extend this gap to fill space left by block
|
||||
existing_gap_ = static_cast<GapBlock*>(next);
|
||||
}
|
||||
|
||||
if (existing_gap_) {
|
||||
// Extend an existing gap
|
||||
new_gap_length += existing_gap_->length();
|
||||
existing_gap_->set_length_and_media_out(new_gap_length);
|
||||
track_->RippleRemoveBlock(block_);
|
||||
|
||||
existing_gap_precedes_ = (existing_gap_ == previous);
|
||||
} else {
|
||||
// No gap exists to fill this space, create a new one and swap it in
|
||||
if (!our_gap_) {
|
||||
our_gap_ = new GapBlock();
|
||||
our_gap_->set_length_and_media_out(new_gap_length);
|
||||
}
|
||||
|
||||
our_gap_->setParent(track_->parent());
|
||||
track_->ReplaceBlock(block_, our_gap_);
|
||||
|
||||
if (!position_command_) {
|
||||
position_command_ = new NodeSetPositionAsChildCommand(our_gap_, track_, our_gap_->index(), track_->Blocks().size(), true);
|
||||
}
|
||||
position_command_->redo();
|
||||
}
|
||||
|
||||
track_->EndOperation();
|
||||
|
||||
track_->Node::InvalidateCache(invalidate_range, Track::kBlockInput);
|
||||
|
||||
} else {
|
||||
// Block is at the end of the track, simply remove it
|
||||
|
||||
// Determine if it's proceeded by a gap, and remove that gap if so
|
||||
Block* preceding = block_->previous();
|
||||
if (dynamic_cast<GapBlock*>(preceding)) {
|
||||
track_->RippleRemoveBlock(preceding);
|
||||
preceding->setParent(&memory_manager_);
|
||||
|
||||
existing_merged_gap_ = static_cast<GapBlock*>(preceding);
|
||||
}
|
||||
|
||||
// Remove block in question
|
||||
track_->RippleRemoveBlock(block_);
|
||||
}
|
||||
}
|
||||
|
||||
void TrackReplaceBlockWithGapCommand::undo()
|
||||
{
|
||||
if (our_gap_ || existing_gap_) {
|
||||
track_->BeginOperation();
|
||||
|
||||
if (our_gap_) {
|
||||
|
||||
// We made this gap, simply swap our gap back
|
||||
track_->ReplaceBlock(our_gap_, block_);
|
||||
our_gap_->setParent(&memory_manager_);
|
||||
|
||||
position_command_->undo();
|
||||
|
||||
} else {
|
||||
|
||||
// If we're here, assume that we extended an existing gap
|
||||
rational original_gap_length = existing_gap_->length() - block_->length();
|
||||
|
||||
// If we merged two gaps together, restore the second one now
|
||||
if (existing_merged_gap_) {
|
||||
original_gap_length -= existing_merged_gap_->length();
|
||||
existing_merged_gap_->setParent(track_->parent());
|
||||
track_->InsertBlockAfter(existing_merged_gap_, existing_gap_);
|
||||
existing_merged_gap_ = nullptr;
|
||||
}
|
||||
|
||||
// Restore original block
|
||||
if (existing_gap_precedes_) {
|
||||
track_->InsertBlockAfter(block_, existing_gap_);
|
||||
} else {
|
||||
track_->InsertBlockBefore(block_, existing_gap_);
|
||||
}
|
||||
|
||||
// Restore gap's original length
|
||||
existing_gap_->set_length_and_media_out(original_gap_length);
|
||||
|
||||
existing_gap_ = nullptr;
|
||||
|
||||
}
|
||||
|
||||
track_->EndOperation();
|
||||
|
||||
track_->Node::InvalidateCache(TimeRange(block_->in(), block_->out()), Track::kBlockInput);
|
||||
} else {
|
||||
|
||||
// Our gap and existing gap were both null, our block must have been at the end and thus
|
||||
// required no gap extension/replacement
|
||||
|
||||
// However, we may have removed an unnecessary gap that preceded it
|
||||
if (existing_merged_gap_) {
|
||||
existing_merged_gap_->setParent(track_->parent());
|
||||
track_->AppendBlock(existing_merged_gap_);
|
||||
existing_merged_gap_ = nullptr;
|
||||
}
|
||||
|
||||
// Restore block
|
||||
track_->AppendBlock(block_);
|
||||
|
||||
}
|
||||
|
||||
for (auto it=transition_remove_commands_.crbegin(); it!=transition_remove_commands_.crend(); it++) {
|
||||
(*it)->undo();
|
||||
}
|
||||
}
|
||||
|
||||
void TrackReplaceBlockWithGapCommand::CreateRemoveTransitionCommandIfNecessary(bool next)
|
||||
{
|
||||
Block* relevant_block;
|
||||
|
||||
if (next) {
|
||||
relevant_block = block_->next();
|
||||
} else {
|
||||
relevant_block = block_->previous();
|
||||
}
|
||||
|
||||
TransitionBlock* transition_cast_test = dynamic_cast<TransitionBlock*>(relevant_block);
|
||||
|
||||
if (transition_cast_test) {
|
||||
if ((next && transition_cast_test->connected_out_block() == block_ && !transition_cast_test->connected_in_block())
|
||||
|| (!next && transition_cast_test->connected_in_block() == block_ && !transition_cast_test->connected_out_block())) {
|
||||
TransitionRemoveCommand* command = new TransitionRemoveCommand(transition_cast_test, true);
|
||||
transition_remove_commands_.append(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -44,6 +44,10 @@
|
||||
#include "tool/zoom.h"
|
||||
#include "tool/tool.h"
|
||||
#include "trackview/trackview.h"
|
||||
#include "undo/timelineundogeneral.h"
|
||||
#include "undo/timelineundopointer.h"
|
||||
#include "undo/timelineundoripple.h"
|
||||
#include "undo/timelineundoworkarea.h"
|
||||
#include "widget/menu/menu.h"
|
||||
#include "widget/menu/menushared.h"
|
||||
#include "widget/nodeview/nodeviewundo.h"
|
||||
@@ -895,7 +899,7 @@ void TimelineWidget::RemoveBlock(Block *block)
|
||||
selected_blocks_.removeAt(select_index);
|
||||
RemoveSelection(block);
|
||||
|
||||
emit BlocksDeselected({block});
|
||||
SignalBlockSelectionChange();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1111,6 +1115,11 @@ void TimelineWidget::SetScrollZoomsByDefaultOnAllViews(bool e)
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineWidget::SignalBlockSelectionChange()
|
||||
{
|
||||
emit BlockSelectionChanged(selected_blocks_);
|
||||
}
|
||||
|
||||
void TimelineWidget::AddGhost(TimelineViewGhostItem *ghost)
|
||||
{
|
||||
ghost_items_.append(ghost);
|
||||
@@ -1192,7 +1201,7 @@ void TimelineWidget::SignalSelectedBlocks(QVector<Block *> input, bool filter)
|
||||
|
||||
selected_blocks_.append(input);
|
||||
|
||||
emit BlocksSelected(input);
|
||||
emit SignalBlockSelectionChange();
|
||||
}
|
||||
|
||||
void TimelineWidget::SignalDeselectedBlocks(const QVector<Block *> &deselected_blocks)
|
||||
@@ -1205,14 +1214,14 @@ void TimelineWidget::SignalDeselectedBlocks(const QVector<Block *> &deselected_b
|
||||
selected_blocks_.removeOne(b);
|
||||
}
|
||||
|
||||
emit BlocksDeselected(deselected_blocks);
|
||||
emit SignalBlockSelectionChange();
|
||||
}
|
||||
|
||||
void TimelineWidget::SignalDeselectedAllBlocks()
|
||||
{
|
||||
if (!selected_blocks_.isEmpty()) {
|
||||
emit BlocksDeselected(selected_blocks_);
|
||||
selected_blocks_.clear();
|
||||
SignalBlockSelectionChange();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -214,6 +214,9 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
virtual Project* GetRelevantProject() const override {return nullptr;}
|
||||
|
||||
protected:
|
||||
virtual void redo() override
|
||||
{
|
||||
timeline_->SetSelections(now_);
|
||||
@@ -224,8 +227,6 @@ public:
|
||||
timeline_->SetSelections(old_);
|
||||
}
|
||||
|
||||
virtual Project* GetRelevantProject() const override {return nullptr;}
|
||||
|
||||
private:
|
||||
TimelineWidget* timeline_;
|
||||
TimelineWidgetSelections old_;
|
||||
@@ -234,9 +235,7 @@ public:
|
||||
};
|
||||
|
||||
signals:
|
||||
void BlocksSelected(const QVector<Block*>& selected_blocks);
|
||||
|
||||
void BlocksDeselected(const QVector<Block*>& deselected_blocks);
|
||||
void BlockSelectionChanged(const QVector<Block*>& selected_blocks);
|
||||
|
||||
protected:
|
||||
virtual void resizeEvent(QResizeEvent *event) override;
|
||||
@@ -355,6 +354,8 @@ private slots:
|
||||
|
||||
void SetScrollZoomsByDefaultOnAllViews(bool e);
|
||||
|
||||
void SignalBlockSelectionChange();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "node/generator/solid/solid.h"
|
||||
#include "node/generator/text/text.h"
|
||||
#include "widget/timelinewidget/timelinewidget.h"
|
||||
#include "widget/timelinewidget/undo/timelineundopointer.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
@@ -108,15 +109,14 @@ void AddTool::MouseRelease(TimelineViewMouseEvent *event)
|
||||
|
||||
NodeGraph* graph = static_cast<NodeGraph*>(parent()->GetConnectedNode()->parent());
|
||||
|
||||
command->add_child(new NodeAddCommand(graph,
|
||||
clip));
|
||||
|
||||
command->add_child(new NodeAddCommand(graph, clip));
|
||||
command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0), false));
|
||||
command->add_child(new TrackPlaceBlockCommand(sequence()->track_list(track.type()),
|
||||
track.index(),
|
||||
clip,
|
||||
ghost_->GetAdjustedIn()));
|
||||
|
||||
QPointF extra_node_offset(-1, 0);
|
||||
Node *node_to_add = nullptr;
|
||||
|
||||
switch (Core::instance()->GetSelectedAddableObject()) {
|
||||
case olive::Tool::kAddableEmpty:
|
||||
@@ -124,24 +124,12 @@ void AddTool::MouseRelease(TimelineViewMouseEvent *event)
|
||||
break;
|
||||
case olive::Tool::kAddableSolid:
|
||||
{
|
||||
Node* solid = new SolidGenerator();
|
||||
|
||||
command->add_child(new NodeAddCommand(graph,
|
||||
solid));
|
||||
|
||||
command->add_child(new NodeEdgeAddCommand(solid, NodeInput(clip, ClipBlock::kBufferIn)));
|
||||
command->add_child(new NodeSetPositionToOffsetOfAnotherNodeCommand(solid, clip, extra_node_offset));
|
||||
node_to_add = new SolidGenerator();
|
||||
break;
|
||||
}
|
||||
case olive::Tool::kAddableTitle:
|
||||
{
|
||||
Node* text = new TextGenerator();
|
||||
|
||||
command->add_child(new NodeAddCommand(graph,
|
||||
text));
|
||||
|
||||
command->add_child(new NodeEdgeAddCommand(text, NodeInput(clip, ClipBlock::kBufferIn)));
|
||||
command->add_child(new NodeSetPositionToOffsetOfAnotherNodeCommand(text, clip, extra_node_offset));
|
||||
node_to_add = new TextGenerator();
|
||||
break;
|
||||
}
|
||||
case olive::Tool::kAddableBars:
|
||||
@@ -157,6 +145,13 @@ void AddTool::MouseRelease(TimelineViewMouseEvent *event)
|
||||
break;
|
||||
}
|
||||
|
||||
if (node_to_add) {
|
||||
QPointF extra_node_offset(-1, 0);
|
||||
command->add_child(new NodeAddCommand(graph, node_to_add));
|
||||
command->add_child(new NodeEdgeAddCommand(node_to_add, NodeInput(clip, ClipBlock::kBufferIn)));
|
||||
command->add_child(new NodeSetPositionCommand(node_to_add, clip, extra_node_offset, false));
|
||||
}
|
||||
|
||||
Core::instance()->undo_stack()->push(command);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "node/math/math/math.h"
|
||||
#include "node/project/sequence/sequence.h"
|
||||
#include "widget/nodeview/nodeviewundo.h"
|
||||
#include "widget/timelinewidget/undo/timelineundopointer.h"
|
||||
#include "window/mainwindow/mainwindow.h"
|
||||
#include "window/mainwindow/mainwindowundo.h"
|
||||
|
||||
@@ -353,6 +354,7 @@ void ImportTool::DropGhosts(bool insert)
|
||||
|
||||
command->add_child(new NodeAddCommand(dst_graph, new_sequence));
|
||||
command->add_child(new FolderAddChild(Core::instance()->GetSelectedFolderInActiveProject(), new_sequence));
|
||||
command->add_child(new NodeSetPositionCommand(new_sequence, new_sequence, QPointF(0, 0), false));
|
||||
new_sequence->add_default_nodes(command);
|
||||
|
||||
FootageToGhosts(0, dragged_footage_, new_sequence->GetVideoParams().time_base(), 0);
|
||||
@@ -391,7 +393,12 @@ void ImportTool::DropGhosts(bool insert)
|
||||
clip->set_length_and_media_out(ghost->GetLength());
|
||||
clip->SetLabel(footage_stream.footage->GetLabel());
|
||||
command->add_child(new NodeAddCommand(dst_graph, clip));
|
||||
command->add_child(new NodeSetPositionToOffsetOfAnotherNodeCommand(clip, footage_stream.footage, QPointF(2, 0)));
|
||||
|
||||
// Position clip in its own context
|
||||
command->add_child(new NodeSetPositionCommand(clip, clip, QPointF(0, 0), false));
|
||||
|
||||
// Position footage in its context
|
||||
command->add_child(new NodeSetPositionCommand(footage_stream.footage, clip, QPointF(-2, 0), false));
|
||||
|
||||
switch (Track::Reference::TypeFromString(footage_stream.output)) {
|
||||
case Track::kVideo:
|
||||
@@ -401,7 +408,7 @@ void ImportTool::DropGhosts(bool insert)
|
||||
|
||||
command->add_child(new NodeEdgeAddCommand(corresponding_output, NodeInput(transform, TransformDistortNode::kTextureInput)));
|
||||
command->add_child(new NodeEdgeAddCommand(transform, NodeInput(clip, ClipBlock::kBufferIn)));
|
||||
command->add_child(new NodeSetPositionToOffsetOfAnotherNodeCommand(transform, clip, QPointF(-1, 0)));
|
||||
command->add_child(new NodeSetPositionCommand(transform, clip, QPointF(-1, 0), false));
|
||||
break;
|
||||
}
|
||||
case Track::kAudio:
|
||||
@@ -411,7 +418,7 @@ void ImportTool::DropGhosts(bool insert)
|
||||
|
||||
command->add_child(new NodeEdgeAddCommand(corresponding_output, NodeInput(volume_node, VolumeNode::kSamplesInput)));
|
||||
command->add_child(new NodeEdgeAddCommand(volume_node, NodeInput(clip, ClipBlock::kBufferIn)));
|
||||
command->add_child(new NodeSetPositionToOffsetOfAnotherNodeCommand(volume_node, clip, QPointF(-1, 0)));
|
||||
command->add_child(new NodeSetPositionCommand(volume_node, clip, QPointF(-1, 0), false));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "node/block/transition/transition.h"
|
||||
#include "pointer.h"
|
||||
#include "widget/nodeview/nodeviewundo.h"
|
||||
#include "widget/timelinewidget/undo/timelineundopointer.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "razor.h"
|
||||
#include "widget/timelinewidget/timelinewidget.h"
|
||||
#include "widget/timelinewidget/undo/timelineundosplit.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "node/block/gap/gap.h"
|
||||
#include "ripple.h"
|
||||
#include "widget/nodeview/nodeviewundo.h"
|
||||
#include "widget/timelinewidget/undo/timelineundoripple.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "config/config.h"
|
||||
#include "slip.h"
|
||||
#include "widget/timelinewidget/undo/timelineundogeneral.h"
|
||||
|
||||
namespace olive {
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user