diff --git a/.travis/script.sh b/.travis/script.sh
index 9afa8e615..defeda5b2 100644
--- a/.travis/script.sh
+++ b/.travis/script.sh
@@ -29,6 +29,9 @@ if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
wget -c -nv https://github.com/arl/macdeployqtfix/raw/master/macdeployqtfix.py
python2 macdeployqtfix.py $BUNDLE_NAME/Contents/MacOS/Olive /usr/local/Cellar/qt5/5.*/
+ # Fix deps on crash handler
+ python2 macdeployqtfix.py $BUNDLE_NAME/Contents/MacOS/olive-crashhandler /usr/local/Cellar/qt5/5.*/
+
# Fix OpenEXR libs that seem to be missed by both macdeployqt _and_ macdeployqtfix
cd $BUNDLE_NAME/Contents/Frameworks
exrlib=(libImath-*.dylib libHalf-*.dylib libIexMath-*.dylib libIex-*.dylib libIlmThread-*.dylib)
diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
index 03494d590..4cc08827f 100644
--- a/app/CMakeLists.txt
+++ b/app/CMakeLists.txt
@@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+# Set Olive sources and resources
set(OLIVE_SOURCES
${OLIVE_SOURCES}
core.h
@@ -21,9 +22,12 @@ set(OLIVE_SOURCES
main.cpp
)
-set(OLIVE_RESOURCES
- ${OLIVE_RESOURCES}
-)
+if (WIN32)
+ set(OLIVE_RESOURCES
+ ${OLIVE_RESOURCES}
+ packaging/windows/resources.rc
+ )
+endif()
add_subdirectory(audio)
add_subdirectory(codec)
@@ -43,18 +47,20 @@ add_subdirectory(undo)
add_subdirectory(widget)
add_subdirectory(window)
+# Create main application target
set(OLIVE_TARGET "olive-editor")
if(APPLE)
set(OLIVE_TARGET "Olive")
-endif()
-if (WIN32)
+ set(OLIVE_ICON packaging/macos/olive.icns)
+
set(OLIVE_RESOURCES
${OLIVE_RESOURCES}
- packaging/windows/resources.rc
+ ${OLIVE_ICON}
)
endif()
+# Add executable
add_executable(${OLIVE_TARGET}
${OLIVE_SOURCES}
${OLIVE_RESOURCES}
@@ -62,14 +68,18 @@ add_executable(${OLIVE_TARGET}
)
if(APPLE)
- SET_TARGET_PROPERTIES(${OLIVE_TARGET} PROPERTIES
+ set_target_properties(${OLIVE_TARGET} PROPERTIES
MACOSX_BUNDLE TRUE
- MACOSX_FRAMEWORK_IDENTIFIER org.olivevideoeditor.Olive
+ MACOSX_BUNDLE_GUI_IDENTIFIER org.olivevideoeditor.Olive
+ MACOSX_BUNDLE_ICON_FILE olive.icns
+ RESOURCE "${OLIVE_ICON}"
)
endif()
+# Set compiler definitions
target_compile_definitions(${OLIVE_TARGET} PRIVATE ${OLIVE_DEFINITIONS})
+# Set compiler options
if(MSVC)
target_compile_options(
${OLIVE_TARGET}
@@ -100,6 +110,7 @@ else()
)
endif()
+# Set include directories
target_include_directories(
${OLIVE_TARGET}
PRIVATE
@@ -109,6 +120,7 @@ target_include_directories(
${OPENEXR_INCLUDE_DIRS}
)
+# Set link libraries
target_link_libraries(
${OLIVE_TARGET}
PRIVATE
@@ -148,11 +160,6 @@ else()
qt5_add_translation(OLIVE_QM_FILES ${OLIVE_TS_FILES})
endif()
-if(UNIX AND NOT APPLE)
- install(TARGETS ${OLIVE_TARGET} RUNTIME DESTINATION bin)
- install(FILES ${OLIVE_QM_FILES} DESTINATION share/olive-editor/ts)
-endif()
-
add_subdirectory(packaging)
if(DOXYGEN_FOUND)
@@ -191,3 +198,14 @@ target_link_libraries(
Qt5::Gui
Qt5::Widgets
)
+
+if(UNIX AND NOT APPLE)
+ install(TARGETS ${OLIVE_TARGET} ${OLIVE_CRASH_TARGET} RUNTIME DESTINATION bin)
+endif()
+
+if(APPLE)
+ # Move crash handler program inside Mac app bundle
+ add_custom_command(TARGET ${OLIVE_CRASH_TARGET} POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy ${OLIVE_CRASH_TARGET} $
+ )
+endif()
diff --git a/app/audio/audiomanager.cpp b/app/audio/audiomanager.cpp
index df4ca479e..956c4a624 100644
--- a/app/audio/audiomanager.cpp
+++ b/app/audio/audiomanager.cpp
@@ -194,6 +194,7 @@ AudioManager::AudioManager() :
RefreshDevices();
connect(&output_manager_, &AudioOutputManager::SentSamples, this, &AudioManager::SentSamples);
+ connect(&output_manager_, &AudioOutputManager::OutputNotified, this, &AudioManager::OutputNotified);
output_manager_.SetEnableSendingSamples(true);
}
diff --git a/app/audio/audiomanager.h b/app/audio/audiomanager.h
index 8a806652e..73e990114 100644
--- a/app/audio/audiomanager.h
+++ b/app/audio/audiomanager.h
@@ -102,6 +102,8 @@ signals:
void SentSamples(QVector averages);
+ void OutputNotified();
+
private:
AudioManager();
diff --git a/app/audio/outputmanager.cpp b/app/audio/outputmanager.cpp
index de27d92ac..8d601f07c 100644
--- a/app/audio/outputmanager.cpp
+++ b/app/audio/outputmanager.cpp
@@ -54,7 +54,7 @@ void AudioOutputManager::Push(const QByteArray& samples)
ResetToPushMode();
// Start pushing samples to the output
- OutputNotified();
+ PushMoreSamples();
}
void AudioOutputManager::ResetToPushMode()
@@ -92,7 +92,7 @@ void AudioOutputManager::PullFromDevice(QIODevice *device, int playback_speed)
output_->start(&device_proxy_);
}
-void AudioOutputManager::OutputNotified()
+void AudioOutputManager::PushMoreSamples()
{
// Check if we're currently in push mode and if we have samples to push
if (!push_device_ || pushed_samples_.isEmpty()) {
@@ -139,6 +139,7 @@ void AudioOutputManager::SetOutputDevice(QAudioDeviceInfo info, QAudioFormat for
output_ = std::unique_ptr(new QAudioOutput(info, format, this));
output_->setNotifyInterval(1);
push_device_ = output_->start();
+ connect(output_.get(), &QAudioOutput::notify, this, &AudioOutputManager::PushMoreSamples);
connect(output_.get(), &QAudioOutput::notify, this, &AudioOutputManager::OutputNotified);
}
diff --git a/app/audio/outputmanager.h b/app/audio/outputmanager.h
index 4b00208de..06024f4d1 100644
--- a/app/audio/outputmanager.h
+++ b/app/audio/outputmanager.h
@@ -69,6 +69,8 @@ signals:
*/
void SentSamples(QVector averages);
+ void OutputNotified();
+
private:
void ProcessAverages(const char* data, int length);
@@ -83,7 +85,7 @@ private:
AudioOutputDeviceProxy device_proxy_;
private slots:
- void OutputNotified();
+ void PushMoreSamples();
};
#endif // AUDIOHYBRIDDEVICE_H
diff --git a/app/codec/ffmpeg/ffmpegencoder.cpp b/app/codec/ffmpeg/ffmpegencoder.cpp
index e92189472..60f61efe5 100644
--- a/app/codec/ffmpeg/ffmpegencoder.cpp
+++ b/app/codec/ffmpeg/ffmpegencoder.cpp
@@ -355,7 +355,7 @@ bool FFmpegEncoder::InitializeStream(AVMediaType type, AVStream** stream_ptr, AV
}
if (params().video_buffer_size() > 0) {
- video_codec_ctx_->rc_buffer_size = params().video_buffer_size();
+ video_codec_ctx_->rc_buffer_size = static_cast(params().video_buffer_size());
}
}
diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp
index 310e29d03..ea7daff5c 100644
--- a/app/codec/oiio/oiiodecoder.cpp
+++ b/app/codec/oiio/oiiodecoder.cpp
@@ -62,17 +62,23 @@ bool OIIODecoder::Probe(Footage *f, const QAtomicInt *cancelled)
return false;
}
+ is_sequence_ = false;
+
// Heuristically determine whether this file is part of an image sequence or not
if (GetImageSequenceDigitCount(f->filename()) > 0) {
- // We need user feedback here and since UI must occur in the UI thread (and we could be in any thread), we defer
- // to the Core which will definitely be in the UI thread and block here until we get an answer from the user
- QMetaObject::invokeMethod(Core::instance(),
- "ConfirmImageSequence",
- Qt::BlockingQueuedConnection,
- Q_RETURN_ARG(bool, is_sequence_),
- Q_ARG(QString, f->filename()));
- } else {
- is_sequence_ = false;
+ int64_t ind = GetImageSequenceIndex(f->filename());
+
+ // Check if files around exist around it with that follow a sequence
+ if (QFileInfo::exists(TransformImageSequenceFileName(f->filename(), ind - 1))
+ || QFileInfo::exists(TransformImageSequenceFileName(f->filename(), ind + 1))) {
+ // We need user feedback here and since UI must occur in the UI thread (and we could be in any thread), we defer
+ // to the Core which will definitely be in the UI thread and block here until we get an answer from the user
+ QMetaObject::invokeMethod(Core::instance(),
+ "ConfirmImageSequence",
+ Qt::BlockingQueuedConnection,
+ Q_RETURN_ARG(bool, is_sequence_),
+ Q_ARG(QString, f->filename()));
+ }
}
ImageStreamPtr image_stream;
@@ -84,12 +90,25 @@ bool OIIODecoder::Probe(Footage *f, const QAtomicInt *cancelled)
rational default_timebase = Config::Current()["DefaultSequenceFrameRate"].value();
video_stream->set_timebase(default_timebase);
video_stream->set_frame_rate(default_timebase.flipped());
+ video_stream->set_image_sequence(true);
- // FIXME: Get actual start number
- video_stream->set_start_time(1);
+ int64_t seq_index = GetImageSequenceIndex(f->filename());
- // FIXME: Get actual duration
- video_stream->set_duration(200);
+ int64_t start_index = seq_index;
+ int64_t end_index = seq_index;
+
+ // Heuristic to find the first and last images (users can always override this later in FootagePropertiesDialog)
+ while (QFileInfo::exists(TransformImageSequenceFileName(f->filename(), start_index-1))) {
+ start_index--;
+ }
+
+ while (QFileInfo::exists(TransformImageSequenceFileName(f->filename(), end_index+1))) {
+ end_index++;
+ }
+
+ video_stream->set_start_time(start_index);
+
+ video_stream->set_duration(end_index - start_index);
} else {
image_stream = std::make_shared();
}
@@ -260,6 +279,19 @@ QString OIIODecoder::TransformImageSequenceFileName(const QString &filename, con
return file_info.dir().filePath(file_info.fileName().replace(original_basename, new_basename));
}
+int64_t OIIODecoder::GetImageSequenceIndex(const QString &filename)
+{
+ int digit_count = GetImageSequenceDigitCount(filename);
+
+ QFileInfo file_info(filename);
+
+ QString original_basename = file_info.baseName();
+
+ QString number_only = original_basename.mid(original_basename.size() - digit_count);
+
+ return number_only.toLongLong();
+}
+
bool OIIODecoder::OpenImageHandler(const QString &fn)
{
image_ = OIIO::ImageInput::open(fn.toStdString());
diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h
index f4eff4db6..657758bd8 100644
--- a/app/codec/oiio/oiiodecoder.h
+++ b/app/codec/oiio/oiiodecoder.h
@@ -58,6 +58,8 @@ private:
static QString TransformImageSequenceFileName(const QString& filename, const int64_t& number);
+ static int64_t GetImageSequenceIndex(const QString& filename);
+
bool OpenImageHandler(const QString& fn);
void CloseImageHandle();
diff --git a/app/common/CMakeLists.txt b/app/common/CMakeLists.txt
index c02c8acf2..e08078af0 100644
--- a/app/common/CMakeLists.txt
+++ b/app/common/CMakeLists.txt
@@ -45,5 +45,7 @@ set(OLIVE_SOURCES
common/timelinecommon.h
common/timerange.h
common/timerange.cpp
+ common/xmlutils.h
+ common/xmlutils.cpp
PARENT_SCOPE
)
diff --git a/app/common/crashhandler.cpp b/app/common/crashhandler.cpp
index 4844c9736..27958f543 100644
--- a/app/common/crashhandler.cpp
+++ b/app/common/crashhandler.cpp
@@ -13,7 +13,7 @@
#include
#include
#include
-#elif defined(Q_OS_LINUX)
+#elif defined(Q_OS_MAC) || defined(Q_OS_LINUX)
#include
#endif
@@ -100,9 +100,7 @@ void crash_handler(int sig) {
}
SymCleanup(process);
-#elif defined(Q_OS_MAC)
- // FIXME: No Mac backtrace support yet
-#elif defined(Q_OS_LINUX)
+#elif defined(Q_OS_MAC) || defined(Q_OS_LINUX)
void *array[10];
size_t size;
diff --git a/app/common/rational.h b/app/common/rational.h
index 59ed8775e..505ac313f 100644
--- a/app/common/rational.h
+++ b/app/common/rational.h
@@ -118,6 +118,7 @@ private:
QDebug operator<<(QDebug debug, const rational& r);
+// We define these limits at 32-bit to try avoiding integer overflow
#define RATIONAL_MIN rational(INT32_MIN, 1)
#define RATIONAL_MAX rational(INT32_MAX, 1)
diff --git a/app/common/timecodefunctions.cpp b/app/common/timecodefunctions.cpp
index c2725290b..86c9eaf36 100644
--- a/app/common/timecodefunctions.cpp
+++ b/app/common/timecodefunctions.cpp
@@ -258,13 +258,3 @@ int64_t Timecode::time_to_timestamp(const double &time, const rational &timebase
{
return qRound64(time * timebase.flipped().toDouble());
}
-
-Timecode::Display Timecode::CurrentDisplay()
-{
- return static_cast(Config::Current()["TimecodeDisplay"].toInt());
-}
-
-void Timecode::SetCurrentDisplay(Timecode::Display d)
-{
- Config::Current()["TimecodeDisplay"] = d;
-}
diff --git a/app/common/timecodefunctions.h b/app/common/timecodefunctions.h
index e9bb4d35d..0d1748394 100644
--- a/app/common/timecodefunctions.h
+++ b/app/common/timecodefunctions.h
@@ -45,9 +45,6 @@ public:
kMilliseconds
};
- static Display CurrentDisplay();
- static void SetCurrentDisplay(Display d);
-
/**
* @brief Convert a timestamp (according to a rational timebase) to a user-friendly string representation
*/
diff --git a/app/common/xmlreadloop.h b/app/common/xmlreadloop.h
deleted file mode 100644
index 80e101c5f..000000000
--- a/app/common/xmlreadloop.h
+++ /dev/null
@@ -1,11 +0,0 @@
-#ifndef XMLREADLOOP_H
-#define XMLREADLOOP_H
-
-#define XMLReadLoop(reader, section) \
- while (!reader->atEnd() && !(reader->name() == section && reader->isEndElement()) && reader->readNext())
-
-#define XMLAttributeLoop(reader, item) \
- QXmlStreamAttributes __attributes = reader->attributes(); \
- foreach (const QXmlStreamAttribute& item, __attributes)
-
-#endif // XMLREADLOOP_H
diff --git a/app/common/xmlutils.cpp b/app/common/xmlutils.cpp
new file mode 100644
index 000000000..790abe546
--- /dev/null
+++ b/app/common/xmlutils.cpp
@@ -0,0 +1,56 @@
+#include "xmlutils.h"
+
+#include "node/factory.h"
+
+Node* XMLLoadNode(QXmlStreamReader* reader) {
+ QString node_id;
+
+ XMLAttributeLoop(reader, attr) {
+ if (attr.name() == "id") {
+ node_id = attr.value().toString();
+
+ // Currently the only thing we need
+ break;
+ }
+ }
+
+ if (node_id.isEmpty()) {
+ qWarning() << "Found node with no ID";
+ return nullptr;
+ }
+
+ Node* node = NodeFactory::CreateFromID(node_id);
+
+ if (!node) {
+ qWarning() << "Failed to load" << node_id << "- no node with that ID is installed";
+ }
+
+ return node;
+}
+
+void XMLConnectNodes(const QHash& output_ptrs, const QList& desired_connections)
+{
+ foreach (const NodeParam::SerializedConnection& con, desired_connections) {
+ NodeOutput* out = output_ptrs.value(con.output);
+
+ if (out) {
+ NodeParam::ConnectEdge(out, con.input);
+ }
+ }
+}
+
+bool XMLReadNextStartElement(QXmlStreamReader *reader)
+{
+ QXmlStreamReader::TokenType token;
+
+ while ((token = reader->readNext()) != QXmlStreamReader::Invalid
+ && token != QXmlStreamReader::EndDocument) {
+ if (reader->isEndElement()) {
+ return false;
+ } else if (reader->isStartElement()) {
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/app/common/xmlutils.h b/app/common/xmlutils.h
new file mode 100644
index 000000000..d3530abb3
--- /dev/null
+++ b/app/common/xmlutils.h
@@ -0,0 +1,18 @@
+#ifndef XMLREADLOOP_H
+#define XMLREADLOOP_H
+
+#include
+
+#include "node/node.h"
+
+#define XMLAttributeLoop(reader, item) \
+ QXmlStreamAttributes __attributes = reader->attributes(); \
+ foreach (const QXmlStreamAttribute& item, __attributes)
+
+Node *XMLLoadNode(QXmlStreamReader* reader);
+
+void XMLConnectNodes(const QHash &output_ptrs, const QList &desired_connections);
+
+bool XMLReadNextStartElement(QXmlStreamReader* reader);
+
+#endif // XMLREADLOOP_H
diff --git a/app/config/config.cpp b/app/config/config.cpp
index 0d22fc54e..88d80d4c0 100644
--- a/app/config/config.cpp
+++ b/app/config/config.cpp
@@ -29,6 +29,7 @@
#include "common/autoscroll.h"
#include "common/filefunctions.h"
+#include "common/xmlutils.h"
#include "core.h"
#include "window/mainwindow/mainwindow.h"
@@ -75,6 +76,8 @@ void Config::SetDefaults()
config_map_["Autoscroll"] = AutoScroll::kPage;
config_map_["DefaultViewerDivider"] = 2;
config_map_["AutoSelectDivider"] = false;
+ config_map_["SetNameWithMarker"] = false;
+ config_map_["RectifiedWaveforms"] = false;
config_map_["DropWithoutSequenceBehavior"] = TimelineWidget::kDWSAsk;
config_map_["DiskCachePath"] = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
@@ -119,52 +122,50 @@ void Config::Load()
QString config_version;
- while (!reader.atEnd()) {
- reader.readNext();
+ while (XMLReadNextStartElement(&reader)) {
+ if (reader.name() == QStringLiteral("Configuration")) {
+ while (XMLReadNextStartElement(&reader)) {
+ QString key = reader.name().toString();
+ QString value = reader.readElementText();
- if (!reader.isStartElement()) {
- continue;
- }
+ if (key == QStringLiteral("Version")) {
+ config_version = value;
- QString key = reader.name().toString();
+ if (!value.contains(".")) {
+ qDebug() << "CONFIG: This is a 0.1.x config file, upconvert";
+ }
+ } else if (key == QStringLiteral("DefaultSequenceFrameRate") && !config_version.contains('.')) {
+ // 0.1.x stored this value as a float while we now use rationals, we'll use a heuristic to find the closest
+ // supported rational
+ qDebug() << " CONFIG: Finding closest match to" << value;
- reader.readNext();
- QString value = reader.text().toString();
+ double config_fr = value.toDouble();
- if (key == "Configuration") {
- // First element, ignore
- } else if (key == "Version") {
- config_version = value;
+ QList supported_frame_rates = Core::SupportedFrameRates();
- if (!value.contains(".")) {
- qDebug() << "CONFIG: This is a 0.1.x config file, upconvert";
- }
- } else if (key == "DefaultSequenceFrameRate" && !config_version.contains(".")) {
- // 0.1.x stored this value as a float while we now use rationals, we'll use a heuristic to find the closest
- // supported rational
- qDebug() << " CONFIG: Finding closest match to" << value;
+ rational match = supported_frame_rates.first();
+ double match_diff = qAbs(match.toDouble() - config_fr);
- double config_fr = value.toDouble();
+ for (int i=1;i supported_frame_rates = Core::SupportedFrameRates();
+ if (diff < match_diff) {
+ match = supported_frame_rates.at(i);
+ match_diff = diff;
+ }
+ }
- rational match = supported_frame_rates.first();
- double match_diff = qAbs(match.toDouble() - config_fr);
+ qDebug() << " CONFIG: Closest match was" << match.toDouble();
- for (int i=1;imain_window(),
QCoreApplication::translate("Config", "Error loading settings"),
QCoreApplication::translate("Config", "Failed to load application settings. This session will "
- "use defaults."),
+ "use defaults.\n\n%1").arg(reader.errorString()),
QMessageBox::Ok);
current_config_.SetDefaults();
}
diff --git a/app/core.cpp b/app/core.cpp
index d59a38092..e2cf27a53 100644
--- a/app/core.cpp
+++ b/app/core.cpp
@@ -482,6 +482,18 @@ Folder *Core::GetSelectedFolderInActiveProject()
}
}
+Timecode::Display Core::GetTimecodeDisplay() const
+{
+ return static_cast(Config::Current()["TimecodeDisplay"].toInt());
+}
+
+void Core::SetTimecodeDisplay(Timecode::Display d)
+{
+ Config::Current()["TimecodeDisplay"] = d;
+
+ emit TimecodeDisplayChanged(d);
+}
+
void Core::SetProjectModified(bool e)
{
main_window()->setWindowModified(e);
diff --git a/app/core.h b/app/core.h
index eb17fb15a..1d0988485 100644
--- a/app/core.h
+++ b/app/core.h
@@ -26,6 +26,7 @@
#include
#include "common/rational.h"
+#include "common/timecodefunctions.h"
#include "project/item/footage/footage.h"
#include "project/item/sequence/sequence.h"
#include "project/project.h"
@@ -125,6 +126,16 @@ public:
ProjectViewModel* GetActiveProjectModel();
Folder* GetSelectedFolderInActiveProject();
+ /**
+ * @brief Gets current timecode display mode
+ */
+ Timecode::Display GetTimecodeDisplay() const;
+
+ /**
+ * @brief Sets current timecode display mode
+ */
+ void SetTimecodeDisplay(Timecode::Display d);
+
/**
* @brief Sets state to "modified" so that the GUI will prompt the user to save before closing
*
@@ -269,6 +280,11 @@ signals:
*/
void SnappingChanged(const bool& b);
+ /**
+ * @brief Signal emitted when the default timecode display mode changed
+ */
+ void TimecodeDisplayChanged(Timecode::Display d);
+
private:
/**
* @brief Get the file filter than can be used with QFileDialog to open and save compatible projects
diff --git a/app/dialog/footageproperties/footageproperties.cpp b/app/dialog/footageproperties/footageproperties.cpp
index fa5063540..37f2dc467 100644
--- a/app/dialog/footageproperties/footageproperties.cpp
+++ b/app/dialog/footageproperties/footageproperties.cpp
@@ -87,15 +87,26 @@ FootagePropertiesDialog::FootagePropertiesDialog(QWidget *parent, Footage *foota
connect(track_list, SIGNAL(currentRowChanged(int)), stacked_widget_, SLOT(setCurrentIndex(int)));
- QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
+ QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
buttons->setCenterButtons(true);
layout->addWidget(buttons, row, 0, 1, 2);
- connect(buttons, SIGNAL(accepted()), this, SLOT(accept()));
- connect(buttons, SIGNAL(rejected()), this, SLOT(reject()));
+ connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
+ connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
void FootagePropertiesDialog::accept() {
+ // Perform sanity check on all pages
+ for (int i=0;icount();i++) {
+ if (!static_cast(stacked_widget_->widget(i))->SanityCheck()) {
+ // Switch to the failed panel in question
+ stacked_widget_->setCurrentIndex(i);
+
+ // Do nothing (it's up to the property panel itself to throw the error message)
+ return;
+ }
+ }
+
QUndoCommand* command = new QUndoCommand();
if (footage_->name() != footage_name_field_->text()) {
diff --git a/app/dialog/footageproperties/streamproperties/streamproperties.h b/app/dialog/footageproperties/streamproperties/streamproperties.h
index 72840f010..30f8d7aa5 100644
--- a/app/dialog/footageproperties/streamproperties/streamproperties.h
+++ b/app/dialog/footageproperties/streamproperties/streamproperties.h
@@ -30,6 +30,9 @@ public:
StreamProperties(QWidget* parent = nullptr);
virtual void Accept(QUndoCommand*){}
+
+ virtual bool SanityCheck(){return true;}
+
};
#endif // STREAMPROPERTIES_H
diff --git a/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp b/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp
index 333879e67..b4362c7d8 100644
--- a/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp
+++ b/app/dialog/footageproperties/streamproperties/videostreamproperties.cpp
@@ -21,7 +21,9 @@
#include "videostreamproperties.h"
#include
+#include
#include
+#include
#include
namespace OCIO = OCIO_NAMESPACE::v1;
@@ -35,12 +37,16 @@ VideoStreamProperties::VideoStreamProperties(ImageStreamPtr stream) :
QGridLayout* video_layout = new QGridLayout(this);
video_layout->setMargin(0);
- video_layout->addWidget(new QLabel(tr("Color Space:")), 0, 0);
+ int row = 0;
+
+ video_layout->addWidget(new QLabel(tr("Color Space:")), row, 0);
video_color_space_ = new QComboBox();
OCIO::ConstConfigRcPtr config = stream->footage()->project()->color_manager()->GetConfig();
int number_of_colorspaces = config->getNumColorSpaces();
+ video_color_space_->addItem(tr("Default (%1)").arg(stream->footage()->project()->default_input_colorspace()));
+
for (int i=0;igetColorSpaceNameByIndex(i);
@@ -49,22 +55,94 @@ VideoStreamProperties::VideoStreamProperties(ImageStreamPtr stream) :
video_color_space_->setCurrentText(stream_->colorspace());
- video_layout->addWidget(video_color_space_, 0, 1);
+ video_layout->addWidget(video_color_space_, row, 1);
+
+ row++;
video_premultiply_alpha_ = new QCheckBox(tr("Premultiplied Alpha"));
video_premultiply_alpha_->setChecked(stream_->premultiplied_alpha());
- video_layout->addWidget(video_premultiply_alpha_, 1, 0, 1, 2);
+ video_layout->addWidget(video_premultiply_alpha_, row, 0, 1, 2);
+
+ row++;
+
+ if (IsImageSequence(stream.get())) {
+ QGroupBox* imgseq_group = new QGroupBox(tr("Image Sequence"));
+ QGridLayout* imgseq_layout = new QGridLayout(imgseq_group);
+
+ int imgseq_row = 0;
+
+ VideoStream* video_stream = static_cast(stream.get());
+
+ imgseq_layout->addWidget(new QLabel(tr("Start Index:")), imgseq_row, 0);
+
+ imgseq_start_time_ = new IntegerSlider();
+ imgseq_start_time_->SetMinimum(0);
+ imgseq_start_time_->SetValue(video_stream->start_time());
+ imgseq_layout->addWidget(imgseq_start_time_, imgseq_row, 1);
+
+ imgseq_row++;
+
+ imgseq_layout->addWidget(new QLabel(tr("End Index:")), imgseq_row, 0);
+
+ imgseq_end_time_ = new IntegerSlider();
+ imgseq_end_time_->SetMinimum(0);
+ imgseq_end_time_->SetValue(video_stream->start_time() + video_stream->duration());
+ imgseq_layout->addWidget(imgseq_end_time_, imgseq_row, 1);
+
+ video_layout->addWidget(imgseq_group, row, 0, 1, 2);
+ }
}
void VideoStreamProperties::Accept(QUndoCommand *parent)
{
+ QString set_colorspace;
+
+ if (video_color_space_->currentIndex() > 0) {
+ set_colorspace = video_color_space_->currentText();
+ }
+
if (video_premultiply_alpha_->isChecked() != stream_->premultiplied_alpha()
- || video_color_space_->currentText() != stream_->colorspace()) {
+ || set_colorspace != stream_->colorspace(false)) {
+
new VideoStreamChangeCommand(stream_,
video_premultiply_alpha_->isChecked(),
- video_color_space_->currentText(),
+ set_colorspace,
parent);
}
+
+ if (IsImageSequence(stream_.get())) {
+ VideoStreamPtr video_stream = std::static_pointer_cast(stream_);
+
+ int64_t new_dur = imgseq_end_time_->GetValue() - imgseq_start_time_->GetValue();
+
+ if (video_stream->start_time() != imgseq_start_time_->GetValue()
+ || video_stream->duration() != new_dur) {
+ new ImageSequenceChangeCommand(video_stream,
+ imgseq_start_time_->GetValue(),
+ new_dur,
+ parent);
+ }
+ }
+}
+
+bool VideoStreamProperties::SanityCheck()
+{
+ if (IsImageSequence(stream_.get())) {
+ if (imgseq_start_time_->GetValue() >= imgseq_end_time_->GetValue()) {
+ QMessageBox::critical(this,
+ tr("Invalid Configuration"),
+ tr("Image sequence end index must be a value higher than the start index."),
+ QMessageBox::Ok);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool VideoStreamProperties::IsImageSequence(ImageStream *stream)
+{
+ return (stream->type() == Stream::kVideo && static_cast(stream)->is_image_sequence());
}
VideoStreamProperties::VideoStreamChangeCommand::VideoStreamChangeCommand(ImageStreamPtr stream,
@@ -81,7 +159,7 @@ VideoStreamProperties::VideoStreamChangeCommand::VideoStreamChangeCommand(ImageS
void VideoStreamProperties::VideoStreamChangeCommand::redo_internal()
{
old_premultiplied_ = stream_->premultiplied_alpha();
- old_colorspace_ = stream_->colorspace();
+ old_colorspace_ = stream_->colorspace(false);
stream_->set_premultiplied_alpha(new_premultiplied_);
stream_->set_colorspace(new_colorspace_);
@@ -92,3 +170,26 @@ void VideoStreamProperties::VideoStreamChangeCommand::undo_internal()
stream_->set_premultiplied_alpha(old_premultiplied_);
stream_->set_colorspace(old_colorspace_);
}
+
+VideoStreamProperties::ImageSequenceChangeCommand::ImageSequenceChangeCommand(VideoStreamPtr video_stream, int64_t start_index, int64_t duration, QUndoCommand *parent) :
+ UndoCommand(parent),
+ video_stream_(video_stream),
+ new_start_index_(start_index),
+ new_duration_(duration)
+{
+}
+
+void VideoStreamProperties::ImageSequenceChangeCommand::redo_internal()
+{
+ old_start_index_ = video_stream_->start_time();
+ video_stream_->set_start_time(new_start_index_);
+
+ old_duration_ = video_stream_->duration();
+ video_stream_->set_duration(new_duration_);
+}
+
+void VideoStreamProperties::ImageSequenceChangeCommand::undo_internal()
+{
+ video_stream_->set_start_time(old_start_index_);
+ video_stream_->set_duration(old_duration_);
+}
diff --git a/app/dialog/footageproperties/streamproperties/videostreamproperties.h b/app/dialog/footageproperties/streamproperties/videostreamproperties.h
index e59bfb84f..183ab7e32 100644
--- a/app/dialog/footageproperties/streamproperties/videostreamproperties.h
+++ b/app/dialog/footageproperties/streamproperties/videostreamproperties.h
@@ -27,6 +27,7 @@
#include "project/item/footage/videostream.h"
#include "streamproperties.h"
#include "undo/undocommand.h"
+#include "widget/slider/integerslider.h"
class VideoStreamProperties : public StreamProperties
{
@@ -35,7 +36,11 @@ public:
virtual void Accept(QUndoCommand* parent) override;
+ virtual bool SanityCheck() override;
+
private:
+ static bool IsImageSequence(ImageStream* stream);
+
/**
* @brief Attached video stream
*/
@@ -51,6 +56,16 @@ private:
*/
QComboBox* video_color_space_;
+ /**
+ * @brief Sets the start index for image sequences
+ */
+ IntegerSlider* imgseq_start_time_;
+
+ /**
+ * @brief Sets the end index for image sequences
+ */
+ IntegerSlider* imgseq_end_time_;
+
class VideoStreamChangeCommand : public UndoCommand {
public:
VideoStreamChangeCommand(ImageStreamPtr stream,
@@ -70,6 +85,29 @@ private:
bool old_premultiplied_;
QString old_colorspace_;
+
+ };
+
+ class ImageSequenceChangeCommand : public UndoCommand {
+ public:
+ ImageSequenceChangeCommand(VideoStreamPtr video_stream,
+ int64_t start_index,
+ int64_t duration,
+ QUndoCommand* parent = nullptr);
+
+ protected:
+ virtual void redo_internal() override;
+ virtual void undo_internal() override;
+
+ private:
+ VideoStreamPtr video_stream_;
+
+ int64_t new_start_index_;
+ int64_t old_start_index_;
+
+ int64_t new_duration_;
+ int64_t old_duration_;
+
};
};
diff --git a/app/dialog/preferences/tabs/preferencesgeneraltab.cpp b/app/dialog/preferences/tabs/preferencesgeneraltab.cpp
index 4d448c91d..59ab3198a 100644
--- a/app/dialog/preferences/tabs/preferencesgeneraltab.cpp
+++ b/app/dialog/preferences/tabs/preferencesgeneraltab.cpp
@@ -2,8 +2,6 @@
#include
#include
-#include
-#include
#include
#include "common/autoscroll.h"
@@ -74,6 +72,14 @@ PreferencesGeneralTab::PreferencesGeneralTab()
row++;
+ general_layout->addWidget(new QLabel(tr("Rectified Waveforms:")), row, 0);
+
+ rectified_waveforms_ = new QCheckBox();
+ rectified_waveforms_->setChecked(Config::Current()["RectifiedWaveforms"].toBool());
+ general_layout->addWidget(rectified_waveforms_, row, 1);
+
+ row++;
+
general_layout->addWidget(new QLabel(tr("Default Still Image Length:")), row, 0);
default_still_length_ = new FloatSlider();
@@ -101,6 +107,8 @@ void PreferencesGeneralTab::Accept()
Config::Current()["DefaultSequenceAudioFrequency"] = default_sequence_.audio_params().sample_rate();
Config::Current()["DefaultSequenceAudioLayout"] = QVariant::fromValue(default_sequence_.audio_params().channel_layout());
+ Config::Current()["RectifiedWaveforms"] = rectified_waveforms_->isChecked();
+
Config::Current()["Autoscroll"] = autoscroll_method_->currentData();
Config::Current()["DefaultStillLength"] = QVariant::fromValue(rational::fromDouble(default_still_length_->GetValue()));
diff --git a/app/dialog/preferences/tabs/preferencesgeneraltab.h b/app/dialog/preferences/tabs/preferencesgeneraltab.h
index b05633040..f6d6d8f2a 100644
--- a/app/dialog/preferences/tabs/preferencesgeneraltab.h
+++ b/app/dialog/preferences/tabs/preferencesgeneraltab.h
@@ -1,6 +1,7 @@
#ifndef PREFERENCESGENERALTAB_H
#define PREFERENCESGENERALTAB_H
+#include
#include
#include
@@ -27,6 +28,8 @@ private:
QComboBox* autoscroll_method_;
+ QCheckBox* rectified_waveforms_;
+
FloatSlider* default_still_length_;
/**
diff --git a/app/node/CMakeLists.txt b/app/node/CMakeLists.txt
index 68bb5194f..bb8f9a42f 100644
--- a/app/node/CMakeLists.txt
+++ b/app/node/CMakeLists.txt
@@ -46,6 +46,8 @@ set(OLIVE_SOURCES
node/output.cpp
node/param.h
node/param.cpp
+ node/traverser.h
+ node/traverser.cpp
node/value.h
node/value.cpp
PARENT_SCOPE
diff --git a/app/node/block/block.cpp b/app/node/block/block.cpp
index 312f10103..69cda6126 100644
--- a/app/node/block/block.cpp
+++ b/app/node/block/block.cpp
@@ -202,22 +202,28 @@ void Block::LengthInputChanged()
emit LengthChanged(length());
}
-void Block::Link(Block *a, Block *b)
+bool Block::Link(Block *a, Block *b)
{
- if (a == b || a == nullptr || b == nullptr) {
- return;
+ if (a == b || !a || !b) {
+ return false;
}
- // Assume both clips are already linked since Link() and Unlink() should be the only entry points to this array
- if (a->linked_clips_.contains(b)) {
- return;
+ // Prevent duplicate link entries (assume that we only need to check one clip since this should be the only function
+ // that adds to the linked array)
+ if (Block::AreLinked(a, b)) {
+ return false;
}
a->linked_clips_.append(b);
b->linked_clips_.append(a);
+
+ emit a->LinksChanged();
+ emit b->LinksChanged();
+
+ return true;
}
-void Block::Link(QList blocks)
+void Block::Link(const QList& blocks)
{
foreach (Block* a, blocks) {
foreach (Block* b, blocks) {
@@ -226,10 +232,32 @@ void Block::Link(QList blocks)
}
}
-void Block::Unlink(Block *a, Block *b)
+bool Block::Unlink(Block *a, Block *b)
{
+ if (a == b || !a || !b) {
+ return false;
+ }
+
+ if (!Block::AreLinked(a, b)) {
+ return false;
+ }
+
a->linked_clips_.removeOne(b);
b->linked_clips_.removeOne(a);
+
+ emit a->LinksChanged();
+ emit b->LinksChanged();
+
+ return true;
+}
+
+void Block::Unlink(const QList &blocks)
+{
+ foreach (Block* a, blocks) {
+ foreach (Block* b, blocks) {
+ Unlink(a, b);
+ }
+ }
}
bool Block::AreLinked(Block *a, Block *b)
diff --git a/app/node/block/block.h b/app/node/block/block.h
index e0e43f1fe..cb9ea4e93 100644
--- a/app/node/block/block.h
+++ b/app/node/block/block.h
@@ -76,9 +76,10 @@ public:
QString block_name() const;
void set_block_name(const QString& name);
- static void Link(Block* a, Block* b);
- static void Link(QList blocks);
- static void Unlink(Block* a, Block* b);
+ static bool Link(Block* a, Block* b);
+ static void Link(const QList& blocks);
+ static bool Unlink(Block* a, Block* b);
+ static void Unlink(const QList& blocks);
static bool AreLinked(Block* a, Block* b);
const QVector& linked_clips();
bool HasLinks();
@@ -103,6 +104,8 @@ signals:
void LengthChanged(const rational& length);
+ void LinksChanged();
+
protected:
rational SequenceToMediaTime(const rational& sequence_time) const;
diff --git a/app/node/input.cpp b/app/node/input.cpp
index c4a5281f6..8602ab06e 100644
--- a/app/node/input.cpp
+++ b/app/node/input.cpp
@@ -26,7 +26,7 @@
#include "common/bezier.h"
#include "common/lerp.h"
-#include "common/xmlreadloop.h"
+#include "common/xmlutils.h"
#include "node.h"
#include "output.h"
#include "inputarray.h"
@@ -91,111 +91,111 @@ void NodeInput::Load(QXmlStreamReader *reader, QHash& par
return;
}
- if (attr.name() == "keyframing") {
- set_is_keyframing(attr.value() == "1");
+ if (attr.name() == QStringLiteral("keyframing")) {
+ set_is_keyframing(attr.value() == QStringLiteral("1"));
}
}
- XMLReadLoop(reader, "input") {
+ while (XMLReadNextStartElement(reader)) {
if (cancelled && *cancelled) {
return;
}
- if (reader->isStartElement()) {
- if (reader->name() == "standard") {
- // Load standard value
- int val_index = 0;
+ if (reader->name() == QStringLiteral("standard")) {
+ // Load standard value
+ int val_index = 0;
- XMLReadLoop(reader, "standard") {
- if (cancelled && *cancelled) {
- return;
+ while (XMLReadNextStartElement(reader)) {
+ if (cancelled && *cancelled) {
+ return;
+ }
+
+ if (reader->name() == QStringLiteral("value")) {
+ QString value_text = reader->readElementText();
+
+ if (value_text.isEmpty()) {
+ standard_value_.replace(val_index, QVariant());
+ } else {
+ standard_value_.replace(val_index, StringToValue(value_text, footage_connections));
}
- if (reader->isStartElement() && reader->name() == "value") {
- reader->readNext();
+ val_index++;
+ } else {
+ reader->skipCurrentElement();
+ }
+ }
+ } else if (reader->name() == QStringLiteral("keyframes")) {
+ int track = 0;
- QString value_text = reader->text().toString();
+ while (XMLReadNextStartElement(reader)) {
+ if (cancelled && *cancelled) {
+ return;
+ }
- if (value_text.isEmpty()) {
- standard_value_.replace(val_index, QVariant());
- } else {
- standard_value_.replace(val_index, StringToValue(value_text, footage_connections));
+ if (reader->name() == QStringLiteral("track")) {
+ while (XMLReadNextStartElement(reader)) {
+ if (cancelled && *cancelled) {
+ return;
}
- val_index++;
- }
- }
- } else if (reader->name() == "keyframes") {
- int track = 0;
+ if (reader->name() == QStringLiteral("key")) {
+ rational key_time;
+ NodeKeyframe::Type key_type;
+ QVariant key_value;
+ QPointF key_in_handle;
+ QPointF key_out_handle;
- XMLReadLoop(reader, "keyframes") {
- if (cancelled && *cancelled) {
- return;
- }
-
- if (reader->isStartElement() && reader->name() == "track") {
- XMLReadLoop(reader, "track") {
- if (cancelled && *cancelled) {
- return;
- }
-
- if (reader->name() == "key") {
- rational key_time;
- NodeKeyframe::Type key_type;
- QVariant key_value;
- QPointF key_in_handle;
- QPointF key_out_handle;
-
- XMLAttributeLoop(reader, attr) {
- if (cancelled && *cancelled) {
- return;
- }
-
- if (attr.name() == "time") {
- key_time = rational::fromString(attr.value().toString());
- } else if (attr.name() == "type") {
- key_type = static_cast(attr.value().toInt());
- } else if (attr.name() == "inhandlex") {
- key_in_handle.setX(attr.value().toDouble());
- } else if (attr.name() == "inhandley") {
- key_in_handle.setY(attr.value().toDouble());
- } else if (attr.name() == "outhandlex") {
- key_out_handle.setX(attr.value().toDouble());
- } else if (attr.name() == "outhandley") {
- key_out_handle.setY(attr.value().toDouble());
- }
+ XMLAttributeLoop(reader, attr) {
+ if (cancelled && *cancelled) {
+ return;
}
- reader->readNext();
-
- key_value = StringToValue(reader->text().toString(), footage_connections);
-
- NodeKeyframePtr key = NodeKeyframe::Create(key_time, key_value, key_type, track);
- key->set_bezier_control_in(key_in_handle);
- key->set_bezier_control_out(key_out_handle);
- key->set_parent(this);
- keyframe_tracks_[track].append(key);
+ if (attr.name() == QStringLiteral("time")) {
+ key_time = rational::fromString(attr.value().toString());
+ } else if (attr.name() == QStringLiteral("type")) {
+ key_type = static_cast(attr.value().toInt());
+ } else if (attr.name() == QStringLiteral("inhandlex")) {
+ key_in_handle.setX(attr.value().toDouble());
+ } else if (attr.name() == QStringLiteral("inhandley")) {
+ key_in_handle.setY(attr.value().toDouble());
+ } else if (attr.name() == QStringLiteral("outhandlex")) {
+ key_out_handle.setX(attr.value().toDouble());
+ } else if (attr.name() == QStringLiteral("outhandley")) {
+ key_out_handle.setY(attr.value().toDouble());
+ }
}
+
+ key_value = StringToValue(reader->readElementText(), footage_connections);
+
+ NodeKeyframePtr key = NodeKeyframe::Create(key_time, key_value, key_type, track);
+ key->set_bezier_control_in(key_in_handle);
+ key->set_bezier_control_out(key_out_handle);
+ key->set_parent(this);
+ keyframe_tracks_[track].append(key);
+ } else {
+ reader->skipCurrentElement();
}
-
- track++;
}
+
+ track++;
+ } else {
+ reader->skipCurrentElement();
}
- } else if (reader->name() == "connections") {
- XMLReadLoop(reader, "connections") {
- if (cancelled && *cancelled) {
- return;
- }
-
- if (reader->isStartElement() && reader->name() == "connection") {
- reader->readNext();
-
- input_connections.append({this, reader->text().toULongLong()});
- }
- }
- } else {
- LoadInternal(reader, param_ptrs, input_connections, footage_connections, cancelled);
}
+ } else if (reader->name() == QStringLiteral("connections")) {
+ while (XMLReadNextStartElement(reader)) {
+ if (cancelled && *cancelled) {
+ return;
+ }
+
+ if (reader->name() == QStringLiteral("connection")) {
+ input_connections.append({this, reader->readElementText().toULongLong()});
+ } else {
+ reader->skipCurrentElement();
+ }
+ }
+ } else {
+ LoadInternal(reader, param_ptrs, input_connections, footage_connections, cancelled);
}
}
}
@@ -268,8 +268,9 @@ const NodeParam::DataType &NodeInput::data_type() const
return data_type_;
}
-void NodeInput::LoadInternal(QXmlStreamReader*, QHash&, QList&, QList&, const QAtomicInt*)
+void NodeInput::LoadInternal(QXmlStreamReader* reader, QHash&, QList&, QList&, const QAtomicInt*)
{
+ reader->skipCurrentElement();
}
void NodeInput::SaveInternal(QXmlStreamWriter*) const
diff --git a/app/node/input/media/audio/audio.cpp b/app/node/input/media/audio/audio.cpp
index ccfb52f56..6f7992de9 100644
--- a/app/node/input/media/audio/audio.cpp
+++ b/app/node/input/media/audio/audio.cpp
@@ -28,8 +28,3 @@ QString AudioInput::Description() const
{
return tr("Import an audio footage stream.");
}
-
-NodeValueTable AudioInput::Value(const NodeValueDatabase &value) const
-{
- return value[footage_input_];
-}
diff --git a/app/node/input/media/audio/audio.h b/app/node/input/media/audio/audio.h
index 641b4f8de..86aa8d330 100644
--- a/app/node/input/media/audio/audio.h
+++ b/app/node/input/media/audio/audio.h
@@ -15,9 +15,6 @@ public:
virtual QString Category() const override;
virtual QString Description() const override;
-protected:
- virtual NodeValueTable Value(const NodeValueDatabase& value) const override;
-
private:
};
diff --git a/app/node/input/media/media.cpp b/app/node/input/media/media.cpp
index 29189a479..ea27b8c35 100644
--- a/app/node/input/media/media.cpp
+++ b/app/node/input/media/media.cpp
@@ -69,22 +69,18 @@ void MediaInput::FootageChanged()
return;
}
- if (connected_footage_ != nullptr) {
- if (connected_footage_->type() == Stream::kImage || connected_footage_->type() == Stream::kVideo) {
- disconnect(connected_footage_.get(), SIGNAL(ColorSpaceChanged()), this, SLOT(FootageColorSpaceChanged()));
- }
+ if (connected_footage_) {
+ disconnect(connected_footage_.get(), &Stream::ParametersChanged, this, &MediaInput::FootageParametersChanged);
}
connected_footage_ = new_footage;
- if (connected_footage_ != nullptr) {
- if (connected_footage_->type() == Stream::kImage || connected_footage_->type() == Stream::kVideo) {
- connect(connected_footage_.get(), SIGNAL(ColorSpaceChanged()), this, SLOT(FootageColorSpaceChanged()));
- }
+ if (connected_footage_) {
+ connect(connected_footage_.get(), &Stream::ParametersChanged, this, &MediaInput::FootageParametersChanged);
}
}
-void MediaInput::FootageColorSpaceChanged()
+void MediaInput::FootageParametersChanged()
{
InvalidateCache(0, RATIONAL_MAX, footage_input_);
}
diff --git a/app/node/input/media/media.h b/app/node/input/media/media.h
index 0253cc231..a19459cae 100644
--- a/app/node/input/media/media.h
+++ b/app/node/input/media/media.h
@@ -48,7 +48,7 @@ protected:
private slots:
void FootageChanged();
- void FootageColorSpaceChanged();
+ void FootageParametersChanged();
};
diff --git a/app/node/inputarray.cpp b/app/node/inputarray.cpp
index e7d0e0e01..7fe1fc668 100644
--- a/app/node/inputarray.cpp
+++ b/app/node/inputarray.cpp
@@ -2,7 +2,7 @@
#include
-#include "common/xmlreadloop.h"
+#include "common/xmlutils.h"
#include "node.h"
NodeInputArray::NodeInputArray(const QString &id, const DataType &type, const QVariant &default_value) :
@@ -166,13 +166,17 @@ void NodeInputArray::RemoveAt(int index)
void NodeInputArray::LoadInternal(QXmlStreamReader *reader, QHash& param_ptrs, QList &input_connections, QList& footage_connections, const QAtomicInt* cancelled)
{
- if (reader->name() == "subparameters") {
- XMLReadLoop(reader, "subparameters") {
- if (reader->name() == "input") {
+ if (reader->name() == QStringLiteral("subparameters")) {
+ while (XMLReadNextStartElement(reader)) {
+ if (reader->name() == QStringLiteral("input")) {
Append();
At(GetSize() - 1)->Load(reader, param_ptrs, input_connections, footage_connections, cancelled);
+ } else {
+ reader->skipCurrentElement();
}
}
+ } else {
+ NodeInput::Load(reader, param_ptrs, input_connections, footage_connections, cancelled);
}
}
diff --git a/app/node/node.cpp b/app/node/node.cpp
index 38a3b6500..fb12a0093 100644
--- a/app/node/node.cpp
+++ b/app/node/node.cpp
@@ -24,7 +24,7 @@
#include
#include
-#include "common/xmlreadloop.h"
+#include "common/xmlutils.h"
Node::Node() :
can_be_deleted_(true)
@@ -50,39 +50,39 @@ Node::~Node()
}
}
-void Node::Load(QXmlStreamReader *reader, QHash &output_ptrs, QList& input_connections, QList& footage_connections, const QAtomicInt* cancelled, const QString& element)
+void Node::Load(QXmlStreamReader *reader, QHash &output_ptrs, QList& input_connections, QList& footage_connections, const QAtomicInt* cancelled)
{
- XMLReadLoop(reader, (element.isEmpty() ? "node" : element)) {
+ while (XMLReadNextStartElement(reader)) {
if (cancelled && *cancelled) {
return;
}
- if (reader->isStartElement()) {
- if (reader->name() == "input" || reader->name() == "output") {
- QString param_id;
+ if (reader->name() == QStringLiteral("input") || reader->name() == QStringLiteral("output")) {
+ QString param_id;
- XMLAttributeLoop(reader, attr) {
- if (attr.name() == "id") {
- param_id = attr.value().toString();
+ XMLAttributeLoop(reader, attr) {
+ if (attr.name() == QStringLiteral("id")) {
+ param_id = attr.value().toString();
- break;
- }
+ break;
}
-
- if (param_id.isEmpty()) {
- qDebug() << "Found parameter with no ID";
- continue;
- }
-
- NodeParam* param = GetParameterWithID(param_id);
-
- if (!param) {
- qDebug() << "No parameter in" << id() << "with parameter" << param_id;
- continue;
- }
-
- param->Load(reader, output_ptrs, input_connections, footage_connections, cancelled);
}
+
+ if (param_id.isEmpty()) {
+ qDebug() << "Found parameter with no ID";
+ continue;
+ }
+
+ NodeParam* param = GetParameterWithID(param_id);
+
+ if (!param) {
+ qDebug() << "No parameter in" << id() << "with parameter" << param_id;
+ continue;
+ }
+
+ param->Load(reader, output_ptrs, input_connections, footage_connections, cancelled);
+ } else {
+ reader->skipCurrentElement();
}
}
}
diff --git a/app/node/node.h b/app/node/node.h
index d14fef691..b049a8618 100644
--- a/app/node/node.h
+++ b/app/node/node.h
@@ -67,7 +67,7 @@ public:
/**
* @brief Clear current node variables and replace them with
*/
- void Load(QXmlStreamReader* reader, QHash& param_ptrs, QList &input_connections, QList& footage_connections, const QAtomicInt *cancelled, const QString &element = QString());
+ void Load(QXmlStreamReader* reader, QHash& param_ptrs, QList &input_connections, QList& footage_connections, const QAtomicInt *cancelled);
/**
* @brief Save this node into a text/XML format
diff --git a/app/node/output.cpp b/app/node/output.cpp
index dd87ac9c1..79646932f 100644
--- a/app/node/output.cpp
+++ b/app/node/output.cpp
@@ -20,7 +20,7 @@
#include "output.h"
-#include "common/xmlreadloop.h"
+#include "common/xmlutils.h"
#include "node/node.h"
NodeOutput::NodeOutput(const QString &id) :
@@ -55,6 +55,8 @@ void NodeOutput::Load(QXmlStreamReader* reader, QHash& pa
param_ptrs.insert(saved_ptr, this);
}
}
+
+ reader->skipCurrentElement();
}
void NodeOutput::Save(QXmlStreamWriter *writer) const
diff --git a/app/node/output/viewer/viewer.cpp b/app/node/output/viewer/viewer.cpp
index b86623419..f1327dcdd 100644
--- a/app/node/output/viewer/viewer.cpp
+++ b/app/node/output/viewer/viewer.cpp
@@ -20,6 +20,8 @@
#include "viewer.h"
+#include "node/traverser.h"
+
ViewerOutput::ViewerOutput()
{
texture_input_ = new NodeInput("tex_in", NodeInput::kTexture);
@@ -28,9 +30,6 @@ ViewerOutput::ViewerOutput()
samples_input_ = new NodeInput("samples_in", NodeInput::kSamples);
AddInput(samples_input_);
- length_input_ = new NodeInput("length_in", NodeInput::kRational);
- AddInput(length_input_);
-
// Create TrackList instances
track_inputs_.resize(Timeline::kTrackTypeCount);
track_lists_.resize(Timeline::kTrackTypeCount);
@@ -91,11 +90,6 @@ NodeInput *ViewerOutput::samples_input() const
return samples_input_;
}
-NodeInput *ViewerOutput::length_input() const
-{
- return length_input_;
-}
-
void ViewerOutput::InvalidateCache(const rational &start_range, const rational &end_range, NodeInput *from)
{
Node::InvalidateCache(start_range, end_range, from);
@@ -104,8 +98,6 @@ void ViewerOutput::InvalidateCache(const rational &start_range, const rational &
emit VideoChangedBetween(TimeRange(start_range, end_range));
} else if (from == samples_input()) {
emit AudioChangedBetween(TimeRange(start_range, end_range));
- } else if (from == length_input()) {
- emit LengthChanged(Length());
}
SendInvalidateCache(start_range, end_range);
@@ -146,18 +138,23 @@ void ViewerOutput::set_audio_params(const AudioParams &audio)
rational ViewerOutput::Length()
{
- if (!length_input_->IsConnected()) {
- return timeline_length_;
+ NodeTraverser traverser;
+
+ rational video_length;
+
+ if (texture_input_->IsConnected()) {
+ NodeValueTable t = traverser.ProcessNode(NodeDependency(texture_input_->get_connected_node(), 0, 0));
+ video_length = t.Get(NodeParam::kNumber, "length").value();
}
- Node* connected_node = length_input_->get_connected_node();
+ rational audio_length;
- if (connected_node) {
- // This is kind of messy?
- return connected_node->Value(NodeValueDatabase()).Get(NodeParam::kNumber, "length").value();
+ if (samples_input_->IsConnected()) {
+ NodeValueTable t = traverser.ProcessNode(NodeDependency(samples_input_->get_connected_node(), 0, 0));
+ audio_length = t.Get(NodeParam::kNumber, "length").value();
}
- return 0;
+ return qMax(video_length, qMax(audio_length, timeline_length_));
}
const QUuid &ViewerOutput::uuid() const
diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h
index abdcd3f41..2d86dfc0e 100644
--- a/app/node/output/viewer/viewer.h
+++ b/app/node/output/viewer/viewer.h
@@ -52,7 +52,6 @@ public:
NodeInput* texture_input() const;
NodeInput* samples_input() const;
- NodeInput* length_input() const;
virtual void InvalidateCache(const rational &start_range, const rational &end_range, NodeInput *from = nullptr) override;
virtual void InvalidateVisible(NodeInput *from) override;
@@ -117,8 +116,6 @@ private:
NodeInput* samples_input_;
- NodeInput* length_input_;
-
VideoParams video_params_;
AudioParams audio_params_;
diff --git a/app/node/traverser.cpp b/app/node/traverser.cpp
new file mode 100644
index 000000000..fb3f28239
--- /dev/null
+++ b/app/node/traverser.cpp
@@ -0,0 +1,76 @@
+#include "traverser.h"
+
+#include "node.h"
+
+NodeTraverser::NodeTraverser()
+{
+
+}
+
+NodeValueDatabase NodeTraverser::GenerateDatabase(const Node* node, const TimeRange &range)
+{
+ NodeValueDatabase database;
+
+ // We need to insert tables into the database for each input
+ foreach (NodeParam* param, node->parameters()) {
+ if (IsCancelled()) {
+ return NodeValueDatabase();
+ }
+
+ if (param->type() == NodeParam::kInput) {
+ NodeInput* input = static_cast(param);
+ TimeRange input_time = node->InputTimeAdjustment(input, range);
+
+ NodeValueTable table = ProcessInput(input, input_time);
+
+ InputProcessingEvent(input, input_time, &table);
+
+ database.Insert(input, table);
+ }
+ }
+
+ return database;
+}
+
+NodeValueTable NodeTraverser::ProcessNode(const NodeDependency& dep)
+{
+ const Node* node = dep.node();
+
+ if (node->IsTrack()) {
+ // If the range is not wholly contained in this Block, we'll need to do some extra processing
+ return RenderBlock(static_cast(node), dep.range());
+ }
+
+ // FIXME: Cache certain values here if we've already processed them before
+
+ // Generate database of input values of node
+ NodeValueDatabase database = GenerateDatabase(node, dep.range());
+
+ // By this point, the node should have all the inputs it needs to render correctly
+ NodeValueTable table = node->Value(database);
+
+ ProcessNodeEvent(node, dep.range(), database, &table);
+
+ return table;
+}
+
+NodeValueTable NodeTraverser::RenderBlock(const TrackOutput *track, const TimeRange &range)
+{
+ // By default, don't bother traversing blocks
+ return NodeValueTable();
+}
+
+NodeValueTable NodeTraverser::ProcessInput(const NodeInput *input, const TimeRange& range)
+{
+ if (input->IsConnected()) {
+ // Value will equal something from the connected node, follow it
+ return ProcessNode(NodeDependency(input->get_connected_node(), range));
+ } else {
+ // Push onto the table the value at this time from the input
+ QVariant input_value = input->get_value_at_time(range.in());
+
+ NodeValueTable table;
+ table.Push(input->data_type(), input_value);
+ return table;
+ }
+}
diff --git a/app/node/traverser.h b/app/node/traverser.h
new file mode 100644
index 000000000..8166a7987
--- /dev/null
+++ b/app/node/traverser.h
@@ -0,0 +1,31 @@
+#ifndef NODETRAVERSER_H
+#define NODETRAVERSER_H
+
+#include "codec/decoder.h"
+#include "common/cancelableobject.h"
+#include "dependency.h"
+#include "node/output/track/track.h"
+#include "project/item/footage/stream.h"
+#include "value.h"
+
+class NodeTraverser : public CancelableObject
+{
+public:
+ NodeTraverser();
+
+ NodeValueTable ProcessNode(const NodeDependency &dep);
+
+protected:
+ NodeValueDatabase GenerateDatabase(const Node *node, const TimeRange &range);
+
+ virtual NodeValueTable RenderBlock(const TrackOutput *track, const TimeRange& range);
+
+ NodeValueTable ProcessInput(const NodeInput* input, const TimeRange &range);
+
+ virtual void InputProcessingEvent(NodeInput*, const TimeRange&, NodeValueTable*){}
+
+ virtual void ProcessNodeEvent(const Node*, const TimeRange&, const NodeValueDatabase&, NodeValueTable*){}
+
+};
+
+#endif // NODETRAVERSER_H
diff --git a/app/node/value.cpp b/app/node/value.cpp
index 2f5fc11a5..0ef541256 100644
--- a/app/node/value.cpp
+++ b/app/node/value.cpp
@@ -58,13 +58,9 @@ NodeValueTable::NodeValueTable()
QVariant NodeValueTable::Get(const NodeParam::DataType &type, const QString &tag) const
{
- int value_index = GetInternal(type, tag);
+ NodeValue v = GetWithMeta(type, tag);
- if (value_index >= 0) {
- return values_.at(value_index).data();
- }
-
- return QVariant();
+ return v.data();
}
NodeValue NodeValueTable::GetWithMeta(const NodeParam::DataType &type, const QString &tag) const
diff --git a/app/panel/node/node.cpp b/app/panel/node/node.cpp
index d9be8e307..f21fe2098 100644
--- a/app/panel/node/node.cpp
+++ b/app/panel/node/node.cpp
@@ -59,6 +59,21 @@ void NodePanel::DeleteSelected()
node_view_->DeleteSelected();
}
+void NodePanel::CutSelected()
+{
+ node_view_->CopySelected(true);
+}
+
+void NodePanel::CopySelected()
+{
+ node_view_->CopySelected(false);
+}
+
+void NodePanel::Paste()
+{
+ node_view_->Paste();
+}
+
void NodePanel::Select(const QList &nodes)
{
node_view_->Select(nodes);
diff --git a/app/panel/node/node.h b/app/panel/node/node.h
index e84d9c7c8..5ce5b75ca 100644
--- a/app/panel/node/node.h
+++ b/app/panel/node/node.h
@@ -40,6 +40,11 @@ public:
virtual void DeleteSelected() override;
+ virtual void CutSelected() override;
+ virtual void CopySelected() override;
+
+ virtual void Paste() override;
+
public slots:
void Select(const QList& nodes);
void SelectWithDependencies(const QList& nodes);
diff --git a/app/panel/timebased/timebased.cpp b/app/panel/timebased/timebased.cpp
index 2e76ae10f..e0e93e7a7 100644
--- a/app/panel/timebased/timebased.cpp
+++ b/app/panel/timebased/timebased.cpp
@@ -134,3 +134,33 @@ void TimeBasedPanel::Retranslate()
SetSubtitle(tr("(none)"));
}
}
+
+void TimeBasedPanel::SetIn()
+{
+ GetTimeBasedWidget()->SetInAtPlayhead();
+}
+
+void TimeBasedPanel::SetOut()
+{
+ GetTimeBasedWidget()->SetOutAtPlayhead();
+}
+
+void TimeBasedPanel::ResetIn()
+{
+ GetTimeBasedWidget()->ResetIn();
+}
+
+void TimeBasedPanel::ResetOut()
+{
+ GetTimeBasedWidget()->ResetOut();
+}
+
+void TimeBasedPanel::ClearInOut()
+{
+ GetTimeBasedWidget()->ClearInOutPoints();
+}
+
+void TimeBasedPanel::SetMarker()
+{
+ GetTimeBasedWidget()->SetMarker();
+}
diff --git a/app/panel/timebased/timebased.h b/app/panel/timebased/timebased.h
index 59a4ec84e..db4c7f9c5 100644
--- a/app/panel/timebased/timebased.h
+++ b/app/panel/timebased/timebased.h
@@ -44,6 +44,18 @@ public:
virtual void ShuttleRight() override;
+ virtual void SetIn() override;
+
+ virtual void SetOut() override;
+
+ virtual void ResetIn() override;
+
+ virtual void ResetOut() override;
+
+ virtual void ClearInOut() override;
+
+ virtual void SetMarker() override;
+
public slots:
void SetTimebase(const rational& timebase);
diff --git a/app/panel/timeline/timeline.cpp b/app/panel/timeline/timeline.cpp
index 95dfdbb7d..53fd6116c 100644
--- a/app/panel/timeline/timeline.cpp
+++ b/app/panel/timeline/timeline.cpp
@@ -115,6 +115,11 @@ void TimelinePanel::Overwrite()
}
}
+void TimelinePanel::ToggleLinks()
+{
+ static_cast(GetTimeBasedWidget())->ToggleLinksOnSelected();
+}
+
void TimelinePanel::InsertFootageAtPlayhead(const QList &footage)
{
static_cast(GetTimeBasedWidget())->InsertFootageAtPlayhead(footage);
diff --git a/app/panel/timeline/timeline.h b/app/panel/timeline/timeline.h
index 02c09d1e5..3d5c1dd91 100644
--- a/app/panel/timeline/timeline.h
+++ b/app/panel/timeline/timeline.h
@@ -61,6 +61,8 @@ public:
virtual void Overwrite() override;
+ virtual void ToggleLinks() override;
+
void InsertFootageAtPlayhead(const QList &footage);
void OverwriteFootageAtPlayhead(const QList &footage);
diff --git a/app/project/item/folder/folder.cpp b/app/project/item/folder/folder.cpp
index 35a516332..12a221bd3 100644
--- a/app/project/item/folder/folder.cpp
+++ b/app/project/item/folder/folder.cpp
@@ -20,7 +20,7 @@
#include "folder.h"
-#include "common/xmlreadloop.h"
+#include "common/xmlutils.h"
#include "project/item/footage/footage.h"
#include "project/item/sequence/sequence.h"
#include "ui/icons/icons.h"
@@ -46,37 +46,38 @@ QIcon Folder::icon()
void Folder::Load(QXmlStreamReader *reader, QHash &footage_ptrs, QList& footage_connections, const QAtomicInt *cancelled)
{
+ qDebug() << "Hello?";
+
XMLAttributeLoop(reader, attr) {
if (cancelled && *cancelled) {
return;
}
- if (attr.name() == "name") {
+ if (attr.name() == QStringLiteral("name")) {
set_name(attr.value().toString());
}
}
- XMLReadLoop(reader, "folder") {
+ while (XMLReadNextStartElement(reader)) {
if (cancelled && *cancelled) {
return;
}
- if (reader->isStartElement()) {
- ItemPtr child;
+ ItemPtr child;
- if (reader->name() == "folder") {
- child = std::make_shared();
- } else if (reader->name() == "footage") {
- child = std::make_shared();
- } else if (reader->name() == "sequence") {
- child = std::make_shared();
- } else {
- continue;
- }
-
- add_child(child);
- child->Load(reader, footage_ptrs, footage_connections, cancelled);
+ if (reader->name() == QStringLiteral("folder")) {
+ child = std::make_shared();
+ } else if (reader->name() == QStringLiteral("footage")) {
+ child = std::make_shared();
+ } else if (reader->name() == QStringLiteral("sequence")) {
+ child = std::make_shared();
+ } else {
+ reader->skipCurrentElement();
+ continue;
}
+
+ add_child(child);
+ child->Load(reader, footage_ptrs, footage_connections, cancelled);
}
}
diff --git a/app/project/item/footage/footage.cpp b/app/project/item/footage/footage.cpp
index 299597bf2..4434eb531 100644
--- a/app/project/item/footage/footage.cpp
+++ b/app/project/item/footage/footage.cpp
@@ -23,9 +23,9 @@
#include
#include "codec/decoder.h"
-#include "common/timecodefunctions.h"
-#include "common/xmlreadloop.h"
+#include "common/xmlutils.h"
#include "config/config.h"
+#include "core.h"
#include "ui/icons/icons.h"
Footage::Footage()
@@ -43,45 +43,45 @@ void Footage::Load(QXmlStreamReader *reader, QHash& footage
QXmlStreamAttributes attributes = reader->attributes();
foreach (const QXmlStreamAttribute& attr, attributes) {
- if (attr.name() == "name") {
+ if (attr.name() == QStringLiteral("name")) {
set_name(attr.value().toString());
- } else if (attr.name() == "filename") {
+ } else if (attr.name() == QStringLiteral("filename")) {
set_filename(attr.value().toString());
}
}
Decoder::ProbeMedia(this, cancelled);
- XMLReadLoop(reader, "footage") {
+ while (XMLReadNextStartElement(reader)) {
if (cancelled && *cancelled) {
return;
}
- if (reader->isStartElement()) {
- if (reader->name() == "stream") {
- int stream_index = -1;
- quintptr stream_ptr = 0;
+ if (reader->name() == QStringLiteral("stream")) {
+ int stream_index = -1;
+ quintptr stream_ptr = 0;
- XMLAttributeLoop(reader, attr) {
- if (cancelled && *cancelled) {
- return;
- }
-
- if (attr.name() == "index") {
- stream_index = attr.value().toInt();
- } else if (attr.name() == "ptr") {
- stream_ptr = attr.value().toULongLong();
- }
+ XMLAttributeLoop(reader, attr) {
+ if (cancelled && *cancelled) {
+ return;
}
- if (stream_index > -1 && stream_ptr > 0) {
- footage_ptrs.insert(stream_ptr, stream(stream_index));
-
- stream(stream_index)->Load(reader);
- } else {
- qWarning() << "Invalid stream found in project file";
+ if (attr.name() == QStringLiteral("index")) {
+ stream_index = attr.value().toInt();
+ } else if (attr.name() == QStringLiteral("ptr")) {
+ stream_ptr = attr.value().toULongLong();
}
}
+
+ if (stream_index > -1 && stream_ptr > 0) {
+ footage_ptrs.insert(stream_ptr, stream(stream_index));
+
+ stream(stream_index)->Load(reader);
+ } else {
+ qWarning() << "Invalid stream found in project file";
+ }
+ } else {
+ reader->skipCurrentElement();
}
}
}
@@ -232,12 +232,12 @@ QString Footage::duration()
return Timecode::timestamp_to_timecode(duration,
frame_rate_timebase,
- Timecode::CurrentDisplay());
+ Core::instance()->GetTimecodeDisplay());
} else if (streams_.first()->type() == Stream::kAudio) {
AudioStreamPtr audio_stream = std::static_pointer_cast(streams_.first());
// If we're showing in a timecode, we prefer showing audio in seconds instead
- Timecode::Display display = Timecode::CurrentDisplay();
+ Timecode::Display display = Core::instance()->GetTimecodeDisplay();
if (display == Timecode::kTimecodeDropFrame
|| display == Timecode::kTimecodeNonDropFrame) {
display = Timecode::kTimecodeSeconds;
diff --git a/app/project/item/footage/footage.h b/app/project/item/footage/footage.h
index 42226d64d..0e5f31ee9 100644
--- a/app/project/item/footage/footage.h
+++ b/app/project/item/footage/footage.h
@@ -30,6 +30,7 @@
#include "project/item/footage/audiostream.h"
#include "project/item/footage/imagestream.h"
#include "project/item/footage/videostream.h"
+#include "timeline/timelinepoints.h"
/**
* @brief A reference to an external media file with metadata in a project structure
@@ -38,7 +39,7 @@
* Footage objects store a list of Stream objects which store the majority of video/audio metadata. These streams
* are identical to the stream data in the files.
*/
-class Footage : public Item
+class Footage : public Item, public TimelinePoints
{
public:
enum Status {
diff --git a/app/project/item/footage/imagestream.cpp b/app/project/item/footage/imagestream.cpp
index 4cc187623..2e17c0563 100644
--- a/app/project/item/footage/imagestream.cpp
+++ b/app/project/item/footage/imagestream.cpp
@@ -20,7 +20,7 @@
#include "imagestream.h"
-#include "common/xmlreadloop.h"
+#include "common/xmlutils.h"
#include "footage.h"
#include "project/project.h"
#include "render/colormanager.h"
@@ -43,10 +43,11 @@ void ImageStream::FootageSetEvent(Footage *f)
void ImageStream::LoadCustomParameters(QXmlStreamReader *reader)
{
- XMLReadLoop(reader, "stream") {
- if (reader->isStartElement() && reader->name() == "colorspace") {
- reader->readNext();
- set_colorspace(reader->text().toString());
+ while (XMLReadNextStartElement(reader)) {
+ if (reader->name() == QStringLiteral("colorspace")) {
+ set_colorspace(reader->readElementText());
+ } else {
+ reader->skipCurrentElement();
}
}
}
@@ -91,11 +92,13 @@ bool ImageStream::premultiplied_alpha() const
void ImageStream::set_premultiplied_alpha(bool e)
{
premultiplied_alpha_ = e;
+
+ emit ParametersChanged();
}
-const QString &ImageStream::colorspace() const
+const QString &ImageStream::colorspace(bool default_if_empty) const
{
- if (colorspace_.isEmpty()) {
+ if (colorspace_.isEmpty() && default_if_empty) {
return footage()->project()->default_input_colorspace();
} else {
return colorspace_;
@@ -106,7 +109,7 @@ void ImageStream::set_colorspace(const QString &color)
{
colorspace_ = color;
- emit ColorSpaceChanged();
+ emit ParametersChanged();
}
void ImageStream::ColorConfigChanged()
@@ -123,13 +126,13 @@ void ImageStream::ColorConfigChanged()
}
// Either way, the color calculation has likely changed so we signal here
- emit ColorSpaceChanged();
+ emit ParametersChanged();
}
void ImageStream::DefaultColorSpaceChanged()
{
// If no colorspace is set, this stream uses the default color space and it's just changed
if (colorspace_.isEmpty()) {
- emit ColorSpaceChanged();
+ emit ParametersChanged();
}
}
diff --git a/app/project/item/footage/imagestream.h b/app/project/item/footage/imagestream.h
index 952b0c432..7c6adad2c 100644
--- a/app/project/item/footage/imagestream.h
+++ b/app/project/item/footage/imagestream.h
@@ -43,12 +43,9 @@ public:
bool premultiplied_alpha() const;
void set_premultiplied_alpha(bool e);
- const QString& colorspace() const;
+ const QString& colorspace(bool default_if_empty = true) const;
void set_colorspace(const QString& color);
-signals:
- void ColorSpaceChanged();
-
protected:
virtual void FootageSetEvent(Footage*) override;
diff --git a/app/project/item/footage/stream.cpp b/app/project/item/footage/stream.cpp
index c7f273271..4937481b2 100644
--- a/app/project/item/footage/stream.cpp
+++ b/app/project/item/footage/stream.cpp
@@ -107,6 +107,8 @@ const int64_t &Stream::duration() const
void Stream::set_duration(const int64_t &duration)
{
duration_ = duration;
+
+ emit ParametersChanged();
}
bool Stream::enabled() const
@@ -135,10 +137,10 @@ QIcon Stream::IconFromType(const Stream::Type &type)
return QIcon();
}
-StreamID Stream::ToID() const
+/*StreamID Stream::ToID() const
{
return StreamID(footage_->filename(), index_);
-}
+}*/
QMutex* Stream::index_process_lock()
{
@@ -149,16 +151,17 @@ void Stream::FootageSetEvent(Footage*)
{
}
-void Stream::LoadCustomParameters(QXmlStreamReader*)
+void Stream::LoadCustomParameters(QXmlStreamReader* reader)
{
+ reader->skipCurrentElement();
}
void Stream::SaveCustomParameters(QXmlStreamWriter*) const
{
}
-StreamID::StreamID(const QString &filename, const int &stream_index) :
+/*StreamID::StreamID(const QString &filename, const int &stream_index) :
filename_(filename),
stream_index_(stream_index)
{
-}
+}*/
diff --git a/app/project/item/footage/stream.h b/app/project/item/footage/stream.h
index 0a0a7a319..743b9e6c4 100644
--- a/app/project/item/footage/stream.h
+++ b/app/project/item/footage/stream.h
@@ -31,7 +31,7 @@
class Footage;
-class StreamID {
+/*class StreamID {
public:
StreamID(const QString& filename, const int& stream_index);
@@ -40,7 +40,7 @@ private:
int stream_index_;
-};
+};*/
/**
* @brief A base class for keeping metadata about a media stream.
@@ -101,7 +101,7 @@ public:
static QIcon IconFromType(const Type& type);
- StreamID ToID() const;
+ //StreamID ToID() const;
QMutex* index_process_lock();
@@ -115,6 +115,8 @@ protected:
signals:
void IndexChanged();
+ void ParametersChanged();
+
private:
Footage* footage_;
diff --git a/app/project/item/footage/videostream.cpp b/app/project/item/footage/videostream.cpp
index 5a0fb5df4..36d0f7fd5 100644
--- a/app/project/item/footage/videostream.cpp
+++ b/app/project/item/footage/videostream.cpp
@@ -27,7 +27,8 @@
const int64_t VideoStream::kEndTimestamp = AV_NOPTS_VALUE;
VideoStream::VideoStream() :
- start_time_(0)
+ start_time_(0),
+ is_image_sequence_(false)
{
set_type(kVideo);
}
@@ -57,6 +58,17 @@ const int64_t &VideoStream::start_time() const
void VideoStream::set_start_time(const int64_t &start_time)
{
start_time_ = start_time;
+ emit ParametersChanged();
+}
+
+bool VideoStream::is_image_sequence() const
+{
+ return is_image_sequence_;
+}
+
+void VideoStream::set_image_sequence(bool e)
+{
+ is_image_sequence_ = e;
}
int64_t VideoStream::get_closest_timestamp_in_frame_index(const rational &time)
diff --git a/app/project/item/footage/videostream.h b/app/project/item/footage/videostream.h
index 3fed4bfc4..05ec4ac95 100644
--- a/app/project/item/footage/videostream.h
+++ b/app/project/item/footage/videostream.h
@@ -25,6 +25,7 @@
class VideoStream : public ImageStream
{
+ Q_OBJECT
public:
VideoStream();
@@ -43,6 +44,9 @@ public:
const int64_t& start_time() const;
void set_start_time(const int64_t& start_time);
+ bool is_image_sequence() const;
+ void set_image_sequence(bool e);
+
int64_t get_closest_timestamp_in_frame_index(const rational& time);
int64_t get_closest_timestamp_in_frame_index(int64_t timestamp);
void clear_frame_index();
@@ -62,6 +66,8 @@ private:
QMutex index_access_lock_;
+ bool is_image_sequence_;
+
};
using VideoStreamPtr = std::shared_ptr;
diff --git a/app/project/item/item.cpp b/app/project/item/item.cpp
index 661708260..d694bc1e4 100644
--- a/app/project/item/item.cpp
+++ b/app/project/item/item.cpp
@@ -76,11 +76,19 @@ const QList &Item::children() const
return children_;
}
-ItemPtr Item::shared_ptr_from_raw(Item *item)
+ItemPtr Item::shared_ptr_from_raw(Item *item, bool traverse)
{
for (int i=0;iCanHaveChildren()) {
+ ItemPtr grandchild = shared_ptr_from_raw(item);
+
+ if (grandchild) {
+ return grandchild;
+ }
}
}
@@ -147,6 +155,23 @@ void Item::set_project(Project *project)
project_ = project;
}
+QList Item::get_children_of_type(Type type, bool recursive) const
+{
+ QList list;
+
+ foreach (ItemPtr item, children_) {
+ if (item->type() == type) {
+ list.append(item);
+ }
+
+ if (recursive && item->CanHaveChildren()) {
+ list.append(item->get_children_of_type(type, recursive));
+ }
+ }
+
+ return list;
+}
+
bool Item::CanHaveChildren() const
{
return false;
diff --git a/app/project/item/item.h b/app/project/item/item.h
index e8385f09f..42cb83bf7 100644
--- a/app/project/item/item.h
+++ b/app/project/item/item.h
@@ -77,7 +77,7 @@ public:
Item* child(int i) const;
const QList& children() const;
- ItemPtr shared_ptr_from_raw(Item* item);
+ ItemPtr shared_ptr_from_raw(Item* item, bool traverse = false);
const QString& name() const;
void set_name(const QString& n);
@@ -97,6 +97,8 @@ public:
Project* project() const;
void set_project(Project* project);
+ QList get_children_of_type(Type type, bool recursive) const;
+
virtual bool CanHaveChildren() const;
bool ChildExistsWithName(const QString& name);
diff --git a/app/project/item/sequence/sequence.cpp b/app/project/item/sequence/sequence.cpp
index e8edb5601..00101f126 100644
--- a/app/project/item/sequence/sequence.cpp
+++ b/app/project/item/sequence/sequence.cpp
@@ -25,7 +25,7 @@
#include "config/config.h"
#include "common/channellayout.h"
#include "common/timecodefunctions.h"
-#include "common/xmlreadloop.h"
+#include "common/xmlutils.h"
#include "node/factory.h"
#include "panel/panelmanager.h"
#include "panel/node/node.h"
@@ -59,97 +59,68 @@ void Sequence::Load(QXmlStreamReader *reader, QHash &, QLis
QHash output_ptrs;
QList desired_connections;
- XMLReadLoop(reader, "sequence") {
+ while (XMLReadNextStartElement(reader)) {
if (cancelled && *cancelled) {
return;
}
- if (reader->isStartElement()) {
- if (reader->name() == "video") {
- int video_width, video_height;
- rational video_timebase;
+ if (reader->name() == QStringLiteral("video")) {
+ int video_width, video_height;
+ rational video_timebase;
- XMLReadLoop(reader, "video") {
- if (cancelled && *cancelled) {
- return;
- }
-
- if (reader->isStartElement()) {
- if (reader->name() == "width") {
- reader->readNext();
- video_width = reader->text().toInt();
- } else if (reader->name() == "height") {
- reader->readNext();
- video_height = reader->text().toInt();
- } else if (reader->name() == "timebase") {
- reader->readNext();
- video_timebase = rational::fromString(reader->text().toString());
- }
- }
+ while (XMLReadNextStartElement(reader)) {
+ if (cancelled && *cancelled) {
+ return;
}
- set_video_params(VideoParams(video_width, video_height, video_timebase));
- } else if (reader->name() == "audio") {
- int rate;
- uint64_t layout;
-
- XMLReadLoop(reader, "audio") {
- if (reader->isStartElement()) {
- if (reader->name() == "rate") {
- reader->readNext();
- rate = reader->text().toInt();
- } else if (reader->name() == "layout") {
- reader->readNext();
- layout = reader->text().toULongLong();
- }
- }
- }
-
- set_audio_params(AudioParams(rate, layout));
- } else if (reader->name() == "node" || reader->name() == "viewer") {
- Node* node;
-
- if (reader->name() == "node") {
- QString node_id;
-
- XMLAttributeLoop(reader, attr) {
- if (attr.name() == "id") {
- node_id = attr.value().toString();
-
- // Currently the only thing we need
- break;
- }
- }
-
- if (node_id.isEmpty()) {
- qDebug() << "Found node with no ID";
- continue;
- }
-
- node = NodeFactory::CreateFromID(node_id);
-
- if (!node) {
- qDebug() << "Failed to load" << node_id << "- no node with that ID is installed";
- continue;
- }
+ if (reader->name() == QStringLiteral("width")) {
+ video_width = reader->readElementText().toInt();
+ } else if (reader->name() == QStringLiteral("height")) {
+ video_height = reader->readElementText().toInt();
+ } else if (reader->name() == QStringLiteral("timebase")) {
+ video_timebase = rational::fromString(reader->readElementText());
} else {
- node = viewer_output_;
- }
-
- if (node) {
- node->Load(reader, output_ptrs, desired_connections, footage_connections, cancelled, reader->name().toString());
-
- AddNode(node);
+ reader->skipCurrentElement();
}
}
+
+ set_video_params(VideoParams(video_width, video_height, video_timebase));
+ } else if (reader->name() == QStringLiteral("audio")) {
+ int rate;
+ uint64_t layout;
+
+ while (XMLReadNextStartElement(reader)) {
+ if (reader->name() == QStringLiteral("rate")) {
+ rate = reader->readElementText().toInt();
+ } else if (reader->name() == QStringLiteral("layout")) {
+ layout = reader->readElementText().toULongLong();
+ } else {
+ reader->skipCurrentElement();
+ }
+ }
+
+ set_audio_params(AudioParams(rate, layout));
+ } else if (reader->name() == QStringLiteral("node") || reader->name() == QStringLiteral("viewer")) {
+ Node* node;
+
+ if (reader->name() == QStringLiteral("node")) {
+ node = XMLLoadNode(reader);
+ } else {
+ node = viewer_output_;
+ }
+
+ if (node) {
+ node->Load(reader, output_ptrs, desired_connections, footage_connections, cancelled);
+
+ AddNode(node);
+ }
+ } else {
+ reader->skipCurrentElement();
}
}
// Make connections
- foreach (const NodeParam::SerializedConnection& con, desired_connections) {
- NodeParam::ConnectEdge(output_ptrs.value(con.output),
- con.input);
- }
+ XMLConnectNodes(output_ptrs, desired_connections);
// Ensure this and all children are in the main thread
// (FIXME: Weird place for this? This should probably be in ProjectLoadManager somehow)
@@ -228,7 +199,7 @@ QString Sequence::duration()
int64_t timestamp = Timecode::time_to_timestamp(timeline_length, video_params().time_base());
- return Timecode::timestamp_to_timecode(timestamp, video_params().time_base(), Timecode::CurrentDisplay());
+ return Timecode::timestamp_to_timecode(timestamp, video_params().time_base(), Core::instance()->GetTimecodeDisplay());
}
QString Sequence::rate()
diff --git a/app/project/item/sequence/sequence.h b/app/project/item/sequence/sequence.h
index c9311cd40..dd91dc2f4 100644
--- a/app/project/item/sequence/sequence.h
+++ b/app/project/item/sequence/sequence.h
@@ -27,6 +27,7 @@
#include "render/videoparams.h"
#include "project/item/footage/stream.h"
#include "project/item/item.h"
+#include "timeline/timelinepoints.h"
class Sequence;
using SequencePtr = std::shared_ptr;
@@ -34,7 +35,7 @@ using SequencePtr = std::shared_ptr;
/**
* @brief The main timeline object, an graph of edited clips that forms a complete edit
*/
-class Sequence : public Item, public NodeGraph
+class Sequence : public Item, public NodeGraph, public TimelinePoints
{
public:
Sequence();
diff --git a/app/project/project.cpp b/app/project/project.cpp
index 4cb69bd59..7f98197b9 100644
--- a/app/project/project.cpp
+++ b/app/project/project.cpp
@@ -23,7 +23,7 @@
#include
#include
-#include "common/xmlreadloop.h"
+#include "common/xmlutils.h"
#include "core.h"
#include "dialog/progress/progress.h"
#include "window/mainwindow/mainwindow.h"
@@ -35,29 +35,32 @@ Project::Project()
void Project::Load(QXmlStreamReader *reader, const QAtomicInt* cancelled)
{
+ qDebug() << "Hello?";
+
QHash footage_ptrs;
QList footage_connections;
- XMLReadLoop(reader, "project") {
- if (reader->isStartElement()) {
- if (reader->name() == "folder") {
+ while (XMLReadNextStartElement(reader)) {
+ if (reader->name() == QStringLiteral("folder")) {
- // Assume this folder is our root
- root_.Load(reader, footage_ptrs, footage_connections, cancelled);
+ // Assume this folder is our root
+ root_.Load(reader, footage_ptrs, footage_connections, cancelled);
- } else if (reader->name() == "colormanagement") {
+ } else if (reader->name() == QStringLiteral("colormanagement")) {
- // Read color management info
- XMLReadLoop(reader, "colormanagement") {
- if (reader->name() == "config") {
- reader->readNext();
- set_ocio_config(reader->text().toString());
- } else if (reader->name() == "default") {
- reader->readNext();
- set_default_input_colorspace(reader->text().toString());
- }
+ // Read color management info
+ while (XMLReadNextStartElement(reader)) {
+ if (reader->name() == QStringLiteral("config")) {
+ set_ocio_config(reader->readElementText());
+ } else if (reader->name() == QStringLiteral("default")) {
+ set_default_input_colorspace(reader->readElementText());
+ } else {
+ reader->skipCurrentElement();
}
}
+
+ } else {
+ reader->skipCurrentElement();
}
}
@@ -97,7 +100,7 @@ QString Project::name() const
if (filename_.isEmpty()) {
return tr("(untitled)");
} else {
- return QFileInfo(filename_).baseName();
+ return QFileInfo(filename_).completeBaseName();
}
}
@@ -137,3 +140,8 @@ ColorManager *Project::color_manager()
{
return &color_manager_;
}
+
+QList Project::get_items_of_type(Item::Type type) const
+{
+ return root_.get_children_of_type(type, true);
+}
diff --git a/app/project/project.h b/app/project/project.h
index b7ee369e1..3a90bb6f3 100644
--- a/app/project/project.h
+++ b/app/project/project.h
@@ -63,6 +63,8 @@ public:
ColorManager* color_manager();
+ QList get_items_of_type(Item::Type type) const;
+
signals:
void NameChanged();
diff --git a/app/project/projectloadmanager.cpp b/app/project/projectloadmanager.cpp
index a0062f61a..9dd6bbbfe 100644
--- a/app/project/projectloadmanager.cpp
+++ b/app/project/projectloadmanager.cpp
@@ -4,6 +4,8 @@
#include
#include
+#include "common/xmlutils.h"
+
ProjectLoadManager::ProjectLoadManager(const QString &filename) :
filename_(filename)
{
@@ -17,28 +19,32 @@ void ProjectLoadManager::Action()
if (project_file.open(QFile::ReadOnly | QFile::Text)) {
QXmlStreamReader reader(&project_file);
- while (!reader.atEnd()) {
- reader.readNext();
+ qDebug() << "Hello?";
- if (reader.isStartElement()) {
- if (reader.name() == "version") {
- reader.readNext();
+ while (XMLReadNextStartElement(&reader)) {
+ if (reader.name() == QStringLiteral("olive")) {
+ while(XMLReadNextStartElement(&reader)) {
+ if (reader.name() == QStringLiteral("version")) {
+ qDebug() << "Project version:" << reader.readElementText();
+ } else if (reader.name() == QStringLiteral("project")) {
+ ProjectPtr project = std::make_shared();
- qDebug() << "Project version:" << reader.text();
- } else if (reader.name() == "project") {
- ProjectPtr project = std::make_shared();
+ project->set_filename(filename_);
- project->set_filename(filename_);
+ project->Load(&reader, &IsCancelled());
- project->Load(&reader, &IsCancelled());
+ // Ensure project is in main thread
+ moveToThread(qApp->thread());
- // Ensure project is in main thread
- moveToThread(qApp->thread());
-
- if (!IsCancelled()) {
- emit ProjectLoaded(project);
+ if (!IsCancelled()) {
+ emit ProjectLoaded(project);
+ }
+ } else {
+ reader.skipCurrentElement();
}
}
+ } else {
+ reader.skipCurrentElement();
}
}
diff --git a/app/render/audioparams.cpp b/app/render/audioparams.cpp
index f91848e55..099c9ceac 100644
--- a/app/render/audioparams.cpp
+++ b/app/render/audioparams.cpp
@@ -26,6 +26,11 @@ const uint64_t &AudioParams::channel_layout() const
return channel_layout_;
}
+rational AudioParams::time_base() const
+{
+ return rational(1, sample_rate());
+}
+
AudioRenderingParams::AudioRenderingParams() :
format_(SampleFormat::SAMPLE_FMT_INVALID)
{
diff --git a/app/render/audioparams.h b/app/render/audioparams.h
index 566309c87..308f68fae 100644
--- a/app/render/audioparams.h
+++ b/app/render/audioparams.h
@@ -14,6 +14,7 @@ public:
const int& sample_rate() const;
const uint64_t& channel_layout() const;
+ rational time_base() const;
private:
int sample_rate_;
diff --git a/app/render/backend/audiorenderbackend.cpp b/app/render/backend/audiorenderbackend.cpp
index 5baf68c17..70dec1bb2 100644
--- a/app/render/backend/audiorenderbackend.cpp
+++ b/app/render/backend/audiorenderbackend.cpp
@@ -27,6 +27,8 @@ void AudioRenderBackend::SetParameters(const AudioRenderingParams ¶ms)
// Regenerate the cache ID
RegenerateCacheID();
+
+ emit ParamsChanged();
}
void AudioRenderBackend::ConnectViewer(ViewerOutput *node)
@@ -98,6 +100,18 @@ void AudioRenderBackend::ConnectWorkerToThis(RenderWorker *worker)
connect(arw, &AudioRenderWorker::ConformUnavailable, this, &AudioRenderBackend::ConformUnavailable, Qt::QueuedConnection);
}
+TimeRange AudioRenderBackend::PopNextFrameFromQueue()
+{
+ TimeRange range = cache_queue_.first();
+
+ // Limit range per worker to 2 seconds (FIXME: arbitrary, should be tweaked, maybe even in config?)
+ range.set_out(qMin(range.out(), range.in() + rational(2)));
+
+ cache_queue_.RemoveTimeRange(range);
+
+ return range;
+}
+
void AudioRenderBackend::ConformUnavailable(StreamPtr stream, const TimeRange &range, const rational &stream_time, const AudioRenderingParams& params)
{
ConformWaitInfo info = {stream, params, range, stream_time};
diff --git a/app/render/backend/audiorenderbackend.h b/app/render/backend/audiorenderbackend.h
index 775d7991a..5724e4e16 100644
--- a/app/render/backend/audiorenderbackend.h
+++ b/app/render/backend/audiorenderbackend.h
@@ -24,6 +24,9 @@ public:
QString CachePathName();
+signals:
+ void ParamsChanged();
+
protected:
virtual void ConnectViewer(ViewerOutput* node) override;
@@ -44,6 +47,8 @@ protected:
virtual void ConnectWorkerToThis(RenderWorker* worker) override;
+ virtual TimeRange PopNextFrameFromQueue() override;
+
QHash copy_map_;
private:
diff --git a/app/render/backend/opengl/openglproxy.cpp b/app/render/backend/opengl/openglproxy.cpp
index 2d2170da3..d486fbcd8 100644
--- a/app/render/backend/opengl/openglproxy.cpp
+++ b/app/render/backend/opengl/openglproxy.cpp
@@ -397,6 +397,10 @@ void OpenGLProxy::TextureToBuffer(const QVariant &tex_in, void *buffer)
{
OpenGLTextureCache::ReferencePtr texture = tex_in.value();
+ if (!texture) {
+ return;
+ }
+
QOpenGLFunctions* f = QOpenGLContext::currentContext()->functions();
buffer_.Attach(texture->texture());
buffer_.Bind();
diff --git a/app/render/backend/renderworker.cpp b/app/render/backend/renderworker.cpp
index 63aa3d308..07512a4ca 100644
--- a/app/render/backend/renderworker.cpp
+++ b/app/render/backend/renderworker.cpp
@@ -87,95 +87,41 @@ bool RenderWorker::IsStarted()
return started_;
}
-NodeValueTable RenderWorker::ProcessNode(const NodeDependency& dep)
-{
- const Node* node = dep.node();
-
- if (node->IsTrack()) {
- // If the range is not wholly contained in this Block, we'll need to do some extra processing
- return RenderBlock(static_cast(node), dep.range());
- }
-
- // FIXME: Cache certain values here if we've already processed them before
-
- // Generate database of input values of node
- NodeValueDatabase database = GenerateDatabase(node, dep.range());
-
- // By this point, the node should have all the inputs it needs to render correctly
- NodeValueTable table = node->Value(database);
-
- // Check if we have a shader for this output
- RunNodeAccelerated(node, dep.range(), database, &table);
-
- return table;
-}
-
-NodeValueTable RenderWorker::ProcessInput(const NodeInput *input, const TimeRange& range)
-{
- if (input->IsConnected()) {
- // Value will equal something from the connected node, follow it
- return ProcessNode(NodeDependency(input->get_connected_node(), range));
- } else {
- // Push onto the table the value at this time from the input
- QVariant input_value = input->get_value_at_time(range.in());
-
- NodeValueTable table;
- table.Push(input->data_type(), input_value);
- return table;
- }
-}
-
void RenderWorker::ReportUnavailableFootage(StreamPtr stream, Decoder::RetrieveState state, const rational& stream_time)
{
emit FootageUnavailable(stream, state, path_.range(), stream_time);
}
+void RenderWorker::InputProcessingEvent(NodeInput* input, const TimeRange& input_time, NodeValueTable *table)
+{
+ // Exception for Footage types where we actually retrieve some Footage data from a decoder
+ if (input->data_type() == NodeParam::kFootage) {
+ StreamPtr stream = ResolveStreamFromInput(input);
+
+ if (stream) {
+ DecoderPtr decoder = ResolveDecoderFromInput(stream);
+
+ if (decoder) {
+
+ Decoder::RetrieveState state = decoder->GetRetrieveState(input_time.out());
+
+ if (state == Decoder::kReady) {
+ FrameToValue(decoder, stream, input_time, table);
+ } else {
+ ReportUnavailableFootage(stream, state, input_time.out());
+ }
+ }
+ }
+ }
+}
+
+void RenderWorker::ProcessNodeEvent(const Node *node, const TimeRange &range, const NodeValueDatabase &input_params, NodeValueTable* output_params)
+{
+ // Check if we have a shader for this output
+ RunNodeAccelerated(node, range, input_params, output_params);
+}
+
const NodeDependency &RenderWorker::CurrentPath() const
{
return path_;
}
-
-
-#include "common/functiontimer.h"
-NodeValueDatabase RenderWorker::GenerateDatabase(const Node* node, const TimeRange &range)
-{
- NodeValueDatabase database;
-
- // We need to insert tables into the database for each input
- foreach (NodeParam* param, node->parameters()) {
- if (IsCancelled()) {
- return NodeValueDatabase();
- }
-
- if (param->type() == NodeParam::kInput) {
- NodeInput* input = static_cast(param);
- TimeRange input_time = node->InputTimeAdjustment(input, range);
-
- NodeValueTable table = ProcessInput(input, input_time);
-
- // Exception for Footage types where we actually retrieve some Footage data from a decoder
- if (input->data_type() == NodeParam::kFootage) {
- StreamPtr stream = ResolveStreamFromInput(input);
-
- if (stream) {
- DecoderPtr decoder = ResolveDecoderFromInput(stream);
-
- if (decoder) {
-
- Decoder::RetrieveState state = decoder->GetRetrieveState(input_time.out());
-
- if (state == Decoder::kReady) {
- FrameToValue(decoder, stream, input_time, &table);
- } else {
- ReportUnavailableFootage(stream, state, input_time.out());
- }
- }
- }
- }
-
- database.Insert(input, table);
- }
- }
-
- return database;
-}
diff --git a/app/render/backend/renderworker.h b/app/render/backend/renderworker.h
index 7965e297e..885d3c60f 100644
--- a/app/render/backend/renderworker.h
+++ b/app/render/backend/renderworker.h
@@ -3,13 +3,13 @@
#include
-#include "common/cancelableobject.h"
#include "common/constructors.h"
-#include "node/output/track/track.h"
-#include "node/node.h"
#include "decodercache.h"
+#include "node/node.h"
+#include "node/output/track/track.h"
+#include "node/traverser.h"
-class RenderWorker : public QObject, public CancelableObject
+class RenderWorker : public QObject, public NodeTraverser
{
Q_OBJECT
public:
@@ -38,24 +38,21 @@ protected:
virtual void RunNodeAccelerated(const Node *node, const TimeRange& range, const NodeValueDatabase &input_params, NodeValueTable* output_params);
- StreamPtr ResolveStreamFromInput(NodeInput* input);
- DecoderPtr ResolveDecoderFromInput(StreamPtr stream);
-
virtual void FrameToValue(DecoderPtr decoder, StreamPtr stream, const TimeRange &range, NodeValueTable* table) = 0;
- NodeValueTable ProcessNode(const NodeDependency &dep);
-
- virtual NodeValueTable RenderBlock(const TrackOutput *track, const TimeRange& range) = 0;
-
- NodeValueTable ProcessInput(const NodeInput* input, const TimeRange &range);
-
virtual void ReportUnavailableFootage(StreamPtr stream, Decoder::RetrieveState state, const rational& stream_time);
+ virtual void InputProcessingEvent(NodeInput *input, const TimeRange &input_time, NodeValueTable* table) override;
+
+ virtual void ProcessNodeEvent(const Node *node, const TimeRange &range, const NodeValueDatabase &input_params, NodeValueTable* output_params) override;
+
+ StreamPtr ResolveStreamFromInput(NodeInput* input);
+
+ DecoderPtr ResolveDecoderFromInput(StreamPtr stream);
+
const NodeDependency& CurrentPath() const;
private:
- NodeValueDatabase GenerateDatabase(const Node *node, const TimeRange &range);
-
bool started_;
DecoderCache* decoder_cache_;
diff --git a/app/render/backend/videorenderworker.cpp b/app/render/backend/videorenderworker.cpp
index 4c4221426..a2de88f3b 100644
--- a/app/render/backend/videorenderworker.cpp
+++ b/app/render/backend/videorenderworker.cpp
@@ -179,6 +179,8 @@ void VideoRenderWorker::HashNodeRecursively(QCryptographicHash *hash, const Node
if (stream->type() == Stream::kVideo) {
hash->addData(QStringLiteral("%1/%2").arg(QString::number(input_time.numerator()),
QString::number(input_time.denominator())).toUtf8());
+
+ hash->addData(QString::number(static_cast(stream.get())->start_time()).toUtf8());
/*Decoder::RetrieveState state = decoder->GetRetrieveState(input_time);
if (state == Decoder::kReady) {
diff --git a/app/shaders/stroke.frag b/app/shaders/stroke.frag
index 20ff1607f..7388f71b1 100644
--- a/app/shaders/stroke.frag
+++ b/app/shaders/stroke.frag
@@ -10,11 +10,18 @@ uniform sampler2D tex_in;
uniform vec3 color_in;
uniform float radius_in;
uniform float opacity_in;
+uniform bool inner_in;
void main(void) {
- if (radius_in == 0.0 || opacity_in == 0.0) {
+ vec4 pixel_here = texture2D(tex_in, ove_texcoord);
+
+ // Detect no-op situations
+ if (radius_in == 0.0
+ || opacity_in == 0.0
+ || (inner_in && pixel_here.a == 0.0)
+ || (!inner_in && pixel_here.a == 1.0)) {
// No-op, do nothing
- gl_FragColor = texture2D(tex_in, ove_texcoord);
+ gl_FragColor = pixel_here;
return;
}
@@ -31,7 +38,13 @@ void main(void) {
if (abs(length(vec2(i, j))) < radius) {
// Get pixel here
- stroke_weight += texture2D(tex_in, ove_texcoord + vec2(x_coord, y_coord)).a;
+ float alpha = texture2D(tex_in, ove_texcoord + vec2(x_coord, y_coord)).a;
+
+ if (inner_in) {
+ alpha = 1.0 - alpha;
+ }
+
+ stroke_weight += alpha;
if (stroke_weight >= 1.0) {
break;
@@ -47,15 +60,21 @@ void main(void) {
stroke_weight *= opacity_in * 0.01;
+ if (inner_in) {
+ stroke_weight *= pixel_here.a;
+ }
+
// Make RGBA color
vec4 stroke_col = vec4(vec3(1.0) * stroke_weight, stroke_weight);
//vec4 stroke_col = vec4(color_in * stroke_weight, stroke_weight);
- // Alpha over color here
- vec4 pixel_here = texture2D(tex_in, ove_texcoord);
-
- stroke_col *= 1.0 - pixel_here.a;
- stroke_col += pixel_here;
+ if (inner_in) {
+ // Alpha over the stroke over the texture
+ stroke_col = pixel_here * (1.0 - stroke_col.a) + stroke_col;
+ } else {
+ // Alpha over the texture over the stroke
+ stroke_col = stroke_col * (1.0 - pixel_here.a) + pixel_here;
+ }
gl_FragColor = stroke_col;
}
diff --git a/app/shaders/stroke.xml b/app/shaders/stroke.xml
index 04de10d07..bb9c7aebc 100644
--- a/app/shaders/stroke.xml
+++ b/app/shaders/stroke.xml
@@ -38,6 +38,12 @@
100
+
+
+ Inner
+ false
+
+
diff --git a/app/timeline/CMakeLists.txt b/app/timeline/CMakeLists.txt
index c0bd69c9f..000775dcc 100644
--- a/app/timeline/CMakeLists.txt
+++ b/app/timeline/CMakeLists.txt
@@ -18,6 +18,12 @@ set(OLIVE_SOURCES
${OLIVE_SOURCES}
timeline/timelinecoordinate.h
timeline/timelinecoordinate.cpp
+ timeline/timelinemarker.h
+ timeline/timelinemarker.cpp
+ timeline/timelinepoints.h
+ timeline/timelinepoints.cpp
+ timeline/timelineworkarea.h
+ timeline/timelineworkarea.cpp
timeline/trackreference.h
timeline/trackreference.cpp
PARENT_SCOPE
diff --git a/app/timeline/timelinemarker.cpp b/app/timeline/timelinemarker.cpp
new file mode 100644
index 000000000..83a136c7b
--- /dev/null
+++ b/app/timeline/timelinemarker.cpp
@@ -0,0 +1,61 @@
+#include "timelinemarker.h"
+
+TimelineMarker::TimelineMarker(const TimeRange &time, const QString &name, QObject *parent) :
+ QObject(parent),
+ time_(time),
+ name_(name)
+{
+}
+
+const TimeRange &TimelineMarker::time() const
+{
+ return time_;
+}
+
+void TimelineMarker::set_time(const TimeRange &time)
+{
+ time_ = time;
+ emit TimeChanged(time_);
+}
+
+const QString &TimelineMarker::name() const
+{
+ return name_;
+}
+
+void TimelineMarker::set_name(const QString &name)
+{
+ name_ = name;
+ emit NameChanged(name_);
+}
+
+TimelineMarkerList::~TimelineMarkerList()
+{
+ qDeleteAll(markers_);
+}
+
+void TimelineMarkerList::AddMarker(const TimeRange &time, const QString &name)
+{
+ TimelineMarker* m = new TimelineMarker(time, name);
+ markers_.append(m);
+ emit MarkerAdded(m);
+}
+
+void TimelineMarkerList::RemoveMarker(TimelineMarker *marker)
+{
+ for (int i=0;i &TimelineMarkerList::list() const
+{
+ return markers_;
+}
diff --git a/app/timeline/timelinemarker.h b/app/timeline/timelinemarker.h
new file mode 100644
index 000000000..998f27585
--- /dev/null
+++ b/app/timeline/timelinemarker.h
@@ -0,0 +1,56 @@
+#ifndef TIMELINEMARKER_H
+#define TIMELINEMARKER_H
+
+#include
+
+#include "common/timerange.h"
+
+class TimelineMarker : public QObject
+{
+ Q_OBJECT
+public:
+ TimelineMarker(const TimeRange& time = TimeRange(), const QString& name = QString(), QObject* parent = nullptr);
+
+ const TimeRange &time() const;
+ void set_time(const TimeRange& time);
+
+ const QString& name() const;
+ void set_name(const QString& name);
+
+signals:
+ void TimeChanged(const TimeRange& time);
+
+ void NameChanged(const QString& name);
+
+private:
+ TimeRange time_;
+
+ QString name_;
+
+};
+
+class TimelineMarkerList : public QObject
+{
+ Q_OBJECT
+public:
+ TimelineMarkerList() = default;
+
+ virtual ~TimelineMarkerList() override;
+
+ void AddMarker(const TimeRange& time = TimeRange(), const QString& name = QString());
+
+ void RemoveMarker(TimelineMarker* marker);
+
+ const QList &list() const;
+
+signals:
+ void MarkerAdded(TimelineMarker* marker);
+
+ void MarkerRemoved(TimelineMarker* marker);
+
+private:
+ QList markers_;
+
+};
+
+#endif // TIMELINEMARKER_H
diff --git a/app/timeline/timelinepoints.cpp b/app/timeline/timelinepoints.cpp
new file mode 100644
index 000000000..b3aae146c
--- /dev/null
+++ b/app/timeline/timelinepoints.cpp
@@ -0,0 +1,21 @@
+#include "timelinepoints.h"
+
+TimelineMarkerList *TimelinePoints::markers()
+{
+ return &markers_;
+}
+
+const TimelineMarkerList *TimelinePoints::markers() const
+{
+ return &markers_;
+}
+
+const TimelineWorkArea *TimelinePoints::workarea() const
+{
+ return &workarea_;
+}
+
+TimelineWorkArea *TimelinePoints::workarea()
+{
+ return &workarea_;
+}
diff --git a/app/timeline/timelinepoints.h b/app/timeline/timelinepoints.h
new file mode 100644
index 000000000..ed2570f1c
--- /dev/null
+++ b/app/timeline/timelinepoints.h
@@ -0,0 +1,25 @@
+#ifndef TIMELINEPOINTS_H
+#define TIMELINEPOINTS_H
+
+#include "timelinemarker.h"
+#include "timelineworkarea.h"
+
+class TimelinePoints
+{
+public:
+ TimelinePoints() = default;
+
+ TimelineMarkerList* markers();
+ const TimelineMarkerList* markers() const;
+
+ TimelineWorkArea* workarea();
+ const TimelineWorkArea* workarea() const;
+
+private:
+ TimelineMarkerList markers_;
+
+ TimelineWorkArea workarea_;
+
+};
+
+#endif // TIMELINEPOINTS_H
diff --git a/app/timeline/timelineworkarea.cpp b/app/timeline/timelineworkarea.cpp
new file mode 100644
index 000000000..57b29364a
--- /dev/null
+++ b/app/timeline/timelineworkarea.cpp
@@ -0,0 +1,41 @@
+#include "timelineworkarea.h"
+
+const rational TimelineWorkArea::kResetIn = 0;
+const rational TimelineWorkArea::kResetOut = RATIONAL_MAX;
+
+TimelineWorkArea::TimelineWorkArea(QObject *parent) :
+ QObject(parent)
+{
+}
+
+bool TimelineWorkArea::enabled() const
+{
+ return workarea_enabled_;
+}
+
+void TimelineWorkArea::set_enabled(bool e)
+{
+ workarea_enabled_ = e;
+ emit EnabledChanged(workarea_enabled_);
+}
+
+const TimeRange &TimelineWorkArea::range() const
+{
+ return workarea_range_;
+}
+
+void TimelineWorkArea::set_range(const TimeRange &range)
+{
+ workarea_range_ = range;
+ emit RangeChanged(workarea_range_);
+}
+
+const rational &TimelineWorkArea::in() const
+{
+ return workarea_range_.in();
+}
+
+const rational &TimelineWorkArea::out() const
+{
+ return workarea_range_.out();
+}
diff --git a/app/timeline/timelineworkarea.h b/app/timeline/timelineworkarea.h
new file mode 100644
index 000000000..bad23bc82
--- /dev/null
+++ b/app/timeline/timelineworkarea.h
@@ -0,0 +1,37 @@
+#ifndef TIMELINEWORKAREA_H
+#define TIMELINEWORKAREA_H
+
+#include
+
+#include "common/timerange.h"
+
+class TimelineWorkArea : public QObject
+{
+ Q_OBJECT
+public:
+ TimelineWorkArea(QObject* parent = nullptr);
+
+ bool enabled() const;
+ void set_enabled(bool e);
+
+ const rational& in() const;
+ const rational& out() const;
+ const TimeRange& range() const;
+ void set_range(const TimeRange& range);
+
+ static const rational kResetIn;
+ static const rational kResetOut;
+
+signals:
+ void EnabledChanged(bool e);
+
+ void RangeChanged(const TimeRange& r);
+
+private:
+ bool workarea_enabled_;
+
+ TimeRange workarea_range_;
+
+};
+
+#endif // TIMELINEWORKAREA_H
diff --git a/app/widget/menu/menushared.cpp b/app/widget/menu/menushared.cpp
index 6cd1df5f4..fb36b2238 100644
--- a/app/widget/menu/menushared.cpp
+++ b/app/widget/menu/menushared.cpp
@@ -34,25 +34,25 @@ MenuShared::MenuShared()
new_folder_item_ = Menu::CreateItem(this, "newfolder", Core::instance(), SLOT(CreateNewFolder()));
// "Edit" menu shared items
- edit_cut_item_ = Menu::CreateItem(this, "cut", nullptr, nullptr, "Ctrl+X");
- edit_copy_item_ = Menu::CreateItem(this, "copy", nullptr, nullptr, "Ctrl+C");
- edit_paste_item_ = Menu::CreateItem(this, "paste", nullptr, nullptr, "Ctrl+V");
- edit_paste_insert_item_ = Menu::CreateItem(this, "pasteinsert", nullptr, nullptr, "Ctrl+Shift+V");
+ edit_cut_item_ = Menu::CreateItem(this, "cut", this, SLOT(CutTriggered()), "Ctrl+X");
+ edit_copy_item_ = Menu::CreateItem(this, "copy", this, SLOT(CopyTriggered()), "Ctrl+C");
+ edit_paste_item_ = Menu::CreateItem(this, "paste", this, SLOT(PasteTriggered()), "Ctrl+V");
+ edit_paste_insert_item_ = Menu::CreateItem(this, "pasteinsert", this, SLOT(PasteInsertTriggered()), "Ctrl+Shift+V");
edit_duplicate_item_ = Menu::CreateItem(this, "duplicate", nullptr, nullptr, "Ctrl+D");
- edit_delete_item_ = Menu::CreateItem(this, "delete", this, SLOT(DeleteSelected()), "Del");
- edit_ripple_delete_item_ = Menu::CreateItem(this, "rippledelete", this, SLOT(RippleDelete()), "Shift+Del");
- edit_split_item_ = Menu::CreateItem(this, "split", this, SLOT(SplitAtPlayhead()), "Ctrl+K");
+ edit_delete_item_ = Menu::CreateItem(this, "delete", this, SLOT(DeleteSelectedTriggered()), "Del");
+ edit_ripple_delete_item_ = Menu::CreateItem(this, "rippledelete", this, SLOT(RippleDeleteTriggered()), "Shift+Del");
+ edit_split_item_ = Menu::CreateItem(this, "split", this, SLOT(SplitAtPlayheadTriggered()), "Ctrl+K");
// "In/Out" menu shared items
- inout_set_in_item_ = Menu::CreateItem(this, "setinpoint", nullptr, nullptr, "I");
- inout_set_out_item_ = Menu::CreateItem(this, "setoutpoint", nullptr, nullptr, "O");
- inout_reset_in_item_ = Menu::CreateItem(this, "resetin", nullptr, nullptr);
- inout_reset_out_item_ = Menu::CreateItem(this, "resetout", nullptr, nullptr);
- inout_clear_inout_item_ = Menu::CreateItem(this, "clearinout", nullptr, nullptr, "G");
+ inout_set_in_item_ = Menu::CreateItem(this, "setinpoint", this, SLOT(SetInTriggered()), "I");
+ inout_set_out_item_ = Menu::CreateItem(this, "setoutpoint", this, SLOT(SetOutTriggered()), "O");
+ inout_reset_in_item_ = Menu::CreateItem(this, "resetin", this, SLOT(ResetInTriggered()));
+ inout_reset_out_item_ = Menu::CreateItem(this, "resetout", this, SLOT(ResetOutTriggered()));
+ inout_clear_inout_item_ = Menu::CreateItem(this, "clearinout", this, SLOT(ClearInOutTriggered()), "G");
// "Clip Edit" menu shared items
clip_add_default_transition_item_ = Menu::CreateItem(this, "deftransition", nullptr, nullptr, "Ctrl+Shift+D");
- clip_link_unlink_item_ = Menu::CreateItem(this, "linkunlink", nullptr, nullptr, "Ctrl+L");
+ clip_link_unlink_item_ = Menu::CreateItem(this, "linkunlink", this, SLOT(ToggleLinksTriggered()), "Ctrl+L");
clip_enable_disable_item_ = Menu::CreateItem(this, "enabledisable", nullptr, nullptr, "Shift+E");
clip_nest_item_ = Menu::CreateItem(this, "nest", nullptr, nullptr);
@@ -112,7 +112,7 @@ MenuShared *MenuShared::instance()
return instance_;
}
-void MenuShared::SplitAtPlayhead()
+void MenuShared::SplitAtPlayheadTriggered()
{
TimelinePanel* timeline = PanelManager::instance()->MostRecentlyFocused();
@@ -121,16 +121,66 @@ void MenuShared::SplitAtPlayhead()
}
}
-void MenuShared::DeleteSelected()
+void MenuShared::DeleteSelectedTriggered()
{
PanelManager::instance()->CurrentlyFocused()->DeleteSelected();
}
-void MenuShared::RippleDelete()
+void MenuShared::RippleDeleteTriggered()
{
PanelManager::instance()->CurrentlyFocused()->RippleDelete();
}
+void MenuShared::SetInTriggered()
+{
+ PanelManager::instance()->CurrentlyFocused()->SetIn();
+}
+
+void MenuShared::SetOutTriggered()
+{
+ PanelManager::instance()->CurrentlyFocused()->SetOut();
+}
+
+void MenuShared::ResetInTriggered()
+{
+ PanelManager::instance()->CurrentlyFocused()->ResetIn();
+}
+
+void MenuShared::ResetOutTriggered()
+{
+ PanelManager::instance()->CurrentlyFocused()->ResetOut();
+}
+
+void MenuShared::ClearInOutTriggered()
+{
+ PanelManager::instance()->CurrentlyFocused()->ClearInOut();
+}
+
+void MenuShared::ToggleLinksTriggered()
+{
+ PanelManager::instance()->CurrentlyFocused()->ToggleLinks();
+}
+
+void MenuShared::CutTriggered()
+{
+ PanelManager::instance()->CurrentlyFocused()->CutSelected();
+}
+
+void MenuShared::CopyTriggered()
+{
+ PanelManager::instance()->CurrentlyFocused()->CopySelected();
+}
+
+void MenuShared::PasteTriggered()
+{
+ PanelManager::instance()->CurrentlyFocused()->Paste();
+}
+
+void MenuShared::PasteInsertTriggered()
+{
+ PanelManager::instance()->CurrentlyFocused()->PasteInsert();
+}
+
void MenuShared::Retranslate()
{
// "New" menu shared items
diff --git a/app/widget/menu/menushared.h b/app/widget/menu/menushared.h
index 2cee72066..c1e6e54ff 100644
--- a/app/widget/menu/menushared.h
+++ b/app/widget/menu/menushared.h
@@ -75,11 +75,31 @@ private:
static MenuShared* instance_;
private slots:
- void SplitAtPlayhead();
+ void SplitAtPlayheadTriggered();
- void DeleteSelected();
+ void DeleteSelectedTriggered();
- void RippleDelete();
+ void RippleDeleteTriggered();
+
+ void SetInTriggered();
+
+ void SetOutTriggered();
+
+ void ResetInTriggered();
+
+ void ResetOutTriggered();
+
+ void ClearInOutTriggered();
+
+ void ToggleLinksTriggered();
+
+ void CutTriggered();
+
+ void CopyTriggered();
+
+ void PasteTriggered();
+
+ void PasteInsertTriggered();
};
diff --git a/app/widget/nodeview/nodeview.cpp b/app/widget/nodeview/nodeview.cpp
index f1d895a05..9a1ce07cc 100644
--- a/app/widget/nodeview/nodeview.cpp
+++ b/app/widget/nodeview/nodeview.cpp
@@ -20,11 +20,14 @@
#include "nodeview.h"
+#include
#include
+#include
#include "core.h"
#include "nodeviewundo.h"
#include "node/factory.h"
+#include "common/xmlutils.h"
NodeView::NodeView(QWidget *parent) :
QGraphicsView(parent),
@@ -90,16 +93,7 @@ void NodeView::DeleteSelected()
return;
}
- QList selected = scene_.selectedItems();
- QList selected_nodes;
-
- foreach (QGraphicsItem* item, selected) {
- NodeViewItem* node_item = dynamic_cast(item);
-
- if (node_item) {
- selected_nodes.append(node_item->node());
- }
- }
+ QList selected_nodes = scene_.GetSelectedNodes();
if (selected_nodes.isEmpty()) {
return;
@@ -110,20 +104,12 @@ void NodeView::DeleteSelected()
void NodeView::SelectAll()
{
- QList all_items = this->items();
-
- foreach (QGraphicsItem* i, all_items) {
- i->setSelected(true);
- }
+ scene_.SelectAll();
}
void NodeView::DeselectAll()
{
- QList selected_items = scene_.selectedItems();
-
- foreach (QGraphicsItem* i, selected_items) {
- i->setSelected(false);
- }
+ scene_.DeselectAll();
}
void NodeView::Select(const QList &nodes)
@@ -133,9 +119,6 @@ void NodeView::Select(const QList &nodes)
foreach (Node* n, nodes) {
NodeViewItem* item = scene_.NodeToUIObject(n);
- Q_ASSERT(n);
- Q_ASSERT(item);
-
item->setSelected(true);
}
}
@@ -150,6 +133,125 @@ void NodeView::SelectWithDependencies(QList nodes)
Select(nodes);
}
+void NodeView::CopySelected(bool cut)
+{
+ if (!graph_) {
+ return;
+ }
+
+ QList selected = scene_.GetSelectedNodes();
+
+ if (selected.isEmpty()) {
+ return;
+ }
+
+ QString copy_str;
+
+ QXmlStreamWriter writer(©_str);
+ writer.setAutoFormatting(true);
+
+ writer.writeStartDocument();
+ writer.writeStartElement(QStringLiteral("olive"));
+
+ foreach (Node* n, selected) {
+ n->Save(&writer);
+ }
+
+ writer.writeEndElement(); // clipboard
+ writer.writeEndDocument();
+
+ if (cut) {
+ DeleteSelected();
+ }
+
+ QGuiApplication::clipboard()->setText(copy_str);
+}
+
+void NodeView::Paste()
+{
+ if (!graph_) {
+ return;
+ }
+
+ QString clipboard = QGuiApplication::clipboard()->text();
+
+ if (clipboard.isEmpty()) {
+ return;
+ }
+
+ QXmlStreamReader reader(clipboard);
+
+ QList pasted_nodes;
+ QHash output_ptrs;
+ QList desired_connections;
+ QList footage_connections;
+
+ while (XMLReadNextStartElement(&reader)) {
+ if (reader.name() == QStringLiteral("olive")) {
+ while (XMLReadNextStartElement(&reader)) {
+ if (reader.name() == QStringLiteral("node")) {
+ Node* node = XMLLoadNode(&reader);
+
+ if (node) {
+ node->Load(&reader, output_ptrs, desired_connections, footage_connections, nullptr);
+
+ graph_->AddNode(node);
+
+ pasted_nodes.append(node);
+ }
+ } else {
+ reader.skipCurrentElement();
+ }
+ }
+ } else {
+ reader.skipCurrentElement();
+ }
+ }
+
+ // Make connections
+ if (!desired_connections.isEmpty()) {
+ XMLConnectNodes(output_ptrs, desired_connections);
+ }
+
+ // Connect footage to existing footage if it exists
+ if (!footage_connections.isEmpty()) {
+ // Get list of all footage from project
+ // FIXME: Assumes sequence
+ QList footage = static_cast(graph_)->project()->get_items_of_type(Item::kFootage);
+
+ if (!footage.isEmpty()) {
+ foreach (const NodeInput::FootageConnection& con, footage_connections) {
+ if (con.footage) {
+ // Assume this is a pointer to a Stream*
+ Stream* loaded_stream = reinterpret_cast(con.footage);
+
+ bool found = false;
+
+ foreach (ItemPtr item, footage) {
+ const QList& streams = std::static_pointer_cast(item)->streams();
+
+ foreach (StreamPtr s, streams) {
+ if (s.get() == loaded_stream) {
+ con.input->set_standard_value(QVariant::fromValue(s));
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (!pasted_nodes.isEmpty()) {
+ // FIXME: Attach to cursor so user can drop in place
+ }
+}
+
void NodeView::ItemsChanged()
{
QHash::const_iterator i;
@@ -263,21 +365,7 @@ void NodeView::mouseMoveEvent(QMouseEvent *event)
void NodeView::SceneSelectionChangedSlot()
{
- // Get the scene's selected items and convert it into a list of selected nodes
- QList selected_items = scene_.selectedItems();
-
- QList selected_nodes;
-
- for (int i=0;i(selected_items.at(i));
-
- if (item != nullptr) {
- selected_nodes.append(item->node());
- }
- }
-
- emit SelectionChanged(selected_nodes);
+ emit SelectionChanged(scene_.GetSelectedNodes());
}
void NodeView::ShowContextMenu(const QPoint &pos)
diff --git a/app/widget/nodeview/nodeview.h b/app/widget/nodeview/nodeview.h
index 4ea98bea3..7811ae4bb 100644
--- a/app/widget/nodeview/nodeview.h
+++ b/app/widget/nodeview/nodeview.h
@@ -57,6 +57,9 @@ public:
void Select(const QList& nodes);
void SelectWithDependencies(QList nodes);
+ void CopySelected(bool cut);
+ void Paste();
+
signals:
/**
* @brief Signal emitted when the selected nodes have changed
diff --git a/app/widget/nodeview/nodeviewscene.cpp b/app/widget/nodeview/nodeviewscene.cpp
index 750e391a2..dffd8702f 100644
--- a/app/widget/nodeview/nodeviewscene.cpp
+++ b/app/widget/nodeview/nodeviewscene.cpp
@@ -10,6 +10,15 @@ NodeViewScene::NodeViewScene(QObject *parent) :
void NodeViewScene::clear()
{
+ // Deselect everything (prevents signals that a selection has changed after deleting an object)
+ DeselectAll();
+
+ // HACK: QGraphicsScene contains some sort of internal hashing of the selected items which doesn't update unless
+ // we call a function like this. That means even though we deselect all items above, QGraphicsScene will
+ // continue to incorrectly signal selectionChanged() when items that were selected (but are now not) get
+ // deleted. Calling this function appears to update the internal cache and prevent this.
+ selectedItems();
+
{
QHash::const_iterator i;
for (i=item_map_.begin();i!=item_map_.end();i++) {
@@ -27,6 +36,24 @@ void NodeViewScene::clear()
}
}
+void NodeViewScene::SelectAll()
+{
+ QList all_items = this->items();
+
+ foreach (QGraphicsItem* i, all_items) {
+ i->setSelected(true);
+ }
+}
+
+void NodeViewScene::DeselectAll()
+{
+ QList selected_items = this->selectedItems();
+
+ foreach (QGraphicsItem* i, selected_items) {
+ i->setSelected(false);
+ }
+}
+
NodeViewItem *NodeViewScene::NodeToUIObject(Node *n)
{
return item_map_.value(n);
@@ -42,6 +69,34 @@ void NodeViewScene::SetGraph(NodeGraph *graph)
graph_ = graph;
}
+QList NodeViewScene::GetSelectedNodes() const
+{
+ QHash::const_iterator iterator;
+ QList selected;
+
+ for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) {
+ if (iterator.value()->isSelected()) {
+ selected.append(iterator.key());
+ }
+ }
+
+ return selected;
+}
+
+QList NodeViewScene::GetSelectedItems() const
+{
+ QHash::const_iterator iterator;
+ QList selected;
+
+ for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) {
+ if (iterator.value()->isSelected()) {
+ selected.append(iterator.value());
+ }
+ }
+
+ return selected;
+}
+
const QHash &NodeViewScene::item_map() const
{
return item_map_;
diff --git a/app/widget/nodeview/nodeviewscene.h b/app/widget/nodeview/nodeviewscene.h
index 2c768fd11..8a2c22933 100644
--- a/app/widget/nodeview/nodeviewscene.h
+++ b/app/widget/nodeview/nodeviewscene.h
@@ -16,6 +16,9 @@ public:
void clear();
+ void SelectAll();
+ void DeselectAll();
+
/**
* @brief Retrieve the graphical widget corresponding to a specific Node
*
@@ -38,6 +41,9 @@ public:
void SetGraph(NodeGraph* graph);
+ QList GetSelectedNodes() const;
+ QList GetSelectedItems() const;
+
const QHash& item_map() const;
const QHash& edge_map() const;
diff --git a/app/widget/panel/panel.h b/app/widget/panel/panel.h
index e324c149f..ff8ce54b1 100644
--- a/app/widget/panel/panel.h
+++ b/app/widget/panel/panel.h
@@ -118,6 +118,28 @@ public:
virtual void DecreaseTrackHeight(){}
+ virtual void SetIn(){}
+
+ virtual void SetOut(){}
+
+ virtual void ResetIn(){}
+
+ virtual void ResetOut(){}
+
+ virtual void ClearInOut(){}
+
+ virtual void SetMarker(){}
+
+ virtual void ToggleLinks(){}
+
+ virtual void CutSelected(){}
+
+ virtual void CopySelected(){}
+
+ virtual void Paste(){}
+
+ virtual void PasteInsert(){}
+
protected:
/**
* @brief paintEvent
diff --git a/app/widget/playbackcontrols/playbackcontrols.cpp b/app/widget/playbackcontrols/playbackcontrols.cpp
index 523a7a9e5..400fdedba 100644
--- a/app/widget/playbackcontrols/playbackcontrols.cpp
+++ b/app/widget/playbackcontrols/playbackcontrols.cpp
@@ -24,7 +24,7 @@
#include
#include
-#include "common/timecodefunctions.h"
+#include "core.h"
#include "config/config.h"
#include "ui/icons/icons.h"
@@ -120,6 +120,8 @@ PlaybackControls::PlaybackControls(QWidget *parent) :
UpdateIcons();
SetTimebase(0);
+
+ connect(Core::instance(), &Core::TimecodeDisplayChanged, this, &PlaybackControls::TimecodeChanged);
}
void PlaybackControls::SetTimecodeEnabled(bool enabled)
@@ -147,9 +149,11 @@ void PlaybackControls::SetEndTime(const int64_t &r)
return;
}
- end_tc_lbl_->setText(Timecode::timestamp_to_timecode(r,
+ end_time_ = r;
+
+ end_tc_lbl_->setText(Timecode::timestamp_to_timecode(end_time_,
time_base_,
- Timecode::CurrentDisplay()));
+ Core::instance()->GetTimecodeDisplay()));
}
void PlaybackControls::ShowPauseButton()
@@ -181,3 +185,9 @@ void PlaybackControls::UpdateIcons()
next_frame_btn_->setIcon(icon::NextFrame);
go_to_end_btn_->setIcon(icon::GoToEnd);
}
+
+void PlaybackControls::TimecodeChanged()
+{
+ // Update end time
+ SetEndTime(end_time_);
+}
diff --git a/app/widget/playbackcontrols/playbackcontrols.h b/app/widget/playbackcontrols/playbackcontrols.h
index 2c9701d91..9ad983f0b 100644
--- a/app/widget/playbackcontrols/playbackcontrols.h
+++ b/app/widget/playbackcontrols/playbackcontrols.h
@@ -101,6 +101,8 @@ private:
TimeSlider* cur_tc_lbl_;
QLabel* end_tc_lbl_;
+ int64_t end_time_;
+
rational time_base_;
QPushButton* go_to_start_btn_;
@@ -113,7 +115,7 @@ private:
QStackedWidget* playpause_stack_;
private slots:
-
+ void TimecodeChanged();
};
diff --git a/app/widget/resizablescrollbar/resizablescrollbar.cpp b/app/widget/resizablescrollbar/resizablescrollbar.cpp
index fc6a79d44..f18a86037 100644
--- a/app/widget/resizablescrollbar/resizablescrollbar.cpp
+++ b/app/widget/resizablescrollbar/resizablescrollbar.cpp
@@ -39,11 +39,8 @@ void ResizableScrollBar::mouseMoveEvent(QMouseEvent *event)
QRect sr = style()->subControlRect(QStyle::CC_ScrollBar, &opt,
QStyle::SC_ScrollBarSlider, this);
- QRect gr = style()->subControlRect(QStyle::CC_ScrollBar, &opt,
- QStyle::SC_ScrollBarGroove, this);
if (mouse_dragging_) {
-
int new_drag_pos = GetActiveMousePos(event);
int mouse_movement = new_drag_pos - mouse_drag_start_;
mouse_drag_start_ = new_drag_pos;
@@ -52,21 +49,29 @@ void ResizableScrollBar::mouseMoveEvent(QMouseEvent *event)
mouse_movement = -mouse_movement;
}
- double scale_multiplier = static_cast(sr.width()) / static_cast(sr.width() + mouse_movement);
- emit RequestScale(scale_multiplier);
+ double width_adjustment = static_cast(sr.width() + mouse_movement);
- if (mouse_handle_state_ == kInTopHandle) {
- int slider_min = gr.x();
- int slider_max = gr.right() - (sr.width() + mouse_movement);
- int val = QStyle::sliderValueFromPosition(minimum(),
- maximum(),
- event->pos().x() - slider_min,
- slider_max - slider_min,
- opt.upsideDown);
+ // Prevent dividing by zero or emitting a negative scale
+ if (width_adjustment > 0) {
+ double scale_multiplier = static_cast(sr.width()) / width_adjustment;
+ emit RequestScale(scale_multiplier);
- setValue(val);
- } else {
- setValue(qRound(static_cast(value()) * scale_multiplier));
+ if (mouse_handle_state_ == kInTopHandle) {
+ QRect gr = style()->subControlRect(QStyle::CC_ScrollBar, &opt,
+ QStyle::SC_ScrollBarGroove, this);
+
+ int slider_min = gr.x();
+ int slider_max = gr.right() - (sr.width() + mouse_movement);
+ int val = QStyle::sliderValueFromPosition(minimum(),
+ maximum(),
+ event->pos().x() - slider_min,
+ slider_max - slider_min,
+ opt.upsideDown);
+
+ setValue(val);
+ } else {
+ setValue(qRound(static_cast(value()) * scale_multiplier));
+ }
}
} else {
diff --git a/app/widget/slider/timeslider.cpp b/app/widget/slider/timeslider.cpp
index 5899596db..4d59c007c 100644
--- a/app/widget/slider/timeslider.cpp
+++ b/app/widget/slider/timeslider.cpp
@@ -1,11 +1,14 @@
#include "timeslider.h"
#include "common/timecodefunctions.h"
+#include "core.h"
TimeSlider::TimeSlider(QWidget *parent) :
IntegerSlider(parent)
{
SetMinimum(0);
+
+ connect(Core::instance(), &Core::TimecodeDisplayChanged, this, &TimeSlider::TimecodeDisplayChanged);
}
void TimeSlider::SetTimebase(const rational &timebase)
@@ -25,10 +28,15 @@ QString TimeSlider::ValueToString(const QVariant &v)
return Timecode::timestamp_to_timecode(v.toLongLong(),
timebase_,
- Timecode::CurrentDisplay());
+ Core::instance()->GetTimecodeDisplay());
}
QVariant TimeSlider::StringToValue(const QString &s, bool *ok)
{
- return QVariant::fromValue(Timecode::timecode_to_timestamp(s, timebase_, Timecode::CurrentDisplay(), ok));
+ return QVariant::fromValue(Timecode::timecode_to_timestamp(s, timebase_, Core::instance()->GetTimecodeDisplay(), ok));
+}
+
+void TimeSlider::TimecodeDisplayChanged()
+{
+ UpdateLabel(Value());
}
diff --git a/app/widget/slider/timeslider.h b/app/widget/slider/timeslider.h
index 331572d96..a69c0f31b 100644
--- a/app/widget/slider/timeslider.h
+++ b/app/widget/slider/timeslider.h
@@ -6,6 +6,7 @@
class TimeSlider : public IntegerSlider
{
+ Q_OBJECT
public:
TimeSlider(QWidget* parent = nullptr);
@@ -19,6 +20,9 @@ protected:
private:
rational timebase_;
+private slots:
+ void TimecodeDisplayChanged();
+
};
#endif // TIMESLIDER_H
diff --git a/app/widget/timebased/timebased.cpp b/app/widget/timebased/timebased.cpp
index b7feb9bb6..96cd6e025 100644
--- a/app/widget/timebased/timebased.cpp
+++ b/app/widget/timebased/timebased.cpp
@@ -1,11 +1,19 @@
#include "timebased.h"
+#include
+#include
+
#include "common/timecodefunctions.h"
+#include "config/config.h"
+#include "core.h"
+#include "project/item/sequence/sequence.h"
+#include "widget/timelinewidget/undo/undo.h"
TimeBasedWidget::TimeBasedWidget(bool ruler_text_visible, bool ruler_cache_status_visible, QWidget *parent) :
- QWidget(parent),
+ TimelineScaledWidget(parent),
viewer_node_(nullptr),
- auto_max_scrollbar_(false)
+ auto_max_scrollbar_(false),
+ points_(nullptr)
{
ruler_ = new TimeRuler(ruler_text_visible, ruler_cache_status_visible, this);
connect(ruler_, &TimeRuler::TimeChanged, this, &TimeBasedWidget::SetTimeAndSignal);
@@ -44,6 +52,9 @@ void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node)
DisconnectNodeInternal(viewer_node_);
disconnect(viewer_node_, &ViewerOutput::LengthChanged, this, &TimeBasedWidget::UpdateMaximumScroll);
+
+ points_ = nullptr;
+ ruler()->ConnectTimelinePoints(nullptr);
}
viewer_node_ = node;
@@ -51,9 +62,13 @@ void TimeBasedWidget::ConnectViewerNode(ViewerOutput *node)
ConnectedNodeChanged(viewer_node_);
if (viewer_node_) {
- ConnectNodeInternal(viewer_node_);
-
connect(viewer_node_, &ViewerOutput::LengthChanged, this, &TimeBasedWidget::UpdateMaximumScroll);
+
+ if ((points_ = ConnectTimelinePoints())) {
+ ruler()->ConnectTimelinePoints(points_);
+ }
+
+ ConnectNodeInternal(viewer_node_);
}
}
@@ -68,7 +83,25 @@ void TimeBasedWidget::UpdateMaximumScroll()
void TimeBasedWidget::ScrollBarResized(const double &multiplier)
{
- SetScale(GetScale() * multiplier);
+ QScrollBar* bar = static_cast(sender());
+
+ // Our extension area (represented by a TimelineViewEndItem) is NOT scaled, but the ResizableScrollBar doesn't know
+ // this. Here we re-calculate the requested scale knowing that the end item is not affected by scale.
+
+ int current_max = bar->maximum();
+ double proposed_max = static_cast(current_max) * multiplier;
+
+ proposed_max = proposed_max - (bar->width() * 0.5 / multiplier) + (bar->width() * 0.5);
+
+ double corrected_scale;
+
+ if (current_max == 0) {
+ corrected_scale = multiplier;
+ } else {
+ corrected_scale = (proposed_max / static_cast(current_max));
+ }
+
+ SetScale(GetScale() * corrected_scale);
}
TimeRuler *TimeBasedWidget::ruler() const
@@ -114,6 +147,16 @@ void TimeBasedWidget::resizeEvent(QResizeEvent *event)
UpdateMaximumScroll();
}
+TimelinePoints *TimeBasedWidget::ConnectTimelinePoints()
+{
+ return static_cast(viewer_node_->parent());
+}
+
+TimelinePoints *TimeBasedWidget::GetConnectedTimelinePoints() const
+{
+ return points_;
+}
+
void TimeBasedWidget::SetTime(int64_t timestamp)
{
ruler_->SetTime(timestamp);
@@ -128,6 +171,7 @@ void TimeBasedWidget::SetTimebase(const rational &timebase)
void TimeBasedWidget::SetScale(const double &scale)
{
+ // Simple QObject slot wrapper around TimelineScaledObject::SetScale()
TimelineScaledObject::SetScale(scale);
}
@@ -242,3 +286,110 @@ void TimeBasedWidget::CenterScrollOnPlayhead()
{
scrollbar_->setValue(qRound(TimeToScene(Timecode::timestamp_to_time(ruler_->GetTime(), timebase()))) - scrollbar_->width()/2);
}
+
+void TimeBasedWidget::SetPoint(Timeline::MovementMode m, const rational& time)
+{
+ if (!points_) {
+ return;
+ }
+
+ QUndoCommand* command = new QUndoCommand();
+
+ // Enable workarea if it isn't already enabled
+ if (!points_->workarea()->enabled()) {
+ new WorkareaSetEnabledCommand(points_, true, command);
+ }
+
+ // Determine our new range
+ rational in_point, out_point;
+
+ if (m == Timeline::kTrimIn) {
+ in_point = time;
+
+ if (!points_->workarea()->enabled() || points_->workarea()->out() < in_point) {
+ out_point = TimelineWorkArea::kResetOut;
+ } else {
+ out_point = points_->workarea()->out();
+ }
+ } else {
+ out_point = time;
+
+ if (!points_->workarea()->enabled() || points_->workarea()->in() > out_point) {
+ in_point = TimelineWorkArea::kResetIn;
+ } else {
+ in_point = points_->workarea()->in();
+ }
+ }
+
+ // Set workarea
+ new WorkareaSetRangeCommand(points_, TimeRange(in_point, out_point), command);
+
+ Core::instance()->undo_stack()->push(command);
+}
+
+void TimeBasedWidget::ResetPoint(Timeline::MovementMode m)
+{
+ if (!points_ || !points_->workarea()->enabled()) {
+ return;
+ }
+
+ TimeRange r = points_->workarea()->range();
+
+ if (m == Timeline::kTrimIn) {
+ r.set_in(TimelineWorkArea::kResetIn);
+ } else {
+ r.set_out(TimelineWorkArea::kResetOut);
+ }
+
+ Core::instance()->undo_stack()->push(new WorkareaSetRangeCommand(points_, r));
+}
+
+void TimeBasedWidget::SetInAtPlayhead()
+{
+ SetPoint(Timeline::kTrimIn, GetTime());
+}
+
+void TimeBasedWidget::SetOutAtPlayhead()
+{
+ SetPoint(Timeline::kTrimOut, GetTime());
+}
+
+void TimeBasedWidget::ResetIn()
+{
+ ResetPoint(Timeline::kTrimIn);
+}
+
+void TimeBasedWidget::ResetOut()
+{
+ ResetPoint(Timeline::kTrimOut);
+}
+
+void TimeBasedWidget::ClearInOutPoints()
+{
+ if (!points_) {
+ return;
+ }
+
+
+ Core::instance()->undo_stack()->push(new WorkareaSetEnabledCommand(points_, false));
+}
+
+void TimeBasedWidget::SetMarker()
+{
+ if (!points_) {
+ return;
+ }
+
+ bool ok;
+ QString marker_name;
+
+ if (Config::Current()["SetNameWithMarker"].toBool()) {
+ marker_name = QInputDialog::getText(this, tr("Set Marker"), tr("Marker name:"), QLineEdit::Normal, QString(), &ok);
+ } else {
+ ok = true;
+ }
+
+ if (ok) {
+ points_->markers()->AddMarker(TimeRange(GetTime(), GetTime()), marker_name);
+ }
+}
diff --git a/app/widget/timebased/timebased.h b/app/widget/timebased/timebased.h
index 50b0f4882..40e8bf513 100644
--- a/app/widget/timebased/timebased.h
+++ b/app/widget/timebased/timebased.h
@@ -3,12 +3,13 @@
#include
+#include "common/timelinecommon.h"
#include "node/output/viewer/viewer.h"
#include "widget/resizablescrollbar/resizablescrollbar.h"
#include "widget/timelinewidget/timelinescaledobject.h"
#include "widget/timeruler/timeruler.h"
-class TimeBasedWidget : public QWidget, public TimelineScaledObject
+class TimeBasedWidget : public TimelineScaledWidget
{
Q_OBJECT
public:
@@ -48,6 +49,18 @@ public slots:
void GoToNextCut();
+ void SetInAtPlayhead();
+
+ void SetOutAtPlayhead();
+
+ void ResetIn();
+
+ void ResetOut();
+
+ void ClearInOutPoints();
+
+ void SetMarker();
+
TimeRuler* ruler() const;
protected slots:
@@ -72,6 +85,10 @@ protected:
virtual void resizeEvent(QResizeEvent *event) override;
+ virtual TimelinePoints* ConnectTimelinePoints();
+
+ TimelinePoints* GetConnectedTimelinePoints() const;
+
protected slots:
/**
* @brief Slot to center the horizontal scroll bar on the playhead's current position
@@ -84,6 +101,26 @@ signals:
void TimebaseChanged(const rational&);
private:
+ /**
+ * @brief Set either in or out point to the current playhead
+ *
+ * @param m
+ *
+ * Set to kTrimIn or kTrimOut for setting the in point or out point respectively.
+ */
+ void SetPoint(Timeline::MovementMode m, const rational &time);
+
+ /**
+ * @brief Reset either the in or out point
+ *
+ * Sets either the in point to 0 or the out point to `RATIONAL_MAX`.
+ *
+ * @param m
+ *
+ * Set to kTrimIn or kTrimOut for setting the in point or out point respectively.
+ */
+ void ResetPoint(Timeline::MovementMode m);
+
ViewerOutput* viewer_node_;
TimeRuler* ruler_;
@@ -92,6 +129,8 @@ private:
bool auto_max_scrollbar_;
+ TimelinePoints* points_;
+
private slots:
void UpdateMaximumScroll();
diff --git a/app/widget/timelinewidget/timelinescaledobject.cpp b/app/widget/timelinewidget/timelinescaledobject.cpp
index f3a467782..cd0fb9bcc 100644
--- a/app/widget/timelinewidget/timelinescaledobject.cpp
+++ b/app/widget/timelinewidget/timelinescaledobject.cpp
@@ -3,8 +3,11 @@
#include
#include
+#include "common/clamp.h"
+
TimelineScaledObject::TimelineScaledObject() :
scale_(1.0),
+ min_scale_(0),
max_scale_(DBL_MAX)
{
@@ -64,6 +67,15 @@ void TimelineScaledObject::SetMaximumScale(const double &max)
}
}
+void TimelineScaledObject::SetMinimumScale(const double &min)
+{
+ min_scale_ = min;
+
+ if (GetScale() < min_scale_) {
+ SetScale(min_scale_);
+ }
+}
+
const double& TimelineScaledObject::GetScale() const
{
return scale_;
@@ -71,7 +83,14 @@ const double& TimelineScaledObject::GetScale() const
void TimelineScaledObject::SetScale(const double& scale)
{
- scale_ = scale;
+ Q_ASSERT(scale > 0);
+
+ scale_ = clamp(scale, min_scale_, max_scale_);
ScaleChangedEvent(scale_);
}
+
+TimelineScaledWidget::TimelineScaledWidget(QWidget *parent) :
+ QWidget(parent)
+{
+}
diff --git a/app/widget/timelinewidget/timelinescaledobject.h b/app/widget/timelinewidget/timelinescaledobject.h
index ed6a6bcb7..0889064c8 100644
--- a/app/widget/timelinewidget/timelinescaledobject.h
+++ b/app/widget/timelinewidget/timelinescaledobject.h
@@ -1,6 +1,8 @@
#ifndef TIMELINESCALEDOBJECT_H
#define TIMELINESCALEDOBJECT_H
+#include
+
#include "common/rational.h"
class TimelineScaledObject
@@ -30,6 +32,8 @@ protected:
void SetMaximumScale(const double& max);
+ void SetMinimumScale(const double& min);
+
private:
rational timebase_;
@@ -37,8 +41,17 @@ private:
double scale_;
+ double min_scale_;
+
double max_scale_;
};
+class TimelineScaledWidget : public QWidget, public TimelineScaledObject
+{
+ Q_OBJECT
+public:
+ TimelineScaledWidget(QWidget* parent = nullptr);
+};
+
#endif // TIMELINESCALEDOBJECT_H
diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp
index 6173d9df2..2a19176a7 100644
--- a/app/widget/timelinewidget/timelinewidget.cpp
+++ b/app/widget/timelinewidget/timelinewidget.cpp
@@ -335,7 +335,7 @@ void TimelineWidget::SplitAtPlayhead()
}
}
-void TimelineWidget::DeleteSelectedInternal(QList blocks,
+void TimelineWidget::DeleteSelectedInternal(const QList &blocks,
bool transition_aware,
bool remove_from_graph,
QUndoCommand *command)
@@ -380,6 +380,8 @@ void TimelineWidget::DeleteSelectedInternal(QList blocks,
}
if (remove_from_graph) {
+ new BlockUnlinkAllCommand(b, command);
+
new NodeRemoveWithExclusiveDeps(static_cast(b->parent()), b, command);
}
}
@@ -426,7 +428,7 @@ void TimelineWidget::DeleteSelected(bool ripple)
range_list.InsertTimeRange(TimeRange(b->in(), b->out()));
}
- new TimelineRippleDeleteGapsAtRegions(GetConnectedNode(), range_list, command);
+ new TimelineRippleDeleteGapsAtRegionsCommand(GetConnectedNode(), range_list, command);
}
Core::instance()->undo_stack()->pushIfHasChildren(command);
@@ -470,6 +472,30 @@ void TimelineWidget::OverwriteFootageAtPlayhead(const QList &footage)
import_tool_->PlaceAt(footage, GetTime(), false);
}
+void TimelineWidget::ToggleLinksOnSelected()
+{
+ QList sel = GetSelectedBlocks();
+
+ // Prioritize unlinking
+
+ QList blocks;
+ bool link = true;
+
+ foreach (TimelineViewBlockItem* item, sel) {
+ if (link && item->block()->HasLinks()) {
+ link = false;
+ }
+
+ blocks.append(item->block());
+ }
+
+ if (link) {
+ Core::instance()->undo_stack()->push(new BlockLinkManyCommand(blocks, true));
+ } else {
+ Core::instance()->undo_stack()->push(new BlockLinkManyCommand(blocks, false));
+ }
+}
+
QList TimelineWidget::GetSelectedBlocks()
{
QList list;
@@ -685,6 +711,7 @@ void TimelineWidget::AddBlock(Block *block, TrackReference track)
views_.at(track.type())->view()->scene()->addItem(item);
connect(block, &Block::Refreshed, this, &TimelineWidget::BlockChanged);
+ connect(block, &Block::LinksChanged, this, &TimelineWidget::PreviewUpdated);
if (block->type() == Block::kClip) {
connect(static_cast(block), &ClipBlock::PreviewUpdated, this, &TimelineWidget::PreviewUpdated);
diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h
index 2dd6bd758..86534a45a 100644
--- a/app/widget/timelinewidget/timelinewidget.h
+++ b/app/widget/timelinewidget/timelinewidget.h
@@ -57,6 +57,8 @@ public:
void OverwriteFootageAtPlayhead(const QList &footage);
+ void ToggleLinksOnSelected();
+
QList GetSelectedBlocks();
signals:
@@ -349,7 +351,7 @@ private:
bool dual_transition_;
};
- void DeleteSelectedInternal(QList blocks, bool transition_aware, bool remove_from_graph, QUndoCommand* command);
+ void DeleteSelectedInternal(const QList& blocks, bool transition_aware, bool remove_from_graph, QUndoCommand* command);
void SetBlockLinksSelected(Block *block, bool selected);
diff --git a/app/widget/timelinewidget/tool/import.cpp b/app/widget/timelinewidget/tool/import.cpp
index 366f46277..92293cbd4 100644
--- a/app/widget/timelinewidget/tool/import.cpp
+++ b/app/widget/timelinewidget/tool/import.cpp
@@ -143,7 +143,7 @@ void TimelineWidget::ImportTool::DragMove(TimelineViewMouseEvent *event)
int64_t earliest_timestamp = Timecode::time_to_timestamp(earliest_ghost, parent()->timebase());
QString tooltip_text = Timecode::timestamp_to_timecode(earliest_timestamp,
parent()->timebase(),
- Timecode::CurrentDisplay());
+ Core::instance()->GetTimecodeDisplay());
// Force tooltip to update (otherwise the tooltip won't move as written in the documentation, and could get in the way
// of the cursor)
diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp
index e88cad283..80fd34194 100644
--- a/app/widget/timelinewidget/tool/pointer.cpp
+++ b/app/widget/timelinewidget/tool/pointer.cpp
@@ -404,7 +404,7 @@ void TimelineWidget::PointerTool::ProcessDrag(const TimelineCoordinate &mouse_po
int64_t earliest_timestamp = Timecode::time_to_timestamp(time_movement, parent()->timebase());
QString tooltip_text = Timecode::timestamp_to_timecode(earliest_timestamp,
parent()->timebase(),
- Timecode::CurrentDisplay(),
+ Core::instance()->GetTimecodeDisplay(),
true);
// Force tooltip to update (otherwise the tooltip won't move as written in the documentation, and could get in the way
diff --git a/app/widget/timelinewidget/tool/slip.cpp b/app/widget/timelinewidget/tool/slip.cpp
index b13f5297e..299ed5f22 100644
--- a/app/widget/timelinewidget/tool/slip.cpp
+++ b/app/widget/timelinewidget/tool/slip.cpp
@@ -54,7 +54,7 @@ void TimelineWidget::SlipTool::ProcessDrag(const TimelineCoordinate &mouse_pos)
int64_t earliest_timestamp = Timecode::time_to_timestamp(time_movement, parent()->timebase());
QString tooltip_text = Timecode::timestamp_to_timecode(earliest_timestamp,
parent()->timebase(),
- Timecode::CurrentDisplay(),
+ Core::instance()->GetTimecodeDisplay(),
true);
// Force tooltip to update (otherwise the tooltip won't move as written in the documentation, and could get in the way
// of the cursor)
diff --git a/app/widget/timelinewidget/undo/undo.cpp b/app/widget/timelinewidget/undo/undo.cpp
index 09a9d2db8..d9b875c0e 100644
--- a/app/widget/timelinewidget/undo/undo.cpp
+++ b/app/widget/timelinewidget/undo/undo.cpp
@@ -623,14 +623,14 @@ void BlockSetSpeedCommand::undo_internal()
block_->set_speed(old_speed_);
}
-TimelineRippleDeleteGapsAtRegions::TimelineRippleDeleteGapsAtRegions(ViewerOutput *vo, const TimeRangeList ®ions, QUndoCommand *parent) :
+TimelineRippleDeleteGapsAtRegionsCommand::TimelineRippleDeleteGapsAtRegionsCommand(ViewerOutput *vo, const TimeRangeList ®ions, QUndoCommand *parent) :
UndoCommand(parent),
timeline_(vo),
regions_(regions)
{
}
-void TimelineRippleDeleteGapsAtRegions::redo_internal()
+void TimelineRippleDeleteGapsAtRegionsCommand::redo_internal()
{
foreach (const TimeRange& range, regions_) {
rational max_ripple_length = range.length();
@@ -671,7 +671,7 @@ void TimelineRippleDeleteGapsAtRegions::redo_internal()
}
}
-void TimelineRippleDeleteGapsAtRegions::undo_internal()
+void TimelineRippleDeleteGapsAtRegionsCommand::undo_internal()
{
for (int i=commands_.size()-1;i>=0;i--) {
commands_.at(i)->undo();
@@ -679,3 +679,103 @@ void TimelineRippleDeleteGapsAtRegions::undo_internal()
}
commands_.empty();
}
+
+WorkareaSetEnabledCommand::WorkareaSetEnabledCommand(TimelinePoints *points, bool enabled, QUndoCommand *parent) :
+ UndoCommand(parent),
+ points_(points),
+ old_enabled_(points_->workarea()->enabled()),
+ new_enabled_(enabled)
+{
+}
+
+void WorkareaSetEnabledCommand::redo_internal()
+{
+ points_->workarea()->set_enabled(new_enabled_);
+}
+
+void WorkareaSetEnabledCommand::undo_internal()
+{
+ points_->workarea()->set_enabled(old_enabled_);
+}
+
+WorkareaSetRangeCommand::WorkareaSetRangeCommand(TimelinePoints *points, const TimeRange &range, QUndoCommand *parent) :
+ UndoCommand(parent),
+ points_(points),
+ old_range_(points_->workarea()->range()),
+ new_range_(range)
+{
+}
+
+void WorkareaSetRangeCommand::redo_internal()
+{
+ points_->workarea()->set_range(new_range_);
+}
+
+void WorkareaSetRangeCommand::undo_internal()
+{
+ points_->workarea()->set_range(old_range_);
+}
+
+BlockLinkCommand::BlockLinkCommand(Block *a, Block *b, bool link, QUndoCommand *parent) :
+ UndoCommand(parent),
+ a_(a),
+ b_(b),
+ link_(link)
+{
+}
+
+void BlockLinkCommand::redo_internal()
+{
+ if (link_) {
+ done_ = Block::Link(a_, b_);
+ } else {
+ done_ = Block::Unlink(a_, b_);
+ }
+}
+
+void BlockLinkCommand::undo_internal()
+{
+ if (done_) {
+ if (link_) {
+ Block::Unlink(a_, b_);
+ } else {
+ Block::Link(a_, b_);
+ }
+ }
+}
+
+BlockUnlinkAllCommand::BlockUnlinkAllCommand(Block *block, QUndoCommand *parent) :
+ UndoCommand(parent),
+ block_(block)
+{
+}
+
+void BlockUnlinkAllCommand::redo_internal()
+{
+ unlinked_ = block_->linked_clips();
+
+ foreach (Block* link, unlinked_) {
+ Block::Unlink(block_, link);
+ }
+}
+
+void BlockUnlinkAllCommand::undo_internal()
+{
+ foreach (Block* link, unlinked_) {
+ Block::Link(block_, link);
+ }
+
+ unlinked_.clear();
+}
+
+BlockLinkManyCommand::BlockLinkManyCommand(const QList blocks, bool link, QUndoCommand *parent) :
+ UndoCommand(parent)
+{
+ foreach (Block* a, blocks) {
+ foreach (Block* b, blocks) {
+ if (a != b) {
+ new BlockLinkCommand(a, b, link, this);
+ }
+ }
+ }
+}
diff --git a/app/widget/timelinewidget/undo/undo.h b/app/widget/timelinewidget/undo/undo.h
index 8e4d81b9c..3756fe217 100644
--- a/app/widget/timelinewidget/undo/undo.h
+++ b/app/widget/timelinewidget/undo/undo.h
@@ -27,6 +27,7 @@
#include "node/block/gap/gap.h"
#include "node/output/track/track.h"
#include "node/output/track/tracklist.h"
+#include "timeline/timelinepoints.h"
#include "undo/undocommand.h"
class BlockResizeCommand : public UndoCommand {
@@ -280,9 +281,9 @@ private:
};
-class TimelineRippleDeleteGapsAtRegions : public UndoCommand {
+class TimelineRippleDeleteGapsAtRegionsCommand : public UndoCommand {
public:
- TimelineRippleDeleteGapsAtRegions(ViewerOutput* vo, const TimeRangeList& regions, QUndoCommand* parent = nullptr);
+ TimelineRippleDeleteGapsAtRegionsCommand(ViewerOutput* vo, const TimeRangeList& regions, QUndoCommand* parent = nullptr);
protected:
virtual void redo_internal() override;
@@ -296,4 +297,77 @@ private:
};
+class WorkareaSetEnabledCommand : public UndoCommand {
+public:
+ WorkareaSetEnabledCommand(TimelinePoints* points, bool enabled, QUndoCommand* parent = nullptr);
+
+protected:
+ virtual void redo_internal() override;
+ virtual void undo_internal() override;
+
+private:
+ TimelinePoints* points_;
+
+ bool old_enabled_;
+
+ bool new_enabled_;
+
+};
+
+class WorkareaSetRangeCommand : public UndoCommand {
+public:
+ WorkareaSetRangeCommand(TimelinePoints* points, const TimeRange& range, QUndoCommand* parent = nullptr);
+
+protected:
+ virtual void redo_internal() override;
+ virtual void undo_internal() override;
+
+private:
+ TimelinePoints* points_;
+
+ TimeRange old_range_;
+
+ TimeRange new_range_;
+
+};
+
+class BlockLinkManyCommand : public UndoCommand {
+public:
+ BlockLinkManyCommand(const QList blocks, bool link, QUndoCommand* parent = nullptr);
+};
+
+class BlockLinkCommand : public UndoCommand {
+public:
+ BlockLinkCommand(Block* a, Block* b, bool link, QUndoCommand* parent = nullptr);
+
+protected:
+ virtual void redo_internal() override;
+ virtual void undo_internal() override;
+
+private:
+ Block* a_;
+
+ Block* b_;
+
+ bool link_;
+
+ bool done_;
+
+};
+
+class BlockUnlinkAllCommand : public UndoCommand {
+public:
+ BlockUnlinkAllCommand(Block* block, QUndoCommand* parent = nullptr);
+
+protected:
+ virtual void redo_internal() override;
+ virtual void undo_internal() override;
+
+private:
+ Block* block_;
+
+ QVector unlinked_;
+
+};
+
#endif // TIMELINEUNDOABLE_H
diff --git a/app/widget/timelinewidget/view/timelineplayhead.cpp b/app/widget/timelinewidget/view/timelineplayhead.cpp
index 0b9ea95a9..f15e9cf2d 100644
--- a/app/widget/timelinewidget/view/timelineplayhead.cpp
+++ b/app/widget/timelinewidget/view/timelineplayhead.cpp
@@ -22,12 +22,12 @@
#include
-const QColor &TimelinePlayhead::PlayheadColor() const
+const QColor &TimelinePlayhead::GetPlayheadColor() const
{
return playhead_color_;
}
-const QColor &TimelinePlayhead::PlayheadHighlightColor() const
+const QColor &TimelinePlayhead::GetPlayheadHighlightColor() const
{
return playhead_highlight_color_;
}
@@ -45,10 +45,10 @@ void TimelinePlayhead::SetPlayheadHighlightColor(QColor c)
void TimelinePlayhead::Draw(QPainter* painter, const QRectF& playhead_rect) const
{
painter->setPen(Qt::NoPen);
- painter->setBrush(PlayheadHighlightColor());
+ painter->setBrush(GetPlayheadHighlightColor());
painter->drawRect(playhead_rect);
- painter->setPen(PlayheadColor());
+ painter->setPen(GetPlayheadColor());
painter->setBrush(Qt::NoBrush);
painter->drawLine(QLineF(playhead_rect.topLeft(), playhead_rect.bottomLeft()));
}
diff --git a/app/widget/timelinewidget/view/timelineplayhead.h b/app/widget/timelinewidget/view/timelineplayhead.h
index bbdf1f934..15b2f0031 100644
--- a/app/widget/timelinewidget/view/timelineplayhead.h
+++ b/app/widget/timelinewidget/view/timelineplayhead.h
@@ -31,13 +31,13 @@
class TimelinePlayhead : public QWidget
{
Q_OBJECT
- Q_PROPERTY(QColor playheadColor READ PlayheadColor WRITE SetPlayheadColor DESIGNABLE true)
- Q_PROPERTY(QColor playheadHighlightColor READ PlayheadHighlightColor WRITE SetPlayheadHighlightColor DESIGNABLE true)
+ Q_PROPERTY(QColor playheadColor READ GetPlayheadColor WRITE SetPlayheadColor DESIGNABLE true)
+ Q_PROPERTY(QColor playheadHighlightColor READ GetPlayheadHighlightColor WRITE SetPlayheadHighlightColor DESIGNABLE true)
public:
TimelinePlayhead() = default;
- const QColor& PlayheadColor() const;
- const QColor& PlayheadHighlightColor() const;
+ const QColor& GetPlayheadColor() const;
+ const QColor& GetPlayheadHighlightColor() const;
void SetPlayheadColor(QColor c);
void SetPlayheadHighlightColor(QColor c);
diff --git a/app/widget/timelinewidget/view/timelineviewblockitem.cpp b/app/widget/timelinewidget/view/timelineviewblockitem.cpp
index 399f82186..c70c979da 100644
--- a/app/widget/timelinewidget/view/timelineviewblockitem.cpp
+++ b/app/widget/timelinewidget/view/timelineviewblockitem.cpp
@@ -29,11 +29,10 @@
#include
#include
-#include "audio/sumsamples.h"
-#include "common/clamp.h"
#include "common/qtutils.h"
#include "config/config.h"
#include "node/block/transition/transition.h"
+#include "widget/viewer/audiowaveformview.h"
TimelineViewBlockItem::TimelineViewBlockItem(Block *block, QGraphicsItem* parent) :
TimelineViewRect(parent),
@@ -94,45 +93,12 @@ void TimelineViewBlockItem::paint(QPainter *painter, const QStyleOptionGraphicsI
QByteArray w = wave_file.readAll();
// FIXME: Hardcoded channel count
- int channels = 2;
-
- const SampleSummer::Sum* samples = reinterpret_cast(w.constData());
- int nb_samples = w.size() / sizeof(SampleSummer::Sum);
-
- int sample_index, next_sample_index = 0;
-
- QVector summary;
- int summary_index = -1;
-
- int channel_height = rect().height() / channels;
- int channel_half_height = channel_height / 2;
-
- for (int i=0;i(SampleSummer::kSumSampleRate) * static_cast(i+1) / this->GetScale()) * channels);
-
- if (summary_index != sample_index) {
- summary = SampleSummer::ReSumSamples(&samples[sample_index],
- qMax(channels, next_sample_index - sample_index),
- channels);
- summary_index = sample_index;
- }
-
- for (int j=0;jdrawLine(i,
- channel_mid + clamp(qRound(summary.at(j).min * channel_half_height), -channel_half_height, channel_half_height),
- i,
- channel_mid + clamp(qRound(summary.at(j).max * channel_half_height), -channel_half_height, channel_half_height));
- }
- }
+ AudioWaveformView::DrawWaveform(painter,
+ rect().toRect(),
+ this->GetScale(),
+ reinterpret_cast(w.constData()),
+ w.size() / sizeof(SampleSummer::Sum),
+ 2);
wave_file.close();
}
diff --git a/app/widget/timeruler/CMakeLists.txt b/app/widget/timeruler/CMakeLists.txt
index 9fe06158f..9c7882713 100644
--- a/app/widget/timeruler/CMakeLists.txt
+++ b/app/widget/timeruler/CMakeLists.txt
@@ -16,6 +16,8 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
+ widget/timeruler/seekablewidget.h
+ widget/timeruler/seekablewidget.cpp
widget/timeruler/timeruler.h
widget/timeruler/timeruler.cpp
PARENT_SCOPE
diff --git a/app/widget/timeruler/seekablewidget.cpp b/app/widget/timeruler/seekablewidget.cpp
new file mode 100644
index 000000000..624261979
--- /dev/null
+++ b/app/widget/timeruler/seekablewidget.cpp
@@ -0,0 +1,201 @@
+#include "seekablewidget.h"
+
+#include
+#include
+#include
+
+#include "common/qtutils.h"
+
+SeekableWidget::SeekableWidget(QWidget* parent) :
+ TimelineScaledWidget(parent),
+ time_(0),
+ timeline_points_(nullptr),
+ scroll_(0)
+{
+ QFontMetrics fm = fontMetrics();
+
+ text_height_ = fm.height();
+
+ // Set width of playhead marker
+ playhead_width_ = QFontMetricsWidth(fm, "H");
+}
+
+void SeekableWidget::ConnectTimelinePoints(TimelinePoints *points)
+{
+ if (timeline_points_) {
+ disconnect(timeline_points_->workarea(), &TimelineWorkArea::RangeChanged, this, static_cast(&SeekableWidget::update));
+ disconnect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast(&SeekableWidget::update));
+ }
+
+ timeline_points_ = points;
+
+ if (timeline_points_) {
+ connect(timeline_points_->workarea(), &TimelineWorkArea::RangeChanged, this, static_cast(&SeekableWidget::update));
+ connect(timeline_points_->workarea(), &TimelineWorkArea::EnabledChanged, this, static_cast(&SeekableWidget::update));
+ }
+
+ update();
+}
+
+const int64_t &SeekableWidget::GetTime() const
+{
+ return time_;
+}
+
+const int &SeekableWidget::GetScroll() const
+{
+ return scroll_;
+}
+
+void SeekableWidget::mousePressEvent(QMouseEvent *event)
+{
+ SeekToScreenPoint(event->pos().x());
+}
+
+void SeekableWidget::mouseMoveEvent(QMouseEvent *event)
+{
+ if (event->buttons() & Qt::LeftButton) {
+ SeekToScreenPoint(event->pos().x());
+ }
+}
+
+void SeekableWidget::ScaleChangedEvent(const double &)
+{
+ update();
+}
+
+TimelinePoints *SeekableWidget::timeline_points() const
+{
+ return timeline_points_;
+}
+
+void SeekableWidget::SetTime(const int64_t &r)
+{
+ time_ = r;
+
+ update();
+}
+
+void SeekableWidget::SetScroll(int s)
+{
+ scroll_ = s;
+
+ update();
+}
+
+double SeekableWidget::ScreenToUnitFloat(int screen)
+{
+ return (screen + scroll_) / GetScale() / timebase_dbl();
+}
+
+int64_t SeekableWidget::ScreenToUnit(int screen)
+{
+ return qFloor(ScreenToUnitFloat(screen));
+}
+
+int64_t SeekableWidget::ScreenToUnitRounded(int screen)
+{
+ return qRound64(ScreenToUnitFloat(screen));
+}
+
+int SeekableWidget::UnitToScreen(int64_t unit)
+{
+ return qFloor(static_cast(unit) * GetScale() * timebase_dbl()) - scroll_;
+}
+
+int SeekableWidget::TimeToScreen(const rational &time)
+{
+ return qFloor(time.toDouble() * GetScale()) - scroll_;
+}
+
+void SeekableWidget::SeekToScreenPoint(int screen)
+{
+ int64_t timestamp = qMax(static_cast(0), ScreenToUnitRounded(screen));
+
+ SetTime(timestamp);
+
+ emit TimeChanged(timestamp);
+}
+
+void SeekableWidget::DrawTimelinePoints(QPainter* p, int marker_bottom)
+{
+ if (!timeline_points()) {
+ return;
+ }
+
+ // Draw in/out workarea
+ if (timeline_points()->workarea()->enabled()) {
+ int workarea_left = qMax(0, TimeToScreen(timeline_points()->workarea()->in()));
+ int workarea_right;
+
+ if (timeline_points()->workarea()->out() == TimelineWorkArea::kResetOut) {
+ workarea_right = width();
+ } else {
+ workarea_right = qMin(width(), TimeToScreen(timeline_points()->workarea()->out()));
+ }
+
+ p->fillRect(workarea_left, 0, workarea_right - workarea_left, height(), palette().highlight());
+ }
+
+ // Draw markers
+ if (marker_bottom > 0 && !timeline_points()->markers()->list().isEmpty()) {
+
+ int marker_top = marker_bottom - text_height_;
+
+ // FIXME: Hardcoded marker colors
+ p->setPen(Qt::black);
+ p->setBrush(Qt::green);
+
+ foreach (TimelineMarker* marker, timeline_points()->markers()->list()) {
+ int marker_left = TimeToScreen(marker->time().in());
+ int marker_right = TimeToScreen(marker->time().out());
+
+ if (marker_left >= width() || marker_right < 0) {
+ continue;
+ }
+
+ if (marker->time().length() == 0) {
+ // Single point in time marker
+ DrawPlayhead(p, marker_left, marker_bottom);
+ } else {
+ // Marker range
+ int rect_left = qMax(0, marker_left);
+ int rect_right = qMin(width(), marker_right);
+
+ QRect marker_rect(rect_left, marker_top, rect_right - rect_left, marker_bottom - marker_top);
+
+ p->drawRect(marker_rect);
+
+ if (!marker->name().isEmpty()) {
+ p->drawText(marker_rect, marker->name());
+ }
+ }
+ }
+ }
+}
+
+void SeekableWidget::DrawPlayhead(QPainter *p, int x, int y)
+{
+ int half_width = playhead_width_ / 2;
+
+ if (x + half_width < 0 || x - half_width > width()) {
+ return;
+ }
+
+ p->setRenderHint(QPainter::Antialiasing);
+
+ int half_text_height = text_height() / 3;
+
+ QPoint points[] = {
+ QPoint(x, y),
+ QPoint(x - half_width, y - half_text_height),
+ QPoint(x - half_width, y - text_height()),
+ QPoint(x + 1 + half_width, y - text_height()),
+ QPoint(x + 1 + half_width, y - half_text_height),
+ QPoint(x + 1, y),
+ };
+
+ p->drawPolygon(points, 6);
+
+ p->setRenderHint(QPainter::Antialiasing, false);
+}
diff --git a/app/widget/timeruler/seekablewidget.h b/app/widget/timeruler/seekablewidget.h
new file mode 100644
index 000000000..e96c23b53
--- /dev/null
+++ b/app/widget/timeruler/seekablewidget.h
@@ -0,0 +1,88 @@
+#ifndef SEEKABLEWIDGET_H
+#define SEEKABLEWIDGET_H
+
+#include "common/rational.h"
+#include "timeline/timelinepoints.h"
+#include "widget/timelinewidget/view/timelineplayhead.h"
+#include "widget/timelinewidget/timelinescaledobject.h"
+
+class SeekableWidget : public TimelineScaledWidget
+{
+ Q_OBJECT
+public:
+ SeekableWidget(QWidget *parent = nullptr);
+
+ const int64_t& GetTime() const;
+
+ const int& GetScroll() const;
+
+ void ConnectTimelinePoints(TimelinePoints* points);
+
+public slots:
+ void SetTime(const int64_t &r);
+
+ void SetScroll(int s);
+
+protected:
+ void SeekToScreenPoint(int screen);
+
+ virtual void mousePressEvent(QMouseEvent *event) override;
+ virtual void mouseMoveEvent(QMouseEvent *event) override;
+
+ virtual void ScaleChangedEvent(const double&) override;
+
+ void DrawTimelinePoints(QPainter *p, int marker_bottom = 0);
+
+ TimelinePoints* timeline_points() const;
+
+ double ScreenToUnitFloat(int screen);
+
+ int64_t ScreenToUnit(int screen);
+ int64_t ScreenToUnitRounded(int screen);
+
+ int UnitToScreen(int64_t unit);
+
+ int TimeToScreen(const rational& time);
+
+ void DrawPlayhead(QPainter* p, int x, int y);
+
+ inline const int& text_height() const {
+ return text_height_;
+ }
+
+ inline const int& playhead_width() const {
+ return playhead_width_;
+ }
+
+ inline const QColor& GetPlayheadColor() const
+ {
+ return style_.GetPlayheadColor();
+ }
+
+ inline const QColor& GetPlayheadHighlightColor() const
+ {
+ return style_.GetPlayheadHighlightColor();
+ }
+
+signals:
+ /**
+ * @brief Signal emitted whenever the time changes on this ruler, either by user or programatically
+ */
+ void TimeChanged(int64_t);
+
+private:
+ int64_t time_;
+
+ TimelinePlayhead style_;
+
+ TimelinePoints* timeline_points_;
+
+ int scroll_;
+
+ int text_height_;
+
+ int playhead_width_;
+
+};
+
+#endif // SEEKABLEWIDGET_H
diff --git a/app/widget/timeruler/timeruler.cpp b/app/widget/timeruler/timeruler.cpp
index a5ef88a9a..25b504995 100644
--- a/app/widget/timeruler/timeruler.cpp
+++ b/app/widget/timeruler/timeruler.cpp
@@ -21,9 +21,7 @@
#include "timeruler.h"
#include
-#include
#include
-#include
#include "common/timecodefunctions.h"
#include "common/qtutils.h"
@@ -31,12 +29,9 @@
#include "core.h"
TimeRuler::TimeRuler(bool text_visible, bool cache_status_visible, QWidget* parent) :
- QWidget(parent),
- scroll_(0),
+ SeekableWidget(parent),
text_visible_(text_visible),
centered_text_(true),
- scale_(1.0),
- time_(0),
show_cache_status_(cache_status_visible)
{
QFontMetrics fm = fontMetrics();
@@ -44,46 +39,17 @@ TimeRuler::TimeRuler(bool text_visible, bool cache_status_visible, QWidget* pare
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
// Text height is used to calculate widget height
- text_height_ = fm.height();
- cache_status_height_ = text_height_ / 4;
+ cache_status_height_ = text_height() / 4;
// Get the "minimum" space allowed between two line markers on the ruler (in screen pixels)
// Mediocre but reliable way of scaling UI objects by font/DPI size
minimum_gap_between_lines_ = QFontMetricsWidth(fm, "H");
- // Set width of playhead marker
- playhead_width_ = minimum_gap_between_lines_;
-
// Text visibility affects height, so we set that here
UpdateHeight();
-}
-const double &TimeRuler::GetScale()
-{
- return scale_;
-}
-
-void TimeRuler::SetScale(const double &d)
-{
- scale_ = d;
-
- update();
-}
-
-void TimeRuler::SetTimebase(const rational &r)
-{
- timebase_ = r;
-
- timebase_dbl_ = timebase_.toDouble();
-
- timebase_flipped_dbl_ = timebase_.flipped().toDouble();
-
- update();
-}
-
-const int64_t &TimeRuler::GetTime()
-{
- return time_;
+ // Force update if the default timecode display mode changes
+ connect(Core::instance(), &Core::TimecodeDisplayChanged, this, static_cast(&TimeRuler::update));
}
void TimeRuler::SetCacheStatusLength(const rational &length)
@@ -97,20 +63,6 @@ void TimeRuler::SetCacheStatusLength(const rational &length)
}
}
-void TimeRuler::SetTime(const int64_t &r)
-{
- time_ = r;
-
- update();
-}
-
-void TimeRuler::SetScroll(int s)
-{
- scroll_ = s;
-
- update();
-}
-
void TimeRuler::CacheInvalidatedRange(const TimeRange& range)
{
if (show_cache_status_) {
@@ -123,7 +75,7 @@ void TimeRuler::CacheInvalidatedRange(const TimeRange& range)
void TimeRuler::CacheTimeReady(const rational &time)
{
if (show_cache_status_) {
- dirty_cache_ranges_.RemoveTimeRange(TimeRange(time, time + timebase_));
+ dirty_cache_ranges_.RemoveTimeRange(TimeRange(time, time + timebase()));
update();
}
@@ -132,18 +84,33 @@ void TimeRuler::CacheTimeReady(const rational &time)
void TimeRuler::paintEvent(QPaintEvent *)
{
// Nothing to paint if the timebase is invalid
- if (timebase_.isNull()) {
+ if (timebase().isNull()) {
return;
}
QPainter p(this);
- double width_of_frame = timebase_dbl_ * scale_;
+ // Draw timeline points if connected
+ if (timeline_points()) {
+ int marker_bottom = height() - text_height();
+
+ if (show_cache_status_) {
+ marker_bottom -= cache_status_height_;
+ }
+
+ if (text_visible_) {
+ marker_bottom -= cache_status_height_;
+ }
+
+ DrawTimelinePoints(&p, marker_bottom);
+ }
+
+ double width_of_frame = timebase_dbl() * GetScale();
double width_of_second = 0;
do {
- width_of_second += timebase_dbl_;
+ width_of_second += timebase_dbl();
} while (width_of_second < 1.0);
- width_of_second *= scale_;
+ width_of_second *= GetScale();
double width_of_minute = width_of_second * 60;
double width_of_hour = width_of_minute * 60;
double width_of_day = width_of_hour * 24;
@@ -229,7 +196,7 @@ void TimeRuler::paintEvent(QPaintEvent *)
const int kAverageTextWidth = 200;
for (int i=-kAverageTextWidth;i(i + scroll_);
+ double screen_pt = static_cast(i + GetScroll());
if (long_interval > -1) {
int this_long_unit = qFloor(screen_pt/long_interval);
@@ -239,7 +206,7 @@ void TimeRuler::paintEvent(QPaintEvent *)
if (text_visible_) {
QRect text_rect;
Qt::Alignment text_align;
- QString timecode_str = Timecode::timestamp_to_timecode(ScreenToUnit(i), timebase_, Timecode::CurrentDisplay());
+ QString timecode_str = Timecode::timestamp_to_timecode(ScreenToUnit(i), timebase(), Core::instance()->GetTimecodeDisplay());
int timecode_width = QFontMetricsWidth(fm, timecode_str);
int timecode_left;
@@ -308,43 +275,17 @@ void TimeRuler::paintEvent(QPaintEvent *)
}
// Draw the playhead if it's on screen at the moment
- int playhead_pos = UnitToScreen(time_);
- if (playhead_pos + playhead_width_ >= 0 && playhead_pos - playhead_width_ < width()) {
- p.setPen(Qt::NoPen);
- p.setBrush(style_.PlayheadColor());
- DrawPlayhead(&p, playhead_pos, line_bottom);
- }
+ int playhead_pos = UnitToScreen(GetTime());
+ p.setPen(Qt::NoPen);
+ p.setBrush(GetPlayheadColor());
+ DrawPlayhead(&p, playhead_pos, line_bottom);
}
-void TimeRuler::mousePressEvent(QMouseEvent *event)
+void TimeRuler::TimebaseChangedEvent(const rational &tb)
{
- SeekToScreenPoint(event->pos().x());
-}
+ timebase_flipped_dbl_ = tb.flipped().toDouble();
-void TimeRuler::mouseMoveEvent(QMouseEvent *event)
-{
- if (event->buttons() & Qt::LeftButton) {
- SeekToScreenPoint(event->pos().x());
- }
-}
-
-void TimeRuler::DrawPlayhead(QPainter *p, int x, int y)
-{
- p->setRenderHint(QPainter::Antialiasing);
-
- int half_text_height = text_height_ / 3;
- int half_width = playhead_width_ / 2;
-
- QPoint points[] = {
- QPoint(x, y),
- QPoint(x - half_width, y - half_text_height),
- QPoint(x - half_width, y - text_height_),
- QPoint(x + 1 + half_width, y - text_height_),
- QPoint(x + 1 + half_width, y - half_text_height),
- QPoint(x + 1, y),
- };
-
- p->drawPolygon(points, 6);
+ update();
}
int TimeRuler::CacheStatusHeight() const
@@ -352,46 +293,22 @@ int TimeRuler::CacheStatusHeight() const
return fontMetrics().height() / 4;
}
-double TimeRuler::ScreenToUnitFloat(int screen)
-{
- return (screen + scroll_) / scale_ / timebase_dbl_;
-}
-
-int64_t TimeRuler::ScreenToUnit(int screen)
-{
- return qFloor(ScreenToUnitFloat(screen));
-}
-
-int TimeRuler::UnitToScreen(int64_t unit)
-{
- return qFloor(static_cast(unit) * scale_ * timebase_dbl_) - scroll_;
-}
-
-int TimeRuler::TimeToScreen(const rational &time)
-{
- return qFloor(time.toDouble() * scale_) - scroll_;
-}
-
-void TimeRuler::SeekToScreenPoint(int screen)
-{
- int64_t timestamp = qMax(0, qRound(ScreenToUnitFloat(screen)));
-
- SetTime(timestamp);
-
- emit TimeChanged(timestamp);
-}
-
void TimeRuler::UpdateHeight()
{
- int height = text_height_;
+ int height = text_height();
+ // Add text height
if (text_visible_) {
- height += text_height_;
+ height += text_height();
}
+ // Add cache status height
if (show_cache_status_) {
height += cache_status_height_;
}
+ // Add marker height
+ height += text_height();
+
setFixedHeight(height);
}
diff --git a/app/widget/timeruler/timeruler.h b/app/widget/timeruler/timeruler.h
index 706d98e29..83ada935e 100644
--- a/app/widget/timeruler/timeruler.h
+++ b/app/widget/timeruler/timeruler.h
@@ -24,30 +24,18 @@
#include
#include
-#include "common/rational.h"
#include "common/timerange.h"
-#include "widget/timelinewidget/view/timelineplayhead.h"
+#include "seekablewidget.h"
-class TimeRuler : public QWidget
+class TimeRuler : public SeekableWidget
{
Q_OBJECT
public:
TimeRuler(bool text_visible = true, bool cache_status_visible = false, QWidget* parent = nullptr);
- const double& GetScale();
- void SetScale(const double& d);
-
- void SetTimebase(const rational& r);
-
void SetCenteredText(bool c);
- const int64_t& GetTime();
-
public slots:
- void SetTime(const int64_t &r);
-
- void SetScroll(int s);
-
void CacheInvalidatedRange(const TimeRange &range);
void CacheTimeReady(const rational& time);
@@ -57,58 +45,23 @@ public slots:
protected:
virtual void paintEvent(QPaintEvent* e) override;
- virtual void mousePressEvent(QMouseEvent *event) override;
- virtual void mouseMoveEvent(QMouseEvent *event) override;
-
-signals:
- /**
- * @brief Signal emitted whenever the time changes on this ruler, either by user or programmatically
- */
- void TimeChanged(int64_t);
+ virtual void TimebaseChangedEvent(const rational& tb) override;
private:
void UpdateHeight();
- void DrawPlayhead(QPainter* p, int x, int y);
-
int CacheStatusHeight() const;
- double ScreenToUnitFloat(int screen);
-
- int64_t ScreenToUnit(int screen);
-
- int UnitToScreen(int64_t unit);
-
- int TimeToScreen(const rational& time);
-
- void SeekToScreenPoint(int screen);
-
- int text_height_;
-
int cache_status_height_;
int minimum_gap_between_lines_;
- int playhead_width_;
-
- int scroll_;
-
bool text_visible_;
bool centered_text_;
- double scale_;
-
- rational timebase_;
-
- double timebase_dbl_;
-
double timebase_flipped_dbl_;
- int64_t time_;
-
- TimelinePlayhead style_;
-
bool show_cache_status_;
rational cache_length_;
diff --git a/app/widget/viewer/CMakeLists.txt b/app/widget/viewer/CMakeLists.txt
index c5cf17d3b..16693f4de 100644
--- a/app/widget/viewer/CMakeLists.txt
+++ b/app/widget/viewer/CMakeLists.txt
@@ -16,6 +16,8 @@
set(OLIVE_SOURCES
${OLIVE_SOURCES}
+ widget/viewer/audiowaveformview.h
+ widget/viewer/audiowaveformview.cpp
widget/viewer/footageviewer.h
widget/viewer/footageviewer.cpp
widget/viewer/viewer.h
diff --git a/app/widget/viewer/audiowaveformview.cpp b/app/widget/viewer/audiowaveformview.cpp
new file mode 100644
index 000000000..dd7ac643b
--- /dev/null
+++ b/app/widget/viewer/audiowaveformview.cpp
@@ -0,0 +1,189 @@
+#include "audiowaveformview.h"
+
+#include
+#include
+#include
+
+#include "common/clamp.h"
+#include "config/config.h"
+
+AudioWaveformView::AudioWaveformView(QWidget *parent) :
+ SeekableWidget(parent),
+ backend_(nullptr)
+{
+ setAutoFillBackground(true);
+ setBackgroundRole(QPalette::Base);
+}
+
+void AudioWaveformView::SetBackend(AudioRenderBackend *backend)
+{
+ if (backend_) {
+ disconnect(backend_, &AudioRenderBackend::QueueComplete, this, static_cast(&AudioWaveformView::update));
+ disconnect(backend_, &AudioRenderBackend::ParamsChanged, this, &AudioWaveformView::BackendParamsChanged);
+
+ SetTimebase(0);
+ }
+
+ backend_ = backend;
+
+ if (backend_) {
+ connect(backend_, &AudioRenderBackend::QueueComplete, this, static_cast(&AudioWaveformView::update));
+ connect(backend_, &AudioRenderBackend::ParamsChanged, this, &AudioWaveformView::BackendParamsChanged);
+
+ SetTimebase(backend_->params().time_base());
+ }
+
+ update();
+}
+
+void AudioWaveformView::DrawWaveform(QPainter *painter, const QRect& rect, const double& scale, const SampleSummer::Sum* samples, int nb_samples, int channels)
+{
+ int sample_index, next_sample_index = 0;
+
+ QVector summary;
+ int summary_index = -1;
+
+ int channel_height = rect.height() / channels;
+ int channel_half_height = channel_height / 2;
+
+ for (int i=0;i(SampleSummer::kSumSampleRate) * static_cast(i+1) / scale) * channels);
+
+ if (summary_index != sample_index) {
+ summary = SampleSummer::ReSumSamples(&samples[sample_index],
+ qMax(channels, next_sample_index - sample_index),
+ channels);
+ summary_index = sample_index;
+ }
+
+ int line_x = i + rect.x();
+
+ for (int j=0;jdrawLine(line_x,
+ channel_bottom - diff,
+ line_x,
+ channel_bottom);
+ } else{
+ int channel_mid = rect.y() + channel_height * j + channel_half_height;
+
+ painter->drawLine(line_x,
+ channel_mid + clamp(qRound(summary.at(j).min * channel_half_height), -channel_half_height, channel_half_height),
+ line_x,
+ channel_mid + clamp(qRound(summary.at(j).max * channel_half_height), -channel_half_height, channel_half_height));
+ }
+ }
+ }
+}
+
+void AudioWaveformView::paintEvent(QPaintEvent *event)
+{
+ QWidget::paintEvent(event);
+
+ if (!backend_ || backend_->CachePathName().isEmpty() || !backend_->params().is_valid()) {
+ return;
+ }
+
+ const AudioRenderingParams& params = backend_->params();
+
+ if (cached_size_ != size()
+ || cached_scale_ != GetScale()
+ || cached_scroll_ != GetScroll()) {
+
+ cached_waveform_ = QPixmap(size());
+ cached_waveform_.fill(Qt::transparent);
+
+ QFile fs(backend_->CachePathName());
+
+ if (fs.open(QFile::ReadOnly)) {
+
+ QPainter wave_painter(&cached_waveform_);
+
+ // FIXME: Hardcoded color
+ wave_painter.setPen(Qt::green);
+
+ int channel_height = height() / params.channel_count();
+ int channel_half_height = channel_height / 2;
+
+ int drew = 0;
+
+ fs.seek(params.samples_to_bytes(ScreenToUnitRounded(0)));
+
+ for (int x=0; x samples = SampleSummer::SumSamples(reinterpret_cast(read_buffer.constData()),
+ samples_len,
+ params.channel_count());
+
+ for (int i=0;i(channel_half_height),
+ x,
+ channel_mid + samples.at(i).max * static_cast(channel_half_height));
+ }
+
+ drew++;
+ }
+ }
+
+ cached_size_ = size();
+ cached_scale_ = GetScale();
+ cached_scroll_ = GetScroll();
+
+ fs.close();
+
+ }
+ }
+
+ QPainter p(this);
+
+ // Draw in/out points
+ DrawTimelinePoints(&p);
+
+ // Draw cached waveform pixmap
+ p.drawPixmap(0, 0, cached_waveform_);
+
+ // Draw playhead
+ p.setPen(GetPlayheadColor());
+
+ int playhead_x = UnitToScreen(GetTime());
+ p.drawLine(playhead_x, 0, playhead_x, height());
+}
+
+void AudioWaveformView::BackendParamsChanged()
+{
+ SetTimebase(backend_->params().time_base());
+}
diff --git a/app/widget/viewer/audiowaveformview.h b/app/widget/viewer/audiowaveformview.h
new file mode 100644
index 000000000..283670c1d
--- /dev/null
+++ b/app/widget/viewer/audiowaveformview.h
@@ -0,0 +1,39 @@
+#ifndef WAVEFORMVIEW_H
+#define WAVEFORMVIEW_H
+
+#include
+
+#include "audio/sumsamples.h"
+#include "render/audioparams.h"
+#include "render/backend/audiorenderbackend.h"
+#include "widget/timeruler/seekablewidget.h"
+
+class AudioWaveformView : public SeekableWidget
+{
+ Q_OBJECT
+public:
+ AudioWaveformView(QWidget* parent = nullptr);
+
+ //void SetData(const QString& file, const AudioRenderingParams& params);
+
+ void SetBackend(AudioRenderBackend* backend);
+
+ static void DrawWaveform(QPainter* painter, const QRect &rect, const double &scale, const SampleSummer::Sum *samples, int nb_samples, int channels);
+
+protected:
+ virtual void paintEvent(QPaintEvent* event) override;
+
+private:
+ AudioRenderBackend* backend_;
+
+ QPixmap cached_waveform_;
+ QSize cached_size_;
+ double cached_scale_;
+ int cached_scroll_;
+
+private slots:
+ void BackendParamsChanged();
+
+};
+
+#endif // WAVEFORMVIEW_H
diff --git a/app/widget/viewer/footageviewer.cpp b/app/widget/viewer/footageviewer.cpp
index 5a4c4f860..926806f3d 100644
--- a/app/widget/viewer/footageviewer.cpp
+++ b/app/widget/viewer/footageviewer.cpp
@@ -13,10 +13,6 @@ FootageViewerWidget::FootageViewerWidget(QWidget *parent) :
audio_node_ = new AudioInput();
viewer_node_ = new ViewerOutput();
- NodeParam::ConnectEdge(video_node_->output(), viewer_node_->texture_input());
- NodeParam::ConnectEdge(audio_node_->output(), viewer_node_->samples_input());
- NodeParam::ConnectEdge(video_node_->output(), viewer_node_->length_input());
-
connect(gl_widget_, &ViewerGLWidget::DragStarted, this, &FootageViewerWidget::StartFootageDrag);
}
@@ -29,6 +25,9 @@ void FootageViewerWidget::SetFootage(Footage *footage)
{
if (footage_) {
ConnectViewerNode(nullptr);
+
+ NodeParam::DisconnectEdge(video_node_->output(), viewer_node_->texture_input());
+ NodeParam::DisconnectEdge(audio_node_->output(), viewer_node_->samples_input());
}
footage_ = footage;
@@ -55,11 +54,13 @@ void FootageViewerWidget::SetFootage(Footage *footage)
if (video_stream) {
video_node_->SetFootage(video_stream);
viewer_node_->set_video_params(VideoParams(video_stream->width(), video_stream->height(), video_stream->frame_rate().flipped()));
+ NodeParam::ConnectEdge(video_node_->output(), viewer_node_->texture_input());
}
if (audio_stream) {
audio_node_->SetFootage(audio_stream);
viewer_node_->set_audio_params(AudioParams(audio_stream->sample_rate(), audio_stream->channel_layout()));
+ NodeParam::ConnectEdge(audio_node_->output(), viewer_node_->samples_input());
}
ConnectViewerNode(viewer_node_, footage->project()->color_manager());
@@ -68,13 +69,17 @@ void FootageViewerWidget::SetFootage(Footage *footage)
}
}
+TimelinePoints *FootageViewerWidget::ConnectTimelinePoints()
+{
+ return footage_ ? footage_ : nullptr;
+}
+
void FootageViewerWidget::StartFootageDrag()
{
if (!GetFootage()) {
return;
}
- qDebug() << "Drag start!";
QDrag* drag = new QDrag(this);
QMimeData* mimedata = new QMimeData();
diff --git a/app/widget/viewer/footageviewer.h b/app/widget/viewer/footageviewer.h
index 06bbd46e5..dc1634291 100644
--- a/app/widget/viewer/footageviewer.h
+++ b/app/widget/viewer/footageviewer.h
@@ -15,6 +15,9 @@ public:
Footage* GetFootage() const;
void SetFootage(Footage* footage);
+protected:
+ virtual TimelinePoints* ConnectTimelinePoints() override;
+
private:
Footage* footage_;
diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp
index f14080eed..d2408630b 100644
--- a/app/widget/viewer/viewer.cpp
+++ b/app/widget/viewer/viewer.cpp
@@ -47,20 +47,30 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
QVBoxLayout* layout = new QVBoxLayout(this);
layout->setMargin(0);
- // Create main OpenGL-based view
+ // Set up stacked widget to allow switching away from the viewer widget
+ stack_ = new QStackedWidget();
+ layout->addWidget(stack_);
+
+ // Create main OpenGL-based view and sizer
sizer_ = new ViewerSizer();
- layout->addWidget(sizer_);
+ stack_->addWidget(sizer_);
gl_widget_ = new ViewerGLWidget();
connect(gl_widget_, &ViewerGLWidget::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu);
+ connect(sizer_, &ViewerSizer::RequestMatrix, gl_widget_, &ViewerGLWidget::SetMatrix);
sizer_->SetWidget(gl_widget_);
+ // Create waveform view when audio is connected and video isn't
+ waveform_view_ = new AudioWaveformView();
+ stack_->addWidget(waveform_view_);
+
// Create time ruler
layout->addWidget(ruler());
// Create scrollbar
layout->addWidget(scrollbar());
connect(scrollbar(), &QScrollBar::valueChanged, ruler(), &TimeRuler::SetScroll);
+ connect(scrollbar(), &QScrollBar::valueChanged, waveform_view_, &AudioWaveformView::SetScroll);
// Create lower controls
controls_ = new PlaybackControls();
@@ -85,6 +95,9 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
connect(video_renderer_, &VideoRenderBackend::RangeInvalidated, ruler(), &TimeRuler::CacheInvalidatedRange);
audio_renderer_ = new AudioBackend(this);
+ waveform_view_->SetBackend(audio_renderer_);
+ connect(waveform_view_, &AudioWaveformView::TimeChanged, this, &ViewerWidget::SetTimeAndSignal);
+
connect(PixelFormat::instance(), &PixelFormat::FormatChanged, this, &ViewerWidget::UpdateRendererParameters);
SetAutoMaxScrollBar(true);
@@ -97,6 +110,7 @@ void ViewerWidget::TimeChangedEvent(const int64_t &i)
}
controls_->SetTime(i);
+ waveform_view_->SetTime(i);
if (GetConnectedNode() && last_time_ != i) {
rational time_set = Timecode::timestamp_to_time(i, timebase());
@@ -111,13 +125,21 @@ void ViewerWidget::TimeChangedEvent(const int64_t &i)
void ViewerWidget::ConnectNodeInternal(ViewerOutput *n)
{
- SetTimebase(n->video_params().time_base());
+ if (!n->video_params().time_base().isNull()) {
+ SetTimebase(n->video_params().time_base());
+ } else if (n->audio_params().sample_rate() > 0) {
+ SetTimebase(n->audio_params().time_base());
+ } else {
+ SetTimebase(rational());
+ }
connect(n, &ViewerOutput::TimebaseChanged, this, &ViewerWidget::SetTimebase);
connect(n, &ViewerOutput::SizeChanged, this, &ViewerWidget::SizeChangedSlot);
connect(n, &ViewerOutput::LengthChanged, this, &ViewerWidget::LengthChangedSlot);
connect(n, &ViewerOutput::VideoParamsChanged, this, &ViewerWidget::UpdateRendererParameters);
connect(n, &ViewerOutput::VisibleInvalidated, this, &ViewerWidget::InvalidateVisible);
+ connect(n, &ViewerOutput::VideoGraphChanged, this, &ViewerWidget::UpdateStack);
+ connect(n, &ViewerOutput::AudioGraphChanged, this, &ViewerWidget::UpdateStack);
SizeChangedSlot(n->video_params().width(), n->video_params().height());
LengthChangedSlot(n->Length());
@@ -133,24 +155,34 @@ void ViewerWidget::ConnectNodeInternal(ViewerOutput *n)
divider_ = CalculateDivider();
UpdateRendererParameters();
+
+ UpdateStack();
+
+ if (GetConnectedTimelinePoints()) {
+ waveform_view_->ConnectTimelinePoints(GetConnectedTimelinePoints());
+ }
}
void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n)
{
Pause();
- SetTimebase(0);
+ SetTimebase(rational());
disconnect(n, &ViewerOutput::TimebaseChanged, this, &ViewerWidget::SetTimebase);
disconnect(n, &ViewerOutput::SizeChanged, this, &ViewerWidget::SizeChangedSlot);
disconnect(n, &ViewerOutput::LengthChanged, this, &ViewerWidget::LengthChangedSlot);
disconnect(n, &ViewerOutput::VideoParamsChanged, this, &ViewerWidget::UpdateRendererParameters);
disconnect(n, &ViewerOutput::VisibleInvalidated, this, &ViewerWidget::InvalidateVisible);
+ disconnect(n, &ViewerOutput::VideoGraphChanged, this, &ViewerWidget::UpdateStack);
+ disconnect(n, &ViewerOutput::AudioGraphChanged, this, &ViewerWidget::UpdateStack);
// Effectively disables the viewer and clears the state
SizeChangedSlot(0, 0);
gl_widget_->DisconnectColorManager();
+
+ waveform_view_->ConnectTimelinePoints(nullptr);
}
void ViewerWidget::ConnectedNodeChanged(ViewerOutput *n)
@@ -159,6 +191,13 @@ void ViewerWidget::ConnectedNodeChanged(ViewerOutput *n)
audio_renderer_->SetViewerNode(n);
}
+void ViewerWidget::ScaleChangedEvent(const double &s)
+{
+ TimeBasedWidget::ScaleChangedEvent(s);
+
+ waveform_view_->SetScale(s);
+}
+
void ViewerWidget::resizeEvent(QResizeEvent *event)
{
TimeBasedWidget::resizeEvent(event);
@@ -169,6 +208,8 @@ void ViewerWidget::resizeEvent(QResizeEvent *event)
UpdateRendererParameters();
}
+
+ UpdateMinimumScale();
}
void ViewerWidget::TogglePlayPause()
@@ -251,7 +292,11 @@ void ViewerWidget::PlayInternal(int speed)
controls_->ShowPauseButton();
- connect(gl_widget_, &ViewerGLWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate);
+ if (stack_->currentWidget() == sizer_) {
+ connect(gl_widget_, &ViewerGLWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate);
+ } else {
+ connect(AudioManager::instance(), &AudioManager::OutputNotified, this, &ViewerWidget::PlaybackTimerUpdate);
+ }
}
void ViewerWidget::PushScrubbedAudio()
@@ -261,8 +306,8 @@ void ViewerWidget::PushScrubbedAudio()
QIODevice* audio_src = audio_renderer_->GetAudioPullDevice();
if (audio_src && audio_src->open(QFile::ReadOnly)) {
- // Try to get one "frame" of audio
- int size_of_sample = audio_renderer_->params().time_to_bytes(timebase());
+ // FIXME: Hardcoded scrubbing interval (20ms)
+ int size_of_sample = audio_renderer_->params().time_to_bytes(rational(20, 1000));
// Push audio
audio_src->seek(audio_renderer_->params().time_to_bytes(GetTime()));
@@ -287,6 +332,24 @@ int ViewerWidget::CalculateDivider()
return divider_;
}
+void ViewerWidget::UpdateMinimumScale()
+{
+ if (!GetConnectedNode()) {
+ return;
+ }
+
+ SetMinimumScale(static_cast