diff --git a/app/cli/cliexport/cliexportmanager.cpp b/app/cli/cliexport/cliexportmanager.cpp new file mode 100644 index 000000000..de0145729 --- /dev/null +++ b/app/cli/cliexport/cliexportmanager.cpp @@ -0,0 +1,30 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "cliexportmanager.h" + +OLIVE_NAMESPACE_ENTER + +CLIExportManager::CLIExportManager() +{ + +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/cli/cliexport/cliexportmanager.h b/app/cli/cliexport/cliexportmanager.h new file mode 100644 index 000000000..6d3fc346b --- /dev/null +++ b/app/cli/cliexport/cliexportmanager.h @@ -0,0 +1,36 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef CLIEXPORTMANAGER_H +#define CLIEXPORTMANAGER_H + +#include "task/export/export.h" + +OLIVE_NAMESPACE_ENTER + +class CLIExportManager : public QObject +{ +public: + CLIExportManager(); +}; + +OLIVE_NAMESPACE_EXIT + +#endif // CLIEXPORTMANAGER_H diff --git a/app/cli/cliprogress/cliprogressdialog.cpp b/app/cli/cliprogress/cliprogressdialog.cpp index fa846c1ff..88c0cfd57 100644 --- a/app/cli/cliprogress/cliprogressdialog.cpp +++ b/app/cli/cliprogress/cliprogressdialog.cpp @@ -27,9 +27,10 @@ OLIVE_NAMESPACE_ENTER CLIProgressDialog::CLIProgressDialog(const QString& title, QObject *parent) : QObject(parent), title_(title), - progress_(0), + progress_(-1), drawn_(false) { + SetProgress(0); } void CLIProgressDialog::Update() @@ -68,7 +69,7 @@ void CLIProgressDialog::Update() std::cout << "["; // Get UI bar progress - int bar_prog = qRound(progress_ * 0.01 * progress_bar_columns); + int bar_prog = qRound(progress_ * progress_bar_columns); // Draw filled in bar for (int i=0;iGetTitle(), parent) + CLIProgressDialog(task->GetTitle(), parent), + task_(task) { - // FIXME: Still developing this, don't try to use + connect(task_, &Task::ProgressChanged, this, &CLITaskDialog::SetProgress); +} + +bool CLITaskDialog::Run() +{ + return task_->Start(); } OLIVE_NAMESPACE_EXIT diff --git a/app/cli/clitask/clitaskdialog.h b/app/cli/clitask/clitaskdialog.h index 6408b238e..c574b6011 100644 --- a/app/cli/clitask/clitaskdialog.h +++ b/app/cli/clitask/clitaskdialog.h @@ -28,9 +28,15 @@ OLIVE_NAMESPACE_ENTER class CLITaskDialog : public CLIProgressDialog { + Q_OBJECT public: CLITaskDialog(Task *task, QObject* parent = nullptr); + bool Run(); + +private: + Task* task_; + }; OLIVE_NAMESPACE_EXIT diff --git a/app/codec/encoder.cpp b/app/codec/encoder.cpp index 43e0ca1a6..388278525 100644 --- a/app/codec/encoder.cpp +++ b/app/codec/encoder.cpp @@ -158,6 +158,63 @@ void EncodingParams::SetExportLength(const rational &export_length) export_length_ = export_length; } +void EncodingParams::Save(QXmlStreamWriter *writer) const +{ + writer->writeStartElement(QStringLiteral("encode")); + + writer->writeTextElement(QStringLiteral("filename"), filename_); + + writer->writeStartElement(QStringLiteral("video")); + + writer->writeAttribute(QStringLiteral("enabled"), QString::number(video_enabled_)); + + if (video_enabled_) { + writer->writeTextElement(QStringLiteral("codec"), QString::number(video_codec_)); + writer->writeTextElement(QStringLiteral("width"), QString::number(video_params_.width())); + writer->writeTextElement(QStringLiteral("height"), QString::number(video_params_.height())); + writer->writeTextElement(QStringLiteral("format"), QString::number(video_params_.format())); + writer->writeTextElement(QStringLiteral("timebase"), video_params_.time_base().toString()); + writer->writeTextElement(QStringLiteral("divider"), QString::number(video_params_.divider())); + writer->writeTextElement(QStringLiteral("bitrate"), QString::number(video_bit_rate_)); + writer->writeTextElement(QStringLiteral("maxbitrate"), QString::number(video_max_bit_rate_)); + writer->writeTextElement(QStringLiteral("bufsize"), QString::number(video_buffer_size_)); + writer->writeTextElement(QStringLiteral("threads"), QString::number(video_threads_)); + + if (!video_opts_.isEmpty()) { + writer->writeStartElement(QStringLiteral("opts")); + + QHash::const_iterator i; + for (i=video_opts_.constBegin(); i!=video_opts_.constEnd(); i++) { + writer->writeStartElement(QStringLiteral("entry")); + + writer->writeTextElement(QStringLiteral("key"), i.key()); + writer->writeTextElement(QStringLiteral("value"), i.value()); + + writer->writeEndElement(); // entry + } + + writer->writeEndElement(); // opts + } + } + + writer->writeEndElement(); // video + + writer->writeStartElement(QStringLiteral("audio")); + + writer->writeAttribute(QStringLiteral("enabled"), QString::number(audio_enabled_)); + + if (audio_enabled_) { + writer->writeTextElement(QStringLiteral("codec"), QString::number(audio_codec_)); + writer->writeTextElement(QStringLiteral("samplerate"), QString::number(audio_params_.sample_rate())); + writer->writeTextElement(QStringLiteral("channellayout"), QString::number(audio_params_.channel_layout())); + writer->writeTextElement(QStringLiteral("format"), QString::number(audio_params_.format())); + } + + writer->writeEndElement(); // audio + + writer->writeEndElement(); // encode +} + Encoder* Encoder::CreateFromID(const QString &id, const EncodingParams& params) { Q_UNUSED(id) diff --git a/app/codec/encoder.h b/app/codec/encoder.h index 8a60a75cc..260c735a3 100644 --- a/app/codec/encoder.h +++ b/app/codec/encoder.h @@ -23,6 +23,7 @@ #include #include +#include #include "codec/exportcodec.h" #include "codec/exportformat.h" @@ -69,6 +70,8 @@ public: const rational& GetExportLength() const; void SetExportLength(const rational& GetExportLength); + virtual void Save(QXmlStreamWriter* writer) const; + private: QString filename_; diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp index 4d2430e30..3b54fcdb2 100644 --- a/app/codec/ffmpeg/ffmpegdecoder.cpp +++ b/app/codec/ffmpeg/ffmpegdecoder.cpp @@ -458,6 +458,7 @@ bool FFmpegDecoder::Probe(Footage *f, const QAtomicInt* cancelled) video_stream->set_width(avstream->codecpar->width); video_stream->set_height(avstream->codecpar->height); + video_stream->set_format(GetNativePixelFormat(FFmpegCommon::GetCompatiblePixelFormat(static_cast(avstream->codecpar->format)))); video_stream->set_frame_rate(av_guess_frame_rate(fmt_ctx, avstream, nullptr)); video_stream->set_start_time(avstream->start_time); diff --git a/app/codec/oiio/oiiodecoder.cpp b/app/codec/oiio/oiiodecoder.cpp index e5f7b74c8..5a7650e89 100644 --- a/app/codec/oiio/oiiodecoder.cpp +++ b/app/codec/oiio/oiiodecoder.cpp @@ -117,6 +117,7 @@ bool OIIODecoder::Probe(Footage *f, const QAtomicInt *cancelled) image_stream->set_width(in->spec().width); image_stream->set_height(in->spec().height); + image_stream->set_format(GetFormatFromOIIOBasetype(in->spec())); // Images will always have just one stream image_stream->set_index(0); @@ -278,6 +279,23 @@ void OIIODecoder::BufferToFrame(OIIO::ImageBuf *buf, FramePtr frame) #endif } +PixelFormat::Format OIIODecoder::GetFormatFromOIIOBasetype(const OIIO::ImageSpec& spec) +{ + bool has_alpha = (spec.nchannels == kRGBAChannels); + + if (spec.format == OIIO::TypeDesc::UINT8) { + return has_alpha ? PixelFormat::PIX_FMT_RGBA8 : PixelFormat::PIX_FMT_RGB8; + } else if (spec.format == OIIO::TypeDesc::UINT16) { + return has_alpha ? PixelFormat::PIX_FMT_RGBA16U : PixelFormat::PIX_FMT_RGB16U; + } else if (spec.format == OIIO::TypeDesc::HALF) { + return has_alpha ? PixelFormat::PIX_FMT_RGBA16F : PixelFormat::PIX_FMT_RGB16F; + } else if (spec.format == OIIO::TypeDesc::FLOAT) { + return has_alpha ? PixelFormat::PIX_FMT_RGBA32F : PixelFormat::PIX_FMT_RGB32F; + } else { + return PixelFormat::PIX_FMT_INVALID; + } +} + bool OIIODecoder::FileTypeIsSupported(const QString& fn) { // We prioritize OIIO over FFmpeg to pick up still images more effectively, but some OIIO decoders (notably OpenJPEG) @@ -361,16 +379,9 @@ bool OIIODecoder::OpenImageHandler(const QString &fn) is_rgba_ = (spec.nchannels == kRGBAChannels); - // Weirdly, switch statement doesn't work correctly here - if (spec.format == OIIO::TypeDesc::UINT8) { - pix_fmt_ = is_rgba_ ? PixelFormat::PIX_FMT_RGBA8 : PixelFormat::PIX_FMT_RGB8; - } else if (spec.format == OIIO::TypeDesc::UINT16) { - pix_fmt_ = is_rgba_ ? PixelFormat::PIX_FMT_RGBA16U : PixelFormat::PIX_FMT_RGB16U; - } else if (spec.format == OIIO::TypeDesc::HALF) { - pix_fmt_ = is_rgba_ ? PixelFormat::PIX_FMT_RGBA16F : PixelFormat::PIX_FMT_RGB16F; - } else if (spec.format == OIIO::TypeDesc::FLOAT) { - pix_fmt_ = is_rgba_ ? PixelFormat::PIX_FMT_RGBA32F : PixelFormat::PIX_FMT_RGB32F; - } else { + pix_fmt_ = GetFormatFromOIIOBasetype(spec); + + if (pix_fmt_ == PixelFormat::PIX_FMT_INVALID) { qWarning() << "Failed to convert OIIO::ImageDesc to native pixel format"; return false; } diff --git a/app/codec/oiio/oiiodecoder.h b/app/codec/oiio/oiiodecoder.h index f481481b1..990fc3aff 100644 --- a/app/codec/oiio/oiiodecoder.h +++ b/app/codec/oiio/oiiodecoder.h @@ -57,6 +57,8 @@ public: static void BufferToFrame(OIIO::ImageBuf* buf, FramePtr frame); + static PixelFormat::Format GetFormatFromOIIOBasetype(const OIIO::ImageSpec& spec); + private: #if OIIO_VERSION < 10903 OIIO::ImageInput* image_; diff --git a/app/core.cpp b/app/core.cpp index 70fb71f6c..d737a0563 100644 --- a/app/core.cpp +++ b/app/core.cpp @@ -81,18 +81,17 @@ Core *Core::instance() return &instance_; } -bool Core::Start() +int Core::execute(QCoreApplication* a) { - // Reset config (Config sets to default on construction already, but we do it again here as a workaround that fixes - // the fact that some of the config paths set by default rely on the app name having been set (in main()) - Config::Current().SetDefaults(); + int exit_code = 1; + + // Start core + OLIVE_NAMESPACE::Core::instance()->Start(); // // Parse command line arguments // - QCoreApplication* app = QCoreApplication::instance(); - QCommandLineParser parser; parser.addHelpOption(); parser.addVersionOption(); @@ -111,7 +110,7 @@ bool Core::Start() parser.addOption(headless_export_option); // Parse options - parser.process(*app); + parser.process(*a); QStringList args = parser.positionalArguments(); @@ -120,6 +119,44 @@ bool Core::Start() startup_project_ = args.first(); } + gui_active_ = !parser.isSet(headless_export_option); + + if (gui_active_) { + + // Start GUI + StartGUI(parser.isSet(fullscreen_option)); + + // If we have a startup + QMetaObject::invokeMethod(this, "OpenStartupProject", Qt::QueuedConnection); + + // Run application loop and receive exit code + exit_code = a->exec(); + + } else { + + if (parser.isSet(headless_export_option)) { + // Start a headless export + if (StartHeadlessExport()) { + exit_code = 0; + } + } + + } + + + + // Clear core memory + OLIVE_NAMESPACE::Core::instance()->Stop(); + + return exit_code; +} + +void Core::Start() +{ + // Reset config (Config sets to default on construction already, but we do it again here as a workaround that fixes + // the fact that some of the config paths set by default rely on the app name having been set (in main()) + Config::Current().SetDefaults(); + // Declare custom types for Qt signal/slot system DeclareTypesForQt(); @@ -141,53 +178,6 @@ bool Core::Start() // qInfo() << "Using Qt version:" << qVersion(); - - gui_active_ = !parser.isSet(headless_export_option); - - if (gui_active_) { - - // Start GUI - StartGUI(parser.isSet(fullscreen_option)); - - // Load startup project - if (!startup_project_.isEmpty() && !QFileInfo::exists(startup_project_)) { - QMessageBox::warning(main_window(), - tr("Failed to open startup file"), - tr("The project \"%1\" doesn't exist. A new project will be started instead.").arg(startup_project_), - QMessageBox::Ok); - - startup_project_.clear(); - } - - if (startup_project_.isEmpty()) { - // If no load project is set, create a new one on open - CreateNewProject(); - } else { - OpenProjectInternal(startup_project_); - } - - return true; - - } else { - - if (parser.isSet(headless_export_option)) { - - if (startup_project_.isEmpty()) { - qCritical().noquote() << tr("You must specify a project file to export"); - } else { - OpenProjectInternal(startup_project_); - - qDebug() << "Ready for exporting!"; - - return true; - } - - } - - // Error fallback - return false; - - } } void Core::Stop() @@ -539,6 +529,106 @@ void Core::ProjectWasModified(bool e) } } +bool Core::StartHeadlessExport() +{ + if (startup_project_.isEmpty()) { + qCritical().noquote() << tr("You must specify a project file to export"); + return false; + } + + if (!QFileInfo::exists(startup_project_)) { + qCritical().noquote() << tr("Specified project does not exist"); + return false; + } + + // Start a load task and try running it + ProjectLoadTask plm(startup_project_); + CLITaskDialog task_dialog(&plm); + + if (task_dialog.Run()) { + ProjectPtr p = plm.GetLoadedProjects().first(); + QList items = p->get_items_of_type(Item::kSequence); + + // Check if this project contains sequences + if (items.isEmpty()) { + qCritical().noquote() << tr("Project contains no sequences, nothing to export"); + return false; + } + + SequencePtr sequence = nullptr; + + // Check if this project contains multiple sequences + if (items.size() > 1) { + qInfo().noquote() << tr("This project has multiple sequences. Which do you wish to export?"); + for (int i=0;iname().toStdString(); + } + + QTextStream stream(stdin); + QString sequence_read; + int sequence_index = -1; + QString quit_code = QStringLiteral("q"); + std::string prompt = tr("Enter number (or %1 to cancel): ").arg(quit_code).toStdString(); + forever { + std::cout << prompt; + + stream.readLineInto(&sequence_read); + + if (!QString::compare(sequence_read, quit_code, Qt::CaseInsensitive)) { + return false; + } + + bool ok; + sequence_index = sequence_read.toInt(&ok); + + if (ok && sequence_index >= 0 && sequence_index < items.size()) { + break; + } else { + qCritical().noquote() << tr("Invalid sequence number"); + } + } + + sequence = std::static_pointer_cast(items.at(sequence_index)); + } else { + sequence = std::static_pointer_cast(items.first()); + } + + ExportParams params; + ExportTask export_task(sequence->viewer_output(), p->color_manager(), params); + CLITaskDialog export_dialog(&export_task); + if (export_dialog.Run()) { + qInfo().noquote() << tr("Export succeeded"); + return true; + } else { + qInfo().noquote() << tr("Export failed: %1").arg(export_task.GetError()); + return false; + } + } else { + qCritical().noquote() << tr("Project failed to load: %1").arg(plm.GetError()); + return false; + } +} + +void Core::OpenStartupProject() +{ + // Load startup project + if (!startup_project_.isEmpty() && !QFileInfo::exists(startup_project_)) { + QMessageBox::warning(main_window_, + tr("Failed to open startup file"), + tr("The project \"%1\" doesn't exist. A new project will be started instead.").arg(startup_project_), + QMessageBox::Ok); + + startup_project_.clear(); + } + + if (startup_project_.isEmpty()) { + // If no load project is set, create a new one on open + CreateNewProject(); + } else { + OpenProjectInternal(startup_project_); + } +} + void Core::DeclareTypesForQt() { qRegisterMetaType(); @@ -559,6 +649,7 @@ void Core::DeclareTypesForQt() qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType(); } void Core::StartGUI(bool full_screen) @@ -877,6 +968,11 @@ bool Core::SaveProjectAs(ProjectPtr p) GetProjectFilter()); if (!fn.isEmpty()) { + QString extension(QStringLiteral(".ove")); + if (!fn.endsWith(extension, Qt::CaseInsensitive)) { + fn.append(extension); + } + p->set_filename(fn); SaveProjectInternal(p); @@ -927,7 +1023,7 @@ void Core::OpenProjectInternal(const QString &filename) //connect(plm, &ProjectLoadManager::ProjectLoaded, this, &Core::AddOpenProject); - CLITaskDialog task_dialog(plm); + } } diff --git a/app/core.h b/app/core.h index 07aeccb08..9b3d25728 100644 --- a/app/core.h +++ b/app/core.h @@ -66,12 +66,14 @@ public: */ static Core* instance(); + int execute(QCoreApplication *a); + /** * @brief Start Olive Core * * Main application launcher. Parses command line arguments and constructs main window (if entering a GUI mode). */ - bool Start(); + void Start(); /** * @brief Stop Olive Core @@ -405,11 +407,6 @@ private: */ void PushRecentlyOpenedProject(const QString &s); - /** - * @brief Internal project open - */ - void OpenProjectInternal(const QString& filename); - /** * @brief Declare custom types/classes for Qt's signal/slot system * @@ -507,6 +504,15 @@ private slots: void ProjectWasModified(bool e); + bool StartHeadlessExport(); + + void OpenStartupProject(); + + /** + * @brief Internal project open + */ + void OpenProjectInternal(const QString& filename); + }; OLIVE_NAMESPACE_EXIT diff --git a/app/main.cpp b/app/main.cpp index e87f1fd34..0026395f0 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -86,23 +86,5 @@ int main(int argc, char *argv[]) { avfilter_register_all(); #endif - int exit_code; - - // Start core - if (OLIVE_NAMESPACE::Core::instance()->Start()) { - - // Run application loop and receive exit code - exit_code = a.exec(); - - } else { - - // Core failed to start, exit now - exit_code = 1; - - } - - // Clear core memory - OLIVE_NAMESPACE::Core::instance()->Stop(); - - return exit_code; + return OLIVE_NAMESPACE::Core::instance()->execute(&a); } diff --git a/app/node/param.cpp b/app/node/param.cpp index ff8e72b21..ec1eb8b65 100644 --- a/app/node/param.cpp +++ b/app/node/param.cpp @@ -185,6 +185,58 @@ NodeEdgePtr NodeParam::DisconnectForNewOutput(NodeInput *input) return nullptr; } +QString NodeParam::GetPrettyDataTypeName(const NodeParam::DataType &type) +{ + switch (type) { + case kNone: + return tr("None"); + case kInt: + case kCombo: + return tr("Integer"); + case kFloat: + return tr("Float"); + case kRational: + return tr("Rational"); + case kBoolean: + return tr("Boolean"); + case kColor: + return tr("Color"); + case kMatrix: + return tr("Matrix"); + case kText: + return tr("Text"); + case kFont: + return tr("Font"); + case kFile: + return tr("File"); + case kTexture: + return tr("Texture"); + case kSamples: + return tr("Samples"); + case kFootage: + return tr("Footage"); + case kVec2: + return tr("Vector 2D"); + case kVec3: + return tr("Vector 3D"); + case kVec4: + return tr("Vector 4D"); + + case kDecimal: + case kNumber: + case kString: + case kBuffer: + case kVector: + case kShaderJob: + case kSampleJob: + case kGenerateJob: + case kAny: + break; + } + + return tr("Unknown"); +} + QByteArray NodeParam::ValueToBytes(const NodeParam::DataType &type, const QVariant &value) { switch (type) { @@ -222,39 +274,6 @@ QByteArray NodeParam::ValueToBytes(const NodeParam::DataType &type, const QVaria return QByteArray(); } -NodeParam::DataType NodeParam::StringToDataType(const QString &s) -{ - QString type_id = s.toLower(); - - if (type_id == QStringLiteral("float")) { - return kFloat; - } else if (type_id == QStringLiteral("int")) { - return kInt; - } else if (type_id == QStringLiteral("rational")) { - return kRational; - } else if (type_id == QStringLiteral("bool")) { - return kBoolean; - } else if (type_id == QStringLiteral("color")) { - return kColor; - } else if (type_id == QStringLiteral("matrix")) { - return kMatrix; - } else if (type_id == QStringLiteral("text")) { - return kText; - } else if (type_id == QStringLiteral("texture")) { - return kTexture; - } else if (type_id == QStringLiteral("vec2")) { - return kVec2; - } else if (type_id == QStringLiteral("vec3")) { - return kVec3; - } else if (type_id == QStringLiteral("vec4")) { - return kVec4; - } else if (type_id == QStringLiteral("combo")) { - return kCombo; - } - - return kAny; -} - template QByteArray NodeParam::ValueToBytesInternal(const QVariant &v) { diff --git a/app/node/param.h b/app/node/param.h index 40e5a9716..03e667936 100644 --- a/app/node/param.h +++ b/app/node/param.h @@ -378,18 +378,13 @@ public: /** * @brief Get a human-readable translated name for a certain data type */ - static QString GetDefaultDataTypeName(const DataType &type); + static QString GetPrettyDataTypeName(const DataType &type); /** * @brief Convert a value from a NodeParam into bytes */ static QByteArray ValueToBytes(const DataType &type, const QVariant& value); - /** - * @brief Convert a string to a data type - */ - static DataType StringToDataType(const QString& s); - signals: /** * @brief Signal emitted when an edge is added to this parameter diff --git a/app/node/value.cpp b/app/node/value.cpp index d487f0a3d..e2f9913c5 100644 --- a/app/node/value.cpp +++ b/app/node/value.cpp @@ -61,26 +61,11 @@ NodeValue::NodeValue(const NodeParam::DataType &type, const QVariant &data, cons { } -const NodeParam::DataType &NodeValue::type() const -{ - return type_; -} - -const QString &NodeValue::tag() const -{ - return tag_; -} - bool NodeValue::operator==(const NodeValue &rhs) const { return type_ == rhs.type_ && tag_ == rhs.tag_ && data_ == rhs.data_; } -const QVariant &NodeValue::data() const -{ - return data_; -} - QVariant NodeValueTable::Get(const NodeParam::DataType &type, const QString &tag) const { return GetWithMeta(type, tag).data(); diff --git a/app/node/value.h b/app/node/value.h index 387117993..7efb8592a 100644 --- a/app/node/value.h +++ b/app/node/value.h @@ -33,9 +33,25 @@ public: NodeValue(); NodeValue(const NodeParam::DataType& type, const QVariant& data, const Node* from, const QString& tag = QString()); - const NodeParam::DataType& type() const; - const QVariant& data() const; - const QString& tag() const; + const NodeParam::DataType& type() const + { + return type_; + } + + const QVariant& data() const + { + return data_; + } + + const QString& tag() const + { + return tag_; + } + + const Node* source() const + { + return from_; + } bool operator==(const NodeValue& rhs) const; @@ -90,6 +106,16 @@ public: NodeValueTable Merge() const; + using const_iterator = QHash::const_iterator; + + inline QHash::const_iterator begin() const { + return tables_.cbegin(); + } + + inline QHash::const_iterator end() const { + return tables_.cend(); + } + private: QHash tables_; diff --git a/app/panel/CMakeLists.txt b/app/panel/CMakeLists.txt index 0c6423398..75c1c7738 100644 --- a/app/panel/CMakeLists.txt +++ b/app/panel/CMakeLists.txt @@ -23,6 +23,7 @@ add_subdirectory(pixelsampler) add_subdirectory(project) add_subdirectory(scope) add_subdirectory(sequenceviewer) +add_subdirectory(table) add_subdirectory(taskmanager) add_subdirectory(timebased) add_subdirectory(timeline) diff --git a/app/panel/table/CMakeLists.txt b/app/panel/table/CMakeLists.txt new file mode 100644 index 000000000..f332aa646 --- /dev/null +++ b/app/panel/table/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2019 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + panel/table/table.h + panel/table/table.cpp + PARENT_SCOPE +) diff --git a/app/panel/table/table.cpp b/app/panel/table/table.cpp new file mode 100644 index 000000000..7183e9c23 --- /dev/null +++ b/app/panel/table/table.cpp @@ -0,0 +1,44 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "table.h" + +OLIVE_NAMESPACE_ENTER + +NodeTablePanel::NodeTablePanel(QWidget* parent) : + TimeBasedPanel(QStringLiteral("NodeTablePanel"), parent) +{ + view_ = new NodeTableWidget(); + SetTimeBasedWidget(view_); + + Retranslate(); +} + +void NodeTablePanel::SetNodes(const QList &nodes) +{ + view_->SetNodes(nodes); +} + +void NodeTablePanel::Retranslate() +{ + SetTitle(tr("Table View")); +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/panel/table/table.h b/app/panel/table/table.h new file mode 100644 index 000000000..0587cf51b --- /dev/null +++ b/app/panel/table/table.h @@ -0,0 +1,45 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef NODETABLEPANEL_H +#define NODETABLEPANEL_H + +#include "panel/timebased/timebased.h" +#include "widget/nodetableview/nodetablewidget.h" + +OLIVE_NAMESPACE_ENTER + +class NodeTablePanel : public TimeBasedPanel +{ +public: + NodeTablePanel(QWidget* parent); + +public slots: + void SetNodes(const QList& nodes); + +private: + virtual void Retranslate() override; + + NodeTableWidget* view_; +}; + +OLIVE_NAMESPACE_EXIT + +#endif // NODETABLEPANEL_H diff --git a/app/project/item/footage/imagestream.cpp b/app/project/item/footage/imagestream.cpp index beb5f3b3b..41a9d0d23 100644 --- a/app/project/item/footage/imagestream.cpp +++ b/app/project/item/footage/imagestream.cpp @@ -72,26 +72,6 @@ QString ImageStream::description() const QString::number(height())); } -const int &ImageStream::width() const -{ - return width_; -} - -void ImageStream::set_width(const int &width) -{ - width_ = width; -} - -const int &ImageStream::height() const -{ - return height_; -} - -void ImageStream::set_height(const int &height) -{ - height_ = height; -} - bool ImageStream::premultiplied_alpha() const { return premultiplied_alpha_; diff --git a/app/project/item/footage/imagestream.h b/app/project/item/footage/imagestream.h index 9e3b6f72b..30fc712a2 100644 --- a/app/project/item/footage/imagestream.h +++ b/app/project/item/footage/imagestream.h @@ -21,6 +21,7 @@ #ifndef IMAGESTREAM_H #define IMAGESTREAM_H +#include "render/pixelformat.h" #include "stream.h" OLIVE_NAMESPACE_ENTER @@ -36,11 +37,35 @@ public: virtual QString description() const override; - const int& width() const; - void set_width(const int& width); + const int& width() const + { + return width_; + } - const int& height() const; - void set_height(const int& height); + void set_width(const int& width) + { + width_ = width; + } + + const int& height() const + { + return height_; + } + + void set_height(const int& height) + { + height_ = height; + } + + const PixelFormat::Format& format() const + { + return format_; + } + + void set_format(const PixelFormat::Format& format) + { + format_ = format; + } bool premultiplied_alpha() const; void set_premultiplied_alpha(bool e); @@ -63,6 +88,8 @@ private: bool premultiplied_alpha_; QString colorspace_; + PixelFormat::Format format_; + private slots: void ColorConfigChanged(); diff --git a/app/render/backend/renderworker.cpp b/app/render/backend/renderworker.cpp index 7142eae07..1ff861658 100644 --- a/app/render/backend/renderworker.cpp +++ b/app/render/backend/renderworker.cpp @@ -244,14 +244,16 @@ QVariant RenderWorker::GetCachedFrame(const Node* node, const rational& time) if (QFileInfo::exists(fn)) { FramePtr f = FrameHashCache::LoadCacheFrame(hash); - // The cached frame won't load with the correct divider by default, so we enforce it here - f->set_video_params(VideoParams(f->width() * video_params_.divider(), - f->height() * video_params_.divider(), - f->video_params().time_base(), - f->video_params().format(), - video_params_.divider())); + if (f) { + // The cached frame won't load with the correct divider by default, so we enforce it here + f->set_video_params(VideoParams(f->width() * video_params_.divider(), + f->height() * video_params_.divider(), + f->video_params().time_base(), + f->video_params().format(), + video_params_.divider())); - return CachedFrameToTexture(f); + return CachedFrameToTexture(f); + } } } @@ -316,9 +318,7 @@ QVariant RenderWorker::ProcessVideoFootage(StreamPtr stream, const rational &inp // Return a texture from the derived class value = FootageFrameToTexture(stream, frame); - if (value.isNull()) { - qDebug() << "Texture from derivative was blank"; - } else { + if (!value.isNull()) { // Put this into the image cache instead still_image_cache_.insert(stream.get(), {value, colorspace_match, @@ -326,8 +326,6 @@ QVariant RenderWorker::ProcessVideoFootage(StreamPtr stream, const rational &inp video_params_.divider(), time_match}); } - } else { - qDebug() << "Frame from decoder was blank"; } } diff --git a/app/render/framehashcache.cpp b/app/render/framehashcache.cpp index 0bde0581a..58f18a957 100644 --- a/app/render/framehashcache.cpp +++ b/app/render/framehashcache.cpp @@ -206,34 +206,51 @@ FramePtr FrameHashCache::LoadCacheFrame(const QString &fn) FramePtr frame = nullptr; if (!fn.isEmpty() && QFileInfo::exists(fn)) { - auto input = OIIO::ImageInput::open(fn.toStdString()); + Imf::InputFile file(fn.toUtf8(), 0); - if (input) { - - PixelFormat::Format image_format = PixelFormat::OIIOFormatToOliveFormat(input->spec().format, - input->spec().nchannels == kRGBAChannels); - - frame = Frame::Create(); - frame->set_video_params(VideoParams(input->spec().width, - input->spec().height, - image_format)); - - frame->allocate(); - - input->read_image(input->spec().format, - frame->data(), - OIIO::AutoStride, - frame->linesize_bytes()); - - input->close(); - -#if OIIO_VERSION < 10903 - OIIO::ImageInput::destroy(input); -#endif + Imath::Box2i dw = file.header().dataWindow(); + Imf::PixelType pix_type = file.header().channels().begin().channel().type; + int width = dw.max.x - dw.min.x + 1; + int height = dw.max.y - dw.min.y + 1; + bool has_alpha = file.header().channels().findChannel("A"); + PixelFormat::Format image_format; + if (pix_type == Imf::HALF) { + if (has_alpha) { + image_format = PixelFormat::PIX_FMT_RGBA16F; + } else { + image_format = PixelFormat::PIX_FMT_RGB16F; + } } else { - qWarning() << "OIIO Error:" << OIIO::geterror().c_str(); + if (has_alpha) { + image_format = PixelFormat::PIX_FMT_RGBA32F; + } else { + image_format = PixelFormat::PIX_FMT_RGB32F; + } } + + frame = Frame::Create(); + frame->set_video_params(VideoParams(width, + height, + image_format)); + + frame->allocate(); + + int bpc = PixelFormat::BytesPerChannel(image_format); + + size_t xs = PixelFormat::ChannelCount(image_format) * bpc; + size_t ys = frame->linesize_bytes(); + + Imf::FrameBuffer framebuffer; + framebuffer.insert("R", Imf::Slice(pix_type, frame->data(), xs, ys)); + framebuffer.insert("G", Imf::Slice(pix_type, frame->data() + bpc, xs, ys)); + framebuffer.insert("B", Imf::Slice(pix_type, frame->data() + 2*bpc, xs, ys)); + if (has_alpha) { + framebuffer.insert("A", Imf::Slice(pix_type, frame->data() + 3*bpc, xs, ys)); + } + + file.setFrameBuffer(framebuffer); + file.readPixels(dw.min.y, dw.max.y); } return frame; diff --git a/app/render/videoparams.h b/app/render/videoparams.h index b108874d5..56534ed3c 100644 --- a/app/render/videoparams.h +++ b/app/render/videoparams.h @@ -91,4 +91,6 @@ private: OLIVE_NAMESPACE_EXIT +Q_DECLARE_METATYPE(OLIVE_NAMESPACE::VideoParams) + #endif // VIDEOPARAMS_H diff --git a/app/task/export/exportparams.cpp b/app/task/export/exportparams.cpp index a603735ee..299c1fca3 100644 --- a/app/task/export/exportparams.cpp +++ b/app/task/export/exportparams.cpp @@ -100,4 +100,26 @@ QMatrix4x4 ExportParams::GenerateMatrix(ExportParams::VideoScalingMethod method, return preview_matrix; } +void ExportParams::Save(QXmlStreamWriter *writer) const +{ + writer->writeStartElement(QStringLiteral("export")); + + writer->writeTextElement(QStringLiteral("encoder"), encoder_id_); + + writer->writeTextElement(QStringLiteral("vscale"), QString::number(video_scaling_method_)); + + writer->writeTextElement(QStringLiteral("range"), QString::number(has_custom_range_)); + + writer->writeTextElement(QStringLiteral("customrangein"), custom_range_.in().toString()); + + writer->writeTextElement(QStringLiteral("customrangeout"), custom_range_.out().toString()); + + // FIXME: Change this when color chains are implemented + writer->writeTextElement(QStringLiteral("color"), color_transform_.output()); + + EncodingParams::Save(writer); + + writer->writeEndElement(); // export +} + OLIVE_NAMESPACE_EXIT diff --git a/app/task/export/exportparams.h b/app/task/export/exportparams.h index a74ea3c5d..ed6106d67 100644 --- a/app/task/export/exportparams.h +++ b/app/task/export/exportparams.h @@ -56,6 +56,8 @@ public: int source_width, int source_height, int dest_width, int dest_height); + virtual void Save(QXmlStreamWriter* writer) const override; + private: QString encoder_id_; diff --git a/app/task/project/load/load.cpp b/app/task/project/load/load.cpp index 20d05ce7d..932a94c74 100644 --- a/app/task/project/load/load.cpp +++ b/app/task/project/load/load.cpp @@ -70,6 +70,8 @@ bool ProjectLoadTask::Run() project_file.close(); + emit ProgressChanged(1); + if (reader.hasError()) { SetError(reader.errorString()); return false; diff --git a/app/widget/CMakeLists.txt b/app/widget/CMakeLists.txt index dc068e82b..67c20349e 100644 --- a/app/widget/CMakeLists.txt +++ b/app/widget/CMakeLists.txt @@ -31,6 +31,7 @@ add_subdirectory(nodecombobox) add_subdirectory(nodecopypaste) add_subdirectory(nodeview) add_subdirectory(nodeparamview) +add_subdirectory(nodetableview) add_subdirectory(panel) add_subdirectory(pixelsampler) add_subdirectory(playbackcontrols) diff --git a/app/widget/clickablelabel/clickablelabel.cpp b/app/widget/clickablelabel/clickablelabel.cpp index 0ab4dd631..a267a1019 100644 --- a/app/widget/clickablelabel/clickablelabel.cpp +++ b/app/widget/clickablelabel/clickablelabel.cpp @@ -20,6 +20,8 @@ #include "clickablelabel.h" +#include + OLIVE_NAMESPACE_ENTER ClickableLabel::ClickableLabel(const QString &text, QWidget *parent) : @@ -32,16 +34,18 @@ ClickableLabel::ClickableLabel(QWidget *parent) : { } -void ClickableLabel::mouseReleaseEvent(QMouseEvent *) +void ClickableLabel::mouseReleaseEvent(QMouseEvent *event) { - if (underMouse()) { + if (event->button() == Qt::LeftButton && underMouse()) { emit MouseClicked(); } } -void ClickableLabel::mouseDoubleClickEvent(QMouseEvent *) +void ClickableLabel::mouseDoubleClickEvent(QMouseEvent *event) { - emit MouseDoubleClicked(); + if (event->button() == Qt::LeftButton) { + emit MouseDoubleClicked(); + } } OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp b/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp index 612b14f81..43507cac6 100644 --- a/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp +++ b/app/widget/nodeparamview/nodeparamviewconnectedlabel.cpp @@ -23,7 +23,10 @@ #include #include "common/qtutils.h" +#include "core.h" #include "node/node.h" +#include "widget/menu/menu.h" +#include "widget/nodeview/nodeviewundo.h" OLIVE_NAMESPACE_ENTER @@ -39,7 +42,9 @@ NodeParamViewConnectedLabel::NodeParamViewConnectedLabel(NodeInput *input, QWidg connected_to_lbl_ = new ClickableLabel(); connected_to_lbl_->setCursor(Qt::PointingHandCursor); + connected_to_lbl_->setContextMenuPolicy(Qt::CustomContextMenu); connect(connected_to_lbl_, &ClickableLabel::MouseClicked, this, &NodeParamViewConnectedLabel::ConnectionClicked); + connect(connected_to_lbl_, &ClickableLabel::customContextMenuRequested, this, &NodeParamViewConnectedLabel::ShowLabelContextMenu); layout->addWidget(connected_to_lbl_); layout->addStretch(); @@ -69,4 +74,16 @@ void NodeParamViewConnectedLabel::UpdateConnected() connected_to_lbl_->setText(connection_str); } +void NodeParamViewConnectedLabel::ShowLabelContextMenu() +{ + Menu m(this); + + QAction* disconnect_action = m.addAction(tr("Disconnect")); + connect(disconnect_action, &QAction::triggered, this, [this](){ + Core::instance()->undo_stack()->push(new NodeEdgeRemoveCommand(input_->get_connected_output(), input_)); + }); + + m.exec(QCursor::pos()); +} + OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodeparamview/nodeparamviewconnectedlabel.h b/app/widget/nodeparamview/nodeparamviewconnectedlabel.h index 161bf2a0d..04103cae1 100644 --- a/app/widget/nodeparamview/nodeparamviewconnectedlabel.h +++ b/app/widget/nodeparamview/nodeparamviewconnectedlabel.h @@ -37,6 +37,8 @@ signals: private slots: void UpdateConnected(); + void ShowLabelContextMenu(); + private: ClickableLabel* connected_to_lbl_; diff --git a/app/widget/nodetableview/CMakeLists.txt b/app/widget/nodetableview/CMakeLists.txt new file mode 100644 index 000000000..f12dff040 --- /dev/null +++ b/app/widget/nodetableview/CMakeLists.txt @@ -0,0 +1,26 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2019 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + widget/nodetableview/nodetabletraverser.h + widget/nodetableview/nodetabletraverser.cpp + widget/nodetableview/nodetableview.h + widget/nodetableview/nodetableview.cpp + widget/nodetableview/nodetablewidget.h + widget/nodetableview/nodetablewidget.cpp + PARENT_SCOPE +) diff --git a/app/widget/nodetableview/nodetabletraverser.cpp b/app/widget/nodetableview/nodetabletraverser.cpp new file mode 100644 index 000000000..1ca0e1080 --- /dev/null +++ b/app/widget/nodetableview/nodetabletraverser.cpp @@ -0,0 +1,44 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "nodetabletraverser.h" + +OLIVE_NAMESPACE_ENTER + +QVariant NodeTableTraverser::ProcessVideoFootage(StreamPtr stream, const rational &input_time) +{ + ImageStreamPtr video_stream = std::static_pointer_cast(stream); + + return QVariant::fromValue(VideoParams(video_stream->width(), + video_stream->height(), + video_stream->timebase(), + video_stream->format())); +} + +QVariant NodeTableTraverser::ProcessAudioFootage(StreamPtr stream, const TimeRange &input_time) +{ + AudioStreamPtr audio_stream = std::static_pointer_cast(stream); + + return QVariant::fromValue(AudioParams(audio_stream->sample_rate(), + audio_stream->channel_layout(), + SampleFormat::kInternalFormat)); +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodetableview/nodetabletraverser.h b/app/widget/nodetableview/nodetabletraverser.h new file mode 100644 index 000000000..20dae8e2e --- /dev/null +++ b/app/widget/nodetableview/nodetabletraverser.h @@ -0,0 +1,42 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef NODETABLETRAVERSER_H +#define NODETABLETRAVERSER_H + +#include "node/traverser.h" + +OLIVE_NAMESPACE_ENTER + +class NodeTableTraverser : public NodeTraverser +{ +public: + NodeTableTraverser() = default; + +protected: + virtual QVariant ProcessVideoFootage(StreamPtr stream, const rational &input_time); + + virtual QVariant ProcessAudioFootage(StreamPtr stream, const TimeRange &input_time); + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // NODETABLETRAVERSER_H diff --git a/app/widget/nodetableview/nodetableview.cpp b/app/widget/nodetableview/nodetableview.cpp new file mode 100644 index 000000000..8e9f5aab0 --- /dev/null +++ b/app/widget/nodetableview/nodetableview.cpp @@ -0,0 +1,113 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "nodetableview.h" + +#include + +#include "node/param.h" +#include "nodetabletraverser.h" + +OLIVE_NAMESPACE_ENTER + +NodeTableView::NodeTableView(QWidget* parent) : + QTreeWidget(parent) +{ + setColumnCount(3); + setHeaderLabels({tr("Type"), tr("Value"), tr("Source")}); +} + +void NodeTableView::SetNode(Node *n, const rational &time) +{ + clear(); + + NodeTableTraverser traverser; + NodeValueDatabase db = traverser.GenerateDatabase(n, TimeRange(time, time)); + + NodeValueDatabase::const_iterator i; + + for (i=db.begin(); i!=db.end(); i++) { + const NodeValueTable& table = i.value(); + + NodeInput* input = n->GetInputWithID(i.key()); + if (!input) { + // Filters out table entries that aren't inputs (like "global") + continue; + } + + QTreeWidgetItem* top_item = new QTreeWidgetItem(); + top_item->setText(0, input->name()); + top_item->setFirstColumnSpanned(true); + this->addTopLevelItem(top_item); + + for (int j=table.Count()-1; j>=0; j--) { + const NodeValue& value = table.at(j); + + QString value_str = NodeInput::ValueToString(value.type(), value.data()); + + QString source_name; + if (value.source()) { + source_name = value.source()->Name(); + } else { + source_name = tr("(unknown)"); + } + + QTreeWidgetItem* sub_item = new QTreeWidgetItem(); + sub_item->setText(0, NodeParam::GetPrettyDataTypeName(value.type())); + sub_item->setText(1, value_str); + sub_item->setText(2, source_name); + top_item->addChild(sub_item); + + // Special cases + if (value.type() == NodeParam::kTexture) { + // NodeTableTraverser converts footage to VideoParams + QTreeWidgetItem* red_channel = new QTreeWidgetItem(); + red_channel->setText(0, tr("Red")); + sub_item->addChild(red_channel); + + QTreeWidgetItem* green_channel = new QTreeWidgetItem(); + green_channel->setText(0, tr("Green")); + sub_item->addChild(green_channel); + + QTreeWidgetItem* blue_channel = new QTreeWidgetItem(); + blue_channel->setText(0, tr("Blue")); + sub_item->addChild(blue_channel); + + if (PixelFormat::FormatHasAlphaChannel(value.data().value().format())) { + QTreeWidgetItem* alpha_channel = new QTreeWidgetItem(); + alpha_channel->setText(0, tr("Alpha")); + sub_item->addChild(alpha_channel); + } + } + } + } +} + +void NodeTableView::SetMultipleNodeMessage() +{ + this->clear(); + + QTreeWidgetItem* item = new QTreeWidgetItem(); + item->setText(0, tr("Multiple nodes selected")); + item->setFirstColumnSpanned(true); + this->addTopLevelItem(item); +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodetableview/nodetableview.h b/app/widget/nodetableview/nodetableview.h new file mode 100644 index 000000000..e453903b7 --- /dev/null +++ b/app/widget/nodetableview/nodetableview.h @@ -0,0 +1,43 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef NODETABLEVIEW_H +#define NODETABLEVIEW_H + +#include + +#include "node/node.h" + +OLIVE_NAMESPACE_ENTER + +class NodeTableView : public QTreeWidget +{ +public: + NodeTableView(QWidget* parent = nullptr); + + void SetNode(Node* n, const rational& time); + + void SetMultipleNodeMessage(); + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // NODETABLEVIEW_H diff --git a/app/widget/nodetableview/nodetablewidget.cpp b/app/widget/nodetableview/nodetablewidget.cpp new file mode 100644 index 000000000..20db058c2 --- /dev/null +++ b/app/widget/nodetableview/nodetablewidget.cpp @@ -0,0 +1,49 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "nodetablewidget.h" + +#include + +OLIVE_NAMESPACE_ENTER + +NodeTableWidget::NodeTableWidget(QWidget* parent) : + TimeBasedWidget(parent) +{ + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setSpacing(0); + layout->setMargin(0); + + view_ = new NodeTableView(); + layout->addWidget(view_); +} + +void NodeTableWidget::SetNodes(const QList &nodes) +{ + if (nodes.isEmpty()) { + view_->clear(); + } else if (nodes.size() == 1) { + view_->SetNode(nodes.first(), rational()); + } else { + view_->SetMultipleNodeMessage(); + } +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/widget/nodetableview/nodetablewidget.h b/app/widget/nodetableview/nodetablewidget.h new file mode 100644 index 000000000..ae0c82cc4 --- /dev/null +++ b/app/widget/nodetableview/nodetablewidget.h @@ -0,0 +1,43 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef NODETABLEWIDGET_H +#define NODETABLEWIDGET_H + +#include "nodetableview.h" +#include "widget/timebased/timebased.h" + +OLIVE_NAMESPACE_ENTER + +class NodeTableWidget : public TimeBasedWidget +{ +public: + NodeTableWidget(QWidget* parent = nullptr); + + void SetNodes(const QList& nodes); + +private: + NodeTableView* view_; + +}; + +OLIVE_NAMESPACE_EXIT + +#endif // NODETABLEWIDGET_H diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index c011fa558..97f186cca 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -1241,6 +1241,13 @@ void TimelineWidget::UpdateViewTimebases() } } +void TimelineWidget::SetViewBeamCursor(const TimelineCoordinate &coord) +{ + foreach (TimelineAndTrackView* tview, views_) { + tview->view()->SetBeamCursor(coord); + } +} + void TimelineWidget::SetBlockLinksSelected(Block* block, bool selected) { TimelineViewBlockItem* link_item; diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index d8a6c0dc5..fad1da588 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -209,6 +209,15 @@ private: }; + class BeamTool : public Tool + { + public: + BeamTool(TimelineWidget *parent); + + virtual void HoverMove(TimelineViewMouseEvent *event) override; + + }; + class PointerTool : public Tool { public: @@ -327,7 +336,7 @@ private: }; - class EditTool : public Tool + class EditTool : public BeamTool { public: EditTool(TimelineWidget* parent); @@ -337,7 +346,7 @@ private: virtual void MouseRelease(TimelineViewMouseEvent *event) override; }; - class RazorTool : public Tool + class RazorTool : public BeamTool { public: RazorTool(TimelineWidget* parent); @@ -498,6 +507,8 @@ private: void UpdateViewTimebases(); + void SetViewBeamCursor(const TimelineCoordinate& coord); + private slots: void ViewMousePressed(TimelineViewMouseEvent* event); void ViewMouseMoved(TimelineViewMouseEvent* event); diff --git a/app/widget/timelinewidget/tool/CMakeLists.txt b/app/widget/timelinewidget/tool/CMakeLists.txt index 53b862a84..f14d4a8de 100644 --- a/app/widget/timelinewidget/tool/CMakeLists.txt +++ b/app/widget/timelinewidget/tool/CMakeLists.txt @@ -17,6 +17,7 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} widget/timelinewidget/tool/add.cpp + widget/timelinewidget/tool/beam.cpp widget/timelinewidget/tool/edit.cpp widget/timelinewidget/tool/import.cpp widget/timelinewidget/tool/pointer.cpp diff --git a/app/widget/timelinewidget/tool/beam.cpp b/app/widget/timelinewidget/tool/beam.cpp new file mode 100644 index 000000000..a04d14b9e --- /dev/null +++ b/app/widget/timelinewidget/tool/beam.cpp @@ -0,0 +1,35 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2019 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "widget/timelinewidget/timelinewidget.h" + +OLIVE_NAMESPACE_ENTER + +TimelineWidget::BeamTool::BeamTool(TimelineWidget *parent) : + Tool(parent) +{ +} + +void TimelineWidget::BeamTool::HoverMove(TimelineViewMouseEvent *event) +{ + parent()->SetViewBeamCursor(event->GetCoordinates(true)); +} + +OLIVE_NAMESPACE_EXIT diff --git a/app/widget/timelinewidget/tool/edit.cpp b/app/widget/timelinewidget/tool/edit.cpp index d7940f0c4..cb804bebc 100644 --- a/app/widget/timelinewidget/tool/edit.cpp +++ b/app/widget/timelinewidget/tool/edit.cpp @@ -23,7 +23,7 @@ OLIVE_NAMESPACE_ENTER TimelineWidget::EditTool::EditTool(TimelineWidget* parent) : - Tool(parent) + BeamTool(parent) { } diff --git a/app/widget/timelinewidget/tool/razor.cpp b/app/widget/timelinewidget/tool/razor.cpp index d89e9125d..298a36b3b 100644 --- a/app/widget/timelinewidget/tool/razor.cpp +++ b/app/widget/timelinewidget/tool/razor.cpp @@ -23,7 +23,7 @@ OLIVE_NAMESPACE_ENTER TimelineWidget::RazorTool::RazorTool(TimelineWidget* parent) : - Tool(parent) + BeamTool(parent) { } diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 91409bf94..2d3cc4511 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -141,21 +141,21 @@ void TimelineView::wheelEvent(QWheelEvent *event) } QWheelEvent e( -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) - event->position(), - event->globalPosition(), -#else - event->pos(), - event->globalPos(), -#endif - event->pixelDelta(), - angle_delta, - event->buttons(), - event->modifiers(), - event->phase(), - event->inverted(), - event->source() - ); + #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + event->position(), + event->globalPosition(), + #else + event->pos(), + event->globalPos(), + #endif + event->pixelDelta(), + angle_delta, + event->buttons(), + event->modifiers(), + event->phase(), + event->inverted(), + event->source() + ); #else @@ -166,15 +166,15 @@ void TimelineView::wheelEvent(QWheelEvent *event) } QWheelEvent e( - event->pos(), - event->globalPos(), - event->pixelDelta(), - event->angleDelta(), - event->delta(), - orientation, - event->buttons(), - event->modifiers() - ); + event->pos(), + event->globalPos(), + event->pixelDelta(), + event->angleDelta(), + event->delta(), + orientation, + event->buttons(), + event->modifiers() + ); #endif QGraphicsView::wheelEvent(&e); @@ -244,6 +244,26 @@ void TimelineView::drawBackground(QPainter *painter, const QRectF &rect) } } +void TimelineView::drawForeground(QPainter *painter, const QRectF &rect) +{ + TimelineViewBase::drawForeground(painter, rect); + + if (show_beam_cursor_ + && connected_track_list_ + && cursor_coord_.GetTrack().type() == connected_track_list_->type() + && cursor_coord_.GetTrack().index() < connected_track_list_->GetTrackCount()) { + painter->setPen(Qt::gray); + + double cursor_x = TimeToScene(cursor_coord_.GetFrame()); + int track_index = cursor_coord_.GetTrack().index(); + + painter->drawLine(cursor_x, + GetTrackY(track_index), + cursor_x, + GetTrackHeight(track_index)); + } +} + void TimelineView::ToolChangedEvent(Tool::Item tool) { switch (tool) { @@ -261,6 +281,12 @@ void TimelineView::ToolChangedEvent(Tool::Item tool) default: unsetCursor(); } + + // Hide/show cursor if necessary + if (show_beam_cursor_) { + show_beam_cursor_ = false; + viewport()->update(); + } } void TimelineView::SceneRectUpdateEvent(QRectF &rect) @@ -399,6 +425,20 @@ void TimelineView::ConnectTrackList(TrackList *list) } } +void TimelineView::SetBeamCursor(const TimelineCoordinate &coord) +{ + bool update_required = true;/*(coord.GetTrack().type() == connected_track_list_->type() + || cursor_coord_.GetTrack().type() == connected_track_list_->type() + || !show_beam_cursor_);*/ + + show_beam_cursor_ = true; + cursor_coord_ = coord; + + if (update_required) { + viewport()->update(); + } +} + int TimelineView::SceneToTrack(double y) { int track = -1; diff --git a/app/widget/timelinewidget/view/timelineview.h b/app/widget/timelinewidget/view/timelineview.h index 53daceca0..4bacad401 100644 --- a/app/widget/timelinewidget/view/timelineview.h +++ b/app/widget/timelinewidget/view/timelineview.h @@ -61,6 +61,8 @@ public: void ConnectTrackList(TrackList* list); + void SetBeamCursor(const TimelineCoordinate& coord); + signals: void MousePressed(TimelineViewMouseEvent* event); void MouseMoved(TimelineViewMouseEvent* event); @@ -88,6 +90,7 @@ protected: virtual void dropEvent(QDropEvent *event) override; virtual void drawBackground(QPainter *painter, const QRectF &rect) override; + virtual void drawForeground(QPainter *painter, const QRectF &rect) override; virtual void ToolChangedEvent(Tool::Item tool) override; @@ -111,6 +114,10 @@ private: void UpdatePlayheadRect(); + bool show_beam_cursor_; + + TimelineCoordinate cursor_coord_; + TrackList* connected_track_list_; }; diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index c3eaf7e75..a917143f6 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -72,6 +72,7 @@ MainWindow::MainWindow(QWidget *parent) : node_panel_ = PanelManager::instance()->CreatePanel(this); footage_viewer_panel_ = PanelManager::instance()->CreatePanel(this); param_panel_ = PanelManager::instance()->CreatePanel(this); + table_panel_ = PanelManager::instance()->CreatePanel(this); sequence_viewer_panel_ = PanelManager::instance()->CreatePanel(this); pixel_sampler_panel_ = PanelManager::instance()->CreatePanel(this); AppendProjectPanel(); @@ -82,6 +83,7 @@ MainWindow::MainWindow(QWidget *parent) : // Make connections to sequence viewer connect(node_panel_, &NodePanel::SelectionChanged, param_panel_, &ParamPanel::SetNodes); + connect(node_panel_, &NodePanel::SelectionChanged, table_panel_, &NodeTablePanel::SetNodes); connect(param_panel_, &ParamPanel::RequestSelectNode, node_panel_, &NodePanel::Select); connect(sequence_viewer_panel_, &SequenceViewerPanel::TimeChanged, param_panel_, &ParamPanel::SetTimestamp); connect(param_panel_, &ParamPanel::TimeChanged, sequence_viewer_panel_, &SequenceViewerPanel::SetTimestamp); @@ -602,6 +604,10 @@ void MainWindow::SetDefaultLayout() tabifyDockWidget(footage_viewer_panel_, param_panel_); footage_viewer_panel_->raise(); + table_panel_->hide(); + table_panel_->setFloating(true); + addDockWidget(Qt::TopDockWidgetArea, table_panel_); + sequence_viewer_panel_->show(); addDockWidget(Qt::TopDockWidgetArea, sequence_viewer_panel_); diff --git a/app/window/mainwindow/mainwindow.h b/app/window/mainwindow/mainwindow.h index f01f0524a..c9f349051 100644 --- a/app/window/mainwindow/mainwindow.h +++ b/app/window/mainwindow/mainwindow.h @@ -30,6 +30,7 @@ #include "panel/param/param.h" #include "panel/project/project.h" #include "panel/scope/scope.h" +#include "panel/table/table.h" #include "panel/taskmanager/taskmanager.h" #include "panel/timeline/timeline.h" #include "panel/tool/tool.h" @@ -147,6 +148,7 @@ private: QList curve_panels_; PixelSamplerPanel* pixel_sampler_panel_; QList scope_panels_; + NodeTablePanel* table_panel_; #ifdef Q_OS_WINDOWS unsigned int taskbar_btn_id_;