Merge branch 'master' into typos
This commit is contained in:
@@ -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
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -102,6 +102,8 @@ signals:
|
||||
|
||||
void SentSamples(QVector<double> averages);
|
||||
|
||||
void OutputNotified();
|
||||
|
||||
private:
|
||||
AudioManager();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -45,5 +45,7 @@ set(OLIVE_SOURCES
|
||||
common/timelinecommon.h
|
||||
common/timerange.h
|
||||
common/timerange.cpp
|
||||
common/xmlutils.h
|
||||
common/xmlutils.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
@@ -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_;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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_];
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
};
|
||||
|
||||
|
||||
@@ -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_);
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ protected:
|
||||
private slots:
|
||||
void FootageChanged();
|
||||
|
||||
void FootageColorSpaceChanged();
|
||||
void FootageParametersChanged();
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}*/
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -63,6 +63,8 @@ public:
|
||||
|
||||
ColorManager* color_manager();
|
||||
|
||||
QList<ItemPtr> get_items_of_type(Item::Type type) const;
|
||||
|
||||
signals:
|
||||
void NameChanged();
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@ public:
|
||||
|
||||
const int& sample_rate() const;
|
||||
const uint64_t& channel_layout() const;
|
||||
rational time_base() const;
|
||||
|
||||
private:
|
||||
int sample_rate_;
|
||||
|
||||
@@ -27,6 +27,8 @@ void AudioRenderBackend::SetParameters(const AudioRenderingParams ¶ms)
|
||||
|
||||
// Regenerate the cache ID
|
||||
RegenerateCacheID();
|
||||
|
||||
emit ParamsChanged();
|
||||
}
|
||||
|
||||
void AudioRenderBackend::ConnectViewer(ViewerOutput *node)
|
||||
@@ -98,6 +100,18 @@ void AudioRenderBackend::ConnectWorkerToThis(RenderWorker *worker)
|
||||
connect(arw, &AudioRenderWorker::ConformUnavailable, this, &AudioRenderBackend::ConformUnavailable, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
TimeRange AudioRenderBackend::PopNextFrameFromQueue()
|
||||
{
|
||||
TimeRange range = cache_queue_.first();
|
||||
|
||||
// Limit range per worker to 2 seconds (FIXME: arbitrary, should be tweaked, maybe even in config?)
|
||||
range.set_out(qMin(range.out(), range.in() + rational(2)));
|
||||
|
||||
cache_queue_.RemoveTimeRange(range);
|
||||
|
||||
return range;
|
||||
}
|
||||
|
||||
void AudioRenderBackend::ConformUnavailable(StreamPtr stream, const TimeRange &range, const rational &stream_time, const AudioRenderingParams& params)
|
||||
{
|
||||
ConformWaitInfo info = {stream, params, range, stream_time};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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_;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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(©_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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user