Merge branch 'master' into typos

This commit is contained in:
itsmattkc
2020-03-19 12:16:09 +11:00
committed by GitHub
126 changed files with 2951 additions and 934 deletions
+3
View File
@@ -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)
+31 -13
View File
@@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Set Olive sources 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} $<TARGET_FILE_DIR:${OLIVE_TARGET}>
)
endif()
+1
View File
@@ -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);
}
+2
View File
@@ -102,6 +102,8 @@ signals:
void SentSamples(QVector<double> averages);
void OutputNotified();
private:
AudioManager();
+3 -2
View File
@@ -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<QAudioOutput>(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);
}
+3 -1
View File
@@ -69,6 +69,8 @@ signals:
*/
void SentSamples(QVector<double> 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
+1 -1
View File
@@ -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<int>(params().video_buffer_size());
}
}
+45 -13
View File
@@ -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<rational>();
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<ImageStream>();
}
@@ -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());
+2
View File
@@ -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();
+2
View File
@@ -45,5 +45,7 @@ set(OLIVE_SOURCES
common/timelinecommon.h
common/timerange.h
common/timerange.cpp
common/xmlutils.h
common/xmlutils.cpp
PARENT_SCOPE
)
+2 -4
View File
@@ -13,7 +13,7 @@
#include <DbgHelp.h>
#include <stdio.h>
#include <stdlib.h>
#elif defined(Q_OS_LINUX)
#elif defined(Q_OS_MAC) || defined(Q_OS_LINUX)
#include <execinfo.h>
#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;
+1
View File
@@ -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)
-10
View File
@@ -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<Timecode::Display>(Config::Current()["TimecodeDisplay"].toInt());
}
void Timecode::SetCurrentDisplay(Timecode::Display d)
{
Config::Current()["TimecodeDisplay"] = d;
}
-3
View File
@@ -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
*/
-11
View File
@@ -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
+56
View File
@@ -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<quintptr, NodeOutput*>& output_ptrs, const QList<NodeParam::SerializedConnection>& 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;
}
+18
View File
@@ -0,0 +1,18 @@
#ifndef XMLREADLOOP_H
#define XMLREADLOOP_H
#include <QXmlStreamReader>
#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<quintptr, NodeOutput *> &output_ptrs, const QList<NodeParam::SerializedConnection> &desired_connections);
bool XMLReadNextStartElement(QXmlStreamReader* reader);
#endif // XMLREADLOOP_H
+35 -34
View File
@@ -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<rational> 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.size();i++) {
double diff = qAbs(supported_frame_rates.at(i).toDouble() - config_fr);
QList<rational> 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;i<supported_frame_rates.size();i++) {
double diff = qAbs(supported_frame_rates.at(i).toDouble() - config_fr);
if (diff < match_diff) {
match = supported_frame_rates.at(i);
match_diff = diff;
current_config_[key] = QVariant::fromValue(match.flipped());
} else {
current_config_[key] = value;
}
}
qDebug() << " CONFIG: Closest match was" << match.toDouble();
current_config_[key] = QVariant::fromValue(match.flipped());
//reader.skipCurrentElement();
} else {
current_config_[key] = value;
reader.skipCurrentElement();
}
}
@@ -172,7 +173,7 @@ void Config::Load()
QMessageBox::critical(Core::instance()->main_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();
}
+12
View File
@@ -482,6 +482,18 @@ Folder *Core::GetSelectedFolderInActiveProject()
}
}
Timecode::Display Core::GetTimecodeDisplay() const
{
return static_cast<Timecode::Display>(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);
+16
View File
@@ -26,6 +26,7 @@
#include <QTimer>
#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
@@ -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;i<stacked_widget_->count();i++) {
if (!static_cast<StreamProperties*>(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()) {
@@ -30,6 +30,9 @@ public:
StreamProperties(QWidget* parent = nullptr);
virtual void Accept(QUndoCommand*){}
virtual bool SanityCheck(){return true;}
};
#endif // STREAMPROPERTIES_H
@@ -21,7 +21,9 @@
#include "videostreamproperties.h"
#include <QGridLayout>
#include <QGroupBox>
#include <QLabel>
#include <QMessageBox>
#include <OpenColorIO/OpenColorIO.h>
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;i<number_of_colorspaces;i++) {
QString colorspace = config->getColorSpaceNameByIndex(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<VideoStream*>(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<VideoStream>(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<VideoStream*>(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_);
}
@@ -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_;
};
};
@@ -2,8 +2,6 @@
#include <QGridLayout>
#include <QLabel>
#include <QComboBox>
#include <QCheckBox>
#include <QPushButton>
#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()));
@@ -1,6 +1,7 @@
#ifndef PREFERENCESGENERALTAB_H
#define PREFERENCESGENERALTAB_H
#include <QCheckBox>
#include <QComboBox>
#include <QSpinBox>
@@ -27,6 +28,8 @@ private:
QComboBox* autoscroll_method_;
QCheckBox* rectified_waveforms_;
FloatSlider* default_still_length_;
/**
+2
View File
@@ -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
+36 -8
View File
@@ -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<Block *> blocks)
void Block::Link(const QList<Block*>& blocks)
{
foreach (Block* a, blocks) {
foreach (Block* b, blocks) {
@@ -226,10 +232,32 @@ void Block::Link(QList<Block *> 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<Block *> &blocks)
{
foreach (Block* a, blocks) {
foreach (Block* b, blocks) {
Unlink(a, b);
}
}
}
bool Block::AreLinked(Block *a, Block *b)
+6 -3
View File
@@ -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<Block*> blocks);
static void Unlink(Block* a, Block* b);
static bool Link(Block* a, Block* b);
static void Link(const QList<Block*>& blocks);
static bool Unlink(Block* a, Block* b);
static void Unlink(const QList<Block*>& blocks);
static bool AreLinked(Block* a, Block* b);
const QVector<Block*>& linked_clips();
bool HasLinks();
@@ -103,6 +104,8 @@ signals:
void LengthChanged(const rational& length);
void LinksChanged();
protected:
rational SequenceToMediaTime(const rational& sequence_time) const;
+86 -85
View File
@@ -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<quintptr, NodeOutput*>& 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<NodeKeyframe::Type>(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<NodeKeyframe::Type>(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<quintptr, NodeOutput *>&, QList<SerializedConnection>&, QList<FootageConnection>&, const QAtomicInt*)
void NodeInput::LoadInternal(QXmlStreamReader* reader, QHash<quintptr, NodeOutput *>&, QList<SerializedConnection>&, QList<FootageConnection>&, const QAtomicInt*)
{
reader->skipCurrentElement();
}
void NodeInput::SaveInternal(QXmlStreamWriter*) const
-5
View File
@@ -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_];
}
-3
View File
@@ -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:
};
+5 -9
View File
@@ -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_);
}
+1 -1
View File
@@ -48,7 +48,7 @@ protected:
private slots:
void FootageChanged();
void FootageColorSpaceChanged();
void FootageParametersChanged();
};
+8 -4
View File
@@ -2,7 +2,7 @@
#include <QApplication>
#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<quintptr, NodeOutput*>& param_ptrs, QList<SerializedConnection> &input_connections, QList<FootageConnection>& 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);
}
}
+25 -25
View File
@@ -24,7 +24,7 @@
#include <QDebug>
#include <QFile>
#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<quintptr, NodeOutput *> &output_ptrs, QList<NodeInput::SerializedConnection>& input_connections, QList<NodeParam::FootageConnection>& footage_connections, const QAtomicInt* cancelled, const QString& element)
void Node::Load(QXmlStreamReader *reader, QHash<quintptr, NodeOutput *> &output_ptrs, QList<NodeInput::SerializedConnection>& input_connections, QList<NodeParam::FootageConnection>& 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();
}
}
}
+1 -1
View File
@@ -67,7 +67,7 @@ public:
/**
* @brief Clear current node variables and replace them with
*/
void Load(QXmlStreamReader* reader, QHash<quintptr, NodeOutput*>& param_ptrs, QList<NodeInput::SerializedConnection> &input_connections, QList<NodeParam::FootageConnection>& footage_connections, const QAtomicInt *cancelled, const QString &element = QString());
void Load(QXmlStreamReader* reader, QHash<quintptr, NodeOutput*>& param_ptrs, QList<NodeInput::SerializedConnection> &input_connections, QList<NodeParam::FootageConnection>& footage_connections, const QAtomicInt *cancelled);
/**
* @brief Save this node into a text/XML format
+3 -1
View File
@@ -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<quintptr, NodeOutput*>& pa
param_ptrs.insert(saved_ptr, this);
}
}
reader->skipCurrentElement();
}
void NodeOutput::Save(QXmlStreamWriter *writer) const
+14 -17
View File
@@ -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<rational>();
}
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<rational>();
if (samples_input_->IsConnected()) {
NodeValueTable t = traverser.ProcessNode(NodeDependency(samples_input_->get_connected_node(), 0, 0));
audio_length = t.Get(NodeParam::kNumber, "length").value<rational>();
}
return 0;
return qMax(video_length, qMax(audio_length, timeline_length_));
}
const QUuid &ViewerOutput::uuid() const
-3
View File
@@ -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_;
+76
View File
@@ -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<NodeInput*>(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<const TrackOutput*>(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;
}
}
+31
View File
@@ -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
+2 -6
View File
@@ -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
+15
View File
@@ -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<Node *> &nodes)
{
node_view_->Select(nodes);
+5
View File
@@ -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<Node*>& nodes);
void SelectWithDependencies(const QList<Node*>& nodes);
+30
View File
@@ -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();
}
+12
View File
@@ -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);
+5
View File
@@ -115,6 +115,11 @@ void TimelinePanel::Overwrite()
}
}
void TimelinePanel::ToggleLinks()
{
static_cast<TimelineWidget*>(GetTimeBasedWidget())->ToggleLinksOnSelected();
}
void TimelinePanel::InsertFootageAtPlayhead(const QList<Footage *> &footage)
{
static_cast<TimelineWidget*>(GetTimeBasedWidget())->InsertFootageAtPlayhead(footage);
+2
View File
@@ -61,6 +61,8 @@ public:
virtual void Overwrite() override;
virtual void ToggleLinks() override;
void InsertFootageAtPlayhead(const QList<Footage *> &footage);
void OverwriteFootageAtPlayhead(const QList<Footage *> &footage);
+18 -17
View File
@@ -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<quintptr, StreamPtr> &footage_ptrs, QList<NodeParam::FootageConnection>& 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<Folder>();
} else if (reader->name() == "footage") {
child = std::make_shared<Footage>();
} else if (reader->name() == "sequence") {
child = std::make_shared<Sequence>();
} else {
continue;
}
add_child(child);
child->Load(reader, footage_ptrs, footage_connections, cancelled);
if (reader->name() == QStringLiteral("folder")) {
child = std::make_shared<Folder>();
} else if (reader->name() == QStringLiteral("footage")) {
child = std::make_shared<Footage>();
} else if (reader->name() == QStringLiteral("sequence")) {
child = std::make_shared<Sequence>();
} else {
reader->skipCurrentElement();
continue;
}
add_child(child);
child->Load(reader, footage_ptrs, footage_connections, cancelled);
}
}
+27 -27
View File
@@ -23,9 +23,9 @@
#include <QCoreApplication>
#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<quintptr, StreamPtr>& 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<AudioStream>(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;
+2 -1
View File
@@ -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 {
+13 -10
View File
@@ -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();
}
}
+1 -4
View File
@@ -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;
+8 -5
View File
@@ -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)
{
}
}*/
+5 -3
View File
@@ -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_;
+13 -1
View File
@@ -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)
+6
View File
@@ -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<VideoStream>;
+28 -3
View File
@@ -76,11 +76,19 @@ const QList<ItemPtr> &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;i<children_.size();i++) {
if (children_.at(i).get() == item) {
return children_.at(i);
ItemPtr c = children_.at(i);
if (c.get() == item) {
return c;
} else if (traverse && c->CanHaveChildren()) {
ItemPtr grandchild = shared_ptr_from_raw(item);
if (grandchild) {
return grandchild;
}
}
}
@@ -147,6 +155,23 @@ void Item::set_project(Project *project)
project_ = project;
}
QList<ItemPtr> Item::get_children_of_type(Type type, bool recursive) const
{
QList<ItemPtr> 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;
+3 -1
View File
@@ -77,7 +77,7 @@ public:
Item* child(int i) const;
const QList<ItemPtr>& 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<ItemPtr> get_children_of_type(Type type, bool recursive) const;
virtual bool CanHaveChildren() const;
bool ChildExistsWithName(const QString& name);
+50 -79
View File
@@ -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<quintptr, StreamPtr> &, QLis
QHash<quintptr, NodeOutput*> output_ptrs;
QList<NodeParam::SerializedConnection> 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()
+2 -1
View File
@@ -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<Sequence>;
@@ -34,7 +35,7 @@ using SequencePtr = std::shared_ptr<Sequence>;
/**
* @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();
+25 -17
View File
@@ -23,7 +23,7 @@
#include <QDir>
#include <QFileInfo>
#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<quintptr, StreamPtr> footage_ptrs;
QList<NodeInput::FootageConnection> 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<ItemPtr> Project::get_items_of_type(Item::Type type) const
{
return root_.get_children_of_type(type, true);
}
+2
View File
@@ -63,6 +63,8 @@ public:
ColorManager* color_manager();
QList<ItemPtr> get_items_of_type(Item::Type type) const;
signals:
void NameChanged();
+21 -15
View File
@@ -4,6 +4,8 @@
#include <QFile>
#include <QXmlStreamReader>
#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<Project>();
qDebug() << "Project version:" << reader.text();
} else if (reader.name() == "project") {
ProjectPtr project = std::make_shared<Project>();
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();
}
}
+5
View File
@@ -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)
{
+1
View File
@@ -14,6 +14,7 @@ public:
const int& sample_rate() const;
const uint64_t& channel_layout() const;
rational time_base() const;
private:
int sample_rate_;
+14
View File
@@ -27,6 +27,8 @@ void AudioRenderBackend::SetParameters(const AudioRenderingParams &params)
// 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};
+5
View File
@@ -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<Node*, Node*> copy_map_;
private:
@@ -397,6 +397,10 @@ void OpenGLProxy::TextureToBuffer(const QVariant &tex_in, void *buffer)
{
OpenGLTextureCache::ReferencePtr texture = tex_in.value<OpenGLTextureCache::ReferencePtr>();
if (!texture) {
return;
}
QOpenGLFunctions* f = QOpenGLContext::currentContext()->functions();
buffer_.Attach(texture->texture());
buffer_.Bind();
+29 -83
View File
@@ -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<const TrackOutput*>(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<NodeInput*>(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;
}
+12 -15
View File
@@ -3,13 +3,13 @@
#include <QObject>
#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_;
+2
View File
@@ -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<VideoStream*>(stream.get())->start_time()).toUtf8());
/*Decoder::RetrieveState state = decoder->GetRetrieveState(input_time);
if (state == Decoder::kReady) {
+27 -8
View File
@@ -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;
}
+6
View File
@@ -38,6 +38,12 @@
<max>100</max>
</param>
<!-- Parameter: Inner Value -->
<param id="inner_in" type="bool">
<name lang="en_US">Inner</name>
<default>false</default>
</param>
<!-- Qt Resource path to fragment shader -->
<fragment url=":/shaders/stroke.frag" />
</effect>
+6
View File
@@ -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
+61
View File
@@ -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<markers_.size();i++) {
TimelineMarker* m = markers_.at(i);
if (m == marker) {
markers_.removeAt(i);
emit MarkerRemoved(m);
delete m;
break;
}
}
}
const QList<TimelineMarker*> &TimelineMarkerList::list() const
{
return markers_;
}
+56
View File
@@ -0,0 +1,56 @@
#ifndef TIMELINEMARKER_H
#define TIMELINEMARKER_H
#include <QString>
#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<TimelineMarker *> &list() const;
signals:
void MarkerAdded(TimelineMarker* marker);
void MarkerRemoved(TimelineMarker* marker);
private:
QList<TimelineMarker*> markers_;
};
#endif // TIMELINEMARKER_H
+21
View File
@@ -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_;
}
+25
View File
@@ -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
+41
View File
@@ -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();
}
+37
View File
@@ -0,0 +1,37 @@
#ifndef TIMELINEWORKAREA_H
#define TIMELINEWORKAREA_H
#include <QObject>
#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
+66 -16
View File
@@ -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<TimelinePanel>();
@@ -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
+23 -3
View File
@@ -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();
};
+126 -38
View File
@@ -20,11 +20,14 @@
#include "nodeview.h"
#include <QClipboard>
#include <QMouseEvent>
#include <QGuiApplication>
#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<QGraphicsItem*> selected = scene_.selectedItems();
QList<Node*> selected_nodes;
foreach (QGraphicsItem* item, selected) {
NodeViewItem* node_item = dynamic_cast<NodeViewItem*>(item);
if (node_item) {
selected_nodes.append(node_item->node());
}
}
QList<Node*> selected_nodes = scene_.GetSelectedNodes();
if (selected_nodes.isEmpty()) {
return;
@@ -110,20 +104,12 @@ void NodeView::DeleteSelected()
void NodeView::SelectAll()
{
QList<QGraphicsItem *> all_items = this->items();
foreach (QGraphicsItem* i, all_items) {
i->setSelected(true);
}
scene_.SelectAll();
}
void NodeView::DeselectAll()
{
QList<QGraphicsItem *> selected_items = scene_.selectedItems();
foreach (QGraphicsItem* i, selected_items) {
i->setSelected(false);
}
scene_.DeselectAll();
}
void NodeView::Select(const QList<Node *> &nodes)
@@ -133,9 +119,6 @@ void NodeView::Select(const QList<Node *> &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<Node *> nodes)
Select(nodes);
}
void NodeView::CopySelected(bool cut)
{
if (!graph_) {
return;
}
QList<Node*> selected = scene_.GetSelectedNodes();
if (selected.isEmpty()) {
return;
}
QString copy_str;
QXmlStreamWriter writer(&copy_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<Node*> pasted_nodes;
QHash<quintptr, NodeOutput*> output_ptrs;
QList<NodeParam::SerializedConnection> desired_connections;
QList<NodeInput::FootageConnection> 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<ItemPtr> footage = static_cast<Sequence*>(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<Stream*>(con.footage);
bool found = false;
foreach (ItemPtr item, footage) {
const QList<StreamPtr>& streams = std::static_pointer_cast<Footage>(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<NodeEdge *, NodeViewEdge *>::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<QGraphicsItem*> selected_items = scene_.selectedItems();
QList<Node*> selected_nodes;
for (int i=0;i<selected_items.size();i++) {
// Try to dynamically cast to a widget holding a Node
NodeViewItem* item = dynamic_cast<NodeViewItem*>(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)
+3
View File
@@ -57,6 +57,9 @@ public:
void Select(const QList<Node*>& nodes);
void SelectWithDependencies(QList<Node *> nodes);
void CopySelected(bool cut);
void Paste();
signals:
/**
* @brief Signal emitted when the selected nodes have changed
+55
View File
@@ -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<Node*, NodeViewItem*>::const_iterator i;
for (i=item_map_.begin();i!=item_map_.end();i++) {
@@ -27,6 +36,24 @@ void NodeViewScene::clear()
}
}
void NodeViewScene::SelectAll()
{
QList<QGraphicsItem *> all_items = this->items();
foreach (QGraphicsItem* i, all_items) {
i->setSelected(true);
}
}
void NodeViewScene::DeselectAll()
{
QList<QGraphicsItem *> 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<Node *> NodeViewScene::GetSelectedNodes() const
{
QHash<Node*, NodeViewItem*>::const_iterator iterator;
QList<Node *> selected;
for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) {
if (iterator.value()->isSelected()) {
selected.append(iterator.key());
}
}
return selected;
}
QList<NodeViewItem *> NodeViewScene::GetSelectedItems() const
{
QHash<Node*, NodeViewItem*>::const_iterator iterator;
QList<NodeViewItem *> selected;
for (iterator=item_map_.begin();iterator!=item_map_.end();iterator++) {
if (iterator.value()->isSelected()) {
selected.append(iterator.value());
}
}
return selected;
}
const QHash<Node *, NodeViewItem *> &NodeViewScene::item_map() const
{
return item_map_;
+6
View File
@@ -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<Node*> GetSelectedNodes() const;
QList<NodeViewItem*> GetSelectedItems() const;
const QHash<Node*, NodeViewItem*>& item_map() const;
const QHash<NodeEdge*, NodeViewEdge*>& edge_map() const;
+22
View File
@@ -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
@@ -24,7 +24,7 @@
#include <QEvent>
#include <QHBoxLayout>
#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_);
}
@@ -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();
};
@@ -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<double>(sr.width()) / static_cast<double>(sr.width() + mouse_movement);
emit RequestScale(scale_multiplier);
double width_adjustment = static_cast<double>(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<double>(sr.width()) / width_adjustment;
emit RequestScale(scale_multiplier);
setValue(val);
} else {
setValue(qRound(static_cast<double>(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<double>(value()) * scale_multiplier));
}
}
} else {
+10 -2
View File
@@ -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());
}
+4
View File
@@ -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
+156 -5
View File
@@ -1,11 +1,19 @@
#include "timebased.h"
#include <QInputDialog>
#include <QUndoCommand>
#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<QScrollBar*>(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<double>(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<double>(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<Sequence*>(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);
}
}
+40 -1
View File
@@ -3,12 +3,13 @@
#include <QWidget>
#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();
@@ -3,8 +3,11 @@
#include <cfloat>
#include <QtMath>
#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)
{
}
@@ -1,6 +1,8 @@
#ifndef TIMELINESCALEDOBJECT_H
#define TIMELINESCALEDOBJECT_H
#include <QWidget>
#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
+29 -2
View File
@@ -335,7 +335,7 @@ void TimelineWidget::SplitAtPlayhead()
}
}
void TimelineWidget::DeleteSelectedInternal(QList<Block *> blocks,
void TimelineWidget::DeleteSelectedInternal(const QList<Block *> &blocks,
bool transition_aware,
bool remove_from_graph,
QUndoCommand *command)
@@ -380,6 +380,8 @@ void TimelineWidget::DeleteSelectedInternal(QList<Block *> blocks,
}
if (remove_from_graph) {
new BlockUnlinkAllCommand(b, command);
new NodeRemoveWithExclusiveDeps(static_cast<NodeGraph*>(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 *> &footage)
import_tool_->PlaceAt(footage, GetTime(), false);
}
void TimelineWidget::ToggleLinksOnSelected()
{
QList<TimelineViewBlockItem*> sel = GetSelectedBlocks();
// Prioritize unlinking
QList<Block*> 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<TimelineViewBlockItem *> TimelineWidget::GetSelectedBlocks()
{
QList<TimelineViewBlockItem *> 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<ClipBlock*>(block), &ClipBlock::PreviewUpdated, this, &TimelineWidget::PreviewUpdated);
+3 -1
View File
@@ -57,6 +57,8 @@ public:
void OverwriteFootageAtPlayhead(const QList<Footage *> &footage);
void ToggleLinksOnSelected();
QList<TimelineViewBlockItem*> GetSelectedBlocks();
signals:
@@ -349,7 +351,7 @@ private:
bool dual_transition_;
};
void DeleteSelectedInternal(QList<Block *> blocks, bool transition_aware, bool remove_from_graph, QUndoCommand* command);
void DeleteSelectedInternal(const QList<Block *>& blocks, bool transition_aware, bool remove_from_graph, QUndoCommand* command);
void SetBlockLinksSelected(Block *block, bool selected);

Some files were not shown because too many files have changed in this diff Show More