From b67a6b9f4da1b4cd6ed610dd809f91563e5c83fe Mon Sep 17 00:00:00 2001 From: Angelo Francabandiera Date: Fri, 21 Jan 2022 21:17:13 +0100 Subject: [PATCH 01/49] Added the possibility to create cutom GLSL filters and use as nodes. --- app/config/config.cpp | 2 + .../tabs/preferencesbehaviortab.cpp | 16 ++ .../preferences/tabs/preferencesbehaviortab.h | 3 +- app/node/factory.cpp | 30 +++ app/node/factory.h | 2 + app/node/filter/CMakeLists.txt | 1 + app/node/filter/generic/CMakeLists.txt | 22 +++ app/node/filter/generic/generic.cpp | 171 ++++++++++++++++++ app/node/filter/generic/generic.h | 61 +++++++ 9 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 app/node/filter/generic/CMakeLists.txt create mode 100644 app/node/filter/generic/generic.cpp create mode 100644 app/node/filter/generic/generic.h diff --git a/app/config/config.cpp b/app/config/config.cpp index 2f88ea1b98..1bf886a53e 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -135,6 +135,8 @@ void Config::SetDefaults() // Online/offline settings SetEntryInternal(QStringLiteral("OnlinePixelFormat"), NodeValue::kInt, VideoParams::kFormatFloat32); SetEntryInternal(QStringLiteral("OfflinePixelFormat"), NodeValue::kInt, VideoParams::kFormatFloat16); + + SetEntryInternal(QStringLiteral("GlslFileList"), NodeValue::kText, QString("#add script files here")); } void Config::Load() diff --git a/app/dialog/preferences/tabs/preferencesbehaviortab.cpp b/app/dialog/preferences/tabs/preferencesbehaviortab.cpp index 8d71b3df53..c528b33107 100644 --- a/app/dialog/preferences/tabs/preferencesbehaviortab.cpp +++ b/app/dialog/preferences/tabs/preferencesbehaviortab.cpp @@ -23,6 +23,8 @@ #include #include +#include + #include "config/config.h" namespace olive { @@ -102,6 +104,17 @@ PreferencesBehaviorTab::PreferencesBehaviorTab() tr("Multiple clips can share the same nodes. Disable this to automatically share node " "dependencies among clips when copying or splitting them."), node_group); + + // Add a widget for the list of GLSL filters files + QGroupBox* glsl_filters_group = new QGroupBox(tr("GLSL filters (one full path per line)")); + layout->addWidget(glsl_filters_group ); + glsl_filters_group->setLayout( new QVBoxLayout(this)); + + glsl_fileList_edit_ = new QTextEdit( glsl_filters_group); + glsl_fileList_edit_->setLineWrapMode( QTextEdit::NoWrap); + glsl_filters_group->layout()->addWidget( glsl_fileList_edit_); + glsl_fileList_edit_->setText( Config::Current()["GlslFileList"].toString()); + } void PreferencesBehaviorTab::Accept(MultiUndoCommand *command) @@ -113,6 +126,9 @@ void PreferencesBehaviorTab::Accept(MultiUndoCommand *command) for (iterator=config_map_.begin();iterator!=config_map_.end();iterator++) { Config::Current()[iterator.value()] = (iterator.key()->checkState(0) == Qt::Checked); } + + // save the list of GLSL files as a single string + Config::Current()["GlslFileList"] = QVariant(glsl_fileList_edit_->toPlainText()); } QTreeWidgetItem* PreferencesBehaviorTab::AddItem(const QString &text, const QString &config_key, const QString& tooltip, QTreeWidgetItem* parent) diff --git a/app/dialog/preferences/tabs/preferencesbehaviortab.h b/app/dialog/preferences/tabs/preferencesbehaviortab.h index 0367475eaa..613fedb646 100644 --- a/app/dialog/preferences/tabs/preferencesbehaviortab.h +++ b/app/dialog/preferences/tabs/preferencesbehaviortab.h @@ -22,6 +22,7 @@ #define PREFERENCESBEHAVIORTAB_H #include +#include #include "dialog/configbase/configdialogbase.h" @@ -45,7 +46,7 @@ class PreferencesBehaviorTab : public ConfigDialogBaseTab QMap config_map_; QTreeWidget* behavior_tree_; - + QTextEdit * glsl_fileList_edit_; }; } diff --git a/app/node/factory.cpp b/app/node/factory.cpp index 447ba0dbd2..b0bf1735ce 100644 --- a/app/node/factory.cpp +++ b/app/node/factory.cpp @@ -21,6 +21,8 @@ #include "factory.h" #include +#include +#include #include "audio/pan/pan.h" #include "audio/volume/volume.h" @@ -38,6 +40,7 @@ #include "generator/text/text.h" #include "generator/text/textlegacy.h" #include "filter/blur/blur.h" +#include "filter/generic/generic.h" #include "filter/mosaic/mosaicfilternode.h" #include "filter/stroke/stroke.h" #include "input/time/timeinput.h" @@ -51,6 +54,7 @@ #include "project/footage/footage.h" #include "project/sequence/sequence.h" #include "time/timeremap/timeremap.h" +#include "config/config.h" namespace olive { QList NodeFactory::library_; @@ -67,6 +71,9 @@ void NodeFactory::Initialize() library_.append(created_node); } + // create GLSL filters listed in preferences + CreateCustomFilters(); + hidden_.append(kTextGeneratorLegacy); hidden_.append(kGroupNode); } @@ -260,4 +267,27 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id) return nullptr; } +// parse all GLSL files listed in preferences and +// create a new node for each file (if exists) +void NodeFactory::CreateCustomFilters() +{ + QString custom_filter_config = Config::Current()["GlslFileList"].toString(); + + // The configuration holds a text string where each file is separated by + // new line or simbol ";". + // If a new line starts with "#" it is not considered to hold a file path + QStringList glsl_files = custom_filter_config.split( QRegularExpression("[\\n\\r;]+"), Qt::SkipEmptyParts); + + for( QString file_path : glsl_files) { + QString file_path_trim = file_path.trimmed(); + + // create a node only if file path is valid + if ( ( ! file_path_trim.startsWith("#")) && + (QFileInfo::exists(file_path_trim)) ) + { + library_.append( new GenericFilterNode(file_path)); + } + } +} + } diff --git a/app/node/factory.h b/app/node/factory.h index 10101e6485..fd75ae5a6b 100644 --- a/app/node/factory.h +++ b/app/node/factory.h @@ -90,6 +90,8 @@ class NodeFactory static QVector hidden_; +private: + static void CreateCustomFilters(); }; } diff --git a/app/node/filter/CMakeLists.txt b/app/node/filter/CMakeLists.txt index 632aaed336..811cafcabf 100644 --- a/app/node/filter/CMakeLists.txt +++ b/app/node/filter/CMakeLists.txt @@ -15,6 +15,7 @@ # along with this program. If not, see . add_subdirectory(blur) +add_subdirectory(generic) add_subdirectory(mosaic) add_subdirectory(stroke) diff --git a/app/node/filter/generic/CMakeLists.txt b/app/node/filter/generic/CMakeLists.txt new file mode 100644 index 0000000000..6f8ce52d6b --- /dev/null +++ b/app/node/filter/generic/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2021 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + node/filter/generic/generic.h + node/filter/generic/generic.cpp + PARENT_SCOPE +) diff --git a/app/node/filter/generic/generic.cpp b/app/node/filter/generic/generic.cpp new file mode 100644 index 0000000000..4a1c523406 --- /dev/null +++ b/app/node/filter/generic/generic.cpp @@ -0,0 +1,171 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "generic.h" + +#include +#include + +namespace olive { + +const QString GenericFilterNode::kTextureInput = QStringLiteral("tex_in"); + + +GenericFilterNode::GenericFilterNode(const QString & srcFilePath) : + src_file_path_(srcFilePath) +{ + filter_name_ = QFileInfo(srcFilePath).baseName(); + + // Fixed input: an image to work on. More textures can be added as inputs + AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + + // search source file for parameters to be added to the node panel. + // Exposed parametrs are the ones that follow the convention described in + // function 'searchParametersInSourceFile' + // + searchParametersInSourceFile( srcFilePath); +} + +Node *GenericFilterNode::copy() const +{ + return new GenericFilterNode( src_file_path_); +} + +QString GenericFilterNode::Name() const +{ + return QString("GLSL: %1").arg(filter_name_); +} + +QString GenericFilterNode::id() const +{ + // give an ID that depends on file name, but avoid white spaces + QString tag = filter_name_; + tag.replace( QRegularExpression("\\s"), "_"); + return QStringLiteral("org.olivevideoeditor.Olive.glsl.%1").arg(tag); +} + +QVector GenericFilterNode::Category() const +{ + return {kCategoryFilter}; +} + +QString GenericFilterNode::Description() const +{ + return tr("custom filter."); +} + +void GenericFilterNode::Retranslate() +{ + // Retranslate the only fixed input. + // Other inputs are read from the script file + SetInputName( kTextureInput, tr("Input")); +} + +ShaderCode GenericFilterNode::GetShaderCode(const QString &shader_id) const +{ + Q_UNUSED(shader_id) + + QString source = FileFunctions::ReadFileAsString( src_file_path_); + + return ShaderCode(source); +} + +void GenericFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +{ + ShaderJob job; + + job.InsertValue(value); + job.InsertValue(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); + + // If there's no texture, no need to run an operation + if (!job.GetValue(kTextureInput).data().isNull()) { + table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this); + + } else { + // If we're not performing the generic job, just push the texture + table->Push(job.GetValue(kTextureInput)); + } +} + +// parse the GLSL file specified by the user and search for inputs to be exposed in node panel. +// Search, within the file, parameters that are exposed as inputs to the according to the following convention: +// - a script variable that starts with the following prefixes, is exposed as an input of type: +// prefix type +//------------------------------- +// inColor NodeValue::kColor +// inTexture NodeValue::kTexture +// inFloat NodeValue::kFloat +// inInt NodeValue::inInt +// inBool NodeValue::inBool +// +// After semicolon, a comment is expected with the name of the input that appares in GUI. +// Such name is required, otherwise the variable will not be exposed +// +void GenericFilterNode::searchParametersInSourceFile( const QString & src_file_path) +{ + static QRegularExpression param_regEx("uniform\\s+([A-Za-z0-9_]+)\\s+(in[A-Za-z0-9_]+)\\s*;\\s*//\\s*(.*)\\s*$"); + QRegularExpressionMatch match; + + QString source = FileFunctions::ReadFileAsString( src_file_path); + + QStringList lines = source.split(QRegularExpression("[\\n\\r]+"), Qt::SkipEmptyParts); + + for ( QString line : lines) { + + match = param_regEx.match( line); + + if (match.isValid()) { + + QString type = match.captured(1); + QString param_name = match.captured(2); + + NodeValue::Type paramType = typeFromName( param_name); + + if (paramType != NodeValue::kNone) { + AddInput( param_name, paramType); + + // use name written in comment after semicolon + SetInputName( param_name, match.captured(3)); + } + } + } +} + +NodeValue::Type GenericFilterNode::typeFromName(const QString ¶mName) +{ + if (paramName.startsWith("inColor")) + return NodeValue::kColor; + + if (paramName.startsWith("inTexture")) + return NodeValue::kTexture; + + if (paramName.startsWith("inFloat")) + return NodeValue::kFloat; + + if (paramName.startsWith("inInt")) + return NodeValue::kInt; + + if (paramName.startsWith("inBool")) + return NodeValue::kBoolean; + + return NodeValue::kNone; +} + +} diff --git a/app/node/filter/generic/generic.h b/app/node/filter/generic/generic.h new file mode 100644 index 0000000000..88c9aa05cb --- /dev/null +++ b/app/node/filter/generic/generic.h @@ -0,0 +1,61 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef GENERICFILTERNODE_H +#define GENERICFILTERNODE_H + +#include "node/node.h" + +namespace olive { + +class GenericFilterNode : public Node +{ + Q_OBJECT +public: + // constructor accepts the full path of GLSL frag file + GenericFilterNode( const QString & srcFilePath); + + NODE_DEFAULT_DESTRUCTOR(GenericFilterNode) + + virtual Node* copy() const override; + + virtual QString Name() const override; + virtual QString id() const override; + virtual QVector Category() const override; + virtual QString Description() const override; + + virtual void Retranslate() override; + + virtual ShaderCode GetShaderCode(const QString &shader_id) const override; + virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + + static const QString kTextureInput; + +private: + void searchParametersInSourceFile(const QString &src_file_path); + NodeValue::Type typeFromName( const QString & paramName); + + QString src_file_path_; + QString filter_name_; +}; + +} + +#endif // GENERICFILTERNODE_H From 1947e4f7bc0c693e9d86606ac52c50fa113327c8 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Wed, 26 Jan 2022 21:20:58 +0100 Subject: [PATCH 02/49] Work in Progress. The shader filter has been implemented as described in pull request 1839 (https://github.com/olive-editor/olive/pull/1839). The editor is still the default text editor and several parameters are not handled yet. --- app/config/config.cpp | 2 - .../tabs/preferencesbehaviortab.cpp | 14 - .../preferences/tabs/preferencesbehaviortab.h | 2 - app/node/factory.cpp | 30 +- app/node/factory.h | 3 +- app/node/filter/CMakeLists.txt | 2 +- app/node/filter/generic/generic.cpp | 171 ---------- .../filter/{generic => shader}/CMakeLists.txt | 6 +- app/node/filter/shader/shader.cpp | 158 +++++++++ .../{generic/generic.h => shader/shader.h} | 28 +- app/node/filter/shader/shaderinputsparser.cpp | 317 ++++++++++++++++++ app/node/filter/shader/shaderinputsparser.h | 121 +++++++ .../nodeparamview/nodeparamviewtextedit.cpp | 14 +- .../nodeparamview/nodeparamviewtextedit.h | 5 + .../nodeparamviewwidgetbridge.cpp | 6 + 15 files changed, 645 insertions(+), 234 deletions(-) delete mode 100644 app/node/filter/generic/generic.cpp rename app/node/filter/{generic => shader}/CMakeLists.txt (83%) create mode 100644 app/node/filter/shader/shader.cpp rename app/node/filter/{generic/generic.h => shader/shader.h} (71%) create mode 100644 app/node/filter/shader/shaderinputsparser.cpp create mode 100644 app/node/filter/shader/shaderinputsparser.h diff --git a/app/config/config.cpp b/app/config/config.cpp index 1bf886a53e..2f88ea1b98 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -135,8 +135,6 @@ void Config::SetDefaults() // Online/offline settings SetEntryInternal(QStringLiteral("OnlinePixelFormat"), NodeValue::kInt, VideoParams::kFormatFloat32); SetEntryInternal(QStringLiteral("OfflinePixelFormat"), NodeValue::kInt, VideoParams::kFormatFloat16); - - SetEntryInternal(QStringLiteral("GlslFileList"), NodeValue::kText, QString("#add script files here")); } void Config::Load() diff --git a/app/dialog/preferences/tabs/preferencesbehaviortab.cpp b/app/dialog/preferences/tabs/preferencesbehaviortab.cpp index c528b33107..db0239624e 100644 --- a/app/dialog/preferences/tabs/preferencesbehaviortab.cpp +++ b/app/dialog/preferences/tabs/preferencesbehaviortab.cpp @@ -104,17 +104,6 @@ PreferencesBehaviorTab::PreferencesBehaviorTab() tr("Multiple clips can share the same nodes. Disable this to automatically share node " "dependencies among clips when copying or splitting them."), node_group); - - // Add a widget for the list of GLSL filters files - QGroupBox* glsl_filters_group = new QGroupBox(tr("GLSL filters (one full path per line)")); - layout->addWidget(glsl_filters_group ); - glsl_filters_group->setLayout( new QVBoxLayout(this)); - - glsl_fileList_edit_ = new QTextEdit( glsl_filters_group); - glsl_fileList_edit_->setLineWrapMode( QTextEdit::NoWrap); - glsl_filters_group->layout()->addWidget( glsl_fileList_edit_); - glsl_fileList_edit_->setText( Config::Current()["GlslFileList"].toString()); - } void PreferencesBehaviorTab::Accept(MultiUndoCommand *command) @@ -126,9 +115,6 @@ void PreferencesBehaviorTab::Accept(MultiUndoCommand *command) for (iterator=config_map_.begin();iterator!=config_map_.end();iterator++) { Config::Current()[iterator.value()] = (iterator.key()->checkState(0) == Qt::Checked); } - - // save the list of GLSL files as a single string - Config::Current()["GlslFileList"] = QVariant(glsl_fileList_edit_->toPlainText()); } QTreeWidgetItem* PreferencesBehaviorTab::AddItem(const QString &text, const QString &config_key, const QString& tooltip, QTreeWidgetItem* parent) diff --git a/app/dialog/preferences/tabs/preferencesbehaviortab.h b/app/dialog/preferences/tabs/preferencesbehaviortab.h index 613fedb646..90680b64c3 100644 --- a/app/dialog/preferences/tabs/preferencesbehaviortab.h +++ b/app/dialog/preferences/tabs/preferencesbehaviortab.h @@ -22,7 +22,6 @@ #define PREFERENCESBEHAVIORTAB_H #include -#include #include "dialog/configbase/configdialogbase.h" @@ -46,7 +45,6 @@ class PreferencesBehaviorTab : public ConfigDialogBaseTab QMap config_map_; QTreeWidget* behavior_tree_; - QTextEdit * glsl_fileList_edit_; }; } diff --git a/app/node/factory.cpp b/app/node/factory.cpp index 0e3a9ddadb..c3eee5747c 100644 --- a/app/node/factory.cpp +++ b/app/node/factory.cpp @@ -41,7 +41,7 @@ #include "generator/text/text.h" #include "generator/text/textlegacy.h" #include "filter/blur/blur.h" -#include "filter/generic/generic.h" +#include "filter/shader/shader.h" #include "filter/mosaic/mosaicfilternode.h" #include "filter/stroke/stroke.h" #include "input/time/timeinput.h" @@ -72,9 +72,6 @@ void NodeFactory::Initialize() library_.append(created_node); } - // create GLSL filters listed in preferences - CreateCustomFilters(); - hidden_.append(kTextGeneratorLegacy); hidden_.append(kGroupNode); } @@ -226,6 +223,8 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id) return new TimeInput(); case kBlurFilter: return new BlurFilterNode(); + case kShaderFilter: + return new ShaderFilterNode(); case kSolidGenerator: return new SolidGenerator(); case kMerge: @@ -270,27 +269,4 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id) return nullptr; } -// parse all GLSL files listed in preferences and -// create a new node for each file (if exists) -void NodeFactory::CreateCustomFilters() -{ - QString custom_filter_config = Config::Current()["GlslFileList"].toString(); - - // The configuration holds a text string where each file is separated by - // new line or simbol ";". - // If a new line starts with "#" it is not considered to hold a file path - QStringList glsl_files = custom_filter_config.split( QRegularExpression("[\\n\\r;]+"), Qt::SkipEmptyParts); - - for( QString file_path : glsl_files) { - QString file_path_trim = file_path.trimmed(); - - // create a node only if file path is valid - if ( ( ! file_path_trim.startsWith("#")) && - (QFileInfo::exists(file_path_trim)) ) - { - library_.append( new GenericFilterNode(file_path)); - } - } -} - } diff --git a/app/node/factory.h b/app/node/factory.h index ea1c573cd3..c18be63412 100644 --- a/app/node/factory.h +++ b/app/node/factory.h @@ -45,6 +45,7 @@ class NodeFactory kTime, kTrigonometry, kBlurFilter, + kShaderFilter, kSolidGenerator, kMerge, kStrokeFilter, @@ -91,8 +92,6 @@ class NodeFactory static QVector hidden_; -private: - static void CreateCustomFilters(); }; } diff --git a/app/node/filter/CMakeLists.txt b/app/node/filter/CMakeLists.txt index 811cafcabf..0f9f5f4fb8 100644 --- a/app/node/filter/CMakeLists.txt +++ b/app/node/filter/CMakeLists.txt @@ -15,7 +15,7 @@ # along with this program. If not, see . add_subdirectory(blur) -add_subdirectory(generic) +add_subdirectory(shader) add_subdirectory(mosaic) add_subdirectory(stroke) diff --git a/app/node/filter/generic/generic.cpp b/app/node/filter/generic/generic.cpp deleted file mode 100644 index 4a1c523406..0000000000 --- a/app/node/filter/generic/generic.cpp +++ /dev/null @@ -1,171 +0,0 @@ -/*** - - Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -***/ - -#include "generic.h" - -#include -#include - -namespace olive { - -const QString GenericFilterNode::kTextureInput = QStringLiteral("tex_in"); - - -GenericFilterNode::GenericFilterNode(const QString & srcFilePath) : - src_file_path_(srcFilePath) -{ - filter_name_ = QFileInfo(srcFilePath).baseName(); - - // Fixed input: an image to work on. More textures can be added as inputs - AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); - - // search source file for parameters to be added to the node panel. - // Exposed parametrs are the ones that follow the convention described in - // function 'searchParametersInSourceFile' - // - searchParametersInSourceFile( srcFilePath); -} - -Node *GenericFilterNode::copy() const -{ - return new GenericFilterNode( src_file_path_); -} - -QString GenericFilterNode::Name() const -{ - return QString("GLSL: %1").arg(filter_name_); -} - -QString GenericFilterNode::id() const -{ - // give an ID that depends on file name, but avoid white spaces - QString tag = filter_name_; - tag.replace( QRegularExpression("\\s"), "_"); - return QStringLiteral("org.olivevideoeditor.Olive.glsl.%1").arg(tag); -} - -QVector GenericFilterNode::Category() const -{ - return {kCategoryFilter}; -} - -QString GenericFilterNode::Description() const -{ - return tr("custom filter."); -} - -void GenericFilterNode::Retranslate() -{ - // Retranslate the only fixed input. - // Other inputs are read from the script file - SetInputName( kTextureInput, tr("Input")); -} - -ShaderCode GenericFilterNode::GetShaderCode(const QString &shader_id) const -{ - Q_UNUSED(shader_id) - - QString source = FileFunctions::ReadFileAsString( src_file_path_); - - return ShaderCode(source); -} - -void GenericFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const -{ - ShaderJob job; - - job.InsertValue(value); - job.InsertValue(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); - - // If there's no texture, no need to run an operation - if (!job.GetValue(kTextureInput).data().isNull()) { - table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this); - - } else { - // If we're not performing the generic job, just push the texture - table->Push(job.GetValue(kTextureInput)); - } -} - -// parse the GLSL file specified by the user and search for inputs to be exposed in node panel. -// Search, within the file, parameters that are exposed as inputs to the according to the following convention: -// - a script variable that starts with the following prefixes, is exposed as an input of type: -// prefix type -//------------------------------- -// inColor NodeValue::kColor -// inTexture NodeValue::kTexture -// inFloat NodeValue::kFloat -// inInt NodeValue::inInt -// inBool NodeValue::inBool -// -// After semicolon, a comment is expected with the name of the input that appares in GUI. -// Such name is required, otherwise the variable will not be exposed -// -void GenericFilterNode::searchParametersInSourceFile( const QString & src_file_path) -{ - static QRegularExpression param_regEx("uniform\\s+([A-Za-z0-9_]+)\\s+(in[A-Za-z0-9_]+)\\s*;\\s*//\\s*(.*)\\s*$"); - QRegularExpressionMatch match; - - QString source = FileFunctions::ReadFileAsString( src_file_path); - - QStringList lines = source.split(QRegularExpression("[\\n\\r]+"), Qt::SkipEmptyParts); - - for ( QString line : lines) { - - match = param_regEx.match( line); - - if (match.isValid()) { - - QString type = match.captured(1); - QString param_name = match.captured(2); - - NodeValue::Type paramType = typeFromName( param_name); - - if (paramType != NodeValue::kNone) { - AddInput( param_name, paramType); - - // use name written in comment after semicolon - SetInputName( param_name, match.captured(3)); - } - } - } -} - -NodeValue::Type GenericFilterNode::typeFromName(const QString ¶mName) -{ - if (paramName.startsWith("inColor")) - return NodeValue::kColor; - - if (paramName.startsWith("inTexture")) - return NodeValue::kTexture; - - if (paramName.startsWith("inFloat")) - return NodeValue::kFloat; - - if (paramName.startsWith("inInt")) - return NodeValue::kInt; - - if (paramName.startsWith("inBool")) - return NodeValue::kBoolean; - - return NodeValue::kNone; -} - -} diff --git a/app/node/filter/generic/CMakeLists.txt b/app/node/filter/shader/CMakeLists.txt similarity index 83% rename from app/node/filter/generic/CMakeLists.txt rename to app/node/filter/shader/CMakeLists.txt index 6f8ce52d6b..58d3078222 100644 --- a/app/node/filter/generic/CMakeLists.txt +++ b/app/node/filter/shader/CMakeLists.txt @@ -16,7 +16,9 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} - node/filter/generic/generic.h - node/filter/generic/generic.cpp + node/filter/shader/shader.h + node/filter/shader/shader.cpp + node/filter/shader/shaderinputsparser.h + node/filter/shader/shaderinputsparser.cpp PARENT_SCOPE ) diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp new file mode 100644 index 0000000000..1e9ecbc017 --- /dev/null +++ b/app/node/filter/shader/shader.cpp @@ -0,0 +1,158 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "shader.h" + +#include +#include + +#include "shaderinputsparser.h" + +namespace olive { + +const QString ShaderFilterNode::kShaderCode = QStringLiteral("source"); + + +ShaderFilterNode::ShaderFilterNode() +{ + // Full code of the shader. Inputs to be exposed are defined within the shader code + // with mark-up comments. + AddInput(kShaderCode, NodeValue::kText, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + + // mark this text input as code, so it will be edited with code editor and not + // with a simple textbox + SetInputProperty( kShaderCode, QStringLiteral("is_shader_code"), true); +} + +Node *ShaderFilterNode::copy() const +{ + ShaderFilterNode * new_node = new ShaderFilterNode(); + new_node->shader_code_ = this->shader_code_; + new_node->input_list_ = this->input_list_; + new_node->onShaderCodeChanged(); + + return new_node; +} + +QString ShaderFilterNode::Name() const +{ + return QString("shader"); +} + +QString ShaderFilterNode::id() const +{ + return QStringLiteral("org.olivevideoeditor.Olive.shader"); +} + +QVector ShaderFilterNode::Category() const +{ + return {kCategoryFilter}; +} + +void olive::ShaderFilterNode::onShaderCodeChanged() +{ + qDebug() << "parsing shader code"; + + // pre-remove all inputs + for (QString oldInput : input_list_) + { + RemoveInput(oldInput); + } + input_list_.clear(); + + parseShaderCode(); +} + +void ShaderFilterNode::InputValueChangedEvent(const QString &input, int element) +{ + Q_UNUSED(element) + + if (input == kShaderCode) + { + // save shader code to return it by 'GetShaderCode' + shader_code_ = GetStandardValue(kShaderCode).value(); + + // the code of the shader has changed. + // Remove all inputs and re-parse the code + // to fix shader name and input parameters. + onShaderCodeChanged(); + } +} + +QString ShaderFilterNode::Description() const +{ + return tr("a filter made by a GLSL shader code"); +} + +void ShaderFilterNode::Retranslate() +{ + // Retranslate the only fixed input. + // Other inputs are read from the shader code + SetInputName( kShaderCode, tr("Shader code")); +} + +ShaderCode ShaderFilterNode::GetShaderCode(const QString &shader_id) const +{ + Q_UNUSED(shader_id) + + return ShaderCode(shader_code_); +} + +void ShaderFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +{ + ShaderJob job; + + job.InsertValue(value); + job.InsertValue(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); + + // If there's no shader code, no need to run an operation + if (shader_code_.trimmed() != QString()) { + table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this); + } +} + +void ShaderFilterNode::parseShaderCode() +{ + ShaderInputsParser parser(shader_code_); + + parser.Parse(); + + // update name, if defined in script + SetLabel( parser.ShaderName()); + + // update input list + const QList< ShaderInputsParser::InputParam> & input_list = parser.InputList(); + QList< ShaderInputsParser::InputParam>::const_iterator it; + + for( it = input_list.begin(); it != input_list.end(); ++it) { + + if (HasInputWithID(it->uniform_name) == false) { + AddInput( it->uniform_name, it->type, it->default_value, it->flags ); + input_list_.append( it->uniform_name); + } + + SetInputName( it->uniform_name, it->readable_name); + SetInputProperty( it->uniform_name, QStringLiteral("min"), it->min); + SetInputProperty( it->uniform_name, QStringLiteral("max"), it->max); + } +} + +} // namespace olive + diff --git a/app/node/filter/generic/generic.h b/app/node/filter/shader/shader.h similarity index 71% rename from app/node/filter/generic/generic.h rename to app/node/filter/shader/shader.h index 88c9aa05cb..db5c661d49 100644 --- a/app/node/filter/generic/generic.h +++ b/app/node/filter/shader/shader.h @@ -18,21 +18,20 @@ ***/ -#ifndef GENERICFILTERNODE_H -#define GENERICFILTERNODE_H +#ifndef ShaderFilterNode_H +#define ShaderFilterNode_H #include "node/node.h" namespace olive { -class GenericFilterNode : public Node +class ShaderFilterNode : public Node { Q_OBJECT public: - // constructor accepts the full path of GLSL frag file - GenericFilterNode( const QString & srcFilePath); + ShaderFilterNode(); - NODE_DEFAULT_DESTRUCTOR(GenericFilterNode) + NODE_DEFAULT_DESTRUCTOR(ShaderFilterNode) virtual Node* copy() const override; @@ -45,17 +44,22 @@ class GenericFilterNode : public Node virtual ShaderCode GetShaderCode(const QString &shader_id) const override; virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + void InputValueChangedEvent(const QString &input, int element) override; - static const QString kTextureInput; + static const QString kShaderCode; private: - void searchParametersInSourceFile(const QString &src_file_path); - NodeValue::Type typeFromName( const QString & paramName); + void parseShaderCode(); + void onShaderCodeChanged(); + +private: + + QString shader_code_; + // user defined inputs + QStringList input_list_; - QString src_file_path_; - QString filter_name_; }; } -#endif // GENERICFILTERNODE_H +#endif // ShaderFilterNode_H diff --git a/app/node/filter/shader/shaderinputsparser.cpp b/app/node/filter/shader/shaderinputsparser.cpp new file mode 100644 index 0000000000..99ad3292e7 --- /dev/null +++ b/app/node/filter/shader/shaderinputsparser.cpp @@ -0,0 +1,317 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "shaderinputsparser.h" + +#include +#include +#include + +namespace olive { + +namespace { + +// This kind of comments are parsed for input parameters +const char OLIVE_MARKED_COMMENT[] = "//OVE"; +// definition of a uniform variable +const char UNIFORM_TAG[] = "uniform"; + +// per Shader metadata + +// title of the shader. This will become part of node name +QRegularExpression SHADER_NAME_REGEX("^\\s*//OVE\\s*shader_name:\\s*(?.*)\\s*$"); +// description. So far not used by application +QRegularExpression SHADER_DESCRIPTION_REGEX("^\\s*//OVE\\s*shader_description:\\s*(?.*)\\s*$"); +// Version string. So far not used by application +QRegularExpression SHADER_VERSION_REGEX("^\\s*//OVE\\s*shader_version:\\s*(?.*)\\s*$"); + +// per Input metadata + +// human name of the input +QRegularExpression INPUT_NAME_REGEX("^\\s*//OVE\\s*name:\\s*(?.*)\\s*$"); +// name of uniform variable +QRegularExpression INPUT_UNIFORM_REGEX("^\\s*uniform\\s+(.*)\\s+(?[A-Za-z0-9_]+)\\s*;"); +// kind of input +QRegularExpression INPUT_TYPE_REGEX("^\\s*//OVE\\s*type:\\s*(?.*)\\s*$"); +// input flags +QRegularExpression INPUT_FLAG_REGEX("^\\s*//OVE\\s*flag:\\s*(?.*)\\s*$"); +// minimum value +QRegularExpression INPUT_MIN_REGEX("^\\s*//OVE\\s*min:\\s*(?.*)\\s*$"); +// maximum value +QRegularExpression INPUT_MAX_REGEX("^\\s*//OVE\\s*max:\\s*(?.*)\\s*$"); +// default value +QRegularExpression INPUT_DEFAULT_REGEX("^\\s*//OVE\\s*default:\\*(?.*)\\s*$"); +// description. So far not used by application +QRegularExpression INPUT_DESCRIPTION_REGEX("^\\s*//OVE\\s*description:\\s*(?.*)\\s*$"); + +// when this is found, parsing is stopped +QRegularExpression STOP_PARSE_REGEX("^\\s*//OVE\\s*end\\s*$"); + +// lookup table for kind of input +const QMap INPUT_TYPE_TABLE{{"TEXTURE", NodeValue::kTexture}, + {"COLOR", NodeValue::kColor}, + {"FLOAT", NodeValue::kFloat}, + {"INTEGER", NodeValue::kInt}, + {"BOOLEAN", NodeValue::kBoolean} + }; + + +// features of the input that's currently being parsed +ShaderInputsParser::InputParam currentInput; + +void clearCurrentInput() +{ + currentInput.readable_name.clear(); + currentInput.uniform_name.clear(); + currentInput.description.clear(); + currentInput.type = NodeValue::kNone; + currentInput.flags = InputFlags(kInputFlagNormal); + currentInput.min = QVariant(); + currentInput.max = QVariant(); + currentInput.default_value = QVariant(); +} + +} // namespace + + +ShaderInputsParser::ShaderInputsParser( const QString & shader_code) : + shader_code_(shader_code) +{ + clearCurrentInput(); + + // build the table that matches an OVE markup comment with its parse function + INPUT_PARAM_PARSE_TABLE.insert( & SHADER_NAME_REGEX, & ShaderInputsParser::parseShaderName); + INPUT_PARAM_PARSE_TABLE.insert( & SHADER_DESCRIPTION_REGEX, & ShaderInputsParser::parseShaderDescription); + INPUT_PARAM_PARSE_TABLE.insert( & SHADER_VERSION_REGEX, & ShaderInputsParser::parseShaderVerion); + + INPUT_PARAM_PARSE_TABLE.insert( & INPUT_NAME_REGEX, & ShaderInputsParser::parseInputName); + INPUT_PARAM_PARSE_TABLE.insert( & INPUT_UNIFORM_REGEX, & ShaderInputsParser::parseInputUniform); + INPUT_PARAM_PARSE_TABLE.insert( & INPUT_TYPE_REGEX, & ShaderInputsParser::parseInputType); + INPUT_PARAM_PARSE_TABLE.insert( & INPUT_FLAG_REGEX, & ShaderInputsParser::parseInputFlags); + INPUT_PARAM_PARSE_TABLE.insert( & INPUT_MIN_REGEX, & ShaderInputsParser::parseInputMin); + INPUT_PARAM_PARSE_TABLE.insert( & INPUT_MAX_REGEX, & ShaderInputsParser::parseInputMax); + INPUT_PARAM_PARSE_TABLE.insert( & INPUT_DEFAULT_REGEX, & ShaderInputsParser::parseInputDefault); + INPUT_PARAM_PARSE_TABLE.insert( & INPUT_DESCRIPTION_REGEX, & ShaderInputsParser::parseInputDescription); + + INPUT_PARAM_PARSE_TABLE.insert( & STOP_PARSE_REGEX, & ShaderInputsParser::parseInputUniform); +} + +void ShaderInputsParser::Parse() +{ + int fragment_size = shader_code_.length(); + int cursor = 0; + QStringRef line; + InputParseState state = PARSING; + + QRegularExpression LINE_BREAK = QRegularExpression("[\\n\\r]+"); + + input_list_.clear(); + + // parse line by line + while (state != SHADER_COMPLETE) { + + int line_end_index = shader_code_.indexOf( LINE_BREAK, cursor); + + if (line_end_index == -1) { + // last line of file + line = QStringRef( & shader_code_, cursor, fragment_size - cursor); + cursor = fragment_size; + } + else { + line = QStringRef( & shader_code_, cursor, line_end_index - cursor); + cursor = line_end_index; + } + + line = line.trimmed(); + + // check if this line must be parsed + if ( (line.startsWith(OLIVE_MARKED_COMMENT)) || + (line.startsWith(UNIFORM_TAG)) ) { + state = parseSingleLine( line); + + if (state == INPUT_COMPLETE) { + input_list_.append( currentInput); + clearCurrentInput(); + } + } + + if (state != SHADER_COMPLETE) { + + // go to the begin of next line. + // Different platforms have different end of line. + while ((cursor < fragment_size) && + (shader_code_.at(cursor).isSpace()) ) { + ++cursor; + } + + // check for end of fragment + if (cursor >= fragment_size) { + state = SHADER_COMPLETE; + } + } + } +} + + +ShaderInputsParser::InputParseState ShaderInputsParser::parseSingleLine( const QStringRef & line) +{ + QRegularExpressionMatch match; + InputParseState new_state = PARSING; + + QMap::iterator it; + + // iterate over all regular expressions up to the first match. + // return as soon as the first match is found + for (it = INPUT_PARAM_PARSE_TABLE.begin(); it != INPUT_PARAM_PARSE_TABLE.end(); ++it) { + + match = it.key()->match( line); + + if (match.hasMatch()) { + LineParseFunction parseFunction = it.value(); + new_state = (this->*parseFunction)( match); + + break; + } + } + + return new_state; +} + +ShaderInputsParser::InputParseState +ShaderInputsParser::parseShaderName(const QRegularExpressionMatch & match) +{ + shader_name_ = match.captured("name"); + return PARSING; +} + +ShaderInputsParser::InputParseState +ShaderInputsParser::parseShaderDescription(const QRegularExpressionMatch & /*match*/) +{ + // nothing to do. SO far. + return PARSING; +} + +ShaderInputsParser::InputParseState +ShaderInputsParser::parseShaderVerion(const QRegularExpressionMatch & /*match*/) +{ + // nothing to do. SO far. + return PARSING; +} + +ShaderInputsParser::InputParseState +ShaderInputsParser::parseInputName(const QRegularExpressionMatch & match) +{ + currentInput.readable_name = match.captured("name"); + return PARSING; +} + +ShaderInputsParser::InputParseState +ShaderInputsParser::parseInputUniform(const QRegularExpressionMatch & match) +{ + ShaderInputsParser::InputParseState state = PARSING; + + // not all uniforms are exposed as inputs + if (currentInput.type != NodeValue::kNone) { + currentInput.uniform_name = match.captured("name"); + state = INPUT_COMPLETE; + } + + return state; +} + +ShaderInputsParser::InputParseState +ShaderInputsParser::parseInputType(const QRegularExpressionMatch & match) +{ + currentInput.type = INPUT_TYPE_TABLE.value( match.captured("type"), NodeValue::kNone); + + if (currentInput.type == NodeValue::kNone) { + // TODO_ report error + qDebug() << "invalid type: " << match.captured("type"); + } + + return PARSING; +} + +ShaderInputsParser::InputParseState +ShaderInputsParser::parseInputFlags(const QRegularExpressionMatch & /*match*/) +{ + // TODO_ + return PARSING; +} + +ShaderInputsParser::InputParseState +ShaderInputsParser::parseInputMin(const QRegularExpressionMatch & match) +{ + bool ok = true; + + if (currentInput.type == NodeValue::kFloat) { + currentInput.min = match.capturedRef("min").toFloat( &ok); + } + else { + currentInput.min = match.capturedRef("min").toInt( &ok); + } + + if (ok == false) { + // TODO_ report error + } + + return PARSING; +} + +ShaderInputsParser::InputParseState +ShaderInputsParser::parseInputMax(const QRegularExpressionMatch & match) +{ + bool ok = true; + + if (currentInput.type == NodeValue::kFloat) { + currentInput.max = match.capturedRef("max").toFloat( &ok); + } + else { + currentInput.max = match.capturedRef("max").toInt( &ok); + } + + if (ok == false) { + // TODO_ report error + } + + return PARSING; +} + +ShaderInputsParser::InputParseState +ShaderInputsParser::parseInputDefault(const QRegularExpressionMatch & /*match*/) +{ + // TODO_ + return PARSING; +} + +ShaderInputsParser::InputParseState +ShaderInputsParser::parseInputDescription(const QRegularExpressionMatch & match) +{ + currentInput.description.append( match.captured("description")); + return PARSING; +} + +ShaderInputsParser::InputParseState +ShaderInputsParser::stopParse(const QRegularExpressionMatch & /*match*/) +{ + return SHADER_COMPLETE; +} + +} // namespace olive diff --git a/app/node/filter/shader/shaderinputsparser.h b/app/node/filter/shader/shaderinputsparser.h new file mode 100644 index 0000000000..e2befe5045 --- /dev/null +++ b/app/node/filter/shader/shaderinputsparser.h @@ -0,0 +1,121 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef SHADERINPUTSPARSER_H +#define SHADERINPUTSPARSER_H + +#include +#include +#include + +#include "node/value.h" +#include "node/param.h" + +namespace olive { + +/** @brief This class parses a GLSL shader code to find 'uniform' and + * marked comments for inputs to be exposed to the GUI. + * The output is a list of inputs with parameters and a list of parsing errors. + */ +class ShaderInputsParser +{ +public: + + struct InputParam + { + // name that appares in GUI + QString readable_name; + // name of uniform in code + QString uniform_name; + // so far, not used + QString description; + + NodeValue::Type type; + InputFlags flags; + + QVariant min; + QVariant max; + QVariant default_value; + }; + + + // The constructor accepts the full code of the shader + ShaderInputsParser( const QString & shader_code); + + ~ShaderInputsParser() { + input_list_.clear(); + } + + // main function that extracts inputs from shader code + void Parse(); + + // valid after call to 'Parse' + const QList< InputParam> & InputList() const { + return input_list_; + } + + // valid after call to 'Parse' + const QString & ShaderName() const { + return shader_name_; + } + + // TODO_ parse error list + +private: + // value returned after parsing one line + enum InputParseState { + // adding parameters for the current input + PARSING = 0, + // a 'uniform' has been found. An input is complete + INPUT_COMPLETE, + // final tag found. Parsing will be stopped + SHADER_COMPLETE + }; + + InputParseState parseSingleLine( const QStringRef & line); + + // pointer to a function that parse a line that matches an //OVE comment + typedef InputParseState (ShaderInputsParser::* LineParseFunction)( const QRegularExpressionMatch & match); + + InputParseState parseShaderName( const QRegularExpressionMatch &); + InputParseState parseShaderDescription( const QRegularExpressionMatch &); + InputParseState parseShaderVerion( const QRegularExpressionMatch &); + InputParseState parseInputName( const QRegularExpressionMatch &); + InputParseState parseInputUniform( const QRegularExpressionMatch &); + InputParseState parseInputType( const QRegularExpressionMatch &); + InputParseState parseInputFlags( const QRegularExpressionMatch &); + InputParseState parseInputMin( const QRegularExpressionMatch &); + InputParseState parseInputMax( const QRegularExpressionMatch &); + InputParseState parseInputDefault( const QRegularExpressionMatch &); + InputParseState parseInputDescription( const QRegularExpressionMatch &); + InputParseState stopParse( const QRegularExpressionMatch &); + + QMap< const QRegularExpression *, LineParseFunction> INPUT_PARAM_PARSE_TABLE; + +private: + const QString & shader_code_; + QList< InputParam> input_list_; + + QString shader_name_; +}; + +} + +#endif // SHADERINPUTSPARSER_H diff --git a/app/widget/nodeparamview/nodeparamviewtextedit.cpp b/app/widget/nodeparamview/nodeparamviewtextedit.cpp index ab4a772951..e4037ca7d0 100644 --- a/app/widget/nodeparamview/nodeparamviewtextedit.cpp +++ b/app/widget/nodeparamview/nodeparamviewtextedit.cpp @@ -26,10 +26,12 @@ #include "dialog/text/text.h" #include "ui/icons/icons.h" +#include namespace olive { NodeParamViewTextEdit::NodeParamViewTextEdit(QWidget *parent) : - QWidget(parent) + QWidget(parent), + code_editor_flag_(false) { QHBoxLayout* layout = new QHBoxLayout(this); layout->setMargin(0); @@ -62,4 +64,14 @@ void NodeParamViewTextEdit::InnerWidgetTextChanged() emit textEdited(this->text()); } +void NodeParamViewTextEdit::setCodeEditoFlag() +{ + code_editor_flag_ = true; + + // if the text box is a shader code editor, make it read only so that + // the shader code is not re-parsed on every key pressed by the user. + // Please use the Text Dialog to edit code. + line_edit_->setEnabled( false); +} + } diff --git a/app/widget/nodeparamview/nodeparamviewtextedit.h b/app/widget/nodeparamview/nodeparamviewtextedit.h index ece9f03a03..caa8434395 100644 --- a/app/widget/nodeparamview/nodeparamviewtextedit.h +++ b/app/widget/nodeparamview/nodeparamviewtextedit.h @@ -39,6 +39,10 @@ class NodeParamViewTextEdit : public QWidget return line_edit_->toPlainText(); } + // if this function is called, the text editor is used + // for shader code + void setCodeEditoFlag(); + public slots: void setText(const QString &s) { @@ -66,6 +70,7 @@ public slots: private: QPlainTextEdit* line_edit_; + bool code_editor_flag_; private slots: void ShowTextDialog(); diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index 05a5cccce9..9db0c9fa17 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -142,6 +142,12 @@ void NodeParamViewWidgetBridge::CreateWidgets() { NodeParamViewTextEdit* line_edit = new NodeParamViewTextEdit(); widgets_.append(line_edit); + + // plain text editor or code editor + if (GetInnerInput().GetProperty(QStringLiteral("is_shader_code")).toBool()) { + line_edit->setCodeEditoFlag(); + } + connect(line_edit, &NodeParamViewTextEdit::textEdited, this, &NodeParamViewWidgetBridge::WidgetCallback); break; } From 2e42c888c123d5ace2db35e2252eb83871db1fad Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Thu, 27 Jan 2022 19:58:58 +0100 Subject: [PATCH 03/49] Work in progress. Parsing of shader node implemented. The node still uses the default editor to edit code and some issues with node inputs must be fixed. --- app/node/filter/shader/shader.cpp | 60 ++++-- app/node/filter/shader/shader.h | 8 + app/node/filter/shader/shaderinputsparser.cpp | 181 ++++++++++++++++-- app/node/filter/shader/shaderinputsparser.h | 17 +- 4 files changed, 228 insertions(+), 38 deletions(-) diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index 1e9ecbc017..fcee5578c9 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -22,6 +22,7 @@ #include #include +#include #include "shaderinputsparser.h" @@ -44,6 +45,9 @@ ShaderFilterNode::ShaderFilterNode() Node *ShaderFilterNode::copy() const { ShaderFilterNode * new_node = new ShaderFilterNode(); + + // the new node must have the same inputs as the original. + // the constructor only creates the "shader code" input new_node->shader_code_ = this->shader_code_; new_node->input_list_ = this->input_list_; new_node->onShaderCodeChanged(); @@ -66,24 +70,11 @@ QVector ShaderFilterNode::Category() const return {kCategoryFilter}; } -void olive::ShaderFilterNode::onShaderCodeChanged() -{ - qDebug() << "parsing shader code"; - - // pre-remove all inputs - for (QString oldInput : input_list_) - { - RemoveInput(oldInput); - } - input_list_.clear(); - - parseShaderCode(); -} - void ShaderFilterNode::InputValueChangedEvent(const QString &input, int element) { Q_UNUSED(element) + if (input == kShaderCode) { // save shader code to return it by 'GetShaderCode' @@ -96,6 +87,23 @@ void ShaderFilterNode::InputValueChangedEvent(const QString &input, int element) } } +void olive::ShaderFilterNode::onShaderCodeChanged() +{ + // pre-remove all inputs ... + for (QString oldInput : input_list_) + { + if (HasInputWithID(oldInput)) { + RemoveInput(oldInput); + } + } + input_list_.clear(); + + // ... and create new inputs + parseShaderCode(); + + qDebug() << "parsed shader code for " << GetLabel(); +} + QString ShaderFilterNode::Description() const { return tr("a filter made by a GLSL shader code"); @@ -137,7 +145,29 @@ void ShaderFilterNode::parseShaderCode() // update name, if defined in script SetLabel( parser.ShaderName()); - // update input list + reportErrorList( parser); + + updateInputList( parser); +} + +void ShaderFilterNode::reportErrorList( const ShaderInputsParser & parser) +{ + const QList & errors = parser.ErrorList(); + QString message; + + for (ShaderInputsParser::Error e : errors ) { + // need to translate? + message.append(QString("

Line %1: %2

").arg(e.line).arg( e.issue)); + } + + // we use a message box because the editor has been closed + if (message != QString()) { + QMessageBox::warning(nullptr, tr("Shader metadata errors"), message); + } +} + +void ShaderFilterNode::updateInputList( const ShaderInputsParser & parser) +{ const QList< ShaderInputsParser::InputParam> & input_list = parser.InputList(); QList< ShaderInputsParser::InputParam>::const_iterator it; diff --git a/app/node/filter/shader/shader.h b/app/node/filter/shader/shader.h index db5c661d49..a498373dd5 100644 --- a/app/node/filter/shader/shader.h +++ b/app/node/filter/shader/shader.h @@ -25,6 +25,12 @@ namespace olive { +class ShaderInputsParser; + +/** @brief + * A node that implements a GLSL script. The inputs of this node + * are defined in GLSL by markup comments + */ class ShaderFilterNode : public Node { Q_OBJECT @@ -51,6 +57,8 @@ class ShaderFilterNode : public Node private: void parseShaderCode(); void onShaderCodeChanged(); + void reportErrorList( const ShaderInputsParser & parser); + void updateInputList( const ShaderInputsParser & parser); private: diff --git a/app/node/filter/shader/shaderinputsparser.cpp b/app/node/filter/shader/shaderinputsparser.cpp index 99ad3292e7..b78802cafd 100644 --- a/app/node/filter/shader/shaderinputsparser.cpp +++ b/app/node/filter/shader/shaderinputsparser.cpp @@ -23,6 +23,7 @@ #include #include #include +#include namespace olive { @@ -57,7 +58,7 @@ QRegularExpression INPUT_MIN_REGEX("^\\s*//OVE\\s*min:\\s*(?.*)\\s*$"); // maximum value QRegularExpression INPUT_MAX_REGEX("^\\s*//OVE\\s*max:\\s*(?.*)\\s*$"); // default value -QRegularExpression INPUT_DEFAULT_REGEX("^\\s*//OVE\\s*default:\\*(?.*)\\s*$"); +QRegularExpression INPUT_DEFAULT_REGEX("^\\s*//OVE\\s*default:\\s*(?.*)\\s*$"); // description. So far not used by application QRegularExpression INPUT_DESCRIPTION_REGEX("^\\s*//OVE\\s*description:\\s*(?.*)\\s*$"); @@ -88,18 +89,24 @@ void clearCurrentInput() currentInput.default_value = QVariant(); } +QString textForType( NodeValue::Type type) +{ + return INPUT_TYPE_TABLE.key( type, "NONE"); +} + } // namespace ShaderInputsParser::ShaderInputsParser( const QString & shader_code) : - shader_code_(shader_code) + shader_code_(shader_code), + line_number_(1) { clearCurrentInput(); // build the table that matches an OVE markup comment with its parse function INPUT_PARAM_PARSE_TABLE.insert( & SHADER_NAME_REGEX, & ShaderInputsParser::parseShaderName); INPUT_PARAM_PARSE_TABLE.insert( & SHADER_DESCRIPTION_REGEX, & ShaderInputsParser::parseShaderDescription); - INPUT_PARAM_PARSE_TABLE.insert( & SHADER_VERSION_REGEX, & ShaderInputsParser::parseShaderVerion); + INPUT_PARAM_PARSE_TABLE.insert( & SHADER_VERSION_REGEX, & ShaderInputsParser::parseShaderVersion); INPUT_PARAM_PARSE_TABLE.insert( & INPUT_NAME_REGEX, & ShaderInputsParser::parseInputName); INPUT_PARAM_PARSE_TABLE.insert( & INPUT_UNIFORM_REGEX, & ShaderInputsParser::parseInputUniform); @@ -120,9 +127,11 @@ void ShaderInputsParser::Parse() QStringRef line; InputParseState state = PARSING; - QRegularExpression LINE_BREAK = QRegularExpression("[\\n\\r]+"); + static const QRegularExpression LINE_BREAK = QRegularExpression("[\\n\\r]+"); input_list_.clear(); + error_list_.clear(); + line_number_ = 1; // parse line by line while (state != SHADER_COMPLETE) { @@ -144,6 +153,7 @@ void ShaderInputsParser::Parse() // check if this line must be parsed if ( (line.startsWith(OLIVE_MARKED_COMMENT)) || (line.startsWith(UNIFORM_TAG)) ) { + state = parseSingleLine( line); if (state == INPUT_COMPLETE) { @@ -158,6 +168,10 @@ void ShaderInputsParser::Parse() // Different platforms have different end of line. while ((cursor < fragment_size) && (shader_code_.at(cursor).isSpace()) ) { + + if (shader_code_.at(cursor) == '\n') { + line_number_++; + } ++cursor; } @@ -177,8 +191,7 @@ ShaderInputsParser::InputParseState ShaderInputsParser::parseSingleLine( const Q QMap::iterator it; - // iterate over all regular expressions up to the first match. - // return as soon as the first match is found + // iterate over all regular expressions for lines for (it = INPUT_PARAM_PARSE_TABLE.begin(); it != INPUT_PARAM_PARSE_TABLE.end(); ++it) { match = it.key()->match( line); @@ -187,6 +200,7 @@ ShaderInputsParser::InputParseState ShaderInputsParser::parseSingleLine( const Q LineParseFunction parseFunction = it.value(); new_state = (this->*parseFunction)( match); + // stop at the first match break; } } @@ -209,7 +223,7 @@ ShaderInputsParser::parseShaderDescription(const QRegularExpressionMatch & /*mat } ShaderInputsParser::InputParseState -ShaderInputsParser::parseShaderVerion(const QRegularExpressionMatch & /*match*/) +ShaderInputsParser::parseShaderVersion(const QRegularExpressionMatch & /*match*/) { // nothing to do. SO far. return PARSING; @@ -242,34 +256,58 @@ ShaderInputsParser::parseInputType(const QRegularExpressionMatch & match) currentInput.type = INPUT_TYPE_TABLE.value( match.captured("type"), NodeValue::kNone); if (currentInput.type == NodeValue::kNone) { - // TODO_ report error - qDebug() << "invalid type: " << match.captured("type"); + reportError(QObject::tr("type %1 is invalid").arg(match.captured("type"))); } return PARSING; } ShaderInputsParser::InputParseState -ShaderInputsParser::parseInputFlags(const QRegularExpressionMatch & /*match*/) +ShaderInputsParser::parseInputFlags(const QRegularExpressionMatch & line_match) { - // TODO_ + // more flags can be present in one line + static const QRegularExpression FLAG_REGEX("(ARRAY|NOT_CONNECTABLE|NOT_KEYFRAMABLE|HIDDEN)"); + + static const QMap FLAG_TABLE{{"ARRAY", kInputFlagArray}, + {"NOT_CONNECTABLE", kInputFlagNotConnectable}, + {"NOT_KEYFRAMABLE", kInputFlagNotKeyframable}, + {"HIDDEN", kInputFlagHidden}, + }; + + + QStringRef flags_line = line_match.capturedRef("flags"); + QRegularExpressionMatchIterator flag_match = FLAG_REGEX.globalMatch( flags_line); + + while (flag_match.hasNext()) { + QRegularExpressionMatch flag = flag_match.next(); + QString flag_str = flag.captured(1); + + currentInput.flags |= InputFlags(FLAG_TABLE.value( flag_str, kInputFlagNormal)); + } + + return PARSING; } ShaderInputsParser::InputParseState ShaderInputsParser::parseInputMin(const QRegularExpressionMatch & match) { - bool ok = true; + bool ok = false; + QStringRef min_str = match.capturedRef("min"); if (currentInput.type == NodeValue::kFloat) { - currentInput.min = match.capturedRef("min").toFloat( &ok); + currentInput.min = min_str.toFloat( &ok); + } + else if (currentInput.type == NodeValue::kInt) { + currentInput.min = min_str.toInt( &ok); } else { - currentInput.min = match.capturedRef("min").toInt( &ok); + // this type does not support "min" property } if (ok == false) { - // TODO_ report error + reportError(QObject::tr("%1 is not valid as minimum for type %2"). + arg(min_str).arg(textForType(currentInput.type))); } return PARSING; @@ -278,26 +316,87 @@ ShaderInputsParser::parseInputMin(const QRegularExpressionMatch & match) ShaderInputsParser::InputParseState ShaderInputsParser::parseInputMax(const QRegularExpressionMatch & match) { - bool ok = true; + bool ok = false; + QStringRef max_str = match.capturedRef("max"); if (currentInput.type == NodeValue::kFloat) { - currentInput.max = match.capturedRef("max").toFloat( &ok); + currentInput.max = max_str.toFloat( &ok); + } + else if (currentInput.type == NodeValue::kInt) { + currentInput.max = max_str.toInt( &ok); } else { - currentInput.max = match.capturedRef("max").toInt( &ok); + // this type does not support "max" property } if (ok == false) { - // TODO_ report error + reportError(QObject::tr("%1 is not valid as maximum for type %2"). + arg(max_str).arg(textForType(currentInput.type))); } return PARSING; } ShaderInputsParser::InputParseState -ShaderInputsParser::parseInputDefault(const QRegularExpressionMatch & /*match*/) +ShaderInputsParser::parseInputDefault(const QRegularExpressionMatch & match) { - // TODO_ + QStringRef default_string = match.capturedRef("default").trimmed(); + bool ok = false; + float float_value; + int int_value; + + switch (currentInput.type) + { + case NodeValue::kFloat: + float_value = default_string.toFloat( &ok); + + if (ok) + { + currentInput.default_value = QVariant::fromValue(float_value); + } + else + { + reportError(QObject::tr("%1 is not a valid float"). + arg(default_string)); + } + break; + + case NodeValue::kInt: + int_value = default_string.toInt( &ok); + + if (ok) + { + currentInput.default_value = QVariant::fromValue(int_value); + } + else + { + reportError(QObject::tr("%1 is not a valid integer").arg(default_string)); + } + break; + + case NodeValue::kBoolean: + if (default_string == "false") { + currentInput.default_value = QVariant::fromValue(false); + } + else if (default_string == "true") { + currentInput.default_value = QVariant::fromValue(true); + } + else { + reportError(QObject::tr("value must be 'true' or 'false'")); + } + break; + + case NodeValue::kColor: + currentInput.default_value = parseColor( default_string); + break; + + default: + case NodeValue::kTexture: + case NodeValue::kNone: + reportError(QObject::tr("type %1 does not support a default value").arg(textForType(currentInput.type))); + break; + } + return PARSING; } @@ -314,4 +413,44 @@ ShaderInputsParser::stopParse(const QRegularExpressionMatch & /*match*/) return SHADER_COMPLETE; } +// Assume 'line' has format RGBA( r,g,b,a), where 'r,g,b,a' are in range 0 .. 255 +// and stand for 'red', 'green', 'blue', 'alpha' +QVariant ShaderInputsParser::parseColor(const QStringRef& line) +{ + static const QRegularExpression + COLOR_REGEX("RGBA\\(\\s*(?\\d+)\\s*,\\s*(?\\d+)\\s*,\\s*(?\\d+)\\s*,\\s*(?\\d+)\\s*\\)"); + + QVariant color; + QRegularExpressionMatch match = COLOR_REGEX.match(line); + + if (match.hasMatch()) { + bool ok_r, ok_g, ok_b, ok_a; + int r,g,b,a; + + r = match.captured("r").toInt( & ok_r); + g = match.captured("g").toInt( & ok_g); + b = match.captured("b").toInt( & ok_b); + a = match.captured("a").toInt( & ok_a); + + if (ok_r && ok_g && ok_b && ok_a) { + color = QVariant::fromValue( QColor(r,g,b,a)); + } + else { + reportError(QObject::tr("Color must be in format 'RGBA(r,g,b,a)' " + "where r,g,b,a are in range 0-255 ")); + } + } + + return color; +} + +void ShaderInputsParser::reportError(const QString& error) +{ + Error new_err; + new_err.line = line_number_; + new_err.issue = error; + + error_list_.append( new_err); +} + } // namespace olive diff --git a/app/node/filter/shader/shaderinputsparser.h b/app/node/filter/shader/shaderinputsparser.h index e2befe5045..64930b09a7 100644 --- a/app/node/filter/shader/shaderinputsparser.h +++ b/app/node/filter/shader/shaderinputsparser.h @@ -55,6 +55,11 @@ class ShaderInputsParser QVariant default_value; }; + struct Error { + int line; + QString issue; + }; + // The constructor accepts the full code of the shader ShaderInputsParser( const QString & shader_code); @@ -76,7 +81,10 @@ class ShaderInputsParser return shader_name_; } - // TODO_ parse error list + // list of issue found in parsing inputs + const QList & ErrorList() const { + return error_list_; + } private: // value returned after parsing one line @@ -96,7 +104,7 @@ class ShaderInputsParser InputParseState parseShaderName( const QRegularExpressionMatch &); InputParseState parseShaderDescription( const QRegularExpressionMatch &); - InputParseState parseShaderVerion( const QRegularExpressionMatch &); + InputParseState parseShaderVersion( const QRegularExpressionMatch &); InputParseState parseInputName( const QRegularExpressionMatch &); InputParseState parseInputUniform( const QRegularExpressionMatch &); InputParseState parseInputType( const QRegularExpressionMatch &); @@ -109,9 +117,14 @@ class ShaderInputsParser QMap< const QRegularExpression *, LineParseFunction> INPUT_PARAM_PARSE_TABLE; + QVariant parseColor( const QStringRef & line); + void reportError( const QString & error); + private: const QString & shader_code_; QList< InputParam> input_list_; + QList error_list_; + int line_number_; QString shader_name_; }; From 6050436b34397217959dbc15fd060fc1e2648a14 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Sun, 30 Jan 2022 11:53:36 +0100 Subject: [PATCH 04/49] Work in progress: added synch for added/removed inputs with Node parameter view. Keyframes are working but they are not shown in their pane until the project is saved and reloded --- app/node/filter/shader/shader.cpp | 34 +++++++++------- app/node/filter/shader/shader.h | 2 +- app/node/filter/shader/shaderinputsparser.cpp | 40 ++++++++++++------- app/node/filter/shader/shaderinputsparser.h | 2 +- app/node/node.cpp | 1 + app/node/node.h | 4 ++ .../nodeparamview/nodeparamviewitem.cpp | 29 +++++++++++--- app/widget/nodeparamview/nodeparamviewitem.h | 5 +++ 8 files changed, 81 insertions(+), 36 deletions(-) diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index fcee5578c9..fcfd936912 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -46,18 +46,15 @@ Node *ShaderFilterNode::copy() const { ShaderFilterNode * new_node = new ShaderFilterNode(); - // the new node must have the same inputs as the original. - // the constructor only creates the "shader code" input - new_node->shader_code_ = this->shader_code_; - new_node->input_list_ = this->input_list_; - new_node->onShaderCodeChanged(); + // copy all inputs not created in constructor + CopyInputs( this, new_node, false); return new_node; } QString ShaderFilterNode::Name() const { - return QString("shader"); + return QString("Shader"); } QString ShaderFilterNode::id() const @@ -90,13 +87,13 @@ void ShaderFilterNode::InputValueChangedEvent(const QString &input, int element) void olive::ShaderFilterNode::onShaderCodeChanged() { // pre-remove all inputs ... - for (QString oldInput : input_list_) + for (QString oldInput : user_input_list_) { if (HasInputWithID(oldInput)) { RemoveInput(oldInput); } } - input_list_.clear(); + user_input_list_.clear(); // ... and create new inputs parseShaderCode(); @@ -142,12 +139,12 @@ void ShaderFilterNode::parseShaderCode() parser.Parse(); - // update name, if defined in script - SetLabel( parser.ShaderName()); - reportErrorList( parser); - updateInputList( parser); + + // update name, if defined in script; otherwise use a default. + QString label = (parser.ShaderName().isEmpty()) ? "unnamed" : parser.ShaderName(); + SetLabel( label); } void ShaderFilterNode::reportErrorList( const ShaderInputsParser & parser) @@ -155,6 +152,12 @@ void ShaderFilterNode::reportErrorList( const ShaderInputsParser & parser) const QList & errors = parser.ErrorList(); QString message; + if (errors.length() > 0) { + message.append(tr("

Shader %1 has metadata errors. " + "These are not related to the shader code, just to metadata

"). + arg("parser.ShaderName()")); + } + for (ShaderInputsParser::Error e : errors ) { // need to translate? message.append(QString("

Line %1: %2

").arg(e.line).arg( e.issue)); @@ -174,14 +177,17 @@ void ShaderFilterNode::updateInputList( const ShaderInputsParser & parser) for( it = input_list.begin(); it != input_list.end(); ++it) { if (HasInputWithID(it->uniform_name) == false) { + qDebug() << "adding " << it->human_name; AddInput( it->uniform_name, it->type, it->default_value, it->flags ); - input_list_.append( it->uniform_name); + user_input_list_.append( it->uniform_name); } - SetInputName( it->uniform_name, it->readable_name); + SetInputName( it->uniform_name, it->human_name); SetInputProperty( it->uniform_name, QStringLiteral("min"), it->min); SetInputProperty( it->uniform_name, QStringLiteral("max"), it->max); } + + emit InputListChanged(); } } // namespace olive diff --git a/app/node/filter/shader/shader.h b/app/node/filter/shader/shader.h index a498373dd5..95571c0c87 100644 --- a/app/node/filter/shader/shader.h +++ b/app/node/filter/shader/shader.h @@ -64,7 +64,7 @@ class ShaderFilterNode : public Node QString shader_code_; // user defined inputs - QStringList input_list_; + QStringList user_input_list_; }; diff --git a/app/node/filter/shader/shaderinputsparser.cpp b/app/node/filter/shader/shaderinputsparser.cpp index b78802cafd..46480d2a81 100644 --- a/app/node/filter/shader/shaderinputsparser.cpp +++ b/app/node/filter/shader/shaderinputsparser.cpp @@ -23,7 +23,8 @@ #include #include #include -#include + +#include "render/color.h" namespace olive { @@ -79,7 +80,7 @@ ShaderInputsParser::InputParam currentInput; void clearCurrentInput() { - currentInput.readable_name.clear(); + currentInput.human_name.clear(); currentInput.uniform_name.clear(); currentInput.description.clear(); currentInput.type = NodeValue::kNone; @@ -232,7 +233,7 @@ ShaderInputsParser::parseShaderVersion(const QRegularExpressionMatch & /*match*/ ShaderInputsParser::InputParseState ShaderInputsParser::parseInputName(const QRegularExpressionMatch & match) { - currentInput.readable_name = match.captured("name"); + currentInput.human_name = match.captured("name"); return PARSING; } @@ -413,34 +414,43 @@ ShaderInputsParser::stopParse(const QRegularExpressionMatch & /*match*/) return SHADER_COMPLETE; } -// Assume 'line' has format RGBA( r,g,b,a), where 'r,g,b,a' are in range 0 .. 255 +// Assume 'line' has format RGBA( r,g,b,a), where 'r,g,b,a' are in range 0.0 .. 1.0 // and stand for 'red', 'green', 'blue', 'alpha' QVariant ShaderInputsParser::parseColor(const QStringRef& line) { static const QRegularExpression - COLOR_REGEX("RGBA\\(\\s*(?\\d+)\\s*,\\s*(?\\d+)\\s*,\\s*(?\\d+)\\s*,\\s*(?
\\d+)\\s*\\)"); + COLOR_REGEX("RGBA\\(\\s*(?[\\d\\.]+)\\s*,\\s*(?[\\d\\.]+)\\s*,\\s*(?[\\d\\.]+)\\s*,\\s*(?[\\d\\.]+)\\s*\\)"); QVariant color; QRegularExpressionMatch match = COLOR_REGEX.match(line); + bool valid = false; if (match.hasMatch()) { bool ok_r, ok_g, ok_b, ok_a; - int r,g,b,a; + float r,g,b,a; + + r = match.captured("r").toFloat( & ok_r); + g = match.captured("g").toFloat( & ok_g); + b = match.captured("b").toFloat( & ok_b); + a = match.captured("a").toFloat( & ok_a); - r = match.captured("r").toInt( & ok_r); - g = match.captured("g").toInt( & ok_g); - b = match.captured("b").toInt( & ok_b); - a = match.captured("a").toInt( & ok_a); + // check ranges + ok_r &= ((r>=0.0f) && (r <= 1.0f)); + ok_g &= ((g>=0.0f) && (g <= 1.0f)); + ok_b &= ((b>=0.0f) && (b <= 1.0f)); + ok_a &= ((a>=0.0f) && (a <= 1.0f)); if (ok_r && ok_g && ok_b && ok_a) { - color = QVariant::fromValue( QColor(r,g,b,a)); - } - else { - reportError(QObject::tr("Color must be in format 'RGBA(r,g,b,a)' " - "where r,g,b,a are in range 0-255 ")); + color = QVariant::fromValue( Color(r,g,b,a)); + valid = true; } } + if (valid == false) { + reportError(QObject::tr("Color must be in format 'RGBA(r,g,b,a)' " + "where r,g,b,a are in range (0,1)")); + } + return color; } diff --git a/app/node/filter/shader/shaderinputsparser.h b/app/node/filter/shader/shaderinputsparser.h index 64930b09a7..3bda867837 100644 --- a/app/node/filter/shader/shaderinputsparser.h +++ b/app/node/filter/shader/shaderinputsparser.h @@ -41,7 +41,7 @@ class ShaderInputsParser struct InputParam { // name that appares in GUI - QString readable_name; + QString human_name; // name of uniform in code QString uniform_name; // so far, not used diff --git a/app/node/node.cpp b/app/node/node.cpp index 57bf193b50..4aa347c8d5 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -1230,6 +1230,7 @@ void Node::SetInputName(const QString &id, const QString &name) Input* i = GetInternalInputData(id); if (i) { + qDebug() << "setting name " << name; i->human_name = name; emit InputNameChanged(id, name); diff --git a/app/node/node.h b/app/node/node.h index cd168d84f8..83da755dcc 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -1094,6 +1094,10 @@ class Node : public QObject void NodeRemovedFromContext(Node *node); + // emitted after one or more inputs have been added or removed. + // Only applicable for nodes that change input list dynamically. + void InputListChanged(); + private: class ArrayInsertCommand : public UndoCommand { diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index e1bfe51b3c..b60046ed17 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -59,6 +59,9 @@ NodeParamViewItem::NodeParamViewItem(Node *node, NodeParamViewCheckBoxBehavior c connect(node_, &Node::LabelChanged, this, &NodeParamViewItem::Retranslate); + // for dynamically changed inputs + connect(node_, &Node::InputListChanged, this, &NodeParamViewItem::OnInputListChanged); + setBackgroundRole(QPalette::Window); Retranslate(); @@ -73,6 +76,22 @@ void NodeParamViewItem::Retranslate() body_->Retranslate(); } +void NodeParamViewItem::OnInputListChanged() +{ + // delete old widgets and create new ones, based on current inputs + + disconnect(body_, &NodeParamViewItemBody::RequestSelectNode, this, &NodeParamViewItem::RequestSelectNode); + disconnect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime); + disconnect(body_, &NodeParamViewItemBody::ArrayExpandedChanged, this, &NodeParamViewItem::ArrayExpandedChanged); + disconnect(body_, &NodeParamViewItemBody::InputCheckedChanged, this, &NodeParamViewItem::InputCheckedChanged); + + // delete body_; // TODO_ memory leak? + body_ = new NodeParamViewItemBody(node_, kNoCheckBoxes); // TODO_ NodeParamViewCheckBoxBehavior create_checkboxes + + SetBody(body_); + body_->Retranslate(); +} + int NodeParamViewItem::GetElementY(const NodeInput &c) const { if (IsExpanded()) { @@ -91,10 +110,9 @@ void NodeParamViewItem::SetInputChecked(const NodeInput &input, bool e) NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget *parent) : QWidget(parent), node_(node), - create_checkboxes_(create_checkboxes) + create_checkboxes_(create_checkboxes), + root_layout_(new QGridLayout(this)) { - QGridLayout* root_layout = new QGridLayout(this); - int insert_row = 0; QVector connected_signals; @@ -115,7 +133,7 @@ NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBe input_group_lookup_.insert({resolved.node(), resolved.input()}, {n, input}); if (!(n->GetInputFlags(input) & kInputFlagHidden)) { - CreateWidgets(root_layout, n, input, -1, insert_row); + CreateWidgets(root_layout_, n, input, -1, insert_row); insert_row++; @@ -126,7 +144,7 @@ NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBe QGridLayout* array_layout = new QGridLayout(array_widget); array_layout->setContentsMargins(QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral(" ")), 0, 0, 0); - root_layout->addWidget(array_widget, insert_row, 1, 1, 10); + root_layout_->addWidget(array_widget, insert_row, 1, 1, 10); // Start with zero elements for efficiency. We will make the widgets for them if the user // requests the array UI to be expanded @@ -516,6 +534,7 @@ void NodeParamViewItemBody::OptionalCheckBoxClicked(bool e) } } + NodeParamViewItemBody::InputUI::InputUI() : main_label(nullptr), widget_bridge(nullptr), diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index 93044ad865..20df0fbcef 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -118,6 +118,8 @@ class NodeParamViewItemBody : public QWidget { QHash input_group_lookup_; + QGridLayout *root_layout_; + /** * @brief The column to place the keyframe controls in * @@ -212,6 +214,9 @@ class NodeParamViewItem : public NodeParamViewItemBase protected slots: virtual void Retranslate() override; +private slots: + void OnInputListChanged(); + private: NodeParamViewItemBody* body_; From 7d7f3cd96a142cd128cd30ef9fc0067ff3c31c9e Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Mon, 31 Jan 2022 20:01:25 +0100 Subject: [PATCH 05/49] Work in progress. Keyframe support added for dynamically created inputs. --- app/node/filter/shader/shader.cpp | 1 - app/node/node.cpp | 1 - app/widget/nodeparamview/nodeparamview.cpp | 3 +++ app/widget/nodeparamview/nodeparamviewitem.cpp | 11 ++++++++++- app/widget/nodeparamview/nodeparamviewitem.h | 6 +++++- 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index fcfd936912..36ec758711 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -177,7 +177,6 @@ void ShaderFilterNode::updateInputList( const ShaderInputsParser & parser) for( it = input_list.begin(); it != input_list.end(); ++it) { if (HasInputWithID(it->uniform_name) == false) { - qDebug() << "adding " << it->human_name; AddInput( it->uniform_name, it->type, it->default_value, it->flags ); user_input_list_.append( it->uniform_name); } diff --git a/app/node/node.cpp b/app/node/node.cpp index 4aa347c8d5..57bf193b50 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -1230,7 +1230,6 @@ void Node::SetInputName(const QString &id, const QString &name) Input* i = GetInternalInputData(id); if (i) { - qDebug() << "setting name " << name; i->human_name = name; emit InputNameChanged(id, name); diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 48339d4a34..94675f9ebf 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -425,6 +425,9 @@ void NodeParamView::AddNode(Node *n, NodeParamViewContext *context) connect(item, &NodeParamViewItem::Moved, this, &NodeParamView::QueueKeyframePositionUpdate); item->SetKeyframeConnections(keyframe_view_->AddKeyframesOfNode(n)); + + // needed to update keyframe connections dynamically + item->SetKeyframeView( keyframe_view_); } } diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index b60046ed17..4b8db2587b 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -45,7 +45,8 @@ const int NodeParamViewItemBody::kWidgetStartColumn = 3; NodeParamViewItem::NodeParamViewItem(Node *node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget *parent) : super(parent), - node_(node) + node_(node), + keyframe_view_(nullptr) { node_->Retranslate(); @@ -88,8 +89,16 @@ void NodeParamViewItem::OnInputListChanged() // delete body_; // TODO_ memory leak? body_ = new NodeParamViewItemBody(node_, kNoCheckBoxes); // TODO_ NodeParamViewCheckBoxBehavior create_checkboxes + connect(body_, &NodeParamViewItemBody::RequestSelectNode, this, &NodeParamViewItem::RequestSelectNode); + connect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime); + connect(body_, &NodeParamViewItemBody::ArrayExpandedChanged, this, &NodeParamViewItem::ArrayExpandedChanged); + connect(body_, &NodeParamViewItemBody::InputCheckedChanged, this, &NodeParamViewItem::InputCheckedChanged); + SetBody(body_); body_->Retranslate(); + + Q_ASSERT( keyframe_view_ != nullptr); + keyframe_connections_ = keyframe_view_->AddKeyframesOfNode(node_); } int NodeParamViewItem::GetElementY(const NodeInput &c) const diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index 20df0fbcef..88b8aa94ae 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -202,6 +202,10 @@ class NodeParamViewItem : public NodeParamViewItemBase keyframe_connections_ = c; } + void SetKeyframeView( KeyframeView * kfv) { + keyframe_view_ = kfv; + } + signals: void RequestSetTime(const rational& time); @@ -225,7 +229,7 @@ private slots: rational time_; KeyframeView::NodeConnections keyframe_connections_; - + KeyframeView * keyframe_view_; }; } From f5fa5d12b31ad4ef6b7fbe0c0ada8086658ba120 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Tue, 1 Feb 2022 23:17:17 +0100 Subject: [PATCH 06/49] Work in progress. Added a shader text editor. An issue with keyframes of removed inputs has been fixed --- app/dialog/CMakeLists.txt | 1 + app/dialog/codeeditor/CMakeLists.txt | 26 +++ app/dialog/codeeditor/codeeditordialog.cpp | 111 +++++++++++ app/dialog/codeeditor/codeeditordialog.h | 53 ++++++ app/dialog/codeeditor/editor.cpp | 178 ++++++++++++++++++ app/dialog/codeeditor/editor.h | 82 ++++++++ app/dialog/codeeditor/glslhighlighter.cpp | 176 +++++++++++++++++ app/dialog/codeeditor/glslhighlighter.h | 94 +++++++++ app/node/filter/shader/shader.cpp | 21 ++- app/node/filter/shader/shader.h | 3 +- app/node/filter/shader/shaderinputsparser.cpp | 13 ++ .../nodeparamview/nodeparamviewitem.cpp | 29 ++- app/widget/nodeparamview/nodeparamviewitem.h | 2 + .../nodeparamview/nodeparamviewtextedit.cpp | 25 ++- 14 files changed, 805 insertions(+), 9 deletions(-) create mode 100644 app/dialog/codeeditor/CMakeLists.txt create mode 100644 app/dialog/codeeditor/codeeditordialog.cpp create mode 100644 app/dialog/codeeditor/codeeditordialog.h create mode 100644 app/dialog/codeeditor/editor.cpp create mode 100644 app/dialog/codeeditor/editor.h create mode 100644 app/dialog/codeeditor/glslhighlighter.cpp create mode 100644 app/dialog/codeeditor/glslhighlighter.h diff --git a/app/dialog/CMakeLists.txt b/app/dialog/CMakeLists.txt index a7ecf0f78e..93114f38f5 100644 --- a/app/dialog/CMakeLists.txt +++ b/app/dialog/CMakeLists.txt @@ -17,6 +17,7 @@ add_subdirectory(about) add_subdirectory(actionsearch) add_subdirectory(autorecovery) +add_subdirectory(codeeditor) add_subdirectory(color) add_subdirectory(configbase) add_subdirectory(diskcache) diff --git a/app/dialog/codeeditor/CMakeLists.txt b/app/dialog/codeeditor/CMakeLists.txt new file mode 100644 index 0000000000..8e6ec63412 --- /dev/null +++ b/app/dialog/codeeditor/CMakeLists.txt @@ -0,0 +1,26 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2021 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + dialog/codeeditor/editor.h + dialog/codeeditor/editor.cpp + dialog/codeeditor/glslhighlighter.h + dialog/codeeditor/glslhighlighter.cpp + dialog/codeeditor/codeeditordialog.h + dialog/codeeditor/codeeditordialog.cpp + PARENT_SCOPE +) diff --git a/app/dialog/codeeditor/codeeditordialog.cpp b/app/dialog/codeeditor/codeeditordialog.cpp new file mode 100644 index 0000000000..58b599feaf --- /dev/null +++ b/app/dialog/codeeditor/codeeditordialog.cpp @@ -0,0 +1,111 @@ +#include "codeeditordialog.h" + +#include +#include +#include +#include +#include + +namespace olive { + +CodeEditorDialog::CodeEditorDialog(const QString &start, QWidget* parent) : + QDialog(parent) +{ + QVBoxLayout* layout = new QVBoxLayout(this); + + // Create text edit widget + text_edit_ = new CodeEditor( this); + text_edit_->document()->setPlainText(start); + layout->addWidget(text_edit_); + + // Create buttons + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + layout->addWidget(buttons); + connect(buttons, &QDialogButtonBox::accepted, this, &CodeEditorDialog::accept); + connect(buttons, &QDialogButtonBox::rejected, this, &CodeEditorDialog::reject); + + // this does not work on all platforms + setWindowFlag( Qt::WindowMaximizeButtonHint, true); + + QMenuBar * menu_bar = new QMenuBar( text_edit_); + QMenu * edit_menu = new QMenu(tr("Edit"), text_edit_); + QMenu * edit_addSnippet_menu = new QMenu(tr("add input"), text_edit_); + + edit_menu->addMenu( edit_addSnippet_menu); + menu_bar->addMenu( edit_menu); + + QAction * add_texture_input = edit_addSnippet_menu->addAction(tr("texture")); + QAction * add_color_input = edit_addSnippet_menu->addAction(tr("color")); + QAction * add_float_input = edit_addSnippet_menu->addAction(tr("float")); + QAction * add_int_input = edit_addSnippet_menu->addAction(tr("integer")); + QAction * add_boolean_input = edit_addSnippet_menu->addAction(tr("boolean")); + + connect( add_texture_input, & QAction::triggered, this, & CodeEditorDialog::OnActionAddInputTexture); + connect( add_color_input, & QAction::triggered, this, & CodeEditorDialog::OnActionAddInputColor); + connect( add_float_input, & QAction::triggered, this, & CodeEditorDialog::OnActionAddInputFloat); + connect( add_int_input, & QAction::triggered, this, & CodeEditorDialog::OnActionAddInputInt); + connect( add_boolean_input, & QAction::triggered, this, & CodeEditorDialog::OnActionAddInputBoolean); + + layout->setMenuBar( menu_bar); +} + +void CodeEditorDialog::OnActionAddInputTexture() +{ + text_edit_->insertPlainText( + "//OVE name: my input\n" + "//OVE type: TEXTURE\n" + "//OVE flag: NOT_KEYFRAMABLE\n" + "//OVE description: \n" + "uniform sampler2D my_input;\n"); +} + +void CodeEditorDialog::OnActionAddInputColor() +{ + text_edit_->insertPlainText( + "//OVE name: my color\n" + "//OVE type: COLOR\n" + "//OVE default: RGBA(0.5, 0.5, 1, 1)\n" + "//OVE description: \n" + "uniform vec4 my_color;\n"); +} + +void CodeEditorDialog::OnActionAddInputFloat() +{ + text_edit_->insertPlainText( + "//OVE name: my float\n" + "//OVE type: FLOAT\n" + "//OVE flag: NOT_CONNECTABLE\n" + "//OVE min: 0.0\n" + "//OVE max: 3.0\n" + "//OVE default: 1.0\n" + "//OVE description: \n" + "uniform float my_float;\n"); +} + +void CodeEditorDialog::OnActionAddInputInt() +{ + text_edit_->insertPlainText( + "//OVE name: my int\n" + "//OVE type: INTEGER\n" + "//OVE flag: NOT_CONNECTABLE\n" + "//OVE min: -10\n" + "//OVE max: 10\n" + "//OVE default: 0\n" + "//OVE description:\n" + "uniform float my_int;\n"); +} + +void CodeEditorDialog::OnActionAddInputBoolean() +{ + text_edit_->insertPlainText( + "//OVE name: my boolean\n" + "//OVE type: BOOLEAN\n" + "//OVE flag: NOT_CONNECTABLE, NOT_KEYFRAMABLE\n" + "//OVE default: false\n" + "//OVE description: when True, the input is passed to output as is.\n" + "uniform bool disable;\n"); +} + + +} // namespace olive + diff --git a/app/dialog/codeeditor/codeeditordialog.h b/app/dialog/codeeditor/codeeditordialog.h new file mode 100644 index 0000000000..0443f28ece --- /dev/null +++ b/app/dialog/codeeditor/codeeditordialog.h @@ -0,0 +1,53 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ +#ifndef CODEEDITORDIALOG_H +#define CODEEDITORDIALOG_H + +#include +#include "dialog/codeeditor/editor.h" + +namespace olive { + +/// @brief Wrapper QDialog window for the code editor +class CodeEditorDialog : public QDialog +{ + Q_OBJECT +public: + CodeEditorDialog(const QString &start, QWidget* parent = nullptr); + + QString text() const + { + return text_edit_->toPlainText(); + } + +private: + CodeEditor* text_edit_; + +private slots: + void OnActionAddInputTexture(); + void OnActionAddInputColor(); + void OnActionAddInputFloat(); + void OnActionAddInputInt(); + void OnActionAddInputBoolean(); +}; + +} // namespace olive + +#endif // CODEEDITORDIALOG_H diff --git a/app/dialog/codeeditor/editor.cpp b/app/dialog/codeeditor/editor.cpp new file mode 100644 index 0000000000..845441c4ae --- /dev/null +++ b/app/dialog/codeeditor/editor.cpp @@ -0,0 +1,178 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "editor.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "glslhighlighter.h" + + +namespace olive { + +CodeEditor::CodeEditor(QWidget *parent) : + QPlainTextEdit(parent) +{ + m_lineNumberArea = new LineNumberArea(this); + + connect( this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int))); + connect( this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int))); + connect( this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine())); + + updateLineNumberAreaWidth(0); + highlightCurrentLine(); + + new GlslHighlighter( document()); + + setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded); + setLineWrapMode( QPlainTextEdit::NoWrap); + + setContextMenuPolicy( Qt::DefaultContextMenu); + + // These hard coded settings should be customizable in settings dialog + setMinimumSize( 900, 600); + + QFont font("Monospace"); + font.setStyleHint(QFont::TypeWriter); // use monospaced font for all platform + font.setPointSize( 14); + setFont( font); + + setStyleSheet("QPlainTextEdit { background-color: black }"); +} + +int CodeEditor::lineNumberAreaWidth() +{ + int digits = 1; + int max = qMax(1, blockCount()); + while (max >= 10) + { + max /= 10; + ++digits; + } + + int space = 3 + fontMetrics().horizontalAdvance(QLatin1Char('9')) * digits; + + return space; +} + +void CodeEditor::gotoLineNumber(int lineNumber) +{ + /* block number start from 0 where 'lineNumber' start from 1 */ + QTextCursor cursor(document()->findBlockByLineNumber(lineNumber - 1)); + setTextCursor(cursor); + highlightCurrentLine(); +} + +void CodeEditor::updateLineNumberAreaWidth(int /* newBlockCount */) +{ + setViewportMargins(lineNumberAreaWidth(), 0, 0, 0); +} + + +void CodeEditor::updateLineNumberArea(const QRect &rect, int dy) +{ + if (dy) + { + m_lineNumberArea->scroll(0, dy); + } + else + { + m_lineNumberArea->update(0, rect.y(), m_lineNumberArea->width(), rect.height()); + } + + if (rect.contains(viewport()->rect())) + { + updateLineNumberAreaWidth(0); + } +} + + +void CodeEditor::resizeEvent(QResizeEvent *e) +{ + QPlainTextEdit::resizeEvent(e); + + QRect cr = contentsRect(); + m_lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height())); +} + + +void CodeEditor::highlightCurrentLine() +{ + QList extraSelections; + + QTextEdit::ExtraSelection selection; + + QColor lineColor = QColor(40,40,0); + + selection.format.setBackground( lineColor); + selection.format.setProperty( QTextFormat::FullWidthSelection, true); + selection.cursor = textCursor(); + selection.cursor.clearSelection(); + extraSelections.append(selection); + + setExtraSelections(extraSelections); +} + + +void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent *event) +{ + QPainter painter(m_lineNumberArea); + painter.fillRect(event->rect(), Qt::lightGray); + + + QTextBlock block = firstVisibleBlock(); + int blockNumber = block.blockNumber(); + int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top(); + int bottom = top + (int) blockBoundingRect(block).height(); + + while (block.isValid() && top <= event->rect().bottom()) + { + if (block.isVisible() && bottom >= event->rect().top()) + { + QString number = QString::number(blockNumber + 1); + painter.setPen(Qt::black); + painter.drawText(0, top, m_lineNumberArea->width(), fontMetrics().height(), + Qt::AlignRight, number); + } + + block = block.next(); + top = bottom; + bottom = top + (int) blockBoundingRect(block).height(); + ++blockNumber; + } +} + +QSize LineNumberArea::sizeHint() const +{ + return QSize(code_editor_->lineNumberAreaWidth(), 0); +} + +void LineNumberArea::paintEvent(QPaintEvent *event) +{ + code_editor_->lineNumberAreaPaintEvent(event); +} + +} // namespace olive diff --git a/app/dialog/codeeditor/editor.h b/app/dialog/codeeditor/editor.h new file mode 100644 index 0000000000..5981d486eb --- /dev/null +++ b/app/dialog/codeeditor/editor.h @@ -0,0 +1,82 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef CODEEDITOR_H +#define CODEEDITOR_H + +#include + +namespace olive { + +class LineNumberArea; + +/// @brief A text editor for GLSL shaders +class CodeEditor : public QPlainTextEdit +{ + Q_OBJECT + +public: + CodeEditor( QWidget *parent = nullptr); + + void lineNumberAreaPaintEvent(QPaintEvent *event); + int lineNumberAreaWidth(); + + void setEditMode( bool isEditMode); + + void gotoLineNumber(int lineNumber); + +signals: + void lineDoubleClicked( int blockNumber); + +protected: + void resizeEvent(QResizeEvent *event) override; + +private slots: + void updateLineNumberAreaWidth(int newBlockCount); + void highlightCurrentLine(); + void updateLineNumberArea(const QRect &, int); + + +private: + QWidget * m_lineNumberArea; +}; + +/// @brief helper widget to print line numbers +class LineNumberArea : public QWidget +{ +public: + LineNumberArea(CodeEditor *editor) : QWidget(editor) { + code_editor_ = editor; + } + + QSize sizeHint() const override; + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + CodeEditor *code_editor_; +}; + + +} // namespace olive + + +#endif // CODEEDITOR_H diff --git a/app/dialog/codeeditor/glslhighlighter.cpp b/app/dialog/codeeditor/glslhighlighter.cpp new file mode 100644 index 0000000000..9397a06f0d --- /dev/null +++ b/app/dialog/codeeditor/glslhighlighter.cpp @@ -0,0 +1,176 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "glslhighlighter.h" + +namespace olive { + +namespace { + +const QString keywordPatterns[] = { + QStringLiteral("\\bclass\\b"), QStringLiteral("\\bconst\\b"), QStringLiteral("\\benum\\b"), + QStringLiteral("\\bbreak\\b"), QStringLiteral("\\bshort\\b"), QStringLiteral("\\bif\\b"), + QStringLiteral("\\bfor\\b"), QStringLiteral("\\bwhile\\b"), QStringLiteral("\\bstruct\\b"), + QStringLiteral("\\belse\\b"), QStringLiteral("\\breturn\\b"), QStringLiteral("\\btypedef\\b"), + QStringLiteral("\\bvolatile\\b"), QStringLiteral("\\bgl_FragColor\\b"), QStringLiteral("\\bswitch\\b"), + QStringLiteral("\\btrue\\b"), QStringLiteral("\\bfalse\\b"), QStringLiteral("\\bstatic\\b"), + QStringLiteral("\\bdefault\\b"), QStringLiteral("\\b#define\\b") +}; + +const QString typePatterns[] = { + QStringLiteral("\\bchar\\b"), + QStringLiteral("\\bdouble\\b"), QStringLiteral("\\bfloat\\b"), QStringLiteral("\\blong\\b"), + QStringLiteral("\\bshort\\b"),QStringLiteral("\\bsigned\\b"), QStringLiteral("\\bunsigned\\b"), + QStringLiteral("\\bunion\\b"), QStringLiteral("\\bvoid\\b"),QStringLiteral("\\bbool\\b"), + QStringLiteral("\\buniform\\b"), QStringLiteral("\\bvarying\\b"), QStringLiteral("\\bvec\\b"), + QStringLiteral("\\bvec2\\b"), QStringLiteral("\\bvec3\\b"), QStringLiteral("\\bvec4\\b"), + QStringLiteral("\\bint\\b"), QStringLiteral("\\bsampler2D\\b") +}; + +const QString oliveMarkupPatterns[] = { + QStringLiteral("//OVE\\s+shader_name:[^\n]*"), QStringLiteral("//OVE\\s+shader_description:[^\n]*"), + QStringLiteral("//OVE\\s+shader_version:[^\n]*"), QStringLiteral("//OVE\\s+name:[^\n]*"), + QStringLiteral("//OVE\\s+type:[^\n]*"), QStringLiteral("//OVE\\s+flag:[^\n]*"), + QStringLiteral("//OVE\\s+description:[^\n]*"), QStringLiteral("//OVE\\s+default:[^\n]*"), + QStringLiteral("//OVE\\s+min:[^\n]*"), QStringLiteral("//OVE\\s+max:[^\n]*"), + QStringLiteral("//OVE\\s+end\\b[^\n]*") +}; + +} + +GlslHighlighter::GlslHighlighter(QTextDocument *parent) + : QSyntaxHighlighter(parent) +{ + HighlightingRule rule; + + keyword_format_.setForeground(QColor(110,80,110)); + keyword_format_.setFontWeight(QFont::Bold); + + for (const QString &pattern : keywordPatterns) { + rule.pattern = QRegularExpression(pattern); + rule.format = keyword_format_; + highlighting_rules_.append(rule); + } + + type_format_.setForeground(QColor(70,110,160)); + + for (const QString &pattern : typePatterns) { + rule.pattern = QRegularExpression(pattern); + rule.format = type_format_; + highlighting_rules_.append(rule); + } + + number_format_.setForeground(QColor(120,110,60)); + rule.format = type_format_; + // decimal or float + rule.pattern = QRegularExpression("\\b\\d+\\.?(\\d+)?\\b"); + highlighting_rules_.append(rule); + // hex + rule.pattern = QRegularExpression("\\b0[xX][0-9A-Fa-f]+\\b"); + highlighting_rules_.append(rule); + + single_line_comment_format_.setForeground(QColor(60,180,80)); + rule.pattern = QRegularExpression(QStringLiteral("//[^\n]*")); + rule.format = single_line_comment_format_; + highlighting_rules_.append(rule); + + markup_line_comment_format_.setForeground(QColor(70,120,130)); + for (const QString &pattern : oliveMarkupPatterns) { + rule.pattern = QRegularExpression(pattern); + rule.format = markup_line_comment_format_; + highlighting_rules_.append(rule); + } + + multiline_comment_format_.setForeground(QColor(60,180,80)); + + quotation_format_.setForeground(QColor(130,130,80)); + rule.pattern = QRegularExpression(QStringLiteral("\".*\"")); + rule.format = quotation_format_; + highlighting_rules_.append(rule); + + comment_start_expression_ = QRegularExpression(QStringLiteral("/\\*")); + comment_end_expression_ = QRegularExpression(QStringLiteral("\\*/")); +} + + +void GlslHighlighter::highlightBlock(const QString &text) +{ + for (const HighlightingRule &rule : qAsConst(highlighting_rules_)) { + QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text); + while (matchIterator.hasNext()) { + QRegularExpressionMatch match = matchIterator.next(); + setFormat(match.capturedStart(), match.capturedLength(), rule.format); + } + } + + setCurrentBlockState(0); + + int startIndex = 0; + if (previousBlockState() != 1) + startIndex = text.indexOf(comment_start_expression_); + + while (startIndex >= 0) { + QRegularExpressionMatch match = comment_end_expression_.match(text, startIndex); + int endIndex = match.capturedStart(); + int commentLength = 0; + if (endIndex == -1) { + setCurrentBlockState(1); + commentLength = text.length() - startIndex; + } else { + commentLength = endIndex - startIndex + + match.capturedLength(); + } + setFormat(startIndex, commentLength, multiline_comment_format_); + startIndex = text.indexOf(comment_start_expression_, startIndex + commentLength); + } +} + +} // namespace olive + diff --git a/app/dialog/codeeditor/glslhighlighter.h b/app/dialog/codeeditor/glslhighlighter.h new file mode 100644 index 0000000000..dc2b96e88e --- /dev/null +++ b/app/dialog/codeeditor/glslhighlighter.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef GLSLHIGHLIGHTER_H +#define GLSLHIGHLIGHTER_H + +#include +#include +#include + +class QTextDocument; + +namespace olive { + +class GlslHighlighter : public QSyntaxHighlighter +{ + Q_OBJECT + +public: + GlslHighlighter(QTextDocument *parent = 0); + +protected: + void highlightBlock(const QString &text) override; + +private: + struct HighlightingRule + { + QRegularExpression pattern; + QTextCharFormat format; + }; + QVector highlighting_rules_; + + QRegularExpression comment_start_expression_; + QRegularExpression comment_end_expression_; + + QTextCharFormat keyword_format_; + QTextCharFormat type_format_; + QTextCharFormat single_line_comment_format_; + QTextCharFormat markup_line_comment_format_; + QTextCharFormat multiline_comment_format_; + QTextCharFormat quotation_format_; + QTextCharFormat number_format_; +}; + +} // namespace olive + +#endif // GLSLHIGHLIGHTER_H diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index 36ec758711..50a71a086a 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -174,11 +174,13 @@ void ShaderFilterNode::updateInputList( const ShaderInputsParser & parser) const QList< ShaderInputsParser::InputParam> & input_list = parser.InputList(); QList< ShaderInputsParser::InputParam>::const_iterator it; + QStringList new_input_list; + for( it = input_list.begin(); it != input_list.end(); ++it) { if (HasInputWithID(it->uniform_name) == false) { AddInput( it->uniform_name, it->type, it->default_value, it->flags ); - user_input_list_.append( it->uniform_name); + new_input_list.append( it->uniform_name); } SetInputName( it->uniform_name, it->human_name); @@ -186,8 +188,25 @@ void ShaderFilterNode::updateInputList( const ShaderInputsParser & parser) SetInputProperty( it->uniform_name, QStringLiteral("max"), it->max); } + // compare 'new_input_list' and 'user_input_list_' to find deleted inputs. + checkDeletedInputs( new_input_list); + + // update inputs + user_input_list_.clear(); + user_input_list_ = new_input_list; + emit InputListChanged(); } +void ShaderFilterNode::checkDeletedInputs(const QStringList & new_inputs) +{ + // search old inputs that are not present in new inputs + for( const QString & input : user_input_list_) { + if (new_inputs.contains(input) == false) { + InputRemoved( input); + } + } +} + } // namespace olive diff --git a/app/node/filter/shader/shader.h b/app/node/filter/shader/shader.h index 95571c0c87..e1c339f7bb 100644 --- a/app/node/filter/shader/shader.h +++ b/app/node/filter/shader/shader.h @@ -30,7 +30,7 @@ class ShaderInputsParser; /** @brief * A node that implements a GLSL script. The inputs of this node * are defined in GLSL by markup comments - */ + */ class ShaderFilterNode : public Node { Q_OBJECT @@ -59,6 +59,7 @@ class ShaderFilterNode : public Node void onShaderCodeChanged(); void reportErrorList( const ShaderInputsParser & parser); void updateInputList( const ShaderInputsParser & parser); + void checkDeletedInputs( const QStringList & new_inputs); private: diff --git a/app/node/filter/shader/shaderinputsparser.cpp b/app/node/filter/shader/shaderinputsparser.cpp index 46480d2a81..6a56bf99d5 100644 --- a/app/node/filter/shader/shaderinputsparser.cpp +++ b/app/node/filter/shader/shaderinputsparser.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "render/color.h" @@ -74,6 +75,15 @@ const QMap INPUT_TYPE_TABLE{{"TEXTURE", NodeValue::kTe {"BOOLEAN", NodeValue::kBoolean} }; +// table with default minimum and maximum values for a node type. +// This is needed to avoid that 0 is used as minimum and maximum if user does not specify one +const QMap MINIMUM_TABLE{{NodeValue::kFloat, QVariant( std::numeric_limits::min())}, + {NodeValue::kInt, QVariant( std::numeric_limits::min())} + }; + +const QMap MAXIMUM_TABLE{{NodeValue::kFloat, QVariant( std::numeric_limits::max())}, + {NodeValue::kInt, QVariant()} + }; // features of the input that's currently being parsed ShaderInputsParser::InputParam currentInput; @@ -260,6 +270,9 @@ ShaderInputsParser::parseInputType(const QRegularExpressionMatch & match) reportError(QObject::tr("type %1 is invalid").arg(match.captured("type"))); } + currentInput.min = MINIMUM_TABLE.value( currentInput.type, QVariant()); + currentInput.max = MAXIMUM_TABLE.value( currentInput.type, QVariant()); + return PARSING; } diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 4b8db2587b..43489b6197 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -46,6 +46,7 @@ const int NodeParamViewItemBody::kWidgetStartColumn = 3; NodeParamViewItem::NodeParamViewItem(Node *node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget *parent) : super(parent), node_(node), + create_checkboxes_(create_checkboxes), keyframe_view_(nullptr) { node_->Retranslate(); @@ -62,6 +63,7 @@ NodeParamViewItem::NodeParamViewItem(Node *node, NodeParamViewCheckBoxBehavior c // for dynamically changed inputs connect(node_, &Node::InputListChanged, this, &NodeParamViewItem::OnInputListChanged); + connect(node_, &Node::InputRemoved, this, &NodeParamViewItem::OnInputRemoved); setBackgroundRole(QPalette::Window); @@ -86,8 +88,11 @@ void NodeParamViewItem::OnInputListChanged() disconnect(body_, &NodeParamViewItemBody::ArrayExpandedChanged, this, &NodeParamViewItem::ArrayExpandedChanged); disconnect(body_, &NodeParamViewItemBody::InputCheckedChanged, this, &NodeParamViewItem::InputCheckedChanged); - // delete body_; // TODO_ memory leak? - body_ = new NodeParamViewItemBody(node_, kNoCheckBoxes); // TODO_ NodeParamViewCheckBoxBehavior create_checkboxes + // TODO: Before creating a new 'NodeParamViewItemBody', we should delete the old one. + // However this causes a segmentation fault. Does QT handle this? Is this a memory leak? + + // delete body_; + body_ = new NodeParamViewItemBody(node_, create_checkboxes_); connect(body_, &NodeParamViewItemBody::RequestSelectNode, this, &NodeParamViewItem::RequestSelectNode); connect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime); @@ -98,9 +103,29 @@ void NodeParamViewItem::OnInputListChanged() body_->Retranslate(); Q_ASSERT( keyframe_view_ != nullptr); + keyframe_connections_ = keyframe_view_->AddKeyframesOfNode(node_); } +void NodeParamViewItem::OnInputRemoved( const QString & id) +{ + // remove keyframes of the input, if any. + // It is required to find all keyframe view input connections of the input. + KeyframeView::InputConnections con = keyframe_connections_.value( id); + + QVectorIterator elem_it( con); + + while (elem_it.hasNext()) { + KeyframeView::ElementConnections elem_con = elem_it.next(); + + QVectorIterator keyframe_it(elem_con); + + while (keyframe_it.hasNext()) { + keyframe_view_->RemoveKeyframesOfTrack( keyframe_it.next()); + } + } +} + int NodeParamViewItem::GetElementY(const NodeInput &c) const { if (IsExpanded()) { diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index 88b8aa94ae..5e31374323 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -220,6 +220,7 @@ protected slots: private slots: void OnInputListChanged(); + void OnInputRemoved(const QString &id); private: NodeParamViewItemBody* body_; @@ -228,6 +229,7 @@ private slots: rational time_; + NodeParamViewCheckBoxBehavior create_checkboxes_; KeyframeView::NodeConnections keyframe_connections_; KeyframeView * keyframe_view_; }; diff --git a/app/widget/nodeparamview/nodeparamviewtextedit.cpp b/app/widget/nodeparamview/nodeparamviewtextedit.cpp index e4037ca7d0..d2f3fc1bfa 100644 --- a/app/widget/nodeparamview/nodeparamviewtextedit.cpp +++ b/app/widget/nodeparamview/nodeparamviewtextedit.cpp @@ -24,6 +24,7 @@ #include #include "dialog/text/text.h" +#include "dialog/codeeditor/codeeditordialog.h" #include "ui/icons/icons.h" #include @@ -50,12 +51,26 @@ NodeParamViewTextEdit::NodeParamViewTextEdit(QWidget *parent) : void NodeParamViewTextEdit::ShowTextDialog() { - TextDialog d(this->text(), this); - if (d.exec() == QDialog::Accepted) { - QString s = d.text(); + QString text; - line_edit_->setPlainText(s); - emit textEdited(s); + if (code_editor_flag_) { + CodeEditorDialog d(this->text(), this); + + if (d.exec() == QDialog::Accepted) { + text = d.text(); + } + } + else { + TextDialog d(this->text(), this); + + if (d.exec() == QDialog::Accepted) { + text= d.text(); + } + } + + if (text != QString()) { + line_edit_->setPlainText( text); + emit textEdited( text); } } From 8df0e1b8b7144ccac11707608e6bc3f2daf0dae6 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Fri, 4 Feb 2022 23:40:49 +0100 Subject: [PATCH 07/49] Work inprogress. Fixed an issue with method 'InputValueChangedEvent' called more than once per instance. --- app/node/filter/shader/shader.cpp | 25 +++++++++++-------- app/node/filter/shader/shaderinputsparser.cpp | 18 ++++++------- app/node/filter/shader/shaderinputsparser.h | 2 ++ 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index 50a71a086a..190a1d23af 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -72,15 +72,20 @@ void ShaderFilterNode::InputValueChangedEvent(const QString &input, int element) Q_UNUSED(element) - if (input == kShaderCode) - { - // save shader code to return it by 'GetShaderCode' - shader_code_ = GetStandardValue(kShaderCode).value(); + if (input == kShaderCode) { + // for some reason, this function is called more than once for each input + // of each instance. Parse code only if it has changed + QString new_code = GetStandardValue(kShaderCode).value(); + + if (shader_code_ != new_code) { - // the code of the shader has changed. - // Remove all inputs and re-parse the code - // to fix shader name and input parameters. - onShaderCodeChanged(); + shader_code_ = new_code; + + // the code of the shader has changed. + // Remove all inputs and re-parse the code + // to fix shader name and input parameters. + onShaderCodeChanged(); + } } } @@ -98,7 +103,7 @@ void olive::ShaderFilterNode::onShaderCodeChanged() // ... and create new inputs parseShaderCode(); - qDebug() << "parsed shader code for " << GetLabel(); + qDebug() << "parsed shader code for " << GetLabel() << " @ " << (uint64_t)this; } QString ShaderFilterNode::Description() const @@ -155,7 +160,7 @@ void ShaderFilterNode::reportErrorList( const ShaderInputsParser & parser) if (errors.length() > 0) { message.append(tr("

Shader %1 has metadata errors. " "These are not related to the shader code, just to metadata

"). - arg("parser.ShaderName()")); + arg(parser.ShaderName())); } for (ShaderInputsParser::Error e : errors ) { diff --git a/app/node/filter/shader/shaderinputsparser.cpp b/app/node/filter/shader/shaderinputsparser.cpp index 6a56bf99d5..a7d84eb928 100644 --- a/app/node/filter/shader/shaderinputsparser.cpp +++ b/app/node/filter/shader/shaderinputsparser.cpp @@ -93,6 +93,7 @@ void clearCurrentInput() currentInput.human_name.clear(); currentInput.uniform_name.clear(); currentInput.description.clear(); + currentInput.type_string.clear(); currentInput.type = NodeValue::kNone; currentInput.flags = InputFlags(kInputFlagNormal); currentInput.min = QVariant(); @@ -100,11 +101,6 @@ void clearCurrentInput() currentInput.default_value = QVariant(); } -QString textForType( NodeValue::Type type) -{ - return INPUT_TYPE_TABLE.key( type, "NONE"); -} - } // namespace @@ -264,12 +260,14 @@ ShaderInputsParser::parseInputUniform(const QRegularExpressionMatch & match) ShaderInputsParser::InputParseState ShaderInputsParser::parseInputType(const QRegularExpressionMatch & match) { - currentInput.type = INPUT_TYPE_TABLE.value( match.captured("type"), NodeValue::kNone); + currentInput.type_string = match.captured("type"); + currentInput.type = INPUT_TYPE_TABLE.value( currentInput.type_string, NodeValue::kNone); if (currentInput.type == NodeValue::kNone) { - reportError(QObject::tr("type %1 is invalid").arg(match.captured("type"))); + reportError(QObject::tr("type %1 is invalid").arg(currentInput.type_string)); } + // preset 'min' and 'max', in case they are not defined currentInput.min = MINIMUM_TABLE.value( currentInput.type, QVariant()); currentInput.max = MAXIMUM_TABLE.value( currentInput.type, QVariant()); @@ -321,7 +319,7 @@ ShaderInputsParser::parseInputMin(const QRegularExpressionMatch & match) if (ok == false) { reportError(QObject::tr("%1 is not valid as minimum for type %2"). - arg(min_str).arg(textForType(currentInput.type))); + arg(min_str).arg(currentInput.type_string)); } return PARSING; @@ -345,7 +343,7 @@ ShaderInputsParser::parseInputMax(const QRegularExpressionMatch & match) if (ok == false) { reportError(QObject::tr("%1 is not valid as maximum for type %2"). - arg(max_str).arg(textForType(currentInput.type))); + arg(max_str).arg(currentInput.type_string)); } return PARSING; @@ -407,7 +405,7 @@ ShaderInputsParser::parseInputDefault(const QRegularExpressionMatch & match) default: case NodeValue::kTexture: case NodeValue::kNone: - reportError(QObject::tr("type %1 does not support a default value").arg(textForType(currentInput.type))); + reportError(QObject::tr("type %1 does not support a default value").arg(currentInput.type_string)); break; } diff --git a/app/node/filter/shader/shaderinputsparser.h b/app/node/filter/shader/shaderinputsparser.h index 3bda867837..c72401f188 100644 --- a/app/node/filter/shader/shaderinputsparser.h +++ b/app/node/filter/shader/shaderinputsparser.h @@ -46,6 +46,8 @@ class ShaderInputsParser QString uniform_name; // so far, not used QString description; + // string for input type + QString type_string; NodeValue::Type type; InputFlags flags; From 75827d85961615d61fa86ecc6ac9dea6fa8d5233 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Sat, 5 Feb 2022 14:31:01 +0100 Subject: [PATCH 08/49] Work in progress. Added option page for editors. Options for internal editor implemented --- app/config/config.cpp | 7 ++ app/dialog/codeeditor/editor.cpp | 71 ++++++++++- app/dialog/codeeditor/editor.h | 4 + app/dialog/preferences/preferences.cpp | 2 + app/dialog/preferences/tabs/CMakeLists.txt | 2 + .../preferences/tabs/preferencesedittab.cpp | 111 ++++++++++++++++++ .../preferences/tabs/preferencesedittab.h | 64 ++++++++++ 7 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 app/dialog/preferences/tabs/preferencesedittab.cpp create mode 100644 app/dialog/preferences/tabs/preferencesedittab.h diff --git a/app/config/config.cpp b/app/config/config.cpp index 2f88ea1b98..6002f36079 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -135,6 +135,13 @@ void Config::SetDefaults() // Online/offline settings SetEntryInternal(QStringLiteral("OnlinePixelFormat"), NodeValue::kInt, VideoParams::kFormatFloat32); SetEntryInternal(QStringLiteral("OfflinePixelFormat"), NodeValue::kInt, VideoParams::kFormatFloat16); + + // Code editor + SetEntryInternal(QStringLiteral("EditorUseInternal"), NodeValue::kBoolean, true); + + SetEntryInternal(QStringLiteral("EditorExternalCommand"), NodeValue::kText, QString()); + SetEntryInternal(QStringLiteral("EditorInternalFontSize"), NodeValue::kInt, 14); + SetEntryInternal(QStringLiteral("EditorInternalIndentSize"), NodeValue::kInt, 3); } void Config::Load() diff --git a/app/dialog/codeeditor/editor.cpp b/app/dialog/codeeditor/editor.cpp index 845441c4ae..725a1e73c0 100644 --- a/app/dialog/codeeditor/editor.cpp +++ b/app/dialog/codeeditor/editor.cpp @@ -19,6 +19,7 @@ ***/ #include "editor.h" +#include "config/config.h" #include #include @@ -57,8 +58,17 @@ CodeEditor::CodeEditor(QWidget *parent) : QFont font("Monospace"); font.setStyleHint(QFont::TypeWriter); // use monospaced font for all platform - font.setPointSize( 14); setFont( font); + QFontMetrics metrics(font); + + bool valid; + int fontSize = Config::Current()["EditorInternalFontSize"].toInt( & valid); + fontSize = valid ? fontSize : 14; + font.setPointSize( fontSize); + + indent_size_ = Config::Current()["EditorInternalIndentSize"].toInt( & valid); + indent_size_ = (valid && (indent_size_ > 0)) ? indent_size_ : 3; + setTabStopDistance( (qreal)(indent_size_) * metrics.horizontalAdvance(' ')); setStyleSheet("QPlainTextEdit { background-color: black }"); } @@ -175,4 +185,63 @@ void LineNumberArea::paintEvent(QPaintEvent *event) code_editor_->lineNumberAreaPaintEvent(event); } +void CodeEditor::keyPressEvent(QKeyEvent *event) +{ + // replace tab with spaces + if (event->key() == Qt::Key_Tab) { + int currentColumn = textCursor().positionInBlock(); + + // always add at least one space + do { + insertPlainText(" "); + currentColumn++; + } while( (currentColumn % indent_size_) != 0); + } + else if ((event->key() == Qt::Key_Return) || (event->key() == Qt::Key_Enter)) { + // we have not changed line yet. Count blanks of current line + int n_space = countTrailingSpaces( textCursor().block().position()); + + // append new line + QPlainTextEdit::keyPressEvent( event); + + // indent the new line with the same white spaces as previous line + insertPlainText( QString(' ').repeated(n_space)); + } + else { + QPlainTextEdit::keyPressEvent( event); + } +} + +// count the number of whitespaces at the begin of current line. +// In case of TAB, a number between 1 and 'indent_size_' is added. +// The returned value is always in spaces, not in tabs. +int CodeEditor::countTrailingSpaces(int block_position) +{ + int n_spaces = 0; + QString text = toPlainText(); + bool done = false; + + Q_ASSERT( indent_size_ > 0); + + while( done == false) { + switch (text.at(block_position).toLatin1()) { + case ' ': + n_spaces++; + break; + case '\t': + n_spaces =((n_spaces / indent_size_) * indent_size_) + indent_size_; + break; + default: + done = true; + } + + block_position++; + } + + return n_spaces; +} + + + } // namespace olive + diff --git a/app/dialog/codeeditor/editor.h b/app/dialog/codeeditor/editor.h index 5981d486eb..9f3c55ab08 100644 --- a/app/dialog/codeeditor/editor.h +++ b/app/dialog/codeeditor/editor.h @@ -47,15 +47,19 @@ class CodeEditor : public QPlainTextEdit protected: void resizeEvent(QResizeEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; private slots: void updateLineNumberAreaWidth(int newBlockCount); void highlightCurrentLine(); void updateLineNumberArea(const QRect &, int); +private: + int countTrailingSpaces( int block_position); private: QWidget * m_lineNumberArea; + int indent_size_; }; /// @brief helper widget to print line numbers diff --git a/app/dialog/preferences/preferences.cpp b/app/dialog/preferences/preferences.cpp index 3eee5336d7..b54dfd315c 100644 --- a/app/dialog/preferences/preferences.cpp +++ b/app/dialog/preferences/preferences.cpp @@ -32,6 +32,7 @@ #include "tabs/preferencesdisktab.h" #include "tabs/preferencesaudiotab.h" #include "tabs/preferenceskeyboardtab.h" +#include "tabs/preferencesedittab.h" namespace olive { @@ -46,6 +47,7 @@ PreferencesDialog::PreferencesDialog(QWidget *parent, QMenuBar* main_menu_bar) : AddTab(new PreferencesDiskTab(), tr("Disk")); AddTab(new PreferencesAudioTab(), tr("Audio")); AddTab(new PreferencesKeyboardTab(main_menu_bar), tr("Keyboard")); + AddTab(new PreferencesEditTab(), tr("Text Editor")); } } diff --git a/app/dialog/preferences/tabs/CMakeLists.txt b/app/dialog/preferences/tabs/CMakeLists.txt index 2ccb6938b2..396d353304 100644 --- a/app/dialog/preferences/tabs/CMakeLists.txt +++ b/app/dialog/preferences/tabs/CMakeLists.txt @@ -28,5 +28,7 @@ set(OLIVE_SOURCES dialog/preferences/tabs/preferencesaudiotab.cpp dialog/preferences/tabs/preferenceskeyboardtab.h dialog/preferences/tabs/preferenceskeyboardtab.cpp + dialog/preferences/tabs/preferencesedittab.h + dialog/preferences/tabs/preferencesedittab.cpp PARENT_SCOPE ) diff --git a/app/dialog/preferences/tabs/preferencesedittab.cpp b/app/dialog/preferences/tabs/preferencesedittab.cpp new file mode 100644 index 0000000000..2442bb7cbd --- /dev/null +++ b/app/dialog/preferences/tabs/preferencesedittab.cpp @@ -0,0 +1,111 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "preferencesedittab.h" + +#include +#include +#include +#include +#include +#include + + +#include "common/filefunctions.h" + +namespace olive { + +PreferencesEditTab::PreferencesEditTab() +{ + QVBoxLayout* outer_layout = new QVBoxLayout(this); + + QGroupBox* selection_group = new QGroupBox(tr("Select editor")); + outer_layout->addWidget(selection_group); + + QVBoxLayout* editor_slect_layout = new QVBoxLayout(selection_group); + use_internal_editor_ = new QRadioButton(tr("Use internal code editor")); + use_external_editor_ = new QRadioButton(tr("Use an external code editor")); + + editor_slect_layout->addWidget(use_internal_editor_); + editor_slect_layout->addWidget(use_external_editor_); + + internal_editor_box_ = new QGroupBox(tr("Internal editor")); + external_editor_box_ = new QGroupBox(tr("External editor")); + outer_layout->addWidget(internal_editor_box_); + outer_layout->addWidget(external_editor_box_); + + connect( use_internal_editor_, & QRadioButton::clicked, + internal_editor_box_, & QGroupBox::setEnabled); + connect( use_internal_editor_, & QRadioButton::clicked, + external_editor_box_, & QGroupBox::setDisabled); + connect( use_external_editor_, & QRadioButton::clicked, + external_editor_box_, & QGroupBox::setEnabled); + connect( use_external_editor_, & QRadioButton::clicked, + internal_editor_box_, & QGroupBox::setDisabled); + + //internal editor + QGridLayout * internal_editor_layout = new QGridLayout(internal_editor_box_); + internal_editor_layout->addWidget( new QLabel(tr("Font size")), 1, 1); + font_size_ = new QSpinBox(); + internal_editor_layout->addWidget( font_size_, 1, 2); + internal_editor_layout->addWidget( new QLabel(tr("Indent size")), 2, 1); + indent_size_ = new QSpinBox(); + internal_editor_layout->addWidget( indent_size_, 2, 2); + internal_editor_layout->addItem( new QSpacerItem(1,1, QSizePolicy::Expanding), 1, 3); + internal_editor_layout->addItem( new QSpacerItem(1,1, QSizePolicy::Expanding), 2, 3); + outer_layout->addWidget(internal_editor_box_); + + //external editor + QVBoxLayout * external_editor_layout = new QVBoxLayout(external_editor_box_); + external_editor_layout->addWidget( + new QLabel(tr("

Command to launch external editor.

" + "

Use %FILE for text file path and %LINE for line number.

"))); + ext_command_ = new QLineEdit(); + external_editor_layout->addWidget( ext_command_); + outer_layout->addWidget(external_editor_box_); + + outer_layout->addStretch(); + + bool use_internal = Config::Current()["EditorUseInternal"].toBool(); + use_internal_editor_->setChecked( use_internal); + use_external_editor_->setChecked( ! use_internal); + internal_editor_box_->setEnabled( use_internal); + external_editor_box_->setEnabled( ! use_internal); + font_size_->setValue( Config::Current()["EditorInternalFontSize"].toInt()); + indent_size_->setValue( Config::Current()["EditorInternalIndentSize"].toInt()); + ext_command_->setText( Config::Current()["EditorExternalCommand"].toString()); +} + +bool PreferencesEditTab::Validate() +{ + return true; +} + +void PreferencesEditTab::Accept(MultiUndoCommand *command) +{ + Q_UNUSED(command) + + Config::Current()["EditorUseInternal"] = QVariant::fromValue( use_internal_editor_->isChecked()); + Config::Current()["EditorExternalCommand"] = QVariant::fromValue(ext_command_->text()); + Config::Current()["EditorInternalFontSize"] = QVariant::fromValue(font_size_->value()); + Config::Current()["EditorInternalIndentSize"] = QVariant::fromValue(indent_size_->value()); +} + +} diff --git a/app/dialog/preferences/tabs/preferencesedittab.h b/app/dialog/preferences/tabs/preferencesedittab.h new file mode 100644 index 0000000000..9cd00189ce --- /dev/null +++ b/app/dialog/preferences/tabs/preferencesedittab.h @@ -0,0 +1,64 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef PREFERENCESEDITTAB_H +#define PREFERENCESEDITTAB_H + + +#include "dialog/configbase/configdialogbase.h" + +class QCheckBox; +class QGroupBox; +class QRadioButton; +class QSpinBox; +class QLineEdit; + + +namespace olive { + +class PreferencesEditTab : public ConfigDialogBaseTab +{ + Q_OBJECT +public: + PreferencesEditTab(); + + virtual bool Validate() override; + + virtual void Accept(MultiUndoCommand* command) override; + +private: + // TODO_ can be local !! + QRadioButton * use_internal_editor_; + QRadioButton * use_external_editor_; + + QGroupBox * internal_editor_box_; + QGroupBox * external_editor_box_; + + // for internal editor + QSpinBox * font_size_; + QSpinBox * indent_size_; + + // for external editor + QLineEdit * ext_command_; +}; + +} + +#endif // PREFERENCESEDITTAB_H From 74496505a69e0b8fd275b488a1563ba718acb5b8 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Sat, 5 Feb 2022 23:57:32 +0100 Subject: [PATCH 09/49] Work in progress. Added external editor support. (Only tested on Linux) --- app/dialog/codeeditor/CMakeLists.txt | 2 + app/dialog/codeeditor/editor.cpp | 4 +- app/dialog/codeeditor/externaleditorproxy.cpp | 132 ++++++++++++++++++ app/dialog/codeeditor/externaleditorproxy.h | 68 +++++++++ .../preferences/tabs/preferencesedittab.cpp | 29 ++-- .../preferences/tabs/preferencesedittab.h | 4 - .../nodeparamview/nodeparamviewtextedit.cpp | 25 +++- .../nodeparamview/nodeparamviewtextedit.h | 6 + 8 files changed, 246 insertions(+), 24 deletions(-) create mode 100644 app/dialog/codeeditor/externaleditorproxy.cpp create mode 100644 app/dialog/codeeditor/externaleditorproxy.h diff --git a/app/dialog/codeeditor/CMakeLists.txt b/app/dialog/codeeditor/CMakeLists.txt index 8e6ec63412..1f1731a391 100644 --- a/app/dialog/codeeditor/CMakeLists.txt +++ b/app/dialog/codeeditor/CMakeLists.txt @@ -22,5 +22,7 @@ set(OLIVE_SOURCES dialog/codeeditor/glslhighlighter.cpp dialog/codeeditor/codeeditordialog.h dialog/codeeditor/codeeditordialog.cpp + dialog/codeeditor/externaleditorproxy.h + dialog/codeeditor/externaleditorproxy.cpp PARENT_SCOPE ) diff --git a/app/dialog/codeeditor/editor.cpp b/app/dialog/codeeditor/editor.cpp index 725a1e73c0..895d01865e 100644 --- a/app/dialog/codeeditor/editor.cpp +++ b/app/dialog/codeeditor/editor.cpp @@ -58,14 +58,14 @@ CodeEditor::CodeEditor(QWidget *parent) : QFont font("Monospace"); font.setStyleHint(QFont::TypeWriter); // use monospaced font for all platform - setFont( font); QFontMetrics metrics(font); - bool valid; int fontSize = Config::Current()["EditorInternalFontSize"].toInt( & valid); fontSize = valid ? fontSize : 14; font.setPointSize( fontSize); + setFont( font); + indent_size_ = Config::Current()["EditorInternalIndentSize"].toInt( & valid); indent_size_ = (valid && (indent_size_ > 0)) ? indent_size_ : 3; setTabStopDistance( (qreal)(indent_size_) * metrics.horizontalAdvance(' ')); diff --git a/app/dialog/codeeditor/externaleditorproxy.cpp b/app/dialog/codeeditor/externaleditorproxy.cpp new file mode 100644 index 0000000000..493ff344d0 --- /dev/null +++ b/app/dialog/codeeditor/externaleditorproxy.cpp @@ -0,0 +1,132 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "externaleditorproxy.h" +#include "config/config.h" + +#include +#include +#include +#include +#include + +namespace olive { + +ExternalEditorProxy::ExternalEditorProxy(QObject *parent) : + QObject(parent), + process_(nullptr) +{ + +} + +ExternalEditorProxy::~ExternalEditorProxy() +{ + // stop process + if (process_ && process_->isOpen()) { + process_->kill(); + } + + // delete temporary file + if (QFileInfo::exists(file_path_)) { + QFile::remove( file_path_); + } +} + +void ExternalEditorProxy::launch(const QString &start_text) +{ + if (file_path_ == QString()) + { + // create a file whose name is random but unique for each instance. + // We can use 'this' pointer as file name. + // 'QStandardPaths::TempLocation' is guaranteed not to be empty + file_path_ = QStandardPaths::standardLocations( QStandardPaths::TempLocation).first(); + file_path_ += QString("%1%2.frag").arg(QDir::separator()).arg((long long)this); + + // create file before watching it + QFile out(file_path_); + out.open( QIODevice::WriteOnly); + bool ok = false; + + if (out.isOpen()) + { + ok = watcher_.addPath( file_path_); + out.write( start_text.toLatin1()); + out.close(); + } + + if (ok == false) + { + QMessageBox::warning( nullptr, "Olive", + tr("Can't send data to external editor")); + } + + connect( & watcher_, & QFileSystemWatcher::fileChanged, this, & ExternalEditorProxy::onFileChanged); + } + + if (process_ == nullptr) + { + process_ = new QProcess(this); + connect( process_, static_cast(& QProcess::finished), + this, &ExternalEditorProxy::onProcessFinished); + + QString cmd = Config::Current()["EditorExternalCommand"].toString(); + cmd.replace("%FILE", file_path_); + cmd.replace("%LINE", "1"); // not yet supported + + QStringList params = cmd.split(' ', Qt::SkipEmptyParts); + QString opcode = params.takeAt(0); + process_->start( opcode, params); + } +} + +void ExternalEditorProxy::onFileChanged(const QString &path) +{ + // this should always be true + Q_ASSERT( path == file_path_); + + QFile f(file_path_); + f.open( QIODevice::ReadOnly); + + if (f.isOpen()) + { + QString content = QString::fromLatin1( f.readAll()); + emit textChanged( content); + f.close(); + } + else + { + QMessageBox::warning( nullptr, "Olive", tr("Can't update data from external editor")); + } +} + +void ExternalEditorProxy::onProcessFinished(int /*exitCode*/, QProcess::ExitStatus /*exitStatus*/) +{ + // Process has saved file before exit or has unexpectedly finished. + // Do not emit the text changed signal + + // Also, some editors fork their process and this slot is called when the editor is + // still running. For this reason, do not stop listening for modified file + + process_->deleteLater(); + process_ = nullptr; +} + + +} // namespace olive diff --git a/app/dialog/codeeditor/externaleditorproxy.h b/app/dialog/codeeditor/externaleditorproxy.h new file mode 100644 index 0000000000..ff22084069 --- /dev/null +++ b/app/dialog/codeeditor/externaleditorproxy.h @@ -0,0 +1,68 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef EXTERNALEDITORPROXY_H +#define EXTERNALEDITORPROXY_H + + +#include +#include + +class QProcess; + +namespace olive { + + +/** @brief Interface to edit a shader code in external editor. + * + * This class generates a temporary file initialized with the contents + * passed in constructor. Then an external QProcess is launched and this + * class listens if generated file is changed externally. + * The class destructor deletes the temporary file. + */ +class ExternalEditorProxy : public QObject +{ + Q_OBJECT +public: + explicit ExternalEditorProxy(QObject *parent = nullptr); + + ~ExternalEditorProxy() override; + + // launch an external process, if one is not already running. + // If a process is running, nothing is done. + void launch( const QString & start_text); + +signals: + // emnitted when temporary file is saved + void textChanged( const QString & new_text); + +private: + void onFileChanged(const QString& path); + void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); + +private: + QFileSystemWatcher watcher_; + QString file_path_; + QProcess *process_; +}; + +} // namespace olive + +#endif // EXTERNALEDITORPROXY_H diff --git a/app/dialog/preferences/tabs/preferencesedittab.cpp b/app/dialog/preferences/tabs/preferencesedittab.cpp index 2442bb7cbd..926f0ed44f 100644 --- a/app/dialog/preferences/tabs/preferencesedittab.cpp +++ b/app/dialog/preferences/tabs/preferencesedittab.cpp @@ -38,6 +38,10 @@ PreferencesEditTab::PreferencesEditTab() QGroupBox* selection_group = new QGroupBox(tr("Select editor")); outer_layout->addWidget(selection_group); + QGroupBox * internal_editor_box = new QGroupBox(tr("Internal editor")); + outer_layout->addWidget(internal_editor_box); + QGroupBox * external_editor_box = new QGroupBox(tr("External editor")); + outer_layout->addWidget(external_editor_box); QVBoxLayout* editor_slect_layout = new QVBoxLayout(selection_group); use_internal_editor_ = new QRadioButton(tr("Use internal code editor")); @@ -46,22 +50,17 @@ PreferencesEditTab::PreferencesEditTab() editor_slect_layout->addWidget(use_internal_editor_); editor_slect_layout->addWidget(use_external_editor_); - internal_editor_box_ = new QGroupBox(tr("Internal editor")); - external_editor_box_ = new QGroupBox(tr("External editor")); - outer_layout->addWidget(internal_editor_box_); - outer_layout->addWidget(external_editor_box_); - connect( use_internal_editor_, & QRadioButton::clicked, - internal_editor_box_, & QGroupBox::setEnabled); + internal_editor_box, & QGroupBox::setEnabled); connect( use_internal_editor_, & QRadioButton::clicked, - external_editor_box_, & QGroupBox::setDisabled); + external_editor_box, & QGroupBox::setDisabled); connect( use_external_editor_, & QRadioButton::clicked, - external_editor_box_, & QGroupBox::setEnabled); + external_editor_box, & QGroupBox::setEnabled); connect( use_external_editor_, & QRadioButton::clicked, - internal_editor_box_, & QGroupBox::setDisabled); + internal_editor_box, & QGroupBox::setDisabled); //internal editor - QGridLayout * internal_editor_layout = new QGridLayout(internal_editor_box_); + QGridLayout * internal_editor_layout = new QGridLayout(internal_editor_box); internal_editor_layout->addWidget( new QLabel(tr("Font size")), 1, 1); font_size_ = new QSpinBox(); internal_editor_layout->addWidget( font_size_, 1, 2); @@ -70,24 +69,24 @@ PreferencesEditTab::PreferencesEditTab() internal_editor_layout->addWidget( indent_size_, 2, 2); internal_editor_layout->addItem( new QSpacerItem(1,1, QSizePolicy::Expanding), 1, 3); internal_editor_layout->addItem( new QSpacerItem(1,1, QSizePolicy::Expanding), 2, 3); - outer_layout->addWidget(internal_editor_box_); + outer_layout->addWidget(internal_editor_box); //external editor - QVBoxLayout * external_editor_layout = new QVBoxLayout(external_editor_box_); + QVBoxLayout * external_editor_layout = new QVBoxLayout(external_editor_box); external_editor_layout->addWidget( new QLabel(tr("

Command to launch external editor.

" "

Use %FILE for text file path and %LINE for line number.

"))); ext_command_ = new QLineEdit(); external_editor_layout->addWidget( ext_command_); - outer_layout->addWidget(external_editor_box_); + outer_layout->addWidget(external_editor_box); outer_layout->addStretch(); bool use_internal = Config::Current()["EditorUseInternal"].toBool(); use_internal_editor_->setChecked( use_internal); use_external_editor_->setChecked( ! use_internal); - internal_editor_box_->setEnabled( use_internal); - external_editor_box_->setEnabled( ! use_internal); + internal_editor_box->setEnabled( use_internal); + external_editor_box->setEnabled( ! use_internal); font_size_->setValue( Config::Current()["EditorInternalFontSize"].toInt()); indent_size_->setValue( Config::Current()["EditorInternalIndentSize"].toInt()); ext_command_->setText( Config::Current()["EditorExternalCommand"].toString()); diff --git a/app/dialog/preferences/tabs/preferencesedittab.h b/app/dialog/preferences/tabs/preferencesedittab.h index 9cd00189ce..0098a4c587 100644 --- a/app/dialog/preferences/tabs/preferencesedittab.h +++ b/app/dialog/preferences/tabs/preferencesedittab.h @@ -44,13 +44,9 @@ class PreferencesEditTab : public ConfigDialogBaseTab virtual void Accept(MultiUndoCommand* command) override; private: - // TODO_ can be local !! QRadioButton * use_internal_editor_; QRadioButton * use_external_editor_; - QGroupBox * internal_editor_box_; - QGroupBox * external_editor_box_; - // for internal editor QSpinBox * font_size_; QSpinBox * indent_size_; diff --git a/app/widget/nodeparamview/nodeparamviewtextedit.cpp b/app/widget/nodeparamview/nodeparamviewtextedit.cpp index d2f3fc1bfa..b85b2a9590 100644 --- a/app/widget/nodeparamview/nodeparamviewtextedit.cpp +++ b/app/widget/nodeparamview/nodeparamviewtextedit.cpp @@ -26,6 +26,9 @@ #include "dialog/text/text.h" #include "dialog/codeeditor/codeeditordialog.h" #include "ui/icons/icons.h" +#include "dialog/codeeditor/externaleditorproxy.h" +#include "config/config.h" + #include namespace olive { @@ -47,6 +50,10 @@ NodeParamViewTextEdit::NodeParamViewTextEdit(QWidget *parent) : edit_btn->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); layout->addWidget(edit_btn); connect(edit_btn, &QPushButton::clicked, this, &NodeParamViewTextEdit::ShowTextDialog); + + ext_editor_proxy_ = new ExternalEditorProxy( this); + connect( ext_editor_proxy_, & ExternalEditorProxy::textChanged, + this, & NodeParamViewTextEdit::OnTextChangedExternally); } void NodeParamViewTextEdit::ShowTextDialog() @@ -54,10 +61,16 @@ void NodeParamViewTextEdit::ShowTextDialog() QString text; if (code_editor_flag_) { - CodeEditorDialog d(this->text(), this); - if (d.exec() == QDialog::Accepted) { - text = d.text(); + if (Config::Current()["EditorUseInternal"].toBool()) { + CodeEditorDialog d(this->text(), this); + + if (d.exec() == QDialog::Accepted) { + text = d.text(); + } + } + else { + ext_editor_proxy_->launch( this->text()); } } else { @@ -79,6 +92,12 @@ void NodeParamViewTextEdit::InnerWidgetTextChanged() emit textEdited(this->text()); } +void NodeParamViewTextEdit::OnTextChangedExternally(const QString &new_text) +{ + line_edit_->setPlainText( new_text); + emit textEdited( new_text); +} + void NodeParamViewTextEdit::setCodeEditoFlag() { code_editor_flag_ = true; diff --git a/app/widget/nodeparamview/nodeparamviewtextedit.h b/app/widget/nodeparamview/nodeparamviewtextedit.h index caa8434395..8091b98de4 100644 --- a/app/widget/nodeparamview/nodeparamviewtextedit.h +++ b/app/widget/nodeparamview/nodeparamviewtextedit.h @@ -28,6 +28,9 @@ namespace olive { +class ExternalEditorProxy; + + class NodeParamViewTextEdit : public QWidget { Q_OBJECT @@ -71,12 +74,15 @@ public slots: private: QPlainTextEdit* line_edit_; bool code_editor_flag_; + ExternalEditorProxy * ext_editor_proxy_; private slots: void ShowTextDialog(); void InnerWidgetTextChanged(); + void OnTextChangedExternally( const QString & new_text); + }; } From 66d615cacb048eb3154e998bc68a9557f43c3055 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Wed, 9 Feb 2022 23:24:50 +0100 Subject: [PATCH 10/49] Work in progress. Improved internal editor and error reporting --- app/config/config.cpp | 1 + app/dialog/codeeditor/CMakeLists.txt | 4 + app/dialog/codeeditor/codeeditordialog.cpp | 35 ++- app/dialog/codeeditor/codeeditordialog.h | 5 + app/dialog/codeeditor/editor.cpp | 80 ++++++- app/dialog/codeeditor/editor.h | 12 +- app/dialog/codeeditor/externaleditorproxy.cpp | 9 +- app/dialog/codeeditor/messagehighlighter.cpp | 57 +++++ app/dialog/codeeditor/messagehighlighter.h | 57 +++++ app/dialog/codeeditor/searchtextbar.cpp | 201 ++++++++++++++++++ app/dialog/codeeditor/searchtextbar.h | 80 +++++++ .../preferences/tabs/preferencesedittab.cpp | 11 +- .../preferences/tabs/preferencesedittab.h | 1 + app/dialog/text/text.h | 6 + app/node/filter/shader/shader.cpp | 34 +-- app/node/filter/shader/shader.h | 4 + .../nodeparamview/nodeparamviewtextedit.cpp | 51 +++-- .../nodeparamview/nodeparamviewtextedit.h | 12 +- .../nodeparamviewwidgetbridge.cpp | 10 +- 19 files changed, 619 insertions(+), 51 deletions(-) create mode 100644 app/dialog/codeeditor/messagehighlighter.cpp create mode 100644 app/dialog/codeeditor/messagehighlighter.h create mode 100644 app/dialog/codeeditor/searchtextbar.cpp create mode 100644 app/dialog/codeeditor/searchtextbar.h diff --git a/app/config/config.cpp b/app/config/config.cpp index 6002f36079..0dcb4ad8c9 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -140,6 +140,7 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("EditorUseInternal"), NodeValue::kBoolean, true); SetEntryInternal(QStringLiteral("EditorExternalCommand"), NodeValue::kText, QString()); + SetEntryInternal(QStringLiteral("EditorExternalParams"), NodeValue::kText, QString()); SetEntryInternal(QStringLiteral("EditorInternalFontSize"), NodeValue::kInt, 14); SetEntryInternal(QStringLiteral("EditorInternalIndentSize"), NodeValue::kInt, 3); } diff --git a/app/dialog/codeeditor/CMakeLists.txt b/app/dialog/codeeditor/CMakeLists.txt index 1f1731a391..cb2260979f 100644 --- a/app/dialog/codeeditor/CMakeLists.txt +++ b/app/dialog/codeeditor/CMakeLists.txt @@ -24,5 +24,9 @@ set(OLIVE_SOURCES dialog/codeeditor/codeeditordialog.cpp dialog/codeeditor/externaleditorproxy.h dialog/codeeditor/externaleditorproxy.cpp + dialog/codeeditor/searchtextbar.h + dialog/codeeditor/searchtextbar.cpp + dialog/codeeditor/messagehighlighter.h + dialog/codeeditor/messagehighlighter.cpp PARENT_SCOPE ) diff --git a/app/dialog/codeeditor/codeeditordialog.cpp b/app/dialog/codeeditor/codeeditordialog.cpp index 58b599feaf..040c501d49 100644 --- a/app/dialog/codeeditor/codeeditordialog.cpp +++ b/app/dialog/codeeditor/codeeditordialog.cpp @@ -1,10 +1,13 @@ #include "codeeditordialog.h" +#include "searchtextbar.h" #include #include #include #include #include +#include + namespace olive { @@ -16,6 +19,7 @@ CodeEditorDialog::CodeEditorDialog(const QString &start, QWidget* parent) : // Create text edit widget text_edit_ = new CodeEditor( this); text_edit_->document()->setPlainText(start); + text_edit_->gotoLineNumber(1); layout->addWidget(text_edit_); // Create buttons @@ -40,11 +44,26 @@ CodeEditorDialog::CodeEditorDialog(const QString &start, QWidget* parent) : QAction * add_int_input = edit_addSnippet_menu->addAction(tr("integer")); QAction * add_boolean_input = edit_addSnippet_menu->addAction(tr("boolean")); - connect( add_texture_input, & QAction::triggered, this, & CodeEditorDialog::OnActionAddInputTexture); - connect( add_color_input, & QAction::triggered, this, & CodeEditorDialog::OnActionAddInputColor); - connect( add_float_input, & QAction::triggered, this, & CodeEditorDialog::OnActionAddInputFloat); - connect( add_int_input, & QAction::triggered, this, & CodeEditorDialog::OnActionAddInputInt); - connect( add_boolean_input, & QAction::triggered, this, & CodeEditorDialog::OnActionAddInputBoolean); + connect( add_texture_input, &QAction::triggered, this, &CodeEditorDialog::OnActionAddInputTexture); + connect( add_color_input, &QAction::triggered, this, &CodeEditorDialog::OnActionAddInputColor); + connect( add_float_input, &QAction::triggered, this, &CodeEditorDialog::OnActionAddInputFloat); + connect( add_int_input, &QAction::triggered, this, &CodeEditorDialog::OnActionAddInputInt); + connect( add_boolean_input, &QAction::triggered, this, &CodeEditorDialog::OnActionAddInputBoolean); + + search_bar_ = new SearchTextBar( this); + search_bar_->hide(); + layout->addWidget( search_bar_); + + QAction * show_find_dialog = edit_menu->addAction(tr("find/replace")); + show_find_dialog->setShortcut( QKeySequence(Qt::CTRL + Qt::Key_F)); + + connect( show_find_dialog, &QAction::triggered, this, &CodeEditorDialog::OnFindRequest); + + connect( search_bar_, &SearchTextBar::searchTextForward, text_edit_, &CodeEditor::onSearchForwardRequest); + connect( search_bar_, &SearchTextBar::searchTextBackward, text_edit_, &CodeEditor::onSearchBackwardRequest); + connect( search_bar_, &SearchTextBar::replaceText, text_edit_, &CodeEditor::onReplaceTextRequest); + + connect( text_edit_, &CodeEditor::textNotFound, search_bar_, &SearchTextBar::onTextNotFound); layout->setMenuBar( menu_bar); } @@ -106,6 +125,12 @@ void CodeEditorDialog::OnActionAddInputBoolean() "uniform bool disable;\n"); } +void CodeEditorDialog::OnFindRequest() +{ + QString text_to_find = text_edit_->textCursor().selection().toPlainText(); + search_bar_->showBar( text_to_find); +} + } // namespace olive diff --git a/app/dialog/codeeditor/codeeditordialog.h b/app/dialog/codeeditor/codeeditordialog.h index 0443f28ece..6a5bed9db0 100644 --- a/app/dialog/codeeditor/codeeditordialog.h +++ b/app/dialog/codeeditor/codeeditordialog.h @@ -25,6 +25,9 @@ namespace olive { +class SearchTextBar; + + /// @brief Wrapper QDialog window for the code editor class CodeEditorDialog : public QDialog { @@ -39,6 +42,7 @@ class CodeEditorDialog : public QDialog private: CodeEditor* text_edit_; + SearchTextBar * search_bar_; private slots: void OnActionAddInputTexture(); @@ -46,6 +50,7 @@ private slots: void OnActionAddInputFloat(); void OnActionAddInputInt(); void OnActionAddInputBoolean(); + void OnFindRequest(); }; } // namespace olive diff --git a/app/dialog/codeeditor/editor.cpp b/app/dialog/codeeditor/editor.cpp index 895d01865e..4692f50584 100644 --- a/app/dialog/codeeditor/editor.cpp +++ b/app/dialog/codeeditor/editor.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include "glslhighlighter.h" @@ -96,6 +97,73 @@ void CodeEditor::gotoLineNumber(int lineNumber) highlightCurrentLine(); } +void CodeEditor::onSearchBackwardRequest(const QString &text, QTextDocument::FindFlags flags) +{ + // try to find backword + bool found = find( text, flags | QTextDocument::FindBackward); + + if ( ! found) + { + QTextCursor cursor = textCursor(); + int currentPosition = cursor.position(); + + // search again from the end + cursor.movePosition( QTextCursor::End); + setTextCursor( cursor); + + found = find( text, flags | QTextDocument::FindBackward); + + if ( ! found) + { + // 'text' is not present. Go back where we were + cursor.setPosition( currentPosition ); + setTextCursor( cursor); + + emit textNotFound(); + } + } +} + +void CodeEditor::onSearchForwardRequest(const QString &text, QTextDocument::FindFlags flags) +{ + // try to find forward + bool found = find( text, flags); + + if ( ! found) + { + QTextCursor cursor = textCursor(); + int currentPosition = cursor.position(); + + // search again from the begin + cursor.movePosition( QTextCursor::Start); + setTextCursor( cursor); + + found = find( text, flags); + + if ( ! found) + { + // 'text' is not present. Go back where we were + cursor.setPosition( currentPosition ); + setTextCursor( cursor); + + emit textNotFound(); + } + } +} + +void CodeEditor::onReplaceTextRequest( const QString & src, const QString & dest, + QTextDocument::FindFlags flags, bool thenFind) +{ + // replace only if selection holds 'src' text + if ( src == textCursor().selection().toPlainText()) { + textCursor().insertText( dest); + } + + if (thenFind == true) { + onSearchForwardRequest( src, flags); + } +} + void CodeEditor::updateLineNumberAreaWidth(int /* newBlockCount */) { setViewportMargins(lineNumberAreaWidth(), 0, 0, 0); @@ -201,6 +269,11 @@ void CodeEditor::keyPressEvent(QKeyEvent *event) // we have not changed line yet. Count blanks of current line int n_space = countTrailingSpaces( textCursor().block().position()); + // indent if previous line ends with opening bracket + if (textCursor().block().text().trimmed().endsWith('{')) { + n_space += indent_size_; + } + // append new line QPlainTextEdit::keyPressEvent( event); @@ -219,12 +292,17 @@ int CodeEditor::countTrailingSpaces(int block_position) { int n_spaces = 0; QString text = toPlainText(); + int text_size = text.length(); bool done = false; Q_ASSERT( indent_size_ > 0); while( done == false) { - switch (text.at(block_position).toLatin1()) { + + // read one char until text finishes or a non blank is found + QChar c = (block_position < text_size) ? text.at(block_position) : '*'; + + switch (c.toLatin1()) { case ' ': n_spaces++; break; diff --git a/app/dialog/codeeditor/editor.h b/app/dialog/codeeditor/editor.h index 9f3c55ab08..a3a7b26ca0 100644 --- a/app/dialog/codeeditor/editor.h +++ b/app/dialog/codeeditor/editor.h @@ -22,6 +22,7 @@ #define CODEEDITOR_H #include +#include namespace olive { @@ -38,12 +39,17 @@ class CodeEditor : public QPlainTextEdit void lineNumberAreaPaintEvent(QPaintEvent *event); int lineNumberAreaWidth(); - void setEditMode( bool isEditMode); - void gotoLineNumber(int lineNumber); +public slots: + void onSearchBackwardRequest( const QString & text, QTextDocument::FindFlags flags); + void onSearchForwardRequest( const QString & text, QTextDocument::FindFlags flags); + void onReplaceTextRequest( const QString &src, const QString &dest, + QTextDocument::FindFlags flags, bool thenFind); + signals: - void lineDoubleClicked( int blockNumber); + // search operation failed + void textNotFound(); protected: void resizeEvent(QResizeEvent *event) override; diff --git a/app/dialog/codeeditor/externaleditorproxy.cpp b/app/dialog/codeeditor/externaleditorproxy.cpp index 493ff344d0..2e30c6d62d 100644 --- a/app/dialog/codeeditor/externaleditorproxy.cpp +++ b/app/dialog/codeeditor/externaleditorproxy.cpp @@ -87,12 +87,11 @@ void ExternalEditorProxy::launch(const QString &start_text) this, &ExternalEditorProxy::onProcessFinished); QString cmd = Config::Current()["EditorExternalCommand"].toString(); - cmd.replace("%FILE", file_path_); - cmd.replace("%LINE", "1"); // not yet supported + QString params = Config::Current()["EditorExternalParams"].toString(); + params.replace("%FILE", file_path_); + params.replace("%LINE", "1"); // not yet supported - QStringList params = cmd.split(' ', Qt::SkipEmptyParts); - QString opcode = params.takeAt(0); - process_->start( opcode, params); + process_->start( cmd, params.split(' ', Qt::SkipEmptyParts)); } } diff --git a/app/dialog/codeeditor/messagehighlighter.cpp b/app/dialog/codeeditor/messagehighlighter.cpp new file mode 100644 index 0000000000..deca639aa8 --- /dev/null +++ b/app/dialog/codeeditor/messagehighlighter.cpp @@ -0,0 +1,57 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "messagehighlighter.h" + +namespace olive { + +MessageSyntaxHighlighter::MessageSyntaxHighlighter(QTextDocument *parent) : + QSyntaxHighlighter(parent) +{ + group_format_.setForeground(QColor(110,110,255)); + group_format_.setFontWeight(QFont::Bold); + + line_format_.setForeground(QColor(128,128,128)); + line_format_.setFontItalic(true); + + group_regexp = QRegularExpression(QStringLiteral("\".*\"")); + line_regexp = QRegularExpression(QStringLiteral("line\\s\\d+:")); +} + +void MessageSyntaxHighlighter::highlightBlock(const QString &text) +{ + QRegularExpressionMatchIterator matchIterator = group_regexp.globalMatch(text); + + while (matchIterator.hasNext()) { + QRegularExpressionMatch match = matchIterator.next(); + setFormat(match.capturedStart(), match.capturedLength(), group_format_); + } + + matchIterator = line_regexp.globalMatch(text); + + while (matchIterator.hasNext()) { + QRegularExpressionMatch match = matchIterator.next(); + setFormat(match.capturedStart(), match.capturedLength(), line_format_); + } +} + + +} // namespace olive + diff --git a/app/dialog/codeeditor/messagehighlighter.h b/app/dialog/codeeditor/messagehighlighter.h new file mode 100644 index 0000000000..e8d51d092f --- /dev/null +++ b/app/dialog/codeeditor/messagehighlighter.h @@ -0,0 +1,57 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef MESSAGEHIGHLIGHTER_H +#define MESSAGEHIGHLIGHTER_H + +#include +#include +#include + +class QTextDocument; + +namespace olive { + +#include +#include + +class MessageSyntaxHighlighter : public QSyntaxHighlighter +{ + Q_OBJECT +public: + + MessageSyntaxHighlighter( QTextDocument * parent = nullptr); + + // QSyntaxHighlighter interface +protected: + void highlightBlock(const QString &text) override; + +private: + QTextCharFormat group_format_; + QTextCharFormat line_format_; + + QRegularExpression group_regexp; + QRegularExpression line_regexp; +}; + + +} // namespace olive + +#endif // MESSAGEHIGHLIGHTER_H diff --git a/app/dialog/codeeditor/searchtextbar.cpp b/app/dialog/codeeditor/searchtextbar.cpp new file mode 100644 index 0000000000..eae99912d9 --- /dev/null +++ b/app/dialog/codeeditor/searchtextbar.cpp @@ -0,0 +1,201 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "searchtextbar.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace { +// duration of visual feedback when text is not found +const uint32_t TEXT_NOT_FOUND_FLASH_DURATION_ms = 600u; +} + +namespace olive { + +SearchTextBar::SearchTextBar(QWidget *parent) : + QWidget(parent), + timer_( new QTimer(this)) +{ + // build GUI + QGridLayout * main_layout = new QGridLayout(); + setLayout( main_layout); + main_layout->setSpacing(2); + + QLabel * find_label = new QLabel(tr("Find:"), this); + find_edit_ = new QLineEdit( this); + find_label->setAlignment( Qt::AlignLeft); + find_label->setBuddy( find_edit_); + + case_sensitive_box_ = new QCheckBox( "Aa", this); + case_sensitive_box_->setToolTip(tr("case sensitive")); + whole_word_box_ = new QCheckBox( "[]", this); + whole_word_box_->setToolTip(tr("whole words only")); + + QPushButton * find_forward_button = new QPushButton( ">>", this); + QPushButton * find_backword_button = new QPushButton( "<<", this); + QPushButton * hide_button = new QPushButton( "x", this); + + find_edit_->setSizePolicy( QSizePolicy::Expanding , QSizePolicy::Preferred); + main_layout->addWidget( find_label, 0,0,1,1); + main_layout->addWidget( find_edit_, 0,1,1,1); + main_layout->addWidget( case_sensitive_box_, 0,2,1,1); + main_layout->addWidget( whole_word_box_, 0,3,1,1); + main_layout->addWidget( find_backword_button, 0,4,1,1); + main_layout->addWidget( find_forward_button, 0,5,1,1); + main_layout->addItem( new QSpacerItem(10,10, QSizePolicy::Minimum, QSizePolicy::Minimum), 0,6,1,1); + main_layout->addWidget( hide_button, 0,7,1,1); + + QLabel * replace_label = new QLabel(tr("Replace:"), this); + replace_edit_ = new QLineEdit( this); + replace_label->setAlignment( Qt::AlignLeft); + replace_label->setBuddy( replace_edit_); + + QPushButton * replace_button = new QPushButton(tr("replace"), this); + QPushButton * replace_and_find_button = new QPushButton(tr("replace & find"), this); + + replace_edit_->setSizePolicy( QSizePolicy::Expanding , QSizePolicy::Preferred); + main_layout->addWidget( replace_label, 1,0,1,1); + main_layout->addWidget( replace_edit_, 1,1,1,1); + main_layout->addWidget( replace_button, 1,2,1,2); + main_layout->addWidget( replace_and_find_button, 1,4,1,2); + + connect( find_backword_button, & QPushButton::clicked, this, & SearchTextBar::onPrevButtonClicked); + connect( find_forward_button, & QPushButton::clicked, this, & SearchTextBar::onNextButtonClicked); + connect( hide_button, & QPushButton::clicked, this, & SearchTextBar::onHideButtonClicked); + connect( replace_button, & QPushButton::clicked, this, & SearchTextBar::onReplaceButtonCLicked); + connect( replace_and_find_button, & QPushButton::clicked, this, & SearchTextBar::onReplaceAndFindButtonCLicked); + + find_edit_->setTabOrder( find_edit_, replace_edit_); + + // visual feedback timer + connect( timer_, SIGNAL(timeout()), this, SLOT(onTimeoutExpired())); +} + +SearchTextBar::~SearchTextBar() +{ +} + +void SearchTextBar::activateBar() +{ + setVisible( true); + find_edit_->setFocus(); +} + +void SearchTextBar::onTextNotFound() +{ + // give a visual feedback that text can't be found + find_edit_->setStyleSheet("background-color: red;"); + timer_->start( TEXT_NOT_FOUND_FLASH_DURATION_ms); +} + +void SearchTextBar::showBar( const QString & search_text) +{ + show(); + find_edit_->setFocus(); + find_edit_->setText( search_text); +} + +void SearchTextBar::onTimeoutExpired() +{ + // end of visual feedback + find_edit_->setStyleSheet(""); + timer_->stop(); +} + +void SearchTextBar::keyReleaseEvent(QKeyEvent * event) +{ + QWidget::keyReleaseEvent( event); + + // enter or return key search forward; use shift to search backward + if ((event->key() == Qt::Key_Enter) || + (event->key() == Qt::Key_Return)) + { + QTextDocument::FindFlags flags = getCurrentFlags(); + + if (event->modifiers() & Qt::ShiftModifier) + { + emit searchTextBackward( find_edit_->text(), flags); + } + else + { + emit searchTextForward( find_edit_->text(), flags); + } + } + else if (event->key() == Qt::Key_Escape) + { + hide(); + } +} + + +void SearchTextBar::onHideButtonClicked() +{ + hide(); +} + +void SearchTextBar::onPrevButtonClicked() +{ + QTextDocument::FindFlags flags = getCurrentFlags(); + emit searchTextForward( find_edit_->text(), flags); +} + +void SearchTextBar::onNextButtonClicked() +{ + QTextDocument::FindFlags flags = getCurrentFlags(); + emit searchTextBackward( find_edit_->text(), flags); +} + +void SearchTextBar::onReplaceButtonCLicked() +{ + QTextDocument::FindFlags flags = getCurrentFlags(); + emit replaceText( find_edit_->text(), replace_edit_->text(), flags, false); +} + +void SearchTextBar::onReplaceAndFindButtonCLicked() +{ + QTextDocument::FindFlags flags = getCurrentFlags(); + emit replaceText( find_edit_->text(), replace_edit_->text(), flags, false); +} + +QTextDocument::FindFlags SearchTextBar::getCurrentFlags() +{ + // default constructor sets to 0 + QTextDocument::FindFlags flags; + + if (case_sensitive_box_->isChecked()) { + flags |= QTextDocument::FindCaseSensitively; + } + + if (whole_word_box_->isChecked()) { + flags |= QTextDocument::FindWholeWords; + } + + return flags; +} + +} // namespace olive diff --git a/app/dialog/codeeditor/searchtextbar.h b/app/dialog/codeeditor/searchtextbar.h new file mode 100644 index 0000000000..7a1531198b --- /dev/null +++ b/app/dialog/codeeditor/searchtextbar.h @@ -0,0 +1,80 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef SEARCHTEXTBAR_H +#define SEARCHTEXTBAR_H + +#include +#include + +class QTimer; +class QCheckBox; +class QLineEdit; + +namespace olive { + +class SearchTextBar : public QWidget +{ + Q_OBJECT + +public: + explicit SearchTextBar(QWidget *parent = nullptr); + ~SearchTextBar() override; + + /** make seach bar visible and set focus */ + void activateBar(); + +public slots: + void onTextNotFound(); + void showBar(const QString &search_text); + +signals: + void searchTextForward( const QString & text, QTextDocument::FindFlags flgs); + void searchTextBackward( const QString & text, QTextDocument::FindFlags flgs); + // thenFind: when TRUE, replace and then find + void replaceText( const QString & src, const QString & dest, + QTextDocument::FindFlags flags, bool thenFind); + +private: + QTimer * timer_; + QCheckBox * case_sensitive_box_; + QCheckBox * whole_word_box_; + QLineEdit * find_edit_; + QLineEdit * replace_edit_; + + // QWidget interface +protected: + void keyReleaseEvent(QKeyEvent * event) override; + +private slots: + void onTimeoutExpired(); + void onHideButtonClicked(); + void onPrevButtonClicked(); + void onNextButtonClicked(); + void onReplaceButtonCLicked(); + void onReplaceAndFindButtonCLicked(); + +private: + QTextDocument::FindFlags getCurrentFlags(); +}; + +} // namespace olive + +#endif // SEARCHTEXTBAR_H diff --git a/app/dialog/preferences/tabs/preferencesedittab.cpp b/app/dialog/preferences/tabs/preferencesedittab.cpp index 926f0ed44f..3c38d3f5e4 100644 --- a/app/dialog/preferences/tabs/preferencesedittab.cpp +++ b/app/dialog/preferences/tabs/preferencesedittab.cpp @@ -74,10 +74,15 @@ PreferencesEditTab::PreferencesEditTab() //external editor QVBoxLayout * external_editor_layout = new QVBoxLayout(external_editor_box); external_editor_layout->addWidget( - new QLabel(tr("

Command to launch external editor.

" - "

Use %FILE for text file path and %LINE for line number.

"))); + new QLabel(tr("Command or full file path of external editor.\n" + "Use double quotes if executable path has spaces")) ); ext_command_ = new QLineEdit(); + ext_params_ = new QLineEdit(); external_editor_layout->addWidget( ext_command_); + external_editor_layout->addWidget( + new QLabel(tr("Parameters of external editor.\n" + "Use %FILE and %LINE for file path and line number")) ); + external_editor_layout->addWidget( ext_params_); outer_layout->addWidget(external_editor_box); outer_layout->addStretch(); @@ -90,6 +95,7 @@ PreferencesEditTab::PreferencesEditTab() font_size_->setValue( Config::Current()["EditorInternalFontSize"].toInt()); indent_size_->setValue( Config::Current()["EditorInternalIndentSize"].toInt()); ext_command_->setText( Config::Current()["EditorExternalCommand"].toString()); + ext_params_->setText( Config::Current()["EditorExternalParams"].toString()); } bool PreferencesEditTab::Validate() @@ -103,6 +109,7 @@ void PreferencesEditTab::Accept(MultiUndoCommand *command) Config::Current()["EditorUseInternal"] = QVariant::fromValue( use_internal_editor_->isChecked()); Config::Current()["EditorExternalCommand"] = QVariant::fromValue(ext_command_->text()); + Config::Current()["EditorExternalParams"] = QVariant::fromValue(ext_params_->text()); Config::Current()["EditorInternalFontSize"] = QVariant::fromValue(font_size_->value()); Config::Current()["EditorInternalIndentSize"] = QVariant::fromValue(indent_size_->value()); } diff --git a/app/dialog/preferences/tabs/preferencesedittab.h b/app/dialog/preferences/tabs/preferencesedittab.h index 0098a4c587..b06aa06bf1 100644 --- a/app/dialog/preferences/tabs/preferencesedittab.h +++ b/app/dialog/preferences/tabs/preferencesedittab.h @@ -53,6 +53,7 @@ class PreferencesEditTab : public ConfigDialogBaseTab // for external editor QLineEdit * ext_command_; + QLineEdit * ext_params_; }; } diff --git a/app/dialog/text/text.h b/app/dialog/text/text.h index 5916b0101d..c71293cbbf 100644 --- a/app/dialog/text/text.h +++ b/app/dialog/text/text.h @@ -24,6 +24,7 @@ #include #include #include +#include #include "common/define.h" #include "widget/slider/floatslider.h" @@ -41,6 +42,11 @@ class TextDialog : public QDialog return text_edit_->toPlainText(); } + void setSyntaxHighlight( QSyntaxHighlighter * highlighter) const + { + highlighter->setDocument(text_edit_->document()); + } + private: QPlainTextEdit* text_edit_; diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index 190a1d23af..b881d32119 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -24,11 +24,16 @@ #include #include +#include +#include +#include + #include "shaderinputsparser.h" namespace olive { const QString ShaderFilterNode::kShaderCode = QStringLiteral("source"); +const QString ShaderFilterNode::kOutputMessages = QStringLiteral("issues"); ShaderFilterNode::ShaderFilterNode() @@ -37,9 +42,13 @@ ShaderFilterNode::ShaderFilterNode() // with mark-up comments. AddInput(kShaderCode, NodeValue::kText, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); - // mark this text input as code, so it will be edited with code editor and not - // with a simple textbox - SetInputProperty( kShaderCode, QStringLiteral("is_shader_code"), true); + // Output messages of shader parser + AddInput(kOutputMessages, NodeValue::kText, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + + // mark this text input as code, so it will be edited with code editor + SetInputProperty( kShaderCode, QStringLiteral("text_type"), QString("shader_code")); + // mark this text input as output messages + SetInputProperty( kOutputMessages, QStringLiteral("text_type"), QString("shader_issues")); } Node *ShaderFilterNode::copy() const @@ -113,9 +122,10 @@ QString ShaderFilterNode::Description() const void ShaderFilterNode::Retranslate() { - // Retranslate the only fixed input. + // Retranslate the only fixed inputs. // Other inputs are read from the shader code SetInputName( kShaderCode, tr("Shader code")); + SetInputName( kOutputMessages, tr("Issues")); } ShaderCode ShaderFilterNode::GetShaderCode(const QString &shader_id) const @@ -155,23 +165,15 @@ void ShaderFilterNode::parseShaderCode() void ShaderFilterNode::reportErrorList( const ShaderInputsParser & parser) { const QList & errors = parser.ErrorList(); - QString message; - if (errors.length() > 0) { - message.append(tr("

Shader %1 has metadata errors. " - "These are not related to the shader code, just to metadata

"). - arg(parser.ShaderName())); - } + QString message = QString(tr("There are %1 issues.\n").arg(errors.size())); for (ShaderInputsParser::Error e : errors ) { - // need to translate? - message.append(QString("

Line %1: %2

").arg(e.line).arg( e.issue)); + message.append(QString("\"%1\" line %2: %3\n"). + arg( parser.ShaderName()).arg(e.line).arg(e.issue)); } - // we use a message box because the editor has been closed - if (message != QString()) { - QMessageBox::warning(nullptr, tr("Shader metadata errors"), message); - } + SetStandardValue( kOutputMessages, QVariant::fromValue(message)); } void ShaderFilterNode::updateInputList( const ShaderInputsParser & parser) diff --git a/app/node/filter/shader/shader.h b/app/node/filter/shader/shader.h index e1c339f7bb..8bad61fb6f 100644 --- a/app/node/filter/shader/shader.h +++ b/app/node/filter/shader/shader.h @@ -26,6 +26,8 @@ namespace olive { class ShaderInputsParser; +class MessageDialog; + /** @brief * A node that implements a GLSL script. The inputs of this node @@ -54,6 +56,8 @@ class ShaderFilterNode : public Node static const QString kShaderCode; + static const QString kOutputMessages; + private: void parseShaderCode(); void onShaderCodeChanged(); diff --git a/app/widget/nodeparamview/nodeparamviewtextedit.cpp b/app/widget/nodeparamview/nodeparamviewtextedit.cpp index b85b2a9590..3cdb1e415e 100644 --- a/app/widget/nodeparamview/nodeparamviewtextedit.cpp +++ b/app/widget/nodeparamview/nodeparamviewtextedit.cpp @@ -25,17 +25,20 @@ #include "dialog/text/text.h" #include "dialog/codeeditor/codeeditordialog.h" -#include "ui/icons/icons.h" #include "dialog/codeeditor/externaleditorproxy.h" +#include "dialog/codeeditor/messagehighlighter.h" +#include "ui/icons/icons.h" #include "config/config.h" + #include namespace olive { NodeParamViewTextEdit::NodeParamViewTextEdit(QWidget *parent) : QWidget(parent), - code_editor_flag_(false) + code_editor_flag_(false), + code_issues_flag_(false) { QHBoxLayout* layout = new QHBoxLayout(this); layout->setMargin(0); @@ -56,26 +59,22 @@ NodeParamViewTextEdit::NodeParamViewTextEdit(QWidget *parent) : this, & NodeParamViewTextEdit::OnTextChangedExternally); } + void NodeParamViewTextEdit::ShowTextDialog() { QString text; if (code_editor_flag_) { - if (Config::Current()["EditorUseInternal"].toBool()) { - CodeEditorDialog d(this->text(), this); - - if (d.exec() == QDialog::Accepted) { - text = d.text(); - } - } - else { - ext_editor_proxy_->launch( this->text()); - } + launchCodeEditor(text); } else { TextDialog d(this->text(), this); + if (code_issues_flag_) { + d.setSyntaxHighlight( new MessageSyntaxHighlighter()); + } + if (d.exec() == QDialog::Accepted) { text= d.text(); } @@ -87,6 +86,24 @@ void NodeParamViewTextEdit::ShowTextDialog() } } +void olive::NodeParamViewTextEdit::launchCodeEditor(QString & text) +{ + if (Config::Current()["EditorUseInternal"].toBool()) { + + // internal editor + CodeEditorDialog d(this->text(), this); + + if (d.exec() == QDialog::Accepted) { + text = d.text(); + } + } + else { + + // external editor + ext_editor_proxy_->launch( this->text()); + } +} + void NodeParamViewTextEdit::InnerWidgetTextChanged() { emit textEdited(this->text()); @@ -98,7 +115,7 @@ void NodeParamViewTextEdit::OnTextChangedExternally(const QString &new_text) emit textEdited( new_text); } -void NodeParamViewTextEdit::setCodeEditoFlag() +void NodeParamViewTextEdit::setCodeEditorFlag() { code_editor_flag_ = true; @@ -108,4 +125,12 @@ void NodeParamViewTextEdit::setCodeEditoFlag() line_edit_->setEnabled( false); } +void NodeParamViewTextEdit::setCodeIssuesFlag() +{ + code_issues_flag_ = true; + line_edit_->setReadOnly( true); + + new MessageSyntaxHighlighter( line_edit_->document()); +} + } diff --git a/app/widget/nodeparamview/nodeparamviewtextedit.h b/app/widget/nodeparamview/nodeparamviewtextedit.h index 8091b98de4..97936f9540 100644 --- a/app/widget/nodeparamview/nodeparamviewtextedit.h +++ b/app/widget/nodeparamview/nodeparamviewtextedit.h @@ -42,9 +42,10 @@ class NodeParamViewTextEdit : public QWidget return line_edit_->toPlainText(); } - // if this function is called, the text editor is used - // for shader code - void setCodeEditoFlag(); + // set flag to edit text with code editor + void setCodeEditorFlag(); + // set flag to view text as code issues + void setCodeIssuesFlag(); public slots: void setText(const QString &s) @@ -74,8 +75,12 @@ public slots: private: QPlainTextEdit* line_edit_; bool code_editor_flag_; + bool code_issues_flag_; ExternalEditorProxy * ext_editor_proxy_; +private: + void launchCodeEditor(QString & text); + private slots: void ShowTextDialog(); @@ -87,4 +92,5 @@ private slots: } + #endif // NODEPARAMVIEWTEXTEDIT_H diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index 9db0c9fa17..ce3b97fcd3 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -143,9 +143,13 @@ void NodeParamViewWidgetBridge::CreateWidgets() NodeParamViewTextEdit* line_edit = new NodeParamViewTextEdit(); widgets_.append(line_edit); - // plain text editor or code editor - if (GetInnerInput().GetProperty(QStringLiteral("is_shader_code")).toBool()) { - line_edit->setCodeEditoFlag(); + // check for special type of text + QString text_type = GetInnerInput().GetProperty(QStringLiteral("text_type")).toString(); + if (text_type == "shader_code") { + line_edit->setCodeEditorFlag(); + } + else if (text_type == "shader_issues") { + line_edit->setCodeIssuesFlag(); } connect(line_edit, &NodeParamViewTextEdit::textEdited, this, &NodeParamViewWidgetBridge::WidgetCallback); From de6502eb4292c8a0bee0f89a26252e0fdeff0a3d Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Thu, 10 Feb 2022 20:50:13 +0100 Subject: [PATCH 11/49] Basic functionality discussed in PR 1839 complete, including external editor. More tests should be done. --- app/node/filter/shader/shader.cpp | 5 ++++- app/widget/nodeparamview/nodeparamviewitem.cpp | 5 ++++- app/widget/nodeparamview/nodeparamviewtextedit.cpp | 2 ++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index b881d32119..8c87cb4b93 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -166,7 +166,10 @@ void ShaderFilterNode::reportErrorList( const ShaderInputsParser & parser) { const QList & errors = parser.ErrorList(); - QString message = QString(tr("There are %1 issues.\n").arg(errors.size())); + QString message = QString(tr("None")); + if (errors.size() > 0) { + message = QString(tr("There are %1 issues.\n").arg(errors.size())); + } for (ShaderInputsParser::Error e : errors ) { message.append(QString("\"%1\" line %2: %3\n"). diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 43489b6197..827fd3549b 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -272,7 +272,10 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const for (int i=0; iwidgets().size(); i++) { QWidget* w = ui_objects.widget_bridge->widgets().at(i); - layout->addWidget(w, row, i+kWidgetStartColumn); + // some (non-keyframeable) widgets can use all space to the right + int column_span = (w->property("is_exapandable").toBool()) ? -1 : 1; + + layout->addWidget(w, row, i+kWidgetStartColumn, 1, column_span); } // In case this input is a group, resolve that actual input to use for connected labels diff --git a/app/widget/nodeparamview/nodeparamviewtextedit.cpp b/app/widget/nodeparamview/nodeparamviewtextedit.cpp index 3cdb1e415e..63ca454fe3 100644 --- a/app/widget/nodeparamview/nodeparamviewtextedit.cpp +++ b/app/widget/nodeparamview/nodeparamviewtextedit.cpp @@ -118,6 +118,7 @@ void NodeParamViewTextEdit::OnTextChangedExternally(const QString &new_text) void NodeParamViewTextEdit::setCodeEditorFlag() { code_editor_flag_ = true; + setProperty("is_exapandable", QVariant::fromValue(true)); // if the text box is a shader code editor, make it read only so that // the shader code is not re-parsed on every key pressed by the user. @@ -129,6 +130,7 @@ void NodeParamViewTextEdit::setCodeIssuesFlag() { code_issues_flag_ = true; line_edit_->setReadOnly( true); + setProperty("is_exapandable", QVariant::fromValue(true)); new MessageSyntaxHighlighter( line_edit_->document()); } From 658e0fac3f3bc31566b4c90b63c0a8d89e81e8b1 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Mon, 14 Feb 2022 20:31:35 +0100 Subject: [PATCH 12/49] Added input selected with combo box --- app/dialog/codeeditor/glslhighlighter.cpp | 7 ++-- app/node/filter/shader/shader.cpp | 4 ++ app/node/filter/shader/shaderinputsparser.cpp | 42 ++++++++++++++++++- app/node/filter/shader/shaderinputsparser.h | 3 ++ 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/app/dialog/codeeditor/glslhighlighter.cpp b/app/dialog/codeeditor/glslhighlighter.cpp index 9397a06f0d..8537203498 100644 --- a/app/dialog/codeeditor/glslhighlighter.cpp +++ b/app/dialog/codeeditor/glslhighlighter.cpp @@ -78,9 +78,10 @@ const QString oliveMarkupPatterns[] = { QStringLiteral("//OVE\\s+shader_name:[^\n]*"), QStringLiteral("//OVE\\s+shader_description:[^\n]*"), QStringLiteral("//OVE\\s+shader_version:[^\n]*"), QStringLiteral("//OVE\\s+name:[^\n]*"), QStringLiteral("//OVE\\s+type:[^\n]*"), QStringLiteral("//OVE\\s+flag:[^\n]*"), - QStringLiteral("//OVE\\s+description:[^\n]*"), QStringLiteral("//OVE\\s+default:[^\n]*"), - QStringLiteral("//OVE\\s+min:[^\n]*"), QStringLiteral("//OVE\\s+max:[^\n]*"), - QStringLiteral("//OVE\\s+end\\b[^\n]*") + QStringLiteral("//OVE\\s+values:[^\n]*"), QStringLiteral("//OVE\\s+description:[^\n]*"), + QStringLiteral("//OVE\\s+default:[^\n]*"), QStringLiteral("//OVE\\s+min:[^\n]*"), + QStringLiteral("//OVE\\s+max:[^\n]*"), + QStringLiteral("//OVE\\s+end\\b[^\n]*") }; } diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index 8c87cb4b93..a346fb3662 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -196,6 +196,10 @@ void ShaderFilterNode::updateInputList( const ShaderInputsParser & parser) SetInputName( it->uniform_name, it->human_name); SetInputProperty( it->uniform_name, QStringLiteral("min"), it->min); SetInputProperty( it->uniform_name, QStringLiteral("max"), it->max); + + if (it->type == NodeValue::kCombo) { + SetComboBoxStrings(it->uniform_name, it->values); + } } // compare 'new_input_list' and 'user_input_list_' to find deleted inputs. diff --git a/app/node/filter/shader/shaderinputsparser.cpp b/app/node/filter/shader/shaderinputsparser.cpp index a7d84eb928..f8415db830 100644 --- a/app/node/filter/shader/shaderinputsparser.cpp +++ b/app/node/filter/shader/shaderinputsparser.cpp @@ -27,6 +27,7 @@ #include "render/color.h" + namespace olive { namespace { @@ -61,6 +62,8 @@ QRegularExpression INPUT_MIN_REGEX("^\\s*//OVE\\s*min:\\s*(?.*)\\s*$"); QRegularExpression INPUT_MAX_REGEX("^\\s*//OVE\\s*max:\\s*(?.*)\\s*$"); // default value QRegularExpression INPUT_DEFAULT_REGEX("^\\s*//OVE\\s*default:\\s*(?.*)\\s*$"); +// list of values for a selection combo +QRegularExpression INPUT_VALUES_REGEX("^\\s*//OVE\\s*values:\\s*(?.*)\\s*$"); // description. So far not used by application QRegularExpression INPUT_DESCRIPTION_REGEX("^\\s*//OVE\\s*description:\\s*(?.*)\\s*$"); @@ -72,7 +75,8 @@ const QMap INPUT_TYPE_TABLE{{"TEXTURE", NodeValue::kTe {"COLOR", NodeValue::kColor}, {"FLOAT", NodeValue::kFloat}, {"INTEGER", NodeValue::kInt}, - {"BOOLEAN", NodeValue::kBoolean} + {"BOOLEAN", NodeValue::kBoolean}, + {"SELECTION", NodeValue::kCombo} }; // table with default minimum and maximum values for a node type. @@ -82,7 +86,7 @@ const QMap MINIMUM_TABLE{{NodeValue::kFloat, QVariant }; const QMap MAXIMUM_TABLE{{NodeValue::kFloat, QVariant( std::numeric_limits::max())}, - {NodeValue::kInt, QVariant()} + {NodeValue::kInt, QVariant( std::numeric_limits::max())} }; // features of the input that's currently being parsed @@ -96,6 +100,7 @@ void clearCurrentInput() currentInput.type_string.clear(); currentInput.type = NodeValue::kNone; currentInput.flags = InputFlags(kInputFlagNormal); + currentInput.values.clear(); currentInput.min = QVariant(); currentInput.max = QVariant(); currentInput.default_value = QVariant(); @@ -119,6 +124,7 @@ ShaderInputsParser::ShaderInputsParser( const QString & shader_code) : INPUT_PARAM_PARSE_TABLE.insert( & INPUT_UNIFORM_REGEX, & ShaderInputsParser::parseInputUniform); INPUT_PARAM_PARSE_TABLE.insert( & INPUT_TYPE_REGEX, & ShaderInputsParser::parseInputType); INPUT_PARAM_PARSE_TABLE.insert( & INPUT_FLAG_REGEX, & ShaderInputsParser::parseInputFlags); + INPUT_PARAM_PARSE_TABLE.insert( & INPUT_VALUES_REGEX, & ShaderInputsParser::parseInputValueList); INPUT_PARAM_PARSE_TABLE.insert( & INPUT_MIN_REGEX, & ShaderInputsParser::parseInputMin); INPUT_PARAM_PARSE_TABLE.insert( & INPUT_MAX_REGEX, & ShaderInputsParser::parseInputMax); INPUT_PARAM_PARSE_TABLE.insert( & INPUT_DEFAULT_REGEX, & ShaderInputsParser::parseInputDefault); @@ -301,6 +307,25 @@ ShaderInputsParser::parseInputFlags(const QRegularExpressionMatch & line_match) return PARSING; } +ShaderInputsParser::InputParseState +ShaderInputsParser::parseInputValueList(const QRegularExpressionMatch & line_match) +{ + // entries are enclosed in double quotes + static const QRegularExpression COMBO_ENTRY_REGEX("\"([^\"]+)\""); + + QStringRef values_line = line_match.capturedRef("values"); + QRegularExpressionMatchIterator value_match = COMBO_ENTRY_REGEX.globalMatch( values_line); + + while (value_match.hasNext()) { + QRegularExpressionMatch value = value_match.next(); + QString value_str = value.captured(1); + + currentInput.values << value_str; + } + + return PARSING; +} + ShaderInputsParser::InputParseState ShaderInputsParser::parseInputMin(const QRegularExpressionMatch & match) { @@ -402,6 +427,19 @@ ShaderInputsParser::parseInputDefault(const QRegularExpressionMatch & match) currentInput.default_value = parseColor( default_string); break; + case NodeValue::kCombo: + int_value = default_string.toInt( &ok); + + if (ok) + { + currentInput.default_value = QVariant::fromValue(int_value); + } + else + { + reportError(QObject::tr("Default for SELECTION must be a number from 0 to number of entries minus 1.")); + } + break; + default: case NodeValue::kTexture: case NodeValue::kNone: diff --git a/app/node/filter/shader/shaderinputsparser.h b/app/node/filter/shader/shaderinputsparser.h index c72401f188..5ee333204f 100644 --- a/app/node/filter/shader/shaderinputsparser.h +++ b/app/node/filter/shader/shaderinputsparser.h @@ -51,6 +51,8 @@ class ShaderInputsParser NodeValue::Type type; InputFlags flags; + // list of entries for a selection combo + QStringList values; QVariant min; QVariant max; @@ -111,6 +113,7 @@ class ShaderInputsParser InputParseState parseInputUniform( const QRegularExpressionMatch &); InputParseState parseInputType( const QRegularExpressionMatch &); InputParseState parseInputFlags( const QRegularExpressionMatch &); + InputParseState parseInputValueList( const QRegularExpressionMatch &); InputParseState parseInputMin( const QRegularExpressionMatch &); InputParseState parseInputMax( const QRegularExpressionMatch &); InputParseState parseInputDefault( const QRegularExpressionMatch &); From b015f8ad4d5bca0eab30bc8c8c2305563acfb1bd Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Sat, 19 Feb 2022 23:54:55 +0100 Subject: [PATCH 13/49] Fixed an issue with search bar in code editor --- app/dialog/codeeditor/codeeditordialog.cpp | 17 ++++++++- app/dialog/codeeditor/codeeditordialog.h | 1 + app/dialog/codeeditor/searchtextbar.cpp | 44 +++++++++++++--------- app/dialog/codeeditor/searchtextbar.h | 4 +- 4 files changed, 45 insertions(+), 21 deletions(-) diff --git a/app/dialog/codeeditor/codeeditordialog.cpp b/app/dialog/codeeditor/codeeditordialog.cpp index 040c501d49..7fd935827b 100644 --- a/app/dialog/codeeditor/codeeditordialog.cpp +++ b/app/dialog/codeeditor/codeeditordialog.cpp @@ -43,12 +43,14 @@ CodeEditorDialog::CodeEditorDialog(const QString &start, QWidget* parent) : QAction * add_float_input = edit_addSnippet_menu->addAction(tr("float")); QAction * add_int_input = edit_addSnippet_menu->addAction(tr("integer")); QAction * add_boolean_input = edit_addSnippet_menu->addAction(tr("boolean")); + QAction * add_selection_input = edit_addSnippet_menu->addAction(tr("selection")); connect( add_texture_input, &QAction::triggered, this, &CodeEditorDialog::OnActionAddInputTexture); connect( add_color_input, &QAction::triggered, this, &CodeEditorDialog::OnActionAddInputColor); connect( add_float_input, &QAction::triggered, this, &CodeEditorDialog::OnActionAddInputFloat); connect( add_int_input, &QAction::triggered, this, &CodeEditorDialog::OnActionAddInputInt); connect( add_boolean_input, &QAction::triggered, this, &CodeEditorDialog::OnActionAddInputBoolean); + connect( add_selection_input, &QAction::triggered, this, &CodeEditorDialog::OnActionAddInputSelection); search_bar_ = new SearchTextBar( this); search_bar_->hide(); @@ -121,8 +123,19 @@ void CodeEditorDialog::OnActionAddInputBoolean() "//OVE type: BOOLEAN\n" "//OVE flag: NOT_CONNECTABLE, NOT_KEYFRAMABLE\n" "//OVE default: false\n" - "//OVE description: when True, the input is passed to output as is.\n" - "uniform bool disable;\n"); + "//OVE description: \n" + "uniform bool my_bool;\n"); +} + +void CodeEditorDialog::OnActionAddInputSelection() +{ + text_edit_->insertPlainText( + "//OVE name: my selection\n" + "//OVE type: SELECTION\n" + "//OVE values: \"FIRST CHOICE\", \"SECOND CHOICE\"\n" + "//OVE default: 0\n" + "//OVE description: \n" + "uniform int my_selection;\n"); } void CodeEditorDialog::OnFindRequest() diff --git a/app/dialog/codeeditor/codeeditordialog.h b/app/dialog/codeeditor/codeeditordialog.h index 6a5bed9db0..42c17f71a8 100644 --- a/app/dialog/codeeditor/codeeditordialog.h +++ b/app/dialog/codeeditor/codeeditordialog.h @@ -50,6 +50,7 @@ private slots: void OnActionAddInputFloat(); void OnActionAddInputInt(); void OnActionAddInputBoolean(); + void OnActionAddInputSelection(); void OnFindRequest(); }; diff --git a/app/dialog/codeeditor/searchtextbar.cpp b/app/dialog/codeeditor/searchtextbar.cpp index eae99912d9..1093430cad 100644 --- a/app/dialog/codeeditor/searchtextbar.cpp +++ b/app/dialog/codeeditor/searchtextbar.cpp @@ -76,7 +76,7 @@ SearchTextBar::SearchTextBar(QWidget *parent) : replace_label->setBuddy( replace_edit_); QPushButton * replace_button = new QPushButton(tr("replace"), this); - QPushButton * replace_and_find_button = new QPushButton(tr("replace & find"), this); + QPushButton * replace_and_find_button = new QPushButton(tr("replace and find"), this); replace_edit_->setSizePolicy( QSizePolicy::Expanding , QSizePolicy::Preferred); main_layout->addWidget( replace_label, 1,0,1,1); @@ -88,7 +88,7 @@ SearchTextBar::SearchTextBar(QWidget *parent) : connect( find_forward_button, & QPushButton::clicked, this, & SearchTextBar::onNextButtonClicked); connect( hide_button, & QPushButton::clicked, this, & SearchTextBar::onHideButtonClicked); connect( replace_button, & QPushButton::clicked, this, & SearchTextBar::onReplaceButtonCLicked); - connect( replace_and_find_button, & QPushButton::clicked, this, & SearchTextBar::onReplaceAndFindButtonCLicked); + connect( replace_and_find_button, & QPushButton::clicked, this, & SearchTextBar::onReplaceAndFindButtonClicked); find_edit_->setTabOrder( find_edit_, replace_edit_); @@ -127,28 +127,37 @@ void SearchTextBar::onTimeoutExpired() timer_->stop(); } -void SearchTextBar::keyReleaseEvent(QKeyEvent * event) +void SearchTextBar::keyPressEvent(QKeyEvent * event) { - QWidget::keyReleaseEvent( event); - - // enter or return key search forward; use shift to search backward + // enter or return key search forward; use shift to search backward. + // Perform replace only if "replace" input has focus if ((event->key() == Qt::Key_Enter) || (event->key() == Qt::Key_Return)) { - QTextDocument::FindFlags flags = getCurrentFlags(); - - if (event->modifiers() & Qt::ShiftModifier) - { - emit searchTextBackward( find_edit_->text(), flags); + if (replace_edit_->hasFocus()) { + onReplaceAndFindButtonClicked(); } - else - { - emit searchTextForward( find_edit_->text(), flags); + else { + + if (event->modifiers() & Qt::ShiftModifier) + { + onPrevButtonClicked(); + } + else + { + onNextButtonClicked(); + } } + + event->accept(); } else if (event->key() == Qt::Key_Escape) { hide(); + event->accept(); + } + else { + QWidget::keyPressEvent( event); } } @@ -161,13 +170,13 @@ void SearchTextBar::onHideButtonClicked() void SearchTextBar::onPrevButtonClicked() { QTextDocument::FindFlags flags = getCurrentFlags(); - emit searchTextForward( find_edit_->text(), flags); + emit searchTextBackward( find_edit_->text(), flags); } void SearchTextBar::onNextButtonClicked() { QTextDocument::FindFlags flags = getCurrentFlags(); - emit searchTextBackward( find_edit_->text(), flags); + emit searchTextForward( find_edit_->text(), flags); } void SearchTextBar::onReplaceButtonCLicked() @@ -176,10 +185,11 @@ void SearchTextBar::onReplaceButtonCLicked() emit replaceText( find_edit_->text(), replace_edit_->text(), flags, false); } -void SearchTextBar::onReplaceAndFindButtonCLicked() +void SearchTextBar::onReplaceAndFindButtonClicked() { QTextDocument::FindFlags flags = getCurrentFlags(); emit replaceText( find_edit_->text(), replace_edit_->text(), flags, false); + emit searchTextForward( find_edit_->text(), flags); } QTextDocument::FindFlags SearchTextBar::getCurrentFlags() diff --git a/app/dialog/codeeditor/searchtextbar.h b/app/dialog/codeeditor/searchtextbar.h index 7a1531198b..6674984dde 100644 --- a/app/dialog/codeeditor/searchtextbar.h +++ b/app/dialog/codeeditor/searchtextbar.h @@ -61,7 +61,7 @@ public slots: // QWidget interface protected: - void keyReleaseEvent(QKeyEvent * event) override; + void keyPressEvent(QKeyEvent * event) override; private slots: void onTimeoutExpired(); @@ -69,7 +69,7 @@ private slots: void onPrevButtonClicked(); void onNextButtonClicked(); void onReplaceButtonCLicked(); - void onReplaceAndFindButtonCLicked(); + void onReplaceAndFindButtonClicked(); private: QTextDocument::FindFlags getCurrentFlags(); From 1b93615074b5f0c96318576dd238c7d9ff003c76 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Mon, 14 Mar 2022 18:20:31 +0100 Subject: [PATCH 14/49] minor feature added: matching brackets highlight and configurable window size --- app/config/config.cpp | 2 + app/dialog/codeeditor/codeeditordialog.cpp | 2 +- app/dialog/codeeditor/editor.cpp | 179 +++++++++++++++++- app/dialog/codeeditor/editor.h | 9 +- app/dialog/codeeditor/glslhighlighter.cpp | 72 +++++-- app/dialog/codeeditor/glslhighlighter.h | 39 ++++ .../preferences/tabs/preferencesedittab.cpp | 23 ++- .../preferences/tabs/preferencesedittab.h | 2 + 8 files changed, 302 insertions(+), 26 deletions(-) diff --git a/app/config/config.cpp b/app/config/config.cpp index 0dcb4ad8c9..10ffa2e3e5 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -143,6 +143,8 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("EditorExternalParams"), NodeValue::kText, QString()); SetEntryInternal(QStringLiteral("EditorInternalFontSize"), NodeValue::kInt, 14); SetEntryInternal(QStringLiteral("EditorInternalIndentSize"), NodeValue::kInt, 3); + SetEntryInternal(QStringLiteral("EditorInternalWindowWidth"), NodeValue::kInt, 800); + SetEntryInternal(QStringLiteral("EditorInternalWindowHeight"), NodeValue::kInt, 600); } void Config::Load() diff --git a/app/dialog/codeeditor/codeeditordialog.cpp b/app/dialog/codeeditor/codeeditordialog.cpp index 7fd935827b..544e4816f9 100644 --- a/app/dialog/codeeditor/codeeditordialog.cpp +++ b/app/dialog/codeeditor/codeeditordialog.cpp @@ -113,7 +113,7 @@ void CodeEditorDialog::OnActionAddInputInt() "//OVE max: 10\n" "//OVE default: 0\n" "//OVE description:\n" - "uniform float my_int;\n"); + "uniform int my_int;\n"); } void CodeEditorDialog::OnActionAddInputBoolean() diff --git a/app/dialog/codeeditor/editor.cpp b/app/dialog/codeeditor/editor.cpp index 4692f50584..41f4c0d919 100644 --- a/app/dialog/codeeditor/editor.cpp +++ b/app/dialog/codeeditor/editor.cpp @@ -32,6 +32,17 @@ #include "glslhighlighter.h" +namespace { +// matching bracket for each kind of parenthesis +const QMap BRACKET_PAIR = QMap{{'(', ')'}, {'[', ']'}, {'{', '}'}, + {')', '('}, {']', '['}, {'}', '{'}}; + +const Qt::GlobalColor BRACKET_MATCH_COLOR = Qt::darkGreen; +const Qt::GlobalColor BRACKET_NOT_MATCH_COLOR = Qt::darkRed; + +// used in parentheses match algorithm. +const int BRACKET_LAST = -2; +} namespace olive { @@ -42,7 +53,7 @@ CodeEditor::CodeEditor(QWidget *parent) : connect( this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int))); connect( this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int))); - connect( this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine())); + connect( this, SIGNAL(cursorPositionChanged()), this, SLOT(onCursorPositionChanged())); updateLineNumberAreaWidth(0); highlightCurrentLine(); @@ -54,7 +65,6 @@ CodeEditor::CodeEditor(QWidget *parent) : setContextMenuPolicy( Qt::DefaultContextMenu); - // These hard coded settings should be customizable in settings dialog setMinimumSize( 900, 600); QFont font("Monospace"); @@ -64,6 +74,12 @@ CodeEditor::CodeEditor(QWidget *parent) : int fontSize = Config::Current()["EditorInternalFontSize"].toInt( & valid); fontSize = valid ? fontSize : 14; font.setPointSize( fontSize); + int win_width = Config::Current()["EditorInternalWindowWidth"].toInt( & valid); + win_width = valid ? win_width : 800; + int win_height = Config::Current()["EditorInternalWindowHeight"].toInt( & valid); + win_height = valid ? win_height : 600; + + setMinimumSize( win_width, win_height); setFont( font); @@ -94,7 +110,7 @@ void CodeEditor::gotoLineNumber(int lineNumber) /* block number start from 0 where 'lineNumber' start from 1 */ QTextCursor cursor(document()->findBlockByLineNumber(lineNumber - 1)); setTextCursor(cursor); - highlightCurrentLine(); + onCursorPositionChanged(); } void CodeEditor::onSearchBackwardRequest(const QString &text, QTextDocument::FindFlags flags) @@ -197,10 +213,19 @@ void CodeEditor::resizeEvent(QResizeEvent *e) } -void CodeEditor::highlightCurrentLine() +void CodeEditor::onCursorPositionChanged() { - QList extraSelections; + extra_selections_.clear(); + + highlightCurrentLine(); + matchParenthesis(); + + setExtraSelections(extra_selections_); +} + +void CodeEditor::highlightCurrentLine() +{ QTextEdit::ExtraSelection selection; QColor lineColor = QColor(40,40,0); @@ -209,9 +234,149 @@ void CodeEditor::highlightCurrentLine() selection.format.setProperty( QTextFormat::FullWidthSelection, true); selection.cursor = textCursor(); selection.cursor.clearSelection(); - extraSelections.append(selection); + extra_selections_.append(selection); +} + + +void CodeEditor::matchParenthesis() +{ + TextBlockData * parenthesis_data = static_cast(textCursor().block().userData()); + + if (parenthesis_data) { + const QVector & infos = parenthesis_data->parentheses(); + + int pos = textCursor().block().position(); + for (int i = 0; i < infos.size(); ++i) { + const ParenthesisInfo & info = infos.at(i); + + int cur_pos = textCursor().position() - textCursor().block().position(); + + if ((info.position == (cur_pos)) && (QString("([{").contains(info.character))) { + if (matchLeftParenthesis( info.character, textCursor().block(), i + 1, 0)) { + createParenthesisSelection(pos + info.position, BRACKET_MATCH_COLOR); + } else { + createParenthesisSelection(pos + info.position, BRACKET_NOT_MATCH_COLOR); + } + } else if ((info.position == (cur_pos - 1)) && (QString(")]}").contains(info.character))) { + if (matchRightParenthesis( info.character, textCursor().block(), i - 1, 0)) { + createParenthesisSelection(pos + info.position, BRACKET_MATCH_COLOR); + } else { + createParenthesisSelection(pos + info.position, BRACKET_NOT_MATCH_COLOR); + } + } + } + } +} + + +// search for a right (closing) bracket that matches a given left (opening) bracket. +// The search is performed in the 'currentBlock' or recursively in next one. +// 'numLeftParentheses' is the counter of additional left brackets found in previous calls. +// Param 'i' (index) is used when the search does not start from the begin of the block: in this case, +// we don't have to search brackets after the current cursor position. +// +bool CodeEditor::matchLeftParenthesis( char left, QTextBlock currentBlock, int i, int numLeftParentheses) +{ + TextBlockData * parenthesis_data = static_cast(currentBlock.userData()); + const QVector & infos = parenthesis_data->parentheses(); + + bool found_matching = false; + int doc_pos = currentBlock.position(); + + for (; i < infos.size(); ++i) { + const ParenthesisInfo & info = infos.at(i); + + if (info.character == left) { + // found another opening bracket + ++numLeftParentheses; + + } else if (info.character == BRACKET_PAIR.value(left)) { + + if (numLeftParentheses == 0) { + //found matching closing bracket + createParenthesisSelection(doc_pos + info.position, BRACKET_MATCH_COLOR); + found_matching = true; + } else { + //found closing bracket, but not the right one + --numLeftParentheses; + } + } + } + + if (found_matching == false) { + currentBlock = currentBlock.next(); + if (currentBlock.isValid()) { + found_matching = matchLeftParenthesis( left, currentBlock, 0, numLeftParentheses); + } + } + + return found_matching; +} + +// search for a left (opening) bracket that matches a given right (closing) bracket. +// The search is performed in the 'currentBlock' or recursively in previous one. +// 'numRightParentheses' is the counter of additional right brackets found in previous calls. +// Param 'i' (index) is used when the search does not start from the end of the block: in this case, +// we don't have to search brackets before the current cursor position. Value "BRACKET_LAST" means +// to start from last item +// +bool CodeEditor::matchRightParenthesis(char right, QTextBlock currentBlock, int i, int numRightParentheses) +{ + TextBlockData * parenthesis_data = static_cast(currentBlock.userData()); + const QVector & parentheses = parenthesis_data->parentheses(); + + bool found_matching = false; + int doc_pos = currentBlock.position(); + + if ((i == BRACKET_LAST) && (parentheses.size() > 0)){ + // start from last item + i = parentheses.size() - 1; + } + + for (; (i > -1) && (parentheses.size() > 0); --i) { + const ParenthesisInfo & info = parentheses.at(i); + + if (info.character == right) { + // found another closing bracket + ++numRightParentheses; + + } else if (info.character == BRACKET_PAIR.value(right)) { + + if (numRightParentheses == 0) { + //found matching opening bracket + createParenthesisSelection(doc_pos + info.position, BRACKET_MATCH_COLOR); + found_matching = true; + } else { + // found opening bracket, but not the right one + --numRightParentheses; + } + } + } + + if (found_matching == false) { + currentBlock = currentBlock.previous(); + if (currentBlock.isValid()) { + found_matching = matchRightParenthesis( right, currentBlock, BRACKET_LAST, numRightParentheses); + } + } + + return found_matching; +} + + +void CodeEditor::createParenthesisSelection(int pos, Qt::GlobalColor color) +{ + QTextEdit::ExtraSelection selection; + QTextCharFormat format = selection.format; + format.setBackground(color); + selection.format = format; + + QTextCursor cursor = textCursor(); + cursor.setPosition(pos); + cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); + selection.cursor = cursor; - setExtraSelections(extraSelections); + extra_selections_.append(selection); } diff --git a/app/dialog/codeeditor/editor.h b/app/dialog/codeeditor/editor.h index a3a7b26ca0..3446601bea 100644 --- a/app/dialog/codeeditor/editor.h +++ b/app/dialog/codeeditor/editor.h @@ -23,6 +23,7 @@ #include #include +#include namespace olive { @@ -57,15 +58,21 @@ public slots: private slots: void updateLineNumberAreaWidth(int newBlockCount); - void highlightCurrentLine(); + void onCursorPositionChanged(); + bool matchLeftParenthesis(char left, QTextBlock currentBlock, int i, int numLeftParentheses); + bool matchRightParenthesis( char right, QTextBlock currentBlock, int i, int numRightParentheses); void updateLineNumberArea(const QRect &, int); private: + void highlightCurrentLine(); + void matchParenthesis(); int countTrailingSpaces( int block_position); + void createParenthesisSelection(int pos, Qt::GlobalColor color); private: QWidget * m_lineNumberArea; int indent_size_; + QList extra_selections_; }; /// @brief helper widget to print line numbers diff --git a/app/dialog/codeeditor/glslhighlighter.cpp b/app/dialog/codeeditor/glslhighlighter.cpp index 8537203498..489048edde 100644 --- a/app/dialog/codeeditor/glslhighlighter.cpp +++ b/app/dialog/codeeditor/glslhighlighter.cpp @@ -54,6 +54,10 @@ namespace olive { namespace { +// flag to indicate to not store info about matching parentheses. +// This is true for comments and string literals +const int IS_NON_PARSABLE = 0x1000; + const QString keywordPatterns[] = { QStringLiteral("\\bclass\\b"), QStringLiteral("\\bconst\\b"), QStringLiteral("\\benum\\b"), QStringLiteral("\\bbreak\\b"), QStringLiteral("\\bshort\\b"), QStringLiteral("\\bif\\b"), @@ -61,7 +65,7 @@ const QString keywordPatterns[] = { QStringLiteral("\\belse\\b"), QStringLiteral("\\breturn\\b"), QStringLiteral("\\btypedef\\b"), QStringLiteral("\\bvolatile\\b"), QStringLiteral("\\bgl_FragColor\\b"), QStringLiteral("\\bswitch\\b"), QStringLiteral("\\btrue\\b"), QStringLiteral("\\bfalse\\b"), QStringLiteral("\\bstatic\\b"), - QStringLiteral("\\bdefault\\b"), QStringLiteral("\\b#define\\b") + QStringLiteral("\\bdefault\\b"), QStringLiteral("#define\\b") }; const QString typePatterns[] = { @@ -118,11 +122,13 @@ GlslHighlighter::GlslHighlighter(QTextDocument *parent) highlighting_rules_.append(rule); single_line_comment_format_.setForeground(QColor(60,180,80)); + single_line_comment_format_.setProperty( IS_NON_PARSABLE, QVariant::fromValue(true)); rule.pattern = QRegularExpression(QStringLiteral("//[^\n]*")); rule.format = single_line_comment_format_; highlighting_rules_.append(rule); markup_line_comment_format_.setForeground(QColor(70,120,130)); + markup_line_comment_format_.setProperty( IS_NON_PARSABLE, QVariant::fromValue(true)); for (const QString &pattern : oliveMarkupPatterns) { rule.pattern = QRegularExpression(pattern); rule.format = markup_line_comment_format_; @@ -130,8 +136,10 @@ GlslHighlighter::GlslHighlighter(QTextDocument *parent) } multiline_comment_format_.setForeground(QColor(60,180,80)); + multiline_comment_format_.setProperty( IS_NON_PARSABLE, QVariant::fromValue(true)); quotation_format_.setForeground(QColor(130,130,80)); + quotation_format_.setProperty( IS_NON_PARSABLE, QVariant::fromValue(true)); rule.pattern = QRegularExpression(QStringLiteral("\".*\"")); rule.format = quotation_format_; highlighting_rules_.append(rule); @@ -142,6 +150,14 @@ GlslHighlighter::GlslHighlighter(QTextDocument *parent) void GlslHighlighter::highlightBlock(const QString &text) +{ + highlightKeywords(text); + highlightMultilineComments(text); + + storeParenthesisInfo(text); +} + +void olive::GlslHighlighter::highlightKeywords(const QString &text) { for (const HighlightingRule &rule : qAsConst(highlighting_rules_)) { QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text); @@ -150,28 +166,58 @@ void GlslHighlighter::highlightBlock(const QString &text) setFormat(match.capturedStart(), match.capturedLength(), rule.format); } } +} + + +void olive::GlslHighlighter::highlightMultilineComments(const QString &text) +{ + int start_index = 0; setCurrentBlockState(0); - int startIndex = 0; - if (previousBlockState() != 1) - startIndex = text.indexOf(comment_start_expression_); + if (previousBlockState() != 1) { + start_index = text.indexOf(comment_start_expression_); + } - while (startIndex >= 0) { - QRegularExpressionMatch match = comment_end_expression_.match(text, startIndex); - int endIndex = match.capturedStart(); + while (start_index >= 0) { + QRegularExpressionMatch match = comment_end_expression_.match(text, start_index); + int end_index = match.capturedStart(); int commentLength = 0; - if (endIndex == -1) { + if (end_index == -1) { setCurrentBlockState(1); - commentLength = text.length() - startIndex; + commentLength = text.length() - start_index; } else { - commentLength = endIndex - startIndex - + match.capturedLength(); + commentLength = end_index - start_index + match.capturedLength(); } - setFormat(startIndex, commentLength, multiline_comment_format_); - startIndex = text.indexOf(comment_start_expression_, startIndex + commentLength); + setFormat(start_index, commentLength, multiline_comment_format_); + start_index = text.indexOf(comment_start_expression_, start_index + commentLength); } } + +void olive::GlslHighlighter::storeParenthesisInfo(const QString &text) +{ + static const QRegularExpression ParenthesisRegExp("[\\(\\)\\[\\]\\{\\}]{1}"); + + /* the ownership of this will be passed to QTextBlock */ + TextBlockData *data = new TextBlockData; + + QRegularExpressionMatchIterator i = ParenthesisRegExp.globalMatch(text); + while (i.hasNext()) { + QRegularExpressionMatch match = i.next(); + + // store parenthesis info only if not in comments or string literals + if (format(match.capturedStart(0)).hasProperty(IS_NON_PARSABLE) == false) { + + ParenthesisInfo info; + info.character = match.captured().toLatin1().at(0); + info.position = match.capturedStart(0); + data->insert( info); + } + } + + setCurrentBlockUserData(data); +} + } // namespace olive diff --git a/app/dialog/codeeditor/glslhighlighter.h b/app/dialog/codeeditor/glslhighlighter.h index dc2b96e88e..7320f36597 100644 --- a/app/dialog/codeeditor/glslhighlighter.h +++ b/app/dialog/codeeditor/glslhighlighter.h @@ -55,6 +55,7 @@ #include #include + class QTextDocument; namespace olive { @@ -87,6 +88,44 @@ class GlslHighlighter : public QSyntaxHighlighter QTextCharFormat multiline_comment_format_; QTextCharFormat quotation_format_; QTextCharFormat number_format_; + +private: + void highlightKeywords(const QString &text); + void highlightMultilineComments(const QString &text); + void storeParenthesisInfo(const QString &text); +}; + + +struct ParenthesisInfo +{ + char character; + int position; +}; + +/* utility class to store brakets info */ +class TextBlockData : public QTextBlockUserData +{ +public: + TextBlockData() { + } + + const QVector & parentheses() { + return m_parentheses; + } + + void insert(ParenthesisInfo & info) { + int i = 0; + while (i < m_parentheses.size() && + info.position > m_parentheses.at(i).position) + { + ++i; + } + + m_parentheses.insert(i, info); + } + +private: + QVector m_parentheses; }; } // namespace olive diff --git a/app/dialog/preferences/tabs/preferencesedittab.cpp b/app/dialog/preferences/tabs/preferencesedittab.cpp index 3c38d3f5e4..b5acbf05d6 100644 --- a/app/dialog/preferences/tabs/preferencesedittab.cpp +++ b/app/dialog/preferences/tabs/preferencesedittab.cpp @@ -61,16 +61,27 @@ PreferencesEditTab::PreferencesEditTab() //internal editor QGridLayout * internal_editor_layout = new QGridLayout(internal_editor_box); - internal_editor_layout->addWidget( new QLabel(tr("Font size")), 1, 1); + internal_editor_layout->addWidget( new QLabel(tr("Font size")), 0, 0); font_size_ = new QSpinBox(); - internal_editor_layout->addWidget( font_size_, 1, 2); - internal_editor_layout->addWidget( new QLabel(tr("Indent size")), 2, 1); + internal_editor_layout->addWidget( font_size_, 0, 1); + internal_editor_layout->addWidget( new QLabel(tr("Indent size")), 1, 0); indent_size_ = new QSpinBox(); - internal_editor_layout->addWidget( indent_size_, 2, 2); + internal_editor_layout->addWidget( indent_size_, 1, 1); + internal_editor_layout->addWidget( new QLabel(tr("Window size (WxH)")), 2, 0); + window_width_ = new QSpinBox(); + window_heigth_ = new QSpinBox(); + internal_editor_layout->addWidget( window_width_, 2, 1); + internal_editor_layout->addWidget( window_heigth_, 2, 2); + internal_editor_layout->addItem( new QSpacerItem(1,1, QSizePolicy::Expanding), 0, 3); internal_editor_layout->addItem( new QSpacerItem(1,1, QSizePolicy::Expanding), 1, 3); internal_editor_layout->addItem( new QSpacerItem(1,1, QSizePolicy::Expanding), 2, 3); outer_layout->addWidget(internal_editor_box); + window_heigth_->setMinimum(10); + window_heigth_->setMaximum(4096); + window_width_->setMinimum(10); + window_width_->setMaximum(4096); + //external editor QVBoxLayout * external_editor_layout = new QVBoxLayout(external_editor_box); external_editor_layout->addWidget( @@ -96,6 +107,8 @@ PreferencesEditTab::PreferencesEditTab() indent_size_->setValue( Config::Current()["EditorInternalIndentSize"].toInt()); ext_command_->setText( Config::Current()["EditorExternalCommand"].toString()); ext_params_->setText( Config::Current()["EditorExternalParams"].toString()); + window_heigth_->setValue( Config::Current()["EditorInternalWindowHeight"].toInt()); + window_width_->setValue( Config::Current()["EditorInternalWindowWidth"].toInt()); } bool PreferencesEditTab::Validate() @@ -112,6 +125,8 @@ void PreferencesEditTab::Accept(MultiUndoCommand *command) Config::Current()["EditorExternalParams"] = QVariant::fromValue(ext_params_->text()); Config::Current()["EditorInternalFontSize"] = QVariant::fromValue(font_size_->value()); Config::Current()["EditorInternalIndentSize"] = QVariant::fromValue(indent_size_->value()); + Config::Current()["EditorInternalWindowHeight"] = QVariant::fromValue(window_heigth_->value()); + Config::Current()["EditorInternalWindowWidth"] = QVariant::fromValue(window_width_->value()); } } diff --git a/app/dialog/preferences/tabs/preferencesedittab.h b/app/dialog/preferences/tabs/preferencesedittab.h index b06aa06bf1..3ea0224d3b 100644 --- a/app/dialog/preferences/tabs/preferencesedittab.h +++ b/app/dialog/preferences/tabs/preferencesedittab.h @@ -50,6 +50,8 @@ class PreferencesEditTab : public ConfigDialogBaseTab // for internal editor QSpinBox * font_size_; QSpinBox * indent_size_; + QSpinBox * window_width_; + QSpinBox * window_heigth_; // for external editor QLineEdit * ext_command_; From fd9f2beb5bab3a515210b0c7fb45d6478834072d Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Sun, 20 Mar 2022 12:12:06 +0100 Subject: [PATCH 15/49] Added input kind POINT, to define one or more points in the shader --- app/dialog/codeeditor/codeeditordialog.cpp | 12 +++ app/dialog/codeeditor/codeeditordialog.h | 1 + app/dialog/codeeditor/editor.cpp | 8 +- app/node/filter/shader/shader.cpp | 82 ++++++++++++++++++- app/node/filter/shader/shader.h | 16 ++++ app/node/filter/shader/shaderinputsparser.cpp | 41 +++++++++- app/node/filter/shader/shaderinputsparser.h | 4 +- 7 files changed, 151 insertions(+), 13 deletions(-) diff --git a/app/dialog/codeeditor/codeeditordialog.cpp b/app/dialog/codeeditor/codeeditordialog.cpp index 544e4816f9..af52a2d906 100644 --- a/app/dialog/codeeditor/codeeditordialog.cpp +++ b/app/dialog/codeeditor/codeeditordialog.cpp @@ -44,6 +44,7 @@ CodeEditorDialog::CodeEditorDialog(const QString &start, QWidget* parent) : QAction * add_int_input = edit_addSnippet_menu->addAction(tr("integer")); QAction * add_boolean_input = edit_addSnippet_menu->addAction(tr("boolean")); QAction * add_selection_input = edit_addSnippet_menu->addAction(tr("selection")); + QAction * add_point_input = edit_addSnippet_menu->addAction(tr("point")); connect( add_texture_input, &QAction::triggered, this, &CodeEditorDialog::OnActionAddInputTexture); connect( add_color_input, &QAction::triggered, this, &CodeEditorDialog::OnActionAddInputColor); @@ -51,6 +52,7 @@ CodeEditorDialog::CodeEditorDialog(const QString &start, QWidget* parent) : connect( add_int_input, &QAction::triggered, this, &CodeEditorDialog::OnActionAddInputInt); connect( add_boolean_input, &QAction::triggered, this, &CodeEditorDialog::OnActionAddInputBoolean); connect( add_selection_input, &QAction::triggered, this, &CodeEditorDialog::OnActionAddInputSelection); + connect( add_point_input, &QAction::triggered, this, &CodeEditorDialog::OnActionAddInputPoint); search_bar_ = new SearchTextBar( this); search_bar_->hide(); @@ -138,6 +140,16 @@ void CodeEditorDialog::OnActionAddInputSelection() "uniform int my_selection;\n"); } +void CodeEditorDialog::OnActionAddInputPoint() +{ + text_edit_->insertPlainText( + "//OVE name: my point\n" + "//OVE type: POINT\n" + "//OVE default: (0.4, 0.2)\n" + "//OVE description: \n" + "uniform vec2 my_point;\n"); +} + void CodeEditorDialog::OnFindRequest() { QString text_to_find = text_edit_->textCursor().selection().toPlainText(); diff --git a/app/dialog/codeeditor/codeeditordialog.h b/app/dialog/codeeditor/codeeditordialog.h index 42c17f71a8..dfaa0ba062 100644 --- a/app/dialog/codeeditor/codeeditordialog.h +++ b/app/dialog/codeeditor/codeeditordialog.h @@ -51,6 +51,7 @@ private slots: void OnActionAddInputInt(); void OnActionAddInputBoolean(); void OnActionAddInputSelection(); + void OnActionAddInputPoint(); void OnFindRequest(); }; diff --git a/app/dialog/codeeditor/editor.cpp b/app/dialog/codeeditor/editor.cpp index 41f4c0d919..e1a1136257 100644 --- a/app/dialog/codeeditor/editor.cpp +++ b/app/dialog/codeeditor/editor.cpp @@ -283,7 +283,7 @@ bool CodeEditor::matchLeftParenthesis( char left, QTextBlock currentBlock, int i bool found_matching = false; int doc_pos = currentBlock.position(); - for (; i < infos.size(); ++i) { + for (; (i < infos.size()) && ( ! found_matching); ++i) { const ParenthesisInfo & info = infos.at(i); if (info.character == left) { @@ -297,7 +297,7 @@ bool CodeEditor::matchLeftParenthesis( char left, QTextBlock currentBlock, int i createParenthesisSelection(doc_pos + info.position, BRACKET_MATCH_COLOR); found_matching = true; } else { - //found closing bracket, but not the right one + //found closing bracket, but not the correct one --numLeftParentheses; } } @@ -333,7 +333,7 @@ bool CodeEditor::matchRightParenthesis(char right, QTextBlock currentBlock, int i = parentheses.size() - 1; } - for (; (i > -1) && (parentheses.size() > 0); --i) { + for (; (i > -1) && (parentheses.size() > 0) && ( ! found_matching); --i) { const ParenthesisInfo & info = parentheses.at(i); if (info.character == right) { @@ -347,7 +347,7 @@ bool CodeEditor::matchRightParenthesis(char right, QTextBlock currentBlock, int createParenthesisSelection(doc_pos + info.position, BRACKET_MATCH_COLOR); found_matching = true; } else { - // found opening bracket, but not the right one + // found opening bracket, but not the correct one --numRightParentheses; } } diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index a346fb3662..c06bcd6182 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -141,9 +141,10 @@ void ShaderFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globa job.InsertValue(value); job.InsertValue(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); + job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); // If there's no shader code, no need to run an operation - if (shader_code_.trimmed() != QString()) { + if (shader_code_ != QString()) { table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this); } } @@ -194,8 +195,12 @@ void ShaderFilterNode::updateInputList( const ShaderInputsParser & parser) } SetInputName( it->uniform_name, it->human_name); - SetInputProperty( it->uniform_name, QStringLiteral("min"), it->min); - SetInputProperty( it->uniform_name, QStringLiteral("max"), it->max); + if (it->min.isValid()) { + SetInputProperty( it->uniform_name, QStringLiteral("min"), it->min); + } + if (it->max.isValid()) { + SetInputProperty( it->uniform_name, QStringLiteral("max"), it->max); + } if (it->type == NodeValue::kCombo) { SetComboBoxStrings(it->uniform_name, it->values); @@ -224,3 +229,74 @@ void ShaderFilterNode::checkDeletedInputs(const QStringList & new_inputs) } // namespace olive + + +bool olive::ShaderFilterNode::HasGizmos() const +{ + return (handle_table_.size() > 0); +} + +void olive::ShaderFilterNode::DrawGizmos(const NodeValueRow &row, const NodeGlobals & globals, QPainter *p) +{ + p->setPen(QPen(Qt::white, 0)); + QFont font; + font.setPixelSize(40); + p->setFont( font); + QVector2D resolution = globals.resolution(); + + handle_table_.clear(); + + for (QString aInput : user_input_list_) + { + if (HasInputWithID(aInput)) { + if (row[aInput].type() == NodeValue::kVec2) { + + QVector2D pos = row[aInput].data().value() * resolution; + QRectF handleRect = CreateGizmoHandleRect(QPointF(pos.x(), pos.y()), 10); + p->fillRect( handleRect, Qt::white); + handle_table_[aInput] = handleRect; + + p->drawText( pos.x()+15, pos.y()+15, GetInputName(aInput)); + } + } + } +} + +bool olive::ShaderFilterNode::GizmoPress(const NodeValueRow & /*row*/, const NodeGlobals & globals, const QPointF &p) +{ + resolution_ = globals.resolution(); + + for (const QString & point : handle_table_.keys()) { + if (handle_table_[point].contains(p)) { + currently_dragged_input_ = point; + return true; + } + } + + return false; +} + +void olive::ShaderFilterNode::GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers & /*modifiers*/) +{ + if ( ! dragger_[0].IsStarted()) + { + dragger_[0].Start(NodeKeyframeTrackReference(NodeInput(this, currently_dragged_input_), 0), time); + dragger_[1].Start(NodeKeyframeTrackReference(NodeInput(this, currently_dragged_input_), 1), time); + } + + if (resolution_.x() > 0.0) { + dragger_[0].Drag(p.x()/resolution_.x()); + } + + if (resolution_.y() > 0.0) { + dragger_[1].Drag(p.y()/resolution_.y()); + } +} + +void olive::ShaderFilterNode::GizmoRelease(MultiUndoCommand *command) +{ + dragger_[0].End( command); + dragger_[1].End( command); + + currently_dragged_input_.clear(); +} diff --git a/app/node/filter/shader/shader.h b/app/node/filter/shader/shader.h index 8bad61fb6f..e037664183 100644 --- a/app/node/filter/shader/shader.h +++ b/app/node/filter/shader/shader.h @@ -22,6 +22,7 @@ #define ShaderFilterNode_H #include "node/node.h" +#include "node/inputdragger.h" namespace olive { @@ -71,6 +72,21 @@ class ShaderFilterNode : public Node // user defined inputs QStringList user_input_list_; + // two draggers for x and y, shared by all vec2 inputs + NodeInputDragger dragger_[2]; + // one handle for every vec2 input + QMap handle_table_; + + QString currently_dragged_input_; + QVector2D resolution_; + + // Node interface +public: + bool HasGizmos() const override; + void DrawGizmos(const NodeValueRow &row, const NodeGlobals &globals, QPainter *p) override; + bool GizmoPress(const NodeValueRow &row, const NodeGlobals &globals, const QPointF &p) override; + void GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) override; + void GizmoRelease(MultiUndoCommand *command) override; }; } diff --git a/app/node/filter/shader/shaderinputsparser.cpp b/app/node/filter/shader/shaderinputsparser.cpp index f8415db830..fce11104e5 100644 --- a/app/node/filter/shader/shaderinputsparser.cpp +++ b/app/node/filter/shader/shaderinputsparser.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "render/color.h" @@ -76,13 +77,14 @@ const QMap INPUT_TYPE_TABLE{{"TEXTURE", NodeValue::kTe {"FLOAT", NodeValue::kFloat}, {"INTEGER", NodeValue::kInt}, {"BOOLEAN", NodeValue::kBoolean}, - {"SELECTION", NodeValue::kCombo} + {"SELECTION", NodeValue::kCombo}, + {"POINT", NodeValue::kVec2} }; // table with default minimum and maximum values for a node type. // This is needed to avoid that 0 is used as minimum and maximum if user does not specify one -const QMap MINIMUM_TABLE{{NodeValue::kFloat, QVariant( std::numeric_limits::min())}, - {NodeValue::kInt, QVariant( std::numeric_limits::min())} +const QMap MINIMUM_TABLE{{NodeValue::kFloat, QVariant( std::numeric_limits::lowest())}, + {NodeValue::kInt, QVariant( std::numeric_limits::lowest())} }; const QMap MAXIMUM_TABLE{{NodeValue::kFloat, QVariant( std::numeric_limits::max())}, @@ -238,7 +240,7 @@ ShaderInputsParser::parseShaderDescription(const QRegularExpressionMatch & /*mat ShaderInputsParser::InputParseState ShaderInputsParser::parseShaderVersion(const QRegularExpressionMatch & /*match*/) { - // nothing to do. SO far. + // nothing to do. So far. return PARSING; } @@ -440,6 +442,10 @@ ShaderInputsParser::parseInputDefault(const QRegularExpressionMatch & match) } break; + case NodeValue::kVec2: + currentInput.default_value = parsePoint( default_string); + break; + default: case NodeValue::kTexture: case NodeValue::kNone: @@ -503,6 +509,33 @@ QVariant ShaderInputsParser::parseColor(const QStringRef& line) return color; } +QVariant ShaderInputsParser::parsePoint(const QStringRef &line) +{ + static const QRegularExpression + POINT_REGEX("\\s*\\(\\s*(?[\\d\\.]+)\\s*,\\s*(?[\\d\\.]+)\\s*\\)"); + + QVariant point; + QRegularExpressionMatch match = POINT_REGEX.match(line); + + if (match.hasMatch()) { + float x,y; + bool ok_x, ok_y; + + x = match.captured("x").toFloat( & ok_x); + y = match.captured("y").toFloat( & ok_y); + + if (ok_x && ok_y) { + point = QVariant::fromValue({x,y}); + } + } + + if (point == QVariant()) { + reportError(QObject::tr("Point must be in format '(x,y)' where x and y are float values")); + } + + return point; +} + void ShaderInputsParser::reportError(const QString& error) { Error new_err; diff --git a/app/node/filter/shader/shaderinputsparser.h b/app/node/filter/shader/shaderinputsparser.h index 5ee333204f..044cd0c30a 100644 --- a/app/node/filter/shader/shaderinputsparser.h +++ b/app/node/filter/shader/shaderinputsparser.h @@ -120,9 +120,8 @@ class ShaderInputsParser InputParseState parseInputDescription( const QRegularExpressionMatch &); InputParseState stopParse( const QRegularExpressionMatch &); - QMap< const QRegularExpression *, LineParseFunction> INPUT_PARAM_PARSE_TABLE; - QVariant parseColor( const QStringRef & line); + QVariant parsePoint( const QStringRef & line); void reportError( const QString & error); private: @@ -132,6 +131,7 @@ class ShaderInputsParser int line_number_; QString shader_name_; + QMap< const QRegularExpression *, LineParseFunction> INPUT_PARAM_PARSE_TABLE; }; } From c5b2d7a5d966ae87ae16496f3e04dd7df61122c6 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Wed, 23 Mar 2022 23:22:54 +0100 Subject: [PATCH 16/49] changed syntax highlight for openGL 3.2 --- app/dialog/codeeditor/glslhighlighter.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/dialog/codeeditor/glslhighlighter.cpp b/app/dialog/codeeditor/glslhighlighter.cpp index 489048edde..119544d773 100644 --- a/app/dialog/codeeditor/glslhighlighter.cpp +++ b/app/dialog/codeeditor/glslhighlighter.cpp @@ -63,9 +63,10 @@ const QString keywordPatterns[] = { QStringLiteral("\\bbreak\\b"), QStringLiteral("\\bshort\\b"), QStringLiteral("\\bif\\b"), QStringLiteral("\\bfor\\b"), QStringLiteral("\\bwhile\\b"), QStringLiteral("\\bstruct\\b"), QStringLiteral("\\belse\\b"), QStringLiteral("\\breturn\\b"), QStringLiteral("\\btypedef\\b"), - QStringLiteral("\\bvolatile\\b"), QStringLiteral("\\bgl_FragColor\\b"), QStringLiteral("\\bswitch\\b"), + QStringLiteral("\\bvolatile\\b"), QStringLiteral("\\bfrag_color\\b"), QStringLiteral("\\bswitch\\b"), QStringLiteral("\\btrue\\b"), QStringLiteral("\\bfalse\\b"), QStringLiteral("\\bstatic\\b"), - QStringLiteral("\\bdefault\\b"), QStringLiteral("#define\\b") + QStringLiteral("\\bdefault\\b"), QStringLiteral("#define\\b"), QStringLiteral("\\bove_texcoord\\b"), + QStringLiteral("\\bin\\b"), QStringLiteral("\\bout\\b"), QStringLiteral("\\bresolution_in\\b") }; const QString typePatterns[] = { @@ -73,7 +74,7 @@ const QString typePatterns[] = { QStringLiteral("\\bdouble\\b"), QStringLiteral("\\bfloat\\b"), QStringLiteral("\\blong\\b"), QStringLiteral("\\bshort\\b"),QStringLiteral("\\bsigned\\b"), QStringLiteral("\\bunsigned\\b"), QStringLiteral("\\bunion\\b"), QStringLiteral("\\bvoid\\b"),QStringLiteral("\\bbool\\b"), - QStringLiteral("\\buniform\\b"), QStringLiteral("\\bvarying\\b"), QStringLiteral("\\bvec\\b"), + QStringLiteral("\\buniform\\b"), QStringLiteral("\\bvec\\b"), QStringLiteral("\\bvec2\\b"), QStringLiteral("\\bvec3\\b"), QStringLiteral("\\bvec4\\b"), QStringLiteral("\\bint\\b"), QStringLiteral("\\bsampler2D\\b") }; From 8fd73a953a3fb58ab9559ccadcf834d98bbfc207 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Fri, 1 Apr 2022 21:34:47 +0200 Subject: [PATCH 17/49] Aligned to OpenGL 3.2 --- app/node/filter/shader/shader.cpp | 99 +++++++++++++------------------ app/node/filter/shader/shader.h | 24 ++++---- app/node/node.h | 4 ++ 3 files changed, 56 insertions(+), 71 deletions(-) diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index c06bcd6182..4a75f58418 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -109,12 +109,20 @@ void olive::ShaderFilterNode::onShaderCodeChanged() } user_input_list_.clear(); - // ... and create new inputs + // remove all gizmos + QMap::iterator i; + for (i = handle_table_.begin(); i != handle_table_.end(); ++i) { + RemoveGizmo(i.value()); + } + handle_table_.clear(); + + // ... and create new inputs and gizmos parseShaderCode(); qDebug() << "parsed shader code for " << GetLabel() << " @ " << (uint64_t)this; } + QString ShaderFilterNode::Description() const { return tr("a filter made by a GLSL shader code"); @@ -122,7 +130,7 @@ QString ShaderFilterNode::Description() const void ShaderFilterNode::Retranslate() { - // Retranslate the only fixed inputs. + // Retranslate only fixed inputs. // Other inputs are read from the shader code SetInputName( kShaderCode, tr("Shader code")); SetInputName( kOutputMessages, tr("Issues")); @@ -157,6 +165,7 @@ void ShaderFilterNode::parseShaderCode() reportErrorList( parser); updateInputList( parser); + updateGizmoList(); // update name, if defined in script; otherwise use a default. QString label = (parser.ShaderName().isEmpty()) ? "unnamed" : parser.ShaderName(); @@ -217,6 +226,24 @@ void ShaderFilterNode::updateInputList( const ShaderInputsParser & parser) emit InputListChanged(); } +// this function assumes that 'updateInputList' has been called already. +void ShaderFilterNode::updateGizmoList() +{ + for (QString aInput : user_input_list_) + { + if (HasInputWithID(aInput)) { + if ( GetInputDataType(aInput) == NodeValue::kVec2) { + PointGizmo * g = AddDraggableGizmo(); + g->AddInput(NodeKeyframeTrackReference(NodeInput(this, aInput), 0)); + g->AddInput(NodeKeyframeTrackReference(NodeInput(this, aInput), 1)); + g->SetDragValueBehavior(PointGizmo::kAbsolute); + + handle_table_.insert( aInput, g); + } + } + } +} + void ShaderFilterNode::checkDeletedInputs(const QStringList & new_inputs) { // search old inputs that are not present in new inputs @@ -227,76 +254,34 @@ void ShaderFilterNode::checkDeletedInputs(const QStringList & new_inputs) } } -} // namespace olive - +void ShaderFilterNode::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers & /*modifiers*/) +{ + DraggableGizmo *gizmo = static_cast(sender()); + Q_ASSERT( resolution_.x() != 0.); + Q_ASSERT( resolution_.y() != 0.); -bool olive::ShaderFilterNode::HasGizmos() const -{ - return (handle_table_.size() > 0); + gizmo->GetDraggers()[0].Drag( x/resolution_.x()); + gizmo->GetDraggers()[1].Drag( y/resolution_.y()); } -void olive::ShaderFilterNode::DrawGizmos(const NodeValueRow &row, const NodeGlobals & globals, QPainter *p) +void ShaderFilterNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals & globals) { - p->setPen(QPen(Qt::white, 0)); - QFont font; - font.setPixelSize(40); - p->setFont( font); - QVector2D resolution = globals.resolution(); - - handle_table_.clear(); + resolution_ = globals.resolution(); for (QString aInput : user_input_list_) { if (HasInputWithID(aInput)) { if (row[aInput].type() == NodeValue::kVec2) { + QVector2D pos_vec = row[aInput].data().value() * resolution_; + QPointF pos( pos_vec.x(), pos_vec.y()); - QVector2D pos = row[aInput].data().value() * resolution; - QRectF handleRect = CreateGizmoHandleRect(QPointF(pos.x(), pos.y()), 10); - p->fillRect( handleRect, Qt::white); - handle_table_[aInput] = handleRect; - - p->drawText( pos.x()+15, pos.y()+15, GetInputName(aInput)); + handle_table_[aInput]->SetPoint( pos); } } } } -bool olive::ShaderFilterNode::GizmoPress(const NodeValueRow & /*row*/, const NodeGlobals & globals, const QPointF &p) -{ - resolution_ = globals.resolution(); - - for (const QString & point : handle_table_.keys()) { - if (handle_table_[point].contains(p)) { - currently_dragged_input_ = point; - return true; - } - } - - return false; -} -void olive::ShaderFilterNode::GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers & /*modifiers*/) -{ - if ( ! dragger_[0].IsStarted()) - { - dragger_[0].Start(NodeKeyframeTrackReference(NodeInput(this, currently_dragged_input_), 0), time); - dragger_[1].Start(NodeKeyframeTrackReference(NodeInput(this, currently_dragged_input_), 1), time); - } - - if (resolution_.x() > 0.0) { - dragger_[0].Drag(p.x()/resolution_.x()); - } - - if (resolution_.y() > 0.0) { - dragger_[1].Drag(p.y()/resolution_.y()); - } -} - -void olive::ShaderFilterNode::GizmoRelease(MultiUndoCommand *command) -{ - dragger_[0].End( command); - dragger_[1].End( command); +} // namespace olive - currently_dragged_input_.clear(); -} diff --git a/app/node/filter/shader/shader.h b/app/node/filter/shader/shader.h index e037664183..adf75a55ff 100644 --- a/app/node/filter/shader/shader.h +++ b/app/node/filter/shader/shader.h @@ -22,6 +22,7 @@ #define ShaderFilterNode_H #include "node/node.h" +#include "node/gizmo/point.h" #include "node/inputdragger.h" namespace olive { @@ -51,6 +52,8 @@ class ShaderFilterNode : public Node virtual void Retranslate() override; + virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; + virtual ShaderCode GetShaderCode(const QString &shader_id) const override; virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; void InputValueChangedEvent(const QString &input, int element) override; @@ -64,29 +67,22 @@ class ShaderFilterNode : public Node void onShaderCodeChanged(); void reportErrorList( const ShaderInputsParser & parser); void updateInputList( const ShaderInputsParser & parser); + void updateGizmoList(); void checkDeletedInputs( const QStringList & new_inputs); + +protected slots: + virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override; + private: QString shader_code_; // user defined inputs QStringList user_input_list_; + // input of type vec2 to gizmo map + QMap handle_table_; - // two draggers for x and y, shared by all vec2 inputs - NodeInputDragger dragger_[2]; - // one handle for every vec2 input - QMap handle_table_; - - QString currently_dragged_input_; QVector2D resolution_; - - // Node interface -public: - bool HasGizmos() const override; - void DrawGizmos(const NodeValueRow &row, const NodeGlobals &globals, QPainter *p) override; - bool GizmoPress(const NodeValueRow &row, const NodeGlobals &globals, const QPointF &p) override; - void GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) override; - void GizmoRelease(MultiUndoCommand *command) override; }; } diff --git a/app/node/node.h b/app/node/node.h index b3de8fd00e..79b5fbd412 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -1062,6 +1062,10 @@ class Node : public QObject return AddDraggableGizmo(refs, behavior); } + void RemoveGizmo( NodeGizmo* gizmo) { + gizmos_.removeAll( gizmo); + } + protected slots: virtual void GizmoDragStart(const olive::NodeValueRow &row, double x, double y, const olive::rational &time){} From 8ccba8a2d35b5fdc7a7b43c799ffd0f480edaa5c Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Mon, 18 Apr 2022 14:30:15 +0200 Subject: [PATCH 18/49] Minor changes to GLSL syntax highlighter and default script template added. --- app/dialog/codeeditor/glslhighlighter.cpp | 44 ++++++++++++++++------- app/dialog/codeeditor/glslhighlighter.h | 1 + app/node/filter/shader/shader.cpp | 23 +++++++++++- 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/app/dialog/codeeditor/glslhighlighter.cpp b/app/dialog/codeeditor/glslhighlighter.cpp index 119544d773..8ff175e229 100644 --- a/app/dialog/codeeditor/glslhighlighter.cpp +++ b/app/dialog/codeeditor/glslhighlighter.cpp @@ -64,9 +64,10 @@ const QString keywordPatterns[] = { QStringLiteral("\\bfor\\b"), QStringLiteral("\\bwhile\\b"), QStringLiteral("\\bstruct\\b"), QStringLiteral("\\belse\\b"), QStringLiteral("\\breturn\\b"), QStringLiteral("\\btypedef\\b"), QStringLiteral("\\bvolatile\\b"), QStringLiteral("\\bfrag_color\\b"), QStringLiteral("\\bswitch\\b"), - QStringLiteral("\\btrue\\b"), QStringLiteral("\\bfalse\\b"), QStringLiteral("\\bstatic\\b"), - QStringLiteral("\\bdefault\\b"), QStringLiteral("#define\\b"), QStringLiteral("\\bove_texcoord\\b"), - QStringLiteral("\\bin\\b"), QStringLiteral("\\bout\\b"), QStringLiteral("\\bresolution_in\\b") + QStringLiteral("\\bcase\\b"), QStringLiteral("\\btrue\\b"), QStringLiteral("\\bfalse\\b"), + QStringLiteral("\\bstatic\\b"), QStringLiteral("\\bdefault\\b"), QStringLiteral("#define\\b"), + QStringLiteral("\\bove_texcoord\\b"), QStringLiteral("\\bin\\b"), QStringLiteral("\\bout\\b"), + QStringLiteral("\\bresolution_in\\b") }; const QString typePatterns[] = { @@ -79,6 +80,17 @@ const QString typePatterns[] = { QStringLiteral("\\bint\\b"), QStringLiteral("\\bsampler2D\\b") }; +// a small set of built-in functions +const QString functionPatterns[] = { + QStringLiteral("\\bmain\\b"), QStringLiteral("\\btexture2D\\b"), QStringLiteral("\\bfract\\b"), + QStringLiteral("\\babs\\b"), QStringLiteral("\\bdot\\b"), QStringLiteral("\\bmix\\b"), + QStringLiteral("\\bceil\\b"), QStringLiteral("\\bfloor\\b"), QStringLiteral("\\bclamp\\b"), + QStringLiteral("\\bsin\\b"), QStringLiteral("\\bcos\\b"), QStringLiteral("\\btan\\b"), + QStringLiteral("\\bexp(2)?\\b"), QStringLiteral("\\blog(2)?\\b"), QStringLiteral("\\b\\b"), + QStringLiteral("\\bmin\\b"), QStringLiteral("\\bmax\\b"), QStringLiteral("\\bpow\\b"), + QStringLiteral("\\bsqrt\\b") +}; + const QString oliveMarkupPatterns[] = { QStringLiteral("//OVE\\s+shader_name:[^\n]*"), QStringLiteral("//OVE\\s+shader_description:[^\n]*"), QStringLiteral("//OVE\\s+shader_version:[^\n]*"), QStringLiteral("//OVE\\s+name:[^\n]*"), @@ -99,6 +111,12 @@ GlslHighlighter::GlslHighlighter(QTextDocument *parent) keyword_format_.setForeground(QColor(110,80,110)); keyword_format_.setFontWeight(QFont::Bold); + quotation_format_.setForeground(QColor(130,130,80)); + quotation_format_.setProperty( IS_NON_PARSABLE, QVariant::fromValue(true)); + rule.pattern = QRegularExpression(QStringLiteral("\".*\"")); + rule.format = quotation_format_; + highlighting_rules_.append(rule); + for (const QString &pattern : keywordPatterns) { rule.pattern = QRegularExpression(pattern); rule.format = keyword_format_; @@ -113,6 +131,14 @@ GlslHighlighter::GlslHighlighter(QTextDocument *parent) highlighting_rules_.append(rule); } + function_format_.setForeground(QColor(120,110,30)); + + for (const QString &pattern : functionPatterns) { + rule.pattern = QRegularExpression(pattern); + rule.format = function_format_; + highlighting_rules_.append(rule); + } + number_format_.setForeground(QColor(120,110,60)); rule.format = type_format_; // decimal or float @@ -122,6 +148,9 @@ GlslHighlighter::GlslHighlighter(QTextDocument *parent) rule.pattern = QRegularExpression("\\b0[xX][0-9A-Fa-f]+\\b"); highlighting_rules_.append(rule); + comment_start_expression_ = QRegularExpression(QStringLiteral("/\\*")); + comment_end_expression_ = QRegularExpression(QStringLiteral("\\*/")); + single_line_comment_format_.setForeground(QColor(60,180,80)); single_line_comment_format_.setProperty( IS_NON_PARSABLE, QVariant::fromValue(true)); rule.pattern = QRegularExpression(QStringLiteral("//[^\n]*")); @@ -138,15 +167,6 @@ GlslHighlighter::GlslHighlighter(QTextDocument *parent) multiline_comment_format_.setForeground(QColor(60,180,80)); multiline_comment_format_.setProperty( IS_NON_PARSABLE, QVariant::fromValue(true)); - - quotation_format_.setForeground(QColor(130,130,80)); - quotation_format_.setProperty( IS_NON_PARSABLE, QVariant::fromValue(true)); - rule.pattern = QRegularExpression(QStringLiteral("\".*\"")); - rule.format = quotation_format_; - highlighting_rules_.append(rule); - - comment_start_expression_ = QRegularExpression(QStringLiteral("/\\*")); - comment_end_expression_ = QRegularExpression(QStringLiteral("\\*/")); } diff --git a/app/dialog/codeeditor/glslhighlighter.h b/app/dialog/codeeditor/glslhighlighter.h index 7320f36597..9314f87071 100644 --- a/app/dialog/codeeditor/glslhighlighter.h +++ b/app/dialog/codeeditor/glslhighlighter.h @@ -88,6 +88,7 @@ class GlslHighlighter : public QSyntaxHighlighter QTextCharFormat multiline_comment_format_; QTextCharFormat quotation_format_; QTextCharFormat number_format_; + QTextCharFormat function_format_; private: void highlightKeywords(const QString &text); diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index 4a75f58418..356b79c5ab 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -32,6 +32,26 @@ namespace olive { +namespace { +const QString TEMPLATE( + "//OVE shader_name: \n" + "//OVE shader_description: \n\n" + "//OVE name: input\n" + "//OVE type: TEXTURE\n" + "//OVE flag: NOT_KEYFRAMABLE\n" + "//OVE description:\n" + "uniform sampler2D texture_in;\n\n" + "//OVE end\n\n\n" + "// pixel coordinates in range [0..1]x[0..1]\n" + "in vec2 ove_texcoord;\n" + "// output color\n" + "out vec4 frag_color;\n\n" + "void main(void) {\n" + " vec4 textureColor = texture2D(texture_in, ove_texcoord);\n" + " frag_color= textureColor;\n" + "}\n"); +} + const QString ShaderFilterNode::kShaderCode = QStringLiteral("source"); const QString ShaderFilterNode::kOutputMessages = QStringLiteral("issues"); @@ -40,7 +60,8 @@ ShaderFilterNode::ShaderFilterNode() { // Full code of the shader. Inputs to be exposed are defined within the shader code // with mark-up comments. - AddInput(kShaderCode, NodeValue::kText, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + AddInput(kShaderCode, NodeValue::kText, QVariant::fromValue(TEMPLATE), + InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); // Output messages of shader parser AddInput(kOutputMessages, NodeValue::kText, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); From eabd36909296b21f7e5eb9bb0b439d8b62c33cab Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Sat, 23 Apr 2022 20:59:49 +0200 Subject: [PATCH 19/49] Minor changes: "find whole word" works with underscore; a pair of GLSL functions highlighted --- app/dialog/codeeditor/editor.cpp | 75 +++++++++++++++++++++-- app/dialog/codeeditor/editor.h | 1 + app/dialog/codeeditor/glslhighlighter.cpp | 3 +- 3 files changed, 74 insertions(+), 5 deletions(-) diff --git a/app/dialog/codeeditor/editor.cpp b/app/dialog/codeeditor/editor.cpp index e1a1136257..0416506765 100644 --- a/app/dialog/codeeditor/editor.cpp +++ b/app/dialog/codeeditor/editor.cpp @@ -42,6 +42,10 @@ const Qt::GlobalColor BRACKET_NOT_MATCH_COLOR = Qt::darkRed; // used in parentheses match algorithm. const int BRACKET_LAST = -2; + +// forward declarations +QString regExpEscape( const QStringView &str); +QString buildSearchExpression( const QStringView &str, QTextDocument::FindFlags & flags); } namespace olive { @@ -116,7 +120,7 @@ void CodeEditor::gotoLineNumber(int lineNumber) void CodeEditor::onSearchBackwardRequest(const QString &text, QTextDocument::FindFlags flags) { // try to find backword - bool found = find( text, flags | QTextDocument::FindBackward); + bool found = findNext( text, flags | QTextDocument::FindBackward); if ( ! found) { @@ -127,7 +131,7 @@ void CodeEditor::onSearchBackwardRequest(const QString &text, QTextDocument::Fin cursor.movePosition( QTextCursor::End); setTextCursor( cursor); - found = find( text, flags | QTextDocument::FindBackward); + found = findNext( text, flags | QTextDocument::FindBackward); if ( ! found) { @@ -143,7 +147,7 @@ void CodeEditor::onSearchBackwardRequest(const QString &text, QTextDocument::Fin void CodeEditor::onSearchForwardRequest(const QString &text, QTextDocument::FindFlags flags) { // try to find forward - bool found = find( text, flags); + bool found = findNext( text, flags); if ( ! found) { @@ -154,7 +158,7 @@ void CodeEditor::onSearchForwardRequest(const QString &text, QTextDocument::Find cursor.movePosition( QTextCursor::Start); setTextCursor( cursor); - found = find( text, flags); + found = findNext( text, flags); if ( ! found) { @@ -167,6 +171,12 @@ void CodeEditor::onSearchForwardRequest(const QString &text, QTextDocument::Find } } +bool CodeEditor::findNext(const QString &text, QTextDocument::FindFlags flags) +{ + QString regExpText = buildSearchExpression( text, flags); + return find( QRegularExpression(regExpText), flags); +} + void CodeEditor::onReplaceTextRequest( const QString & src, const QString & dest, QTextDocument::FindFlags flags, bool thenFind) { @@ -488,3 +498,60 @@ int CodeEditor::countTrailingSpaces(int block_position) } // namespace olive +namespace { + +// This is taken from QT 5.15 source code for "QRegularExpression::escape". +// Olive must compile with QT 5.9 on. +QString regExpEscape(const QStringView &str) +{ + QString result; + const int count = str.size(); + result.reserve(count * 2); + // everything but [a-zA-Z0-9_] gets escaped, + // cf. perldoc -f quotemeta + for (int i = 0; i < count; ++i) { + const QChar current = str.at(i); + if (current == QChar::Null) { + // unlike Perl, a literal NUL must be escaped with + // "\\0" (backslash + 0) and not "\\\0" (backslash + NUL), + // because pcre16_compile uses a NUL-terminated string + result.append(QLatin1Char('\\')); + result.append(QLatin1Char('0')); + } else if ( (current < QLatin1Char('a') || current > QLatin1Char('z')) && + (current < QLatin1Char('A') || current > QLatin1Char('Z')) && + (current < QLatin1Char('0') || current > QLatin1Char('9')) && + current != QLatin1Char('_') ) + { + result.append(QLatin1Char('\\')); + result.append(current); + if (current.isHighSurrogate() && i < (count - 1)) + result.append(str.at(++i)); + } else { + result.append(current); + } + } + result.squeeze(); + return result; +} + +// This function is required to search for "whole word". The default QT function considers +// underscore "_" as a word breaker, so if you searh for "hello" as whole word it will match +// "hello_world". +// For this reason, we always search for a regular expression and remove "FindWholeWords" +// from 'flags', if present. +QString buildSearchExpression( const QStringView &str, QTextDocument::FindFlags & flags) +{ + QString regExpString = regExpEscape( str); + + if (flags & QTextDocument::FindWholeWords) { + regExpString.prepend("\\b"); + regExpString.append("\\b"); + + flags &= (~QTextDocument::FindWholeWords); + } + + return regExpString; +} + +} + diff --git a/app/dialog/codeeditor/editor.h b/app/dialog/codeeditor/editor.h index 3446601bea..7b51917b63 100644 --- a/app/dialog/codeeditor/editor.h +++ b/app/dialog/codeeditor/editor.h @@ -68,6 +68,7 @@ private slots: void matchParenthesis(); int countTrailingSpaces( int block_position); void createParenthesisSelection(int pos, Qt::GlobalColor color); + bool findNext(const QString &text, QTextDocument::FindFlags flags); private: QWidget * m_lineNumberArea; diff --git a/app/dialog/codeeditor/glslhighlighter.cpp b/app/dialog/codeeditor/glslhighlighter.cpp index 8ff175e229..4192e674a7 100644 --- a/app/dialog/codeeditor/glslhighlighter.cpp +++ b/app/dialog/codeeditor/glslhighlighter.cpp @@ -88,7 +88,8 @@ const QString functionPatterns[] = { QStringLiteral("\\bsin\\b"), QStringLiteral("\\bcos\\b"), QStringLiteral("\\btan\\b"), QStringLiteral("\\bexp(2)?\\b"), QStringLiteral("\\blog(2)?\\b"), QStringLiteral("\\b\\b"), QStringLiteral("\\bmin\\b"), QStringLiteral("\\bmax\\b"), QStringLiteral("\\bpow\\b"), - QStringLiteral("\\bsqrt\\b") + QStringLiteral("\\bsqrt\\b"), QStringLiteral("\\bstep\\b"), QStringLiteral("\\bsmoothstep\\b"), + QStringLiteral("\\bdistance\\b") }; const QString oliveMarkupPatterns[] = { From a1435b55f79eb707a8b173156ff5b80afa78d3c3 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Tue, 3 May 2022 18:47:23 +0200 Subject: [PATCH 20/49] Added transaltion of 'super' node to translate 'enable' input --- app/node/filter/shader/shader.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index 356b79c5ab..cefd15b69c 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -32,6 +32,8 @@ namespace olive { +#define super Node + namespace { const QString TEMPLATE( "//OVE shader_name: \n" @@ -151,6 +153,8 @@ QString ShaderFilterNode::Description() const void ShaderFilterNode::Retranslate() { + super::Retranslate(); + // Retranslate only fixed inputs. // Other inputs are read from the shader code SetInputName( kShaderCode, tr("Shader code")); From 954b580d1bd049228e9e44e36bd768c1bef42a48 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Wed, 4 May 2022 23:22:42 +0200 Subject: [PATCH 21/49] Fixed 'Type' of Push function to align to master repo. --- app/node/filter/shader/shader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index cefd15b69c..759dc35f92 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -178,7 +178,7 @@ void ShaderFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globa // If there's no shader code, no need to run an operation if (shader_code_ != QString()) { - table->Push(NodeValue::kShaderJob, QVariant::fromValue(job), this); + table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); } } From c3bc4588aac71daba39e533c0b8565f31ada6757 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Tue, 10 May 2022 23:42:27 +0200 Subject: [PATCH 22/49] Aligned to Olive master --- .../preferences/tabs/preferencesedittab.cpp | 2 +- .../preferences/tabs/preferencesedittab.h | 2 +- app/node/factory.cpp | 3 - app/node/filter/shader/shader.cpp | 60 +++++++++++-------- app/node/filter/shader/shader.h | 4 +- app/widget/nodeparamview/nodeparamview.cpp | 3 +- .../nodeparamview/nodeparamviewitem.cpp | 18 ++++-- app/widget/nodeparamview/nodeparamviewitem.h | 6 +- 8 files changed, 54 insertions(+), 44 deletions(-) diff --git a/app/dialog/preferences/tabs/preferencesedittab.cpp b/app/dialog/preferences/tabs/preferencesedittab.cpp index b5acbf05d6..a9ef9bb250 100644 --- a/app/dialog/preferences/tabs/preferencesedittab.cpp +++ b/app/dialog/preferences/tabs/preferencesedittab.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/dialog/preferences/tabs/preferencesedittab.h b/app/dialog/preferences/tabs/preferencesedittab.h index 3ea0224d3b..b8270125f8 100644 --- a/app/dialog/preferences/tabs/preferencesedittab.h +++ b/app/dialog/preferences/tabs/preferencesedittab.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 diff --git a/app/node/factory.cpp b/app/node/factory.cpp index 17a29cd543..7aa3b241fb 100644 --- a/app/node/factory.cpp +++ b/app/node/factory.cpp @@ -21,8 +21,6 @@ #include "factory.h" #include -#include -#include #include "audio/pan/pan.h" #include "audio/volume/volume.h" @@ -66,7 +64,6 @@ #include "project/sequence/sequence.h" #include "time/timeoffset/timeoffsetnode.h" #include "time/timeremap/timeremap.h" -#include "config/config.h" namespace olive { diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index 759dc35f92..c9c3deb566 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 @@ -34,6 +34,7 @@ namespace olive { #define super Node + namespace { const QString TEMPLATE( "//OVE shader_name: \n" @@ -123,23 +124,8 @@ void ShaderFilterNode::InputValueChangedEvent(const QString &input, int element) void olive::ShaderFilterNode::onShaderCodeChanged() { - // pre-remove all inputs ... - for (QString oldInput : user_input_list_) - { - if (HasInputWithID(oldInput)) { - RemoveInput(oldInput); - } - } - user_input_list_.clear(); - // remove all gizmos - QMap::iterator i; - for (i = handle_table_.begin(); i != handle_table_.end(); ++i) { - RemoveGizmo(i.value()); - } - handle_table_.clear(); - - // ... and create new inputs and gizmos + // create new inputs and gizmos parseShaderCode(); qDebug() << "parsed shader code for " << GetLabel() << " @ " << (uint64_t)this; @@ -161,19 +147,19 @@ void ShaderFilterNode::Retranslate() SetInputName( kOutputMessages, tr("Issues")); } -ShaderCode ShaderFilterNode::GetShaderCode(const QString &shader_id) const +ShaderCode ShaderFilterNode::GetShaderCode(const Node::ShaderRequest &request) const { - Q_UNUSED(shader_id) - + Q_UNUSED(request); return ShaderCode(shader_code_); } + void ShaderFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { ShaderJob job; - job.InsertValue(value); - job.InsertValue(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); + job.Insert(value); + job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); // If there's no shader code, no need to run an operation @@ -223,11 +209,25 @@ void ShaderFilterNode::updateInputList( const ShaderInputsParser & parser) for( it = input_list.begin(); it != input_list.end(); ++it) { + new_input_list.append( it->uniform_name); + if (HasInputWithID(it->uniform_name) == false) { + // this is a new input AddInput( it->uniform_name, it->type, it->default_value, it->flags ); - new_input_list.append( it->uniform_name); + + } + else { + // input already present. Check if some feature has changed + if (GetInputDataType( it->uniform_name) != it->type) { + SetInputDataType( it->uniform_name, it->type); + } + + if (GetInputFlags( it->uniform_name) != it->flags) { + SetInputFlags( it->uniform_name, it->flags); + } } + // set other features even if not changed SetInputName( it->uniform_name, it->human_name); if (it->min.isValid()) { SetInputProperty( it->uniform_name, QStringLiteral("min"), it->min); @@ -252,12 +252,14 @@ void ShaderFilterNode::updateInputList( const ShaderInputsParser & parser) } // this function assumes that 'updateInputList' has been called already. +// Add a point gizmo for each 'kVec2' that does not already have one void ShaderFilterNode::updateGizmoList() { for (QString aInput : user_input_list_) { if (HasInputWithID(aInput)) { - if ( GetInputDataType(aInput) == NodeValue::kVec2) { + if ( (GetInputDataType(aInput) == NodeValue::kVec2) && + (handle_table_.contains(aInput) == false) ){ PointGizmo * g = AddDraggableGizmo(); g->AddInput(NodeKeyframeTrackReference(NodeInput(this, aInput), 0)); g->AddInput(NodeKeyframeTrackReference(NodeInput(this, aInput), 1)); @@ -274,7 +276,13 @@ void ShaderFilterNode::checkDeletedInputs(const QStringList & new_inputs) // search old inputs that are not present in new inputs for( const QString & input : user_input_list_) { if (new_inputs.contains(input) == false) { - InputRemoved( input); + RemoveInput( input); + + // remove gizmo, if any + if (handle_table_.contains(input)) { + RemoveGizmo( handle_table_[input]); + handle_table_.remove( input); + } } } } @@ -298,7 +306,7 @@ void ShaderFilterNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeG { if (HasInputWithID(aInput)) { if (row[aInput].type() == NodeValue::kVec2) { - QVector2D pos_vec = row[aInput].data().value() * resolution_; + QVector2D pos_vec = row[aInput].value() * resolution_; QPointF pos( pos_vec.x(), pos_vec.y()); handle_table_[aInput]->SetPoint( pos); diff --git a/app/node/filter/shader/shader.h b/app/node/filter/shader/shader.h index adf75a55ff..2a54827808 100644 --- a/app/node/filter/shader/shader.h +++ b/app/node/filter/shader/shader.h @@ -1,7 +1,7 @@ /*** Olive - Non-Linear Video Editor - Copyright (C) 2021 Olive Team + Copyright (C) 2022 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 @@ -54,7 +54,7 @@ class ShaderFilterNode : public Node virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; - virtual ShaderCode GetShaderCode(const QString &shader_id) const override; + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; void InputValueChangedEvent(const QString &input, int element) override; diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index fb51184ec4..ecc0d7f475 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -594,10 +594,9 @@ void NodeParamView::AddNode(Node *n, Node *ctx, NodeParamViewContext *context) connect(item, &NodeParamViewItem::ArrayExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate); connect(item, &NodeParamViewItem::ExpandedChanged, this, &NodeParamView::QueueKeyframePositionUpdate); connect(item, &NodeParamViewItem::Moved, this, &NodeParamView::QueueKeyframePositionUpdate); + connect(item, &NodeParamViewItem::InputsChanged, this, &NodeParamView::UpdateElementY); item->SetKeyframeConnections(keyframe_view_->AddKeyframesOfNode(n)); - - // needed to update keyframe connections dynamically item->SetKeyframeView( keyframe_view_); } } diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 9a40aff161..12ec6c96c6 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -60,6 +60,7 @@ NodeParamViewItem::NodeParamViewItem(Node *node, NodeParamViewCheckBoxBehavior c // for dynamically changed inputs connect(node_, &Node::InputListChanged, this, &NodeParamViewItem::OnInputListChanged); + connect(node_, &Node::InputAdded, this, &NodeParamViewItem::OnInputAdded); connect(node_, &Node::InputRemoved, this, &NodeParamViewItem::OnInputRemoved); // FIXME: Implemented to pick up when an input is set to hidden or not - DEFINITELY not a fast @@ -126,9 +127,13 @@ void NodeParamViewItem::OnInputListChanged() SetBody(body_); body_->Retranslate(); - Q_ASSERT( keyframe_view_ != nullptr); + emit InputsChanged(); +} - keyframe_connections_ = keyframe_view_->AddKeyframesOfNode(node_); +void NodeParamViewItem::OnInputAdded(const QString &id) +{ + KeyframeView::InputConnections input_conn = keyframe_view_->AddKeyframesOfInput( node_, id); + keyframe_connections_.insert( id, input_conn); } void NodeParamViewItem::OnInputRemoved( const QString & id) @@ -168,9 +173,10 @@ void NodeParamViewItem::SetInputChecked(const NodeInput &input, bool e) NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget *parent) : QWidget(parent), node_(node), - create_checkboxes_(create_checkboxes), - root_layout_(new QGridLayout(this)) + create_checkboxes_(create_checkboxes) { + QGridLayout* root_layout = new QGridLayout(this); + int insert_row = 0; QVector connected_signals; @@ -191,7 +197,7 @@ NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBe input_group_lookup_.insert({resolved.node(), resolved.input()}, {n, input}); if (!(n->GetInputFlags(input) & kInputFlagHidden)) { - CreateWidgets(root_layout_, n, input, -1, insert_row); + CreateWidgets(root_layout, n, input, -1, insert_row); insert_row++; @@ -202,7 +208,7 @@ NodeParamViewItemBody::NodeParamViewItemBody(Node* node, NodeParamViewCheckBoxBe QGridLayout* array_layout = new QGridLayout(array_widget); array_layout->setContentsMargins(QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral(" ")), 0, 0, 0); - root_layout_->addWidget(array_widget, insert_row, 1, 1, 10); + root_layout->addWidget(array_widget, insert_row, 1, 1, 10); // Start with zero elements for efficiency. We will make the widgets for them if the user // requests the array UI to be expanded diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index 63cdba40bc..22fbb67a6c 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -118,8 +118,6 @@ class NodeParamViewItemBody : public QWidget { QHash input_group_lookup_; - QGridLayout *root_layout_; - /** * @brief The column to place the keyframe controls in * @@ -228,11 +226,14 @@ class NodeParamViewItem : public NodeParamViewItemBase void InputCheckedChanged(const NodeInput &input, bool e); + void InputsChanged( void); + protected slots: virtual void Retranslate() override; private slots: void OnInputListChanged(); + void OnInputAdded(const QString &id); void OnInputRemoved(const QString &id); private: @@ -247,7 +248,6 @@ private slots: rational time_; rational timebase_; - NodeParamViewCheckBoxBehavior create_checkboxes_; KeyframeView::NodeConnections keyframe_connections_; KeyframeView * keyframe_view_; From f708d493d06e91357cb434fe43a9919e7d4c2acd Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Tue, 17 May 2022 20:13:34 +0200 Subject: [PATCH 23/49] Alignment to olive master --- app/dialog/preferences/preferences.cpp | 1 - app/dialog/preferences/tabs/preferencesbehaviortab.cpp | 2 -- app/dialog/preferences/tabs/preferencesbehaviortab.h | 1 + app/widget/keyframeview/keyframeview.cpp | 5 ++++- app/widget/nodeparamview/nodeparamviewitem.cpp | 9 --------- app/widget/nodeparamview/nodeparamviewitem.h | 1 + app/widget/viewer/viewerdisplay.cpp | 4 +++- app/widget/viewer/viewerdisplay.h | 2 ++ app/widget/viewer/viewerqueue.h | 2 +- 9 files changed, 12 insertions(+), 15 deletions(-) diff --git a/app/dialog/preferences/preferences.cpp b/app/dialog/preferences/preferences.cpp index ad48edd3e5..59d3935c88 100644 --- a/app/dialog/preferences/preferences.cpp +++ b/app/dialog/preferences/preferences.cpp @@ -35,7 +35,6 @@ #include "tabs/preferencesedittab.h" #include "window/mainwindow/mainwindow.h" - namespace olive { PreferencesDialog::PreferencesDialog(MainWindow *main_window) : diff --git a/app/dialog/preferences/tabs/preferencesbehaviortab.cpp b/app/dialog/preferences/tabs/preferencesbehaviortab.cpp index bb33a74f3e..a081260fe9 100644 --- a/app/dialog/preferences/tabs/preferencesbehaviortab.cpp +++ b/app/dialog/preferences/tabs/preferencesbehaviortab.cpp @@ -23,8 +23,6 @@ #include #include -#include - #include "config/config.h" namespace olive { diff --git a/app/dialog/preferences/tabs/preferencesbehaviortab.h b/app/dialog/preferences/tabs/preferencesbehaviortab.h index 5e3d4ff076..a1a9558ce7 100644 --- a/app/dialog/preferences/tabs/preferencesbehaviortab.h +++ b/app/dialog/preferences/tabs/preferencesbehaviortab.h @@ -45,6 +45,7 @@ class PreferencesBehaviorTab : public ConfigDialogBaseTab QMap config_map_; QTreeWidget* behavior_tree_; + }; } diff --git a/app/widget/keyframeview/keyframeview.cpp b/app/widget/keyframeview/keyframeview.cpp index 51ec2e7246..df03494752 100644 --- a/app/widget/keyframeview/keyframeview.cpp +++ b/app/widget/keyframeview/keyframeview.cpp @@ -226,7 +226,10 @@ bool KeyframeView::Paste(std::function find_node_functi if (node_with_id) { for (NodeKeyframe *key : it.value()) { - key->set_time(key->time() - min); + // Adjust sequence time to node's time + rational t = key->time() - min; + t = GetAdjustedTime(GetTimeTarget(), node_with_id, t, true); + key->set_time(t); if (NodeKeyframe *existing = node_with_id->GetKeyframeAtTimeOnTrack(key->input(), key->time(), key->track(), key->element())) { command->add_child(new NodeParamRemoveKeyframeCommand(existing)); diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index d1124674ab..5671037545 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -300,15 +300,6 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const // Place widgets into layout PlaceWidgetsFromBridge(layout, ui_objects.widget_bridge, row); - // Add widgets for this parameter to the layout - for (int i=0; iwidgets().size(); i++) { - QWidget* w = ui_objects.widget_bridge->widgets().at(i); - - // some (non-keyframeable) widgets can use all space to the right - int column_span = (w->property("is_exapandable").toBool()) ? -1 : 1; - - layout->addWidget(w, row, i+kWidgetStartColumn, 1, column_span); - } // In case this input is a group, resolve that actual input to use for connected labels NodeInput resolved = NodeGroup::ResolveInput(input_ref); diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index 6547ebf572..91c3353745 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -257,6 +257,7 @@ private slots: private slots: void RecreateBody(); + }; } diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 9fc4eeac4a..cdbe6fdc62 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -66,6 +66,7 @@ ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent) : show_fps_(false), frames_skipped_(0), show_widget_background_(false), + playback_speed_(0), push_mode_(kPushNull), add_band_(nullptr), queue_starved_(false) @@ -882,6 +883,7 @@ void ViewerDisplayWidget::RequestStartEditingText() void ViewerDisplayWidget::Play(const int64_t &start_timestamp, const int &playback_speed, const rational &timebase) { playback_timebase_ = timebase; + playback_speed_ = playback_speed; timer_.Start(start_timestamp, playback_speed, timebase.toDouble()); @@ -924,7 +926,7 @@ void ViewerDisplayWidget::UpdateFromQueue() } return; - } else if (pf.timestamp > time) { + } else if ((pf.timestamp > time) == (playback_speed_ > 0)) { // The next frame in the queue is too new, so just do a regular update. Either the // frame we want will arrive in time, or we'll just have to skip it. diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index adb51e6829..7359087f60 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -360,6 +360,8 @@ protected slots: QVariant load_frame_; + int playback_speed_; + enum PushMode { /// New frame to push to internal texture kPushFrame, diff --git a/app/widget/viewer/viewerqueue.h b/app/widget/viewer/viewerqueue.h index 3ad2c0b36d..f8117eac91 100644 --- a/app/widget/viewer/viewerqueue.h +++ b/app/widget/viewer/viewerqueue.h @@ -41,7 +41,7 @@ class ViewerQueue : public std::list { if (this->empty() || (this->back().timestamp < f.timestamp) == (playback_speed > 0)) { this->push_back(f); } else { - for (iterator i=this->begin(); i!=this->end(); i++) { + for (auto i=this->begin(); i!=this->end(); i++) { if ((i->timestamp > f.timestamp) == (playback_speed > 0)) { this->insert(i, f); break; From 9357fa1a9f2a4863f7e1d12c37c0e91012005581 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Sat, 21 May 2022 09:18:10 +0200 Subject: [PATCH 24/49] Fixed an issue with keyframes for a newly created node input --- app/node/keyframe.cpp | 2 ++ .../nodeparamview/nodeparamviewitem.cpp | 28 ++++++------------- app/widget/nodeparamview/nodeparamviewitem.h | 2 ++ 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/app/node/keyframe.cpp b/app/node/keyframe.cpp index eb6d5dea1c..ea158a39a8 100644 --- a/app/node/keyframe.cpp +++ b/app/node/keyframe.cpp @@ -44,6 +44,8 @@ NodeKeyframe::NodeKeyframe(const rational &time, const QVariant &value, Type typ NodeKeyframe::NodeKeyframe() { type_ = NodeKeyframe::kLinear; + previous_ = nullptr; + next_ = nullptr; } NodeKeyframe::~NodeKeyframe() diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 5671037545..37ddf71d36 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -107,26 +107,10 @@ void NodeParamViewItem::RecreateBody() void NodeParamViewItem::OnInputListChanged() { - // delete old widgets and create new ones, based on current inputs - - disconnect(body_, &NodeParamViewItemBody::RequestSelectNode, this, &NodeParamViewItem::RequestSelectNode); - disconnect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime); - disconnect(body_, &NodeParamViewItemBody::ArrayExpandedChanged, this, &NodeParamViewItem::ArrayExpandedChanged); - disconnect(body_, &NodeParamViewItemBody::InputCheckedChanged, this, &NodeParamViewItem::InputCheckedChanged); - - // TODO: Before creating a new 'NodeParamViewItemBody', we should delete the old one. - // However this causes a segmentation fault. Does QT handle this? Is this a memory leak? - - // delete body_; - body_ = new NodeParamViewItemBody(node_, create_checkboxes_); - - connect(body_, &NodeParamViewItemBody::RequestSelectNode, this, &NodeParamViewItem::RequestSelectNode); - connect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime); - connect(body_, &NodeParamViewItemBody::ArrayExpandedChanged, this, &NodeParamViewItem::ArrayExpandedChanged); - connect(body_, &NodeParamViewItemBody::InputCheckedChanged, this, &NodeParamViewItem::InputCheckedChanged); + RecreateBody(); - SetBody(body_); - body_->Retranslate(); + // allow to add keyframes immediately, without changing context + body_->SetTimeTarget(time_target_); emit InputsChanged(); } @@ -442,6 +426,12 @@ void NodeParamViewItemBody::PlaceWidgetsFromBridge(QGridLayout* layout, NodePara colspan = 1; } + // some non-keyframeable widgets can use all space to the right + if (w->property("is_exapandable").toBool()) + { + colspan = -1; + } + layout->addWidget(w, row, col, 1, colspan); } } diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index 91c3353745..5357c479fe 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -170,6 +170,7 @@ class NodeParamViewItem : public NodeParamViewItemBase void SetTimeTarget(Node* target) { body_->SetTimeTarget(target); + time_target_ = target; } void SetTime(const rational& time) @@ -248,6 +249,7 @@ private slots: NodeParamViewCheckBoxBehavior create_checkboxes_; Node *ctx_; + Node * time_target_; rational time_; rational timebase_; From 45f20741762d05992d6ec382dd312705cc186bcb Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Sat, 28 May 2022 14:06:20 +0200 Subject: [PATCH 25/49] In function ShowTextDialog, used 'setText' function instead of 'line_edit_->setPlainText', to fix a segmentation fault. --- app/widget/nodeparamview/nodeparamviewtextedit.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/widget/nodeparamview/nodeparamviewtextedit.cpp b/app/widget/nodeparamview/nodeparamviewtextedit.cpp index cc9e2987cb..9e62b58bcc 100644 --- a/app/widget/nodeparamview/nodeparamviewtextedit.cpp +++ b/app/widget/nodeparamview/nodeparamviewtextedit.cpp @@ -57,7 +57,7 @@ NodeParamViewTextEdit::NodeParamViewTextEdit(QWidget *parent) : connect(edit_in_viewer_btn_, &QPushButton::clicked, this, &NodeParamViewTextEdit::RequestEditInViewer); SetEditInViewerOnlyMode(false); - + ext_editor_proxy_ = new ExternalEditorProxy( this); connect( ext_editor_proxy_, & ExternalEditorProxy::textChanged, this, & NodeParamViewTextEdit::OnTextChangedExternally); @@ -92,7 +92,7 @@ void NodeParamViewTextEdit::ShowTextDialog() } if (text != QString()) { - line_edit_->setPlainText( text); + setText(text); emit textEdited( text); } } @@ -134,7 +134,7 @@ void NodeParamViewTextEdit::setCodeEditorFlag() // if the text box is a shader code editor, make it read only so that // the shader code is not re-parsed on every key pressed by the user. // Please use the Text Dialog to edit code. - line_edit_->setEnabled( false); + line_edit_->setReadOnly( true); } void NodeParamViewTextEdit::setCodeIssuesFlag() From f5f61aa61e2cb5cd2837b1dc4d8b8efaf4c97d7d Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Sat, 4 Jun 2022 11:48:20 +0200 Subject: [PATCH 26/49] Error messages of shader compilation exported to GUI --- app/dialog/codeeditor/glslhighlighter.cpp | 2 +- app/dialog/codeeditor/messagehighlighter.cpp | 20 ++++++-- app/dialog/codeeditor/messagehighlighter.h | 2 + app/node/filter/shader/shader.cpp | 48 ++++++++++++++------ app/node/filter/shader/shader.h | 5 +- 5 files changed, 56 insertions(+), 21 deletions(-) diff --git a/app/dialog/codeeditor/glslhighlighter.cpp b/app/dialog/codeeditor/glslhighlighter.cpp index 4192e674a7..4b441855cf 100644 --- a/app/dialog/codeeditor/glslhighlighter.cpp +++ b/app/dialog/codeeditor/glslhighlighter.cpp @@ -67,7 +67,7 @@ const QString keywordPatterns[] = { QStringLiteral("\\bcase\\b"), QStringLiteral("\\btrue\\b"), QStringLiteral("\\bfalse\\b"), QStringLiteral("\\bstatic\\b"), QStringLiteral("\\bdefault\\b"), QStringLiteral("#define\\b"), QStringLiteral("\\bove_texcoord\\b"), QStringLiteral("\\bin\\b"), QStringLiteral("\\bout\\b"), - QStringLiteral("\\bresolution_in\\b") + QStringLiteral("\\bresolution_in\\b"), QStringLiteral("#version\\b") }; const QString typePatterns[] = { diff --git a/app/dialog/codeeditor/messagehighlighter.cpp b/app/dialog/codeeditor/messagehighlighter.cpp index deca639aa8..7f8d86cf25 100644 --- a/app/dialog/codeeditor/messagehighlighter.cpp +++ b/app/dialog/codeeditor/messagehighlighter.cpp @@ -31,8 +31,11 @@ MessageSyntaxHighlighter::MessageSyntaxHighlighter(QTextDocument *parent) : line_format_.setForeground(QColor(128,128,128)); line_format_.setFontItalic(true); - group_regexp = QRegularExpression(QStringLiteral("\".*\"")); + shader_format_.setForeground(QColor(100,16,16)); + + group_regexp = QRegularExpression(QStringLiteral("\"[^\"]*\"")); line_regexp = QRegularExpression(QStringLiteral("line\\s\\d+:")); + shader_msg_regexp = QRegularExpression(QStringLiteral("\\d\\(\\d+\\)\\s:\\serror\\sC\\d+:")); } void MessageSyntaxHighlighter::highlightBlock(const QString &text) @@ -46,10 +49,17 @@ void MessageSyntaxHighlighter::highlightBlock(const QString &text) matchIterator = line_regexp.globalMatch(text); - while (matchIterator.hasNext()) { - QRegularExpressionMatch match = matchIterator.next(); - setFormat(match.capturedStart(), match.capturedLength(), line_format_); - } + while (matchIterator.hasNext()) { + QRegularExpressionMatch match = matchIterator.next(); + setFormat(match.capturedStart(), match.capturedLength(), line_format_); + } + + matchIterator = shader_msg_regexp.globalMatch(text); + + while (matchIterator.hasNext()) { + QRegularExpressionMatch match = matchIterator.next(); + setFormat(match.capturedStart(), match.capturedLength(), shader_format_); + } } diff --git a/app/dialog/codeeditor/messagehighlighter.h b/app/dialog/codeeditor/messagehighlighter.h index e8d51d092f..0dfeb70670 100644 --- a/app/dialog/codeeditor/messagehighlighter.h +++ b/app/dialog/codeeditor/messagehighlighter.h @@ -46,9 +46,11 @@ class MessageSyntaxHighlighter : public QSyntaxHighlighter private: QTextCharFormat group_format_; QTextCharFormat line_format_; + QTextCharFormat shader_format_; QRegularExpression group_regexp; QRegularExpression line_regexp; + QRegularExpression shader_msg_regexp; }; diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index c9c3deb566..526476824b 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "shaderinputsparser.h" @@ -100,11 +101,11 @@ QVector ShaderFilterNode::Category() const return {kCategoryFilter}; } + void ShaderFilterNode::InputValueChangedEvent(const QString &input, int element) { Q_UNUSED(element) - if (input == kShaderCode) { // for some reason, this function is called more than once for each input // of each instance. Parse code only if it has changed @@ -113,22 +114,20 @@ void ShaderFilterNode::InputValueChangedEvent(const QString &input, int element) if (shader_code_ != new_code) { shader_code_ = new_code; + output_messages_.clear(); // the code of the shader has changed. // Remove all inputs and re-parse the code // to fix shader name and input parameters. - onShaderCodeChanged(); - } - } -} + parseShaderCode(); -void olive::ShaderFilterNode::onShaderCodeChanged() -{ + // check for syntax + checkShaderSyntax(); - // create new inputs and gizmos - parseShaderCode(); - - qDebug() << "parsed shader code for " << GetLabel() << " @ " << (uint64_t)this; + SetStandardValue( kOutputMessages, output_messages_.isEmpty() ? tr("None") : output_messages_); + qDebug() << "parsed shader code for " << GetLabel() << " @ " << (uint64_t)this; + } + } } @@ -168,6 +167,27 @@ void ShaderFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globa } } + +void olive::ShaderFilterNode::checkShaderSyntax() +{ + QOpenGLShader shader(QOpenGLShader::Fragment); + bool ok; + + if (shader_code_.startsWith("#version")) { + + ok=shader.compileSourceCode( shader_code_); + } else { + + ok=shader.compileSourceCode( "#version 150\n" + shader_code_); + } + + if (ok == false) + { + output_messages_ += QString("Compilation errors:\n") + shader.log(); + } +} + + void ShaderFilterNode::parseShaderCode() { ShaderInputsParser parser(shader_code_); @@ -187,9 +207,9 @@ void ShaderFilterNode::reportErrorList( const ShaderInputsParser & parser) { const QList & errors = parser.ErrorList(); - QString message = QString(tr("None")); + QString message; if (errors.size() > 0) { - message = QString(tr("There are %1 issues.\n").arg(errors.size())); + message = QString(tr("There are %1 metadata issues.\n").arg(errors.size())); } for (ShaderInputsParser::Error e : errors ) { @@ -197,7 +217,7 @@ void ShaderFilterNode::reportErrorList( const ShaderInputsParser & parser) arg( parser.ShaderName()).arg(e.line).arg(e.issue)); } - SetStandardValue( kOutputMessages, QVariant::fromValue(message)); + output_messages_ += message; } void ShaderFilterNode::updateInputList( const ShaderInputsParser & parser) diff --git a/app/node/filter/shader/shader.h b/app/node/filter/shader/shader.h index 2a54827808..e6fd4ab2be 100644 --- a/app/node/filter/shader/shader.h +++ b/app/node/filter/shader/shader.h @@ -64,7 +64,7 @@ class ShaderFilterNode : public Node private: void parseShaderCode(); - void onShaderCodeChanged(); + void checkShaderSyntax(); void reportErrorList( const ShaderInputsParser & parser); void updateInputList( const ShaderInputsParser & parser); void updateGizmoList(); @@ -76,7 +76,10 @@ protected slots: private: + // source GLSL code QString shader_code_; + // error in metadata or shader + QString output_messages_; // user defined inputs QStringList user_input_list_; // input of type vec2 to gizmo map From c86c8eb58e9e199d65cf6a763f4b05b019a3b662 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Tue, 7 Jun 2022 21:17:05 +0200 Subject: [PATCH 27/49] Built-in "Enabled" input integrated with Shader node. --- app/node/filter/shader/shader.cpp | 4 +++ app/node/filter/shader/shaderinputsparser.cpp | 26 ++++++++++++++++--- app/node/filter/shader/shaderinputsparser.h | 3 +++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index 526476824b..1f3837ff69 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -259,6 +259,10 @@ void ShaderFilterNode::updateInputList( const ShaderInputsParser & parser) if (it->type == NodeValue::kCombo) { SetComboBoxStrings(it->uniform_name, it->values); } + + if (it->is_effect_input) { + SetEffectInput( it->uniform_name); + } } // compare 'new_input_list' and 'user_input_list_' to find deleted inputs. diff --git a/app/node/filter/shader/shaderinputsparser.cpp b/app/node/filter/shader/shaderinputsparser.cpp index fce11104e5..5e1e3962fc 100644 --- a/app/node/filter/shader/shaderinputsparser.cpp +++ b/app/node/filter/shader/shaderinputsparser.cpp @@ -106,6 +106,7 @@ void clearCurrentInput() currentInput.min = QVariant(); currentInput.max = QVariant(); currentInput.default_value = QVariant(); + currentInput.is_effect_input = false; } } // namespace @@ -282,16 +283,17 @@ ShaderInputsParser::parseInputType(const QRegularExpressionMatch & match) return PARSING; } + ShaderInputsParser::InputParseState ShaderInputsParser::parseInputFlags(const QRegularExpressionMatch & line_match) { // more flags can be present in one line - static const QRegularExpression FLAG_REGEX("(ARRAY|NOT_CONNECTABLE|NOT_KEYFRAMABLE|HIDDEN)"); + static const QRegularExpression FLAG_REGEX("(ARRAY|NOT_CONNECTABLE|NOT_KEYFRAMABLE|HIDDEN|MAIN_INPUT)"); static const QMap FLAG_TABLE{{"ARRAY", kInputFlagArray}, {"NOT_CONNECTABLE", kInputFlagNotConnectable}, {"NOT_KEYFRAMABLE", kInputFlagNotKeyframable}, - {"HIDDEN", kInputFlagHidden}, + {"HIDDEN", kInputFlagHidden} }; @@ -303,12 +305,30 @@ ShaderInputsParser::parseInputFlags(const QRegularExpressionMatch & line_match) QString flag_str = flag.captured(1); currentInput.flags |= InputFlags(FLAG_TABLE.value( flag_str, kInputFlagNormal)); - } + if (flag_str == "MAIN_INPUT") { + // This is not a regular flag for node input. This is the texture passed + // to output when "Enable" input is not checked. + setAsMainInput(); + } + } return PARSING; } + +void olive::ShaderInputsParser::setAsMainInput() +{ + if (currentInput.type == NodeValue::kTexture) { + currentInput.is_effect_input = true; + } + else { + reportError(QObject::tr("Flag MAIN_INPUT is applicable for type TEXTURE only; not for %1."). + arg(currentInput.type_string)); + } +} + + ShaderInputsParser::InputParseState ShaderInputsParser::parseInputValueList(const QRegularExpressionMatch & line_match) { diff --git a/app/node/filter/shader/shaderinputsparser.h b/app/node/filter/shader/shaderinputsparser.h index 044cd0c30a..0454a6836a 100644 --- a/app/node/filter/shader/shaderinputsparser.h +++ b/app/node/filter/shader/shaderinputsparser.h @@ -51,6 +51,8 @@ class ShaderInputsParser NodeValue::Type type; InputFlags flags; + // when true, this input becomes the output when node is disabled + bool is_effect_input; // list of entries for a selection combo QStringList values; @@ -123,6 +125,7 @@ class ShaderInputsParser QVariant parseColor( const QStringRef & line); QVariant parsePoint( const QStringRef & line); void reportError( const QString & error); + void setAsMainInput(); private: const QString & shader_code_; From 7becf3684473f7959d5353763e24251d2b933a25 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Sat, 18 Jun 2022 17:42:33 +0200 Subject: [PATCH 28/49] Added EffectInput to add shader node from NodeParamView menu. Added color and shape for input of type 'POINT'. --- app/dialog/codeeditor/codeeditordialog.cpp | 1 + app/dialog/codeeditor/glslhighlighter.cpp | 8 +-- app/node/filter/shader/shader.cpp | 36 +++++++++---- app/node/filter/shader/shader.h | 4 ++ app/node/filter/shader/shaderinputsparser.cpp | 50 +++++++++++++++++++ app/node/filter/shader/shaderinputsparser.h | 7 +++ app/node/gizmo/point.cpp | 5 ++ 7 files changed, 98 insertions(+), 13 deletions(-) diff --git a/app/dialog/codeeditor/codeeditordialog.cpp b/app/dialog/codeeditor/codeeditordialog.cpp index af52a2d906..91f1290010 100644 --- a/app/dialog/codeeditor/codeeditordialog.cpp +++ b/app/dialog/codeeditor/codeeditordialog.cpp @@ -145,6 +145,7 @@ void CodeEditorDialog::OnActionAddInputPoint() text_edit_->insertPlainText( "//OVE name: my point\n" "//OVE type: POINT\n" + "//OVE shape: SQUARE\n" "//OVE default: (0.4, 0.2)\n" "//OVE description: \n" "uniform vec2 my_point;\n"); diff --git a/app/dialog/codeeditor/glslhighlighter.cpp b/app/dialog/codeeditor/glslhighlighter.cpp index 4b441855cf..4ba661a04e 100644 --- a/app/dialog/codeeditor/glslhighlighter.cpp +++ b/app/dialog/codeeditor/glslhighlighter.cpp @@ -97,12 +97,12 @@ const QString oliveMarkupPatterns[] = { QStringLiteral("//OVE\\s+shader_version:[^\n]*"), QStringLiteral("//OVE\\s+name:[^\n]*"), QStringLiteral("//OVE\\s+type:[^\n]*"), QStringLiteral("//OVE\\s+flag:[^\n]*"), QStringLiteral("//OVE\\s+values:[^\n]*"), QStringLiteral("//OVE\\s+description:[^\n]*"), - QStringLiteral("//OVE\\s+default:[^\n]*"), QStringLiteral("//OVE\\s+min:[^\n]*"), - QStringLiteral("//OVE\\s+max:[^\n]*"), - QStringLiteral("//OVE\\s+end\\b[^\n]*") + QStringLiteral("//OVE\\s+default:[^\n]*"), QStringLiteral("//OVE shape: [^\n]*"), + QStringLiteral("//OVE color: [^\n]*"), QStringLiteral("//OVE\\s+min:[^\n]*"), + QStringLiteral("//OVE\\s+max:[^\n]*"), QStringLiteral("//OVE\\s+end\\b[^\n]*") }; -} +} // namespace GlslHighlighter::GlslHighlighter(QTextDocument *parent) : QSyntaxHighlighter(parent) diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index 1f3837ff69..f30ea4df90 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -37,12 +37,14 @@ namespace olive { namespace { +// a default shader that replicates to output a texture input. +// The input is flagged as MAIN_INPUT to comply with "Video Nodes" menu button const QString TEMPLATE( "//OVE shader_name: \n" "//OVE shader_description: \n\n" "//OVE name: input\n" "//OVE type: TEXTURE\n" - "//OVE flag: NOT_KEYFRAMABLE\n" + "//OVE flag: NOT_KEYFRAMABLE, MAIN_INPUT\n" "//OVE description:\n" "uniform sampler2D texture_in;\n\n" "//OVE end\n\n\n" @@ -74,6 +76,8 @@ ShaderFilterNode::ShaderFilterNode() SetInputProperty( kShaderCode, QStringLiteral("text_type"), QString("shader_code")); // mark this text input as output messages SetInputProperty( kOutputMessages, QStringLiteral("text_type"), QString("shader_issues")); + + SetFlags(kVideoEffect); } Node *ShaderFilterNode::copy() const @@ -263,6 +267,11 @@ void ShaderFilterNode::updateInputList( const ShaderInputsParser & parser) if (it->is_effect_input) { SetEffectInput( it->uniform_name); } + + if (it->type == NodeValue::kVec2) { + handle_shape_table_.insert( it->uniform_name, it->pointShape); + handle_color_table_.insert( it->uniform_name, it->gizmoColor); + } } // compare 'new_input_list' and 'user_input_list_' to find deleted inputs. @@ -282,14 +291,21 @@ void ShaderFilterNode::updateGizmoList() for (QString aInput : user_input_list_) { if (HasInputWithID(aInput)) { - if ( (GetInputDataType(aInput) == NodeValue::kVec2) && - (handle_table_.contains(aInput) == false) ){ - PointGizmo * g = AddDraggableGizmo(); - g->AddInput(NodeKeyframeTrackReference(NodeInput(this, aInput), 0)); - g->AddInput(NodeKeyframeTrackReference(NodeInput(this, aInput), 1)); - g->SetDragValueBehavior(PointGizmo::kAbsolute); - - handle_table_.insert( aInput, g); + if (GetInputDataType(aInput) == NodeValue::kVec2) { + + // is point new? + if (handle_table_.contains(aInput) == false) { + PointGizmo * g = AddDraggableGizmo(); + g->AddInput(NodeKeyframeTrackReference(NodeInput(this, aInput), 0)); + g->AddInput(NodeKeyframeTrackReference(NodeInput(this, aInput), 1)); + g->SetDragValueBehavior(PointGizmo::kAbsolute); + + handle_table_.insert( aInput, g); + } + + PointGizmo * g = handle_table_[aInput]; + g->SetShape( handle_shape_table_.value( aInput, PointGizmo::kSquare)); + g->setProperty("color", QVariant::fromValue(handle_color_table_.value(aInput, Qt::white))); } } } @@ -306,6 +322,8 @@ void ShaderFilterNode::checkDeletedInputs(const QStringList & new_inputs) if (handle_table_.contains(input)) { RemoveGizmo( handle_table_[input]); handle_table_.remove( input); + handle_shape_table_.remove( input); + handle_color_table_.remove( input); } } } diff --git a/app/node/filter/shader/shader.h b/app/node/filter/shader/shader.h index e6fd4ab2be..edf8f1db94 100644 --- a/app/node/filter/shader/shader.h +++ b/app/node/filter/shader/shader.h @@ -84,6 +84,10 @@ protected slots: QStringList user_input_list_; // input of type vec2 to gizmo map QMap handle_table_; + // shape for points in 'handle_table_' + QMap handle_shape_table_; + // color for points in 'handle_table_' + QMap handle_color_table_; QVector2D resolution_; }; diff --git a/app/node/filter/shader/shaderinputsparser.cpp b/app/node/filter/shader/shaderinputsparser.cpp index 5e1e3962fc..bf18b9510e 100644 --- a/app/node/filter/shader/shaderinputsparser.cpp +++ b/app/node/filter/shader/shaderinputsparser.cpp @@ -65,6 +65,10 @@ QRegularExpression INPUT_MAX_REGEX("^\\s*//OVE\\s*max:\\s*(?.*)\\s*$"); QRegularExpression INPUT_DEFAULT_REGEX("^\\s*//OVE\\s*default:\\s*(?.*)\\s*$"); // list of values for a selection combo QRegularExpression INPUT_VALUES_REGEX("^\\s*//OVE\\s*values:\\s*(?.*)\\s*$"); +// shape used to draw a point +QRegularExpression INPUT_SHAPE_REGEX("^\\s*//OVE\\s*shape:\\s*(?.*)\\s*$"); +// color used to draw gizmo +QRegularExpression INPUT_COLOR_REGEX("^\\s*//OVE\\s*color:\\s*(?.*)\\s*$"); // description. So far not used by application QRegularExpression INPUT_DESCRIPTION_REGEX("^\\s*//OVE\\s*description:\\s*(?.*)\\s*$"); @@ -103,6 +107,8 @@ void clearCurrentInput() currentInput.type = NodeValue::kNone; currentInput.flags = InputFlags(kInputFlagNormal); currentInput.values.clear(); + currentInput.pointShape = PointGizmo::kSquare; + currentInput.gizmoColor = Qt::white; currentInput.min = QVariant(); currentInput.max = QVariant(); currentInput.default_value = QVariant(); @@ -128,6 +134,8 @@ ShaderInputsParser::ShaderInputsParser( const QString & shader_code) : INPUT_PARAM_PARSE_TABLE.insert( & INPUT_TYPE_REGEX, & ShaderInputsParser::parseInputType); INPUT_PARAM_PARSE_TABLE.insert( & INPUT_FLAG_REGEX, & ShaderInputsParser::parseInputFlags); INPUT_PARAM_PARSE_TABLE.insert( & INPUT_VALUES_REGEX, & ShaderInputsParser::parseInputValueList); + INPUT_PARAM_PARSE_TABLE.insert( & INPUT_SHAPE_REGEX, & ShaderInputsParser::parseInputPointShape); + INPUT_PARAM_PARSE_TABLE.insert( & INPUT_COLOR_REGEX, & ShaderInputsParser::parseInputGizmoColor); INPUT_PARAM_PARSE_TABLE.insert( & INPUT_MIN_REGEX, & ShaderInputsParser::parseInputMin); INPUT_PARAM_PARSE_TABLE.insert( & INPUT_MAX_REGEX, & ShaderInputsParser::parseInputMax); INPUT_PARAM_PARSE_TABLE.insert( & INPUT_DEFAULT_REGEX, & ShaderInputsParser::parseInputDefault); @@ -348,6 +356,48 @@ ShaderInputsParser::parseInputValueList(const QRegularExpressionMatch & line_mat return PARSING; } +ShaderInputsParser::InputParseState ShaderInputsParser::parseInputPointShape(const QRegularExpressionMatch & line_match) +{ + // lookup table for point shape + static const QMap< QString, PointGizmo::Shape> INPUT_POINT_SHAPE_TABLE{ + {"SQUARE", PointGizmo::kSquare}, + {"CIRCLE", PointGizmo::kCircle}, + {"ANCHOR", PointGizmo::kAnchorPoint} + }; + + if (line_match.hasMatch()) { + QString shape_str = line_match.captured("shape"); + + if (INPUT_POINT_SHAPE_TABLE.contains(shape_str)) { + currentInput.pointShape = INPUT_POINT_SHAPE_TABLE[shape_str]; + } + else { + currentInput.pointShape = PointGizmo::kSquare; + reportError(QObject::tr("'%1' is not a valid point shape."). + arg(shape_str)); + } + } + + return PARSING; +} + +ShaderInputsParser::InputParseState ShaderInputsParser::parseInputGizmoColor(const QRegularExpressionMatch & line_match) +{ + if (currentInput.type == NodeValue::kVec2) { + QStringRef color_string = line_match.capturedRef("color").trimmed(); + Color ove_color = parseColor( color_string).value(); + currentInput.gizmoColor = QColor( int(ove_color.red()*255.0), + int(ove_color.green()*255.0), + int(ove_color.blue()*255.0), 255); + } + else { + reportError( QObject::tr("Attribute 'color' can be used for type POINT, not for %1"). + arg(currentInput.type_string)); + } + + return PARSING; +} + ShaderInputsParser::InputParseState ShaderInputsParser::parseInputMin(const QRegularExpressionMatch & match) { diff --git a/app/node/filter/shader/shaderinputsparser.h b/app/node/filter/shader/shaderinputsparser.h index 0454a6836a..9b7e68e53b 100644 --- a/app/node/filter/shader/shaderinputsparser.h +++ b/app/node/filter/shader/shaderinputsparser.h @@ -27,6 +27,7 @@ #include "node/value.h" #include "node/param.h" +#include "node/gizmo/point.h" namespace olive { @@ -55,6 +56,10 @@ class ShaderInputsParser bool is_effect_input; // list of entries for a selection combo QStringList values; + // only applicable for type kVec2. How the point is drawn + PointGizmo::Shape pointShape; + // color used to draw a point gizmo + QColor gizmoColor; QVariant min; QVariant max; @@ -116,6 +121,8 @@ class ShaderInputsParser InputParseState parseInputType( const QRegularExpressionMatch &); InputParseState parseInputFlags( const QRegularExpressionMatch &); InputParseState parseInputValueList( const QRegularExpressionMatch &); + InputParseState parseInputPointShape( const QRegularExpressionMatch &); + InputParseState parseInputGizmoColor( const QRegularExpressionMatch &); InputParseState parseInputMin( const QRegularExpressionMatch &); InputParseState parseInputMax( const QRegularExpressionMatch &); InputParseState parseInputDefault( const QRegularExpressionMatch &); diff --git a/app/node/gizmo/point.cpp b/app/node/gizmo/point.cpp index d60ccf5bd0..4b9f7c3585 100644 --- a/app/node/gizmo/point.cpp +++ b/app/node/gizmo/point.cpp @@ -44,12 +44,17 @@ PointGizmo::PointGizmo(QObject *parent) : void PointGizmo::Draw(QPainter *p) const { QRectF rect = GetDrawingRect(p->transform(), GetStandardRadius()); + QVariant color = this->property("color"); if (shape_ != kAnchorPoint) { p->setPen(Qt::NoPen); p->setBrush(Qt::white); } + if (color.isValid()) { + p->setBrush(color.value()); + } + switch (shape_) { case kSquare: p->drawRect(rect); From b6bb7f63cf2d8913123eced4985f549862c17212 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Sun, 11 Dec 2022 18:15:53 +0100 Subject: [PATCH 29/49] Aligned to new codebase. --- app/dialog/codeeditor/codeeditordialog.cpp | 2 +- app/node/filter/shader/shader.cpp | 57 +++++++++++-------- app/node/filter/shader/shader.h | 8 ++- app/node/filter/shader/shaderinputsparser.cpp | 56 +++++++----------- app/node/filter/shader/shaderinputsparser.h | 9 +-- app/node/node.h | 7 +++ app/render/renderprocessor.cpp | 6 +- app/widget/nodeparamview/nodeparamviewitem.h | 2 +- 8 files changed, 76 insertions(+), 71 deletions(-) diff --git a/app/dialog/codeeditor/codeeditordialog.cpp b/app/dialog/codeeditor/codeeditordialog.cpp index 91f1290010..f3570401b6 100644 --- a/app/dialog/codeeditor/codeeditordialog.cpp +++ b/app/dialog/codeeditor/codeeditordialog.cpp @@ -59,7 +59,7 @@ CodeEditorDialog::CodeEditorDialog(const QString &start, QWidget* parent) : layout->addWidget( search_bar_); QAction * show_find_dialog = edit_menu->addAction(tr("find/replace")); - show_find_dialog->setShortcut( QKeySequence(Qt::CTRL + Qt::Key_F)); + show_find_dialog->setShortcut( QKeySequence(Qt::CTRL | Qt::Key_F)); connect( show_find_dialog, &QAction::triggered, this, &CodeEditorDialog::OnFindRequest); diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index f30ea4df90..5936c1b90e 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -28,9 +28,12 @@ #include #include #include +#include #include "shaderinputsparser.h" +#include + namespace olive { #define super Node @@ -38,32 +41,35 @@ namespace olive { namespace { // a default shader that replicates to output a texture input. -// The input is flagged as MAIN_INPUT to comply with "Video Nodes" menu button const QString TEMPLATE( "//OVE shader_name: \n" "//OVE shader_description: \n\n" - "//OVE name: input\n" - "//OVE type: TEXTURE\n" - "//OVE flag: NOT_KEYFRAMABLE, MAIN_INPUT\n" - "//OVE description:\n" - "uniform sampler2D texture_in;\n\n" + "//Default texture: this comes on the house:\n" + "uniform sampler2D tex_in;\n\n" "//OVE end\n\n\n" "// pixel coordinates in range [0..1]x[0..1]\n" "in vec2 ove_texcoord;\n" "// output color\n" "out vec4 frag_color;\n\n" "void main(void) {\n" - " vec4 textureColor = texture2D(texture_in, ove_texcoord);\n" - " frag_color= textureColor;\n" + " vec4 textureColor = texture2D(tex_in, ove_texcoord);\n" + " frag_color= textureColor.yzxt;\n" "}\n"); -} +} // namespace + +const QString ShaderFilterNode::kTextureInput = QStringLiteral("tex_in"); const QString ShaderFilterNode::kShaderCode = QStringLiteral("source"); const QString ShaderFilterNode::kOutputMessages = QStringLiteral("issues"); -ShaderFilterNode::ShaderFilterNode() +ShaderFilterNode::ShaderFilterNode(): + invalidate_code_flag_(false) { + // A default texture + AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + SetEffectInput(kTextureInput); + // Full code of the shader. Inputs to be exposed are defined within the shader code // with mark-up comments. AddInput(kShaderCode, NodeValue::kText, QVariant::fromValue(TEMPLATE), @@ -130,10 +136,19 @@ void ShaderFilterNode::InputValueChangedEvent(const QString &input, int element) SetStandardValue( kOutputMessages, output_messages_.isEmpty() ? tr("None") : output_messages_); qDebug() << "parsed shader code for " << GetLabel() << " @ " << (uint64_t)this; + + invalidate_code_flag_ = true; } } } +bool ShaderFilterNode::ShaderCodeInvalidateFlag() const +{ + bool invalid = invalidate_code_flag_; + invalidate_code_flag_ = false; + return invalid; +} + QString ShaderFilterNode::Description() const { @@ -146,6 +161,7 @@ void ShaderFilterNode::Retranslate() // Retranslate only fixed inputs. // Other inputs are read from the shader code + SetInputName( kTextureInput, tr("Input")); SetInputName( kShaderCode, tr("Shader code")); SetInputName( kOutputMessages, tr("Issues")); } @@ -159,19 +175,16 @@ ShaderCode ShaderFilterNode::GetShaderCode(const Node::ShaderRequest &request) c void ShaderFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { - ShaderJob job; - - job.Insert(value); - job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); - job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); - - // If there's no shader code, no need to run an operation - if (shader_code_ != QString()) { - table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); + if (TexturePtr tex = value[kTextureInput].toTexture()) { + ShaderJob job(value); + job.SetShaderID(GetLabel()); // TBD: label may not be enough to identify a shader univocally + job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, tex->virtual_resolution(), this)); + table->Push(NodeValue::kTexture, tex->toJob(job), this); } } + void olive::ShaderFilterNode::checkShaderSyntax() { QOpenGLShader shader(QOpenGLShader::Fragment); @@ -264,10 +277,6 @@ void ShaderFilterNode::updateInputList( const ShaderInputsParser & parser) SetComboBoxStrings(it->uniform_name, it->values); } - if (it->is_effect_input) { - SetEffectInput( it->uniform_name); - } - if (it->type == NodeValue::kVec2) { handle_shape_table_.insert( it->uniform_name, it->pointShape); handle_color_table_.insert( it->uniform_name, it->gizmoColor); @@ -342,7 +351,7 @@ void ShaderFilterNode::GizmoDragMove(double x, double y, const Qt::KeyboardModif void ShaderFilterNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals & globals) { - resolution_ = globals.resolution(); + resolution_ = globals.vparams().resolution(); for (QString aInput : user_input_list_) { diff --git a/app/node/filter/shader/shader.h b/app/node/filter/shader/shader.h index edf8f1db94..63e344dbf3 100644 --- a/app/node/filter/shader/shader.h +++ b/app/node/filter/shader/shader.h @@ -58,8 +58,10 @@ class ShaderFilterNode : public Node virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; void InputValueChangedEvent(const QString &input, int element) override; - static const QString kShaderCode; + bool ShaderCodeInvalidateFlag() const override; + static const QString kTextureInput; + static const QString kShaderCode; static const QString kOutputMessages; private: @@ -76,6 +78,10 @@ protected slots: private: + // set to true when shader code changes. Must be mutable because + // it is reset when it is read + mutable bool invalidate_code_flag_; + // source GLSL code QString shader_code_; // error in metadata or shader diff --git a/app/node/filter/shader/shaderinputsparser.cpp b/app/node/filter/shader/shaderinputsparser.cpp index bf18b9510e..3dab430639 100644 --- a/app/node/filter/shader/shaderinputsparser.cpp +++ b/app/node/filter/shader/shaderinputsparser.cpp @@ -20,7 +20,7 @@ #include "shaderinputsparser.h" -#include +#include #include #include #include @@ -34,9 +34,9 @@ namespace olive { namespace { // This kind of comments are parsed for input parameters -const char OLIVE_MARKED_COMMENT[] = "//OVE"; +const QString OLIVE_MARKED_COMMENT = QString::fromLatin1("//OVE"); // definition of a uniform variable -const char UNIFORM_TAG[] = "uniform"; +const QString UNIFORM_TAG = QString::fromLatin1("uniform"); // per Shader metadata @@ -112,7 +112,6 @@ void clearCurrentInput() currentInput.min = QVariant(); currentInput.max = QVariant(); currentInput.default_value = QVariant(); - currentInput.is_effect_input = false; } } // namespace @@ -148,7 +147,7 @@ void ShaderInputsParser::Parse() { int fragment_size = shader_code_.length(); int cursor = 0; - QStringRef line; + QString line; InputParseState state = PARSING; static const QRegularExpression LINE_BREAK = QRegularExpression("[\\n\\r]+"); @@ -164,19 +163,19 @@ void ShaderInputsParser::Parse() if (line_end_index == -1) { // last line of file - line = QStringRef( & shader_code_, cursor, fragment_size - cursor); + line = QString( shader_code_).mid( cursor, fragment_size - cursor); cursor = fragment_size; } else { - line = QStringRef( & shader_code_, cursor, line_end_index - cursor); + line = QString( shader_code_).mid( cursor, line_end_index - cursor); cursor = line_end_index; } line = line.trimmed(); // check if this line must be parsed - if ( (line.startsWith(OLIVE_MARKED_COMMENT)) || - (line.startsWith(UNIFORM_TAG)) ) { + if ( (line.startsWith(QString(OLIVE_MARKED_COMMENT))) || + (line.startsWith(QString(UNIFORM_TAG))) ) { state = parseSingleLine( line); @@ -208,7 +207,7 @@ void ShaderInputsParser::Parse() } -ShaderInputsParser::InputParseState ShaderInputsParser::parseSingleLine( const QStringRef & line) +ShaderInputsParser::InputParseState ShaderInputsParser::parseSingleLine( const QString & line) { QRegularExpressionMatch match; InputParseState new_state = PARSING; @@ -305,7 +304,7 @@ ShaderInputsParser::parseInputFlags(const QRegularExpressionMatch & line_match) }; - QStringRef flags_line = line_match.capturedRef("flags"); + QString flags_line = line_match.captured("flags"); QRegularExpressionMatchIterator flag_match = FLAG_REGEX.globalMatch( flags_line); while (flag_match.hasNext()) { @@ -313,29 +312,12 @@ ShaderInputsParser::parseInputFlags(const QRegularExpressionMatch & line_match) QString flag_str = flag.captured(1); currentInput.flags |= InputFlags(FLAG_TABLE.value( flag_str, kInputFlagNormal)); - - if (flag_str == "MAIN_INPUT") { - // This is not a regular flag for node input. This is the texture passed - // to output when "Enable" input is not checked. - setAsMainInput(); - } } return PARSING; } -void olive::ShaderInputsParser::setAsMainInput() -{ - if (currentInput.type == NodeValue::kTexture) { - currentInput.is_effect_input = true; - } - else { - reportError(QObject::tr("Flag MAIN_INPUT is applicable for type TEXTURE only; not for %1."). - arg(currentInput.type_string)); - } -} - ShaderInputsParser::InputParseState ShaderInputsParser::parseInputValueList(const QRegularExpressionMatch & line_match) @@ -343,7 +325,7 @@ ShaderInputsParser::parseInputValueList(const QRegularExpressionMatch & line_mat // entries are enclosed in double quotes static const QRegularExpression COMBO_ENTRY_REGEX("\"([^\"]+)\""); - QStringRef values_line = line_match.capturedRef("values"); + QString values_line = line_match.captured("values"); QRegularExpressionMatchIterator value_match = COMBO_ENTRY_REGEX.globalMatch( values_line); while (value_match.hasNext()) { @@ -384,7 +366,7 @@ ShaderInputsParser::InputParseState ShaderInputsParser::parseInputPointShape(con ShaderInputsParser::InputParseState ShaderInputsParser::parseInputGizmoColor(const QRegularExpressionMatch & line_match) { if (currentInput.type == NodeValue::kVec2) { - QStringRef color_string = line_match.capturedRef("color").trimmed(); + QString color_string = line_match.captured("color").trimmed(); Color ove_color = parseColor( color_string).value(); currentInput.gizmoColor = QColor( int(ove_color.red()*255.0), int(ove_color.green()*255.0), @@ -402,7 +384,7 @@ ShaderInputsParser::InputParseState ShaderInputsParser::parseInputMin(const QRegularExpressionMatch & match) { bool ok = false; - QStringRef min_str = match.capturedRef("min"); + QString min_str = match.captured("min"); if (currentInput.type == NodeValue::kFloat) { currentInput.min = min_str.toFloat( &ok); @@ -426,7 +408,7 @@ ShaderInputsParser::InputParseState ShaderInputsParser::parseInputMax(const QRegularExpressionMatch & match) { bool ok = false; - QStringRef max_str = match.capturedRef("max"); + QString max_str = match.captured("max"); if (currentInput.type == NodeValue::kFloat) { currentInput.max = max_str.toFloat( &ok); @@ -449,7 +431,7 @@ ShaderInputsParser::parseInputMax(const QRegularExpressionMatch & match) ShaderInputsParser::InputParseState ShaderInputsParser::parseInputDefault(const QRegularExpressionMatch & match) { - QStringRef default_string = match.capturedRef("default").trimmed(); + QString default_string = match.captured("default").trimmed(); bool ok = false; float float_value; int int_value; @@ -484,10 +466,10 @@ ShaderInputsParser::parseInputDefault(const QRegularExpressionMatch & match) break; case NodeValue::kBoolean: - if (default_string == "false") { + if (default_string == QString::fromLatin1("false")) { currentInput.default_value = QVariant::fromValue(false); } - else if (default_string == "true") { + else if (default_string == QString::fromLatin1("true")) { currentInput.default_value = QVariant::fromValue(true); } else { @@ -541,7 +523,7 @@ ShaderInputsParser::stopParse(const QRegularExpressionMatch & /*match*/) // Assume 'line' has format RGBA( r,g,b,a), where 'r,g,b,a' are in range 0.0 .. 1.0 // and stand for 'red', 'green', 'blue', 'alpha' -QVariant ShaderInputsParser::parseColor(const QStringRef& line) +QVariant ShaderInputsParser::parseColor(const QString& line) { static const QRegularExpression COLOR_REGEX("RGBA\\(\\s*(?[\\d\\.]+)\\s*,\\s*(?[\\d\\.]+)\\s*,\\s*(?[\\d\\.]+)\\s*,\\s*(?
[\\d\\.]+)\\s*\\)"); @@ -579,7 +561,7 @@ QVariant ShaderInputsParser::parseColor(const QStringRef& line) return color; } -QVariant ShaderInputsParser::parsePoint(const QStringRef &line) +QVariant ShaderInputsParser::parsePoint(const QString &line) { static const QRegularExpression POINT_REGEX("\\s*\\(\\s*(?[\\d\\.]+)\\s*,\\s*(?[\\d\\.]+)\\s*\\)"); diff --git a/app/node/filter/shader/shaderinputsparser.h b/app/node/filter/shader/shaderinputsparser.h index 9b7e68e53b..ef6a8a27d6 100644 --- a/app/node/filter/shader/shaderinputsparser.h +++ b/app/node/filter/shader/shaderinputsparser.h @@ -52,8 +52,6 @@ class ShaderInputsParser NodeValue::Type type; InputFlags flags; - // when true, this input becomes the output when node is disabled - bool is_effect_input; // list of entries for a selection combo QStringList values; // only applicable for type kVec2. How the point is drawn @@ -108,7 +106,7 @@ class ShaderInputsParser SHADER_COMPLETE }; - InputParseState parseSingleLine( const QStringRef & line); + InputParseState parseSingleLine( const QString & line); // pointer to a function that parse a line that matches an //OVE comment typedef InputParseState (ShaderInputsParser::* LineParseFunction)( const QRegularExpressionMatch & match); @@ -129,10 +127,9 @@ class ShaderInputsParser InputParseState parseInputDescription( const QRegularExpressionMatch &); InputParseState stopParse( const QRegularExpressionMatch &); - QVariant parseColor( const QStringRef & line); - QVariant parsePoint( const QStringRef & line); + QVariant parseColor( const QString & line); + QVariant parsePoint( const QString & line); void reportError( const QString & error); - void setAsMainInput(); private: const QString & shader_code_; diff --git a/app/node/node.h b/app/node/node.h index 4c2acc3545..cbbcaa33d3 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -772,6 +772,13 @@ class Node : public QObject */ virtual ShaderCode GetShaderCode(const ShaderRequest &request) const; + /** + * @brief Set to true when shader code that was already compiled must be recompiled. + */ + virtual bool ShaderCodeInvalidateFlag() const { + return false; + } + /** * @brief If Value() pushes a ShaderJob, this is the function that will process them. */ diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index d723d54e79..9a98dc2b92 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -448,7 +448,11 @@ void RenderProcessor::ProcessShader(TexturePtr destination, const Node *node, co QMutexLocker locker(shader_cache_->mutex()); - QVariant shader = shader_cache_->value(full_shader_id); + QVariant shader = QVariant(); + + if (node->ShaderCodeInvalidateFlag() == false) { + shader = shader_cache_->value(full_shader_id); + } if (shader.isNull()) { // Since we have shader code, compile it now diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index d895544d6e..21d8ee7334 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -236,7 +236,7 @@ private slots: NodeParamViewCheckBoxBehavior create_checkboxes_; Node *ctx_; - Node * time_target_; + ViewerOutput * time_target_; rational timebase_; From fb9f79a419ad7d29e14df3b624ba73db85139e19 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Wed, 14 Dec 2022 22:26:43 +0100 Subject: [PATCH 30/49] Added custom name for default texture --- app/dialog/codeeditor/glslhighlighter.cpp | 5 +++-- app/node/filter/shader/shader.cpp | 20 ++++++++++--------- app/node/filter/shader/shader.h | 2 ++ app/node/filter/shader/shaderinputsparser.cpp | 12 ++++++++++- app/node/filter/shader/shaderinputsparser.h | 7 +++++++ 5 files changed, 34 insertions(+), 12 deletions(-) diff --git a/app/dialog/codeeditor/glslhighlighter.cpp b/app/dialog/codeeditor/glslhighlighter.cpp index 4ba661a04e..e2ba5f653e 100644 --- a/app/dialog/codeeditor/glslhighlighter.cpp +++ b/app/dialog/codeeditor/glslhighlighter.cpp @@ -94,12 +94,13 @@ const QString functionPatterns[] = { const QString oliveMarkupPatterns[] = { QStringLiteral("//OVE\\s+shader_name:[^\n]*"), QStringLiteral("//OVE\\s+shader_description:[^\n]*"), - QStringLiteral("//OVE\\s+shader_version:[^\n]*"), QStringLiteral("//OVE\\s+name:[^\n]*"), + QStringLiteral("//OVE\\s+shader_version:[^\n]*"), QStringLiteral("//OVE\\s+main_input_name:[^\n]*"), + QStringLiteral("//OVE\\s+name:[^\n]*"), QStringLiteral("//OVE\\s+end\\b[^\n]*"), QStringLiteral("//OVE\\s+type:[^\n]*"), QStringLiteral("//OVE\\s+flag:[^\n]*"), QStringLiteral("//OVE\\s+values:[^\n]*"), QStringLiteral("//OVE\\s+description:[^\n]*"), QStringLiteral("//OVE\\s+default:[^\n]*"), QStringLiteral("//OVE shape: [^\n]*"), QStringLiteral("//OVE color: [^\n]*"), QStringLiteral("//OVE\\s+min:[^\n]*"), - QStringLiteral("//OVE\\s+max:[^\n]*"), QStringLiteral("//OVE\\s+end\\b[^\n]*") + QStringLiteral("//OVE\\s+max:[^\n]*") }; } // namespace diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index 5936c1b90e..51ac7913a8 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -32,7 +32,6 @@ #include "shaderinputsparser.h" -#include namespace olive { @@ -44,7 +43,7 @@ namespace { const QString TEMPLATE( "//OVE shader_name: \n" "//OVE shader_description: \n\n" - "//Default texture: this comes on the house:\n" + "//OVE main_input_name: Input\n" "uniform sampler2D tex_in;\n\n" "//OVE end\n\n\n" "// pixel coordinates in range [0..1]x[0..1]\n" @@ -64,12 +63,9 @@ const QString ShaderFilterNode::kOutputMessages = QStringLiteral("issues"); ShaderFilterNode::ShaderFilterNode(): - invalidate_code_flag_(false) + invalidate_code_flag_(false), + main_input_name_("Input") { - // A default texture - AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); - SetEffectInput(kTextureInput); - // Full code of the shader. Inputs to be exposed are defined within the shader code // with mark-up comments. AddInput(kShaderCode, NodeValue::kText, QVariant::fromValue(TEMPLATE), @@ -83,6 +79,10 @@ ShaderFilterNode::ShaderFilterNode(): // mark this text input as output messages SetInputProperty( kOutputMessages, QStringLiteral("text_type"), QString("shader_issues")); + // A default texture. Must be connected even for shaders that do not require a texture. + AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + SetEffectInput(kTextureInput); + SetFlags(kVideoEffect); } @@ -135,7 +135,6 @@ void ShaderFilterNode::InputValueChangedEvent(const QString &input, int element) checkShaderSyntax(); SetStandardValue( kOutputMessages, output_messages_.isEmpty() ? tr("None") : output_messages_); - qDebug() << "parsed shader code for " << GetLabel() << " @ " << (uint64_t)this; invalidate_code_flag_ = true; } @@ -161,7 +160,6 @@ void ShaderFilterNode::Retranslate() // Retranslate only fixed inputs. // Other inputs are read from the shader code - SetInputName( kTextureInput, tr("Input")); SetInputName( kShaderCode, tr("Shader code")); SetInputName( kOutputMessages, tr("Issues")); } @@ -283,6 +281,10 @@ void ShaderFilterNode::updateInputList( const ShaderInputsParser & parser) } } + // main input + main_input_name_ = (parser.MainInputName().isEmpty()) ? "Input" : parser.MainInputName(); + SetInputName( kTextureInput, main_input_name_); + // compare 'new_input_list' and 'user_input_list_' to find deleted inputs. checkDeletedInputs( new_input_list); diff --git a/app/node/filter/shader/shader.h b/app/node/filter/shader/shader.h index 63e344dbf3..af6927045c 100644 --- a/app/node/filter/shader/shader.h +++ b/app/node/filter/shader/shader.h @@ -86,6 +86,8 @@ protected slots: QString shader_code_; // error in metadata or shader QString output_messages_; + // name of default texture input + QString main_input_name_; // user defined inputs QStringList user_input_list_; // input of type vec2 to gizmo map diff --git a/app/node/filter/shader/shaderinputsparser.cpp b/app/node/filter/shader/shaderinputsparser.cpp index 3dab430639..d38b800d90 100644 --- a/app/node/filter/shader/shaderinputsparser.cpp +++ b/app/node/filter/shader/shaderinputsparser.cpp @@ -46,6 +46,8 @@ QRegularExpression SHADER_NAME_REGEX("^\\s*//OVE\\s*shader_name:\\s*(?.*)\ QRegularExpression SHADER_DESCRIPTION_REGEX("^\\s*//OVE\\s*shader_description:\\s*(?.*)\\s*$"); // Version string. So far not used by application QRegularExpression SHADER_VERSION_REGEX("^\\s*//OVE\\s*shader_version:\\s*(?.*)\\s*$"); +// human name for default input +QRegularExpression SHADER_MAIN_INPUT_NAME_REGEX("^\\s*//OVE\\s*main_input_name:\\s*(?.*)\\s*$"); // per Input metadata @@ -127,6 +129,7 @@ ShaderInputsParser::ShaderInputsParser( const QString & shader_code) : INPUT_PARAM_PARSE_TABLE.insert( & SHADER_NAME_REGEX, & ShaderInputsParser::parseShaderName); INPUT_PARAM_PARSE_TABLE.insert( & SHADER_DESCRIPTION_REGEX, & ShaderInputsParser::parseShaderDescription); INPUT_PARAM_PARSE_TABLE.insert( & SHADER_VERSION_REGEX, & ShaderInputsParser::parseShaderVersion); + INPUT_PARAM_PARSE_TABLE.insert( & SHADER_MAIN_INPUT_NAME_REGEX, & ShaderInputsParser::parseMainInputName); INPUT_PARAM_PARSE_TABLE.insert( & INPUT_NAME_REGEX, & ShaderInputsParser::parseInputName); INPUT_PARAM_PARSE_TABLE.insert( & INPUT_UNIFORM_REGEX, & ShaderInputsParser::parseInputUniform); @@ -241,7 +244,7 @@ ShaderInputsParser::parseShaderName(const QRegularExpressionMatch & match) ShaderInputsParser::InputParseState ShaderInputsParser::parseShaderDescription(const QRegularExpressionMatch & /*match*/) { - // nothing to do. SO far. + // nothing to do. So far. return PARSING; } @@ -252,6 +255,13 @@ ShaderInputsParser::parseShaderVersion(const QRegularExpressionMatch & /*match*/ return PARSING; } +ShaderInputsParser::InputParseState +ShaderInputsParser::parseMainInputName(const QRegularExpressionMatch & match) +{ + main_input_name_ = match.captured("name"); + return PARSING; +} + ShaderInputsParser::InputParseState ShaderInputsParser::parseInputName(const QRegularExpressionMatch & match) { diff --git a/app/node/filter/shader/shaderinputsparser.h b/app/node/filter/shader/shaderinputsparser.h index ef6a8a27d6..b1c5a7a1de 100644 --- a/app/node/filter/shader/shaderinputsparser.h +++ b/app/node/filter/shader/shaderinputsparser.h @@ -90,6 +90,11 @@ class ShaderInputsParser return shader_name_; } + // name of default input + const QString & MainInputName() const { + return main_input_name_; + } + // list of issue found in parsing inputs const QList & ErrorList() const { return error_list_; @@ -114,6 +119,7 @@ class ShaderInputsParser InputParseState parseShaderName( const QRegularExpressionMatch &); InputParseState parseShaderDescription( const QRegularExpressionMatch &); InputParseState parseShaderVersion( const QRegularExpressionMatch &); + InputParseState parseMainInputName( const QRegularExpressionMatch &); InputParseState parseInputName( const QRegularExpressionMatch &); InputParseState parseInputUniform( const QRegularExpressionMatch &); InputParseState parseInputType( const QRegularExpressionMatch &); @@ -138,6 +144,7 @@ class ShaderInputsParser int line_number_; QString shader_name_; + QString main_input_name_; QMap< const QRegularExpression *, LineParseFunction> INPUT_PARAM_PARSE_TABLE; }; From e743d99fa97b4e7f722799a14f0c549dd8774cb8 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Wed, 29 Mar 2023 22:37:29 +0200 Subject: [PATCH 31/49] Aligned to new code base --- app/node/filter/shader/shader.cpp | 14 +++++++++++--- app/node/filter/shader/shaderinputsparser.cpp | 2 +- app/widget/nodeparamview/nodeparamviewitem.h | 3 --- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index 51ac7913a8..c02c6dc126 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -83,7 +83,7 @@ ShaderFilterNode::ShaderFilterNode(): AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); SetEffectInput(kTextureInput); - SetFlags(kVideoEffect); + SetFlag(kVideoEffect); } Node *ShaderFilterNode::copy() const @@ -193,7 +193,10 @@ void olive::ShaderFilterNode::checkShaderSyntax() ok=shader.compileSourceCode( shader_code_); } else { - ok=shader.compileSourceCode( "#version 150\n" + shader_code_); + // NOTE: this replicates the preamble added in compilation (see openglrenderer.cpp for reference). + // This must be aligned if preamble changes + ok=shader.compileSourceCode( "#version 150\n\n" + "\n\n" + shader_code_); } if (ok == false) @@ -258,7 +261,12 @@ void ShaderFilterNode::updateInputList( const ShaderInputsParser & parser) } if (GetInputFlags( it->uniform_name) != it->flags) { - SetInputFlags( it->uniform_name, it->flags); + + SetInputFlag( it->uniform_name, kInputFlagArray, (it->flags & kInputFlagArray) ? true : false); + SetInputFlag(it->uniform_name, kInputFlagNotKeyframable, (it->flags & kInputFlagNotKeyframable) ? true : false); + SetInputFlag( it->uniform_name, kInputFlagNotConnectable, (it->flags & kInputFlagNotConnectable) ? true : false); + SetInputFlag( it->uniform_name, kInputFlagHidden, (it->flags & kInputFlagHidden) ? true : false); + SetInputFlag( it->uniform_name, kInputFlagIgnoreInvalidations, (it->flags & kInputFlagIgnoreInvalidations) ? true : false); } } diff --git a/app/node/filter/shader/shaderinputsparser.cpp b/app/node/filter/shader/shaderinputsparser.cpp index d38b800d90..744b3b3d8c 100644 --- a/app/node/filter/shader/shaderinputsparser.cpp +++ b/app/node/filter/shader/shaderinputsparser.cpp @@ -26,7 +26,7 @@ #include #include -#include "render/color.h" +// #include "core/util/color.h" // ext\core\include\olive\core\util\color.h namespace olive { diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index cbffb00283..b6372f03ad 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -246,9 +246,6 @@ private slots: Node *ctx_; ViewerOutput * time_target_; - - ViewerOutput *time_target_; - rational timebase_; KeyframeView::NodeConnections keyframe_connections_; From b51a76aadbfa8c2731cd327d5fb28911b8d3ee8e Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Wed, 5 Apr 2023 22:11:12 +0200 Subject: [PATCH 32/49] Fixed a bug with flags comparison --- app/node/filter/shader/shader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index c02c6dc126..2fd43ec8b1 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -260,7 +260,7 @@ void ShaderFilterNode::updateInputList( const ShaderInputsParser & parser) SetInputDataType( it->uniform_name, it->type); } - if (GetInputFlags( it->uniform_name) != it->flags) { + if (GetInputFlags( it->uniform_name).value() != it->flags.value()) { SetInputFlag( it->uniform_name, kInputFlagArray, (it->flags & kInputFlagArray) ? true : false); SetInputFlag(it->uniform_name, kInputFlagNotKeyframable, (it->flags & kInputFlagNotKeyframable) ? true : false); From b55ffa0ffaca1c9a4a7903d127c0eb8eb79286f5 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Mon, 17 Apr 2023 20:57:38 +0200 Subject: [PATCH 33/49] When changing input list or properties, the node editor gets update --- app/widget/nodeview/nodeviewitem.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 09333d27a2..114cc990eb 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -66,6 +66,7 @@ NodeViewItem::NodeViewItem(Node *node, const QString &input, int element, Node * connect(node_, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); connect(node_, &Node::ColorChanged, this, &NodeViewItem::NodeAppearanceChanged); + connect(node_, &Node::InputListChanged, this, &NodeViewItem::NodeAppearanceChanged); if (IsOutputItem()) { connect(node_, &Node::InputAdded, this, &NodeViewItem::RepopulateInputs); From da00bfbbf9417f1b678bb1749a79adef601150f0 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Mon, 1 May 2023 18:23:09 +0200 Subject: [PATCH 34/49] Fixed behavior when input flag "not-keyframable" is changed. --- app/widget/nodeparamview/nodeparamview.cpp | 2 ++ .../nodeparamview/nodeparamviewitem.cpp | 19 +++++++++++++++++++ app/widget/nodeparamview/nodeparamviewitem.h | 3 +++ 3 files changed, 24 insertions(+) diff --git a/app/widget/nodeparamview/nodeparamview.cpp b/app/widget/nodeparamview/nodeparamview.cpp index 32c3b6f308..ba20fd3264 100644 --- a/app/widget/nodeparamview/nodeparamview.cpp +++ b/app/widget/nodeparamview/nodeparamview.cpp @@ -709,6 +709,8 @@ void NodeParamView::AddNode(Node *n, Node *ctx, NodeParamViewContext *context) connect(item, &NodeParamViewItem::Clicked, this, &NodeParamView::ItemClicked); connect(item, &NodeParamViewItem::RequestEditTextInViewer, this, &NodeParamView::RequestEditTextInViewer); + connect( n, & Node::InputFlagsChanged, item, & NodeParamViewItem::OnInputFlagsChanged ); + item->SetContext(ctx); item->SetTimeTarget(GetConnectedNode()); item->SetTimebase(timebase()); diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 8824338004..0de39c18b5 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -108,6 +108,25 @@ void NodeParamViewItem::RecreateBody() } +void NodeParamViewItem::OnInputFlagsChanged(const QString &input, const InputFlags &flags) +{ + if (flags & kInputFlagNotKeyframable) { + + // Delete all keyframes + MultiUndoCommand* command = new MultiUndoCommand(); + NodeInput node_input = NodeInput( node_,input); + + foreach (const NodeKeyframeTrack& track, node_->GetKeyframeTracks(node_input)) { + for (int i=track.size()-1;i>=0;i--) { + command->add_child(new NodeParamRemoveKeyframeCommand(track.at(i))); + } + } + + Core::instance()->undo_stack()->push(command, + QString("Remove keyframes for input %1").arg(input)); + } +} + void NodeParamViewItem::OnInputListChanged() { RecreateBody(); diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index b6372f03ad..f19d9bbabc 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -229,6 +229,9 @@ class NodeParamViewItem : public NodeParamViewItemBase void InputArraySizeChanged(const QString &input, int old_size, int new_size); +public slots: + void OnInputFlagsChanged(const QString &input, const InputFlags &flags); + protected slots: virtual void Retranslate() override; From e33f73965c29d26ca52d710b671793c6415c66c7 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Wed, 3 May 2023 10:14:26 +0200 Subject: [PATCH 35/49] Update of node item view fixed when property "NOT_CONNECTABLE" changes. Removed some warnings raised by "clang-tidy" --- app/dialog/codeeditor/editor.cpp | 37 +------------------ app/node/filter/shader/shader.cpp | 8 ++-- app/node/filter/shader/shaderinputsparser.cpp | 4 +- app/widget/nodeview/nodeviewitem.cpp | 3 +- 4 files changed, 9 insertions(+), 43 deletions(-) diff --git a/app/dialog/codeeditor/editor.cpp b/app/dialog/codeeditor/editor.cpp index 0416506765..a91b10753c 100644 --- a/app/dialog/codeeditor/editor.cpp +++ b/app/dialog/codeeditor/editor.cpp @@ -44,7 +44,6 @@ const Qt::GlobalColor BRACKET_NOT_MATCH_COLOR = Qt::darkRed; const int BRACKET_LAST = -2; // forward declarations -QString regExpEscape( const QStringView &str); QString buildSearchExpression( const QStringView &str, QTextDocument::FindFlags & flags); } @@ -500,40 +499,6 @@ int CodeEditor::countTrailingSpaces(int block_position) namespace { -// This is taken from QT 5.15 source code for "QRegularExpression::escape". -// Olive must compile with QT 5.9 on. -QString regExpEscape(const QStringView &str) -{ - QString result; - const int count = str.size(); - result.reserve(count * 2); - // everything but [a-zA-Z0-9_] gets escaped, - // cf. perldoc -f quotemeta - for (int i = 0; i < count; ++i) { - const QChar current = str.at(i); - if (current == QChar::Null) { - // unlike Perl, a literal NUL must be escaped with - // "\\0" (backslash + 0) and not "\\\0" (backslash + NUL), - // because pcre16_compile uses a NUL-terminated string - result.append(QLatin1Char('\\')); - result.append(QLatin1Char('0')); - } else if ( (current < QLatin1Char('a') || current > QLatin1Char('z')) && - (current < QLatin1Char('A') || current > QLatin1Char('Z')) && - (current < QLatin1Char('0') || current > QLatin1Char('9')) && - current != QLatin1Char('_') ) - { - result.append(QLatin1Char('\\')); - result.append(current); - if (current.isHighSurrogate() && i < (count - 1)) - result.append(str.at(++i)); - } else { - result.append(current); - } - } - result.squeeze(); - return result; -} - // This function is required to search for "whole word". The default QT function considers // underscore "_" as a word breaker, so if you searh for "hello" as whole word it will match // "hello_world". @@ -541,7 +506,7 @@ QString regExpEscape(const QStringView &str) // from 'flags', if present. QString buildSearchExpression( const QStringView &str, QTextDocument::FindFlags & flags) { - QString regExpString = regExpEscape( str); + QString regExpString = QRegularExpression::escape(str); if (flags & QTextDocument::FindWholeWords) { regExpString.prepend("\\b"); diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index 2fd43ec8b1..54aa01873c 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -230,7 +230,7 @@ void ShaderFilterNode::reportErrorList( const ShaderInputsParser & parser) message = QString(tr("There are %1 metadata issues.\n").arg(errors.size())); } - for (ShaderInputsParser::Error e : errors ) { + for (const ShaderInputsParser::Error & e : errors ) { message.append(QString("\"%1\" line %2: %3\n"). arg( parser.ShaderName()).arg(e.line).arg(e.issue)); } @@ -307,7 +307,7 @@ void ShaderFilterNode::updateInputList( const ShaderInputsParser & parser) // Add a point gizmo for each 'kVec2' that does not already have one void ShaderFilterNode::updateGizmoList() { - for (QString aInput : user_input_list_) + for (QString & aInput : user_input_list_) { if (HasInputWithID(aInput)) { if (GetInputDataType(aInput) == NodeValue::kVec2) { @@ -333,7 +333,7 @@ void ShaderFilterNode::updateGizmoList() void ShaderFilterNode::checkDeletedInputs(const QStringList & new_inputs) { // search old inputs that are not present in new inputs - for( const QString & input : user_input_list_) { + for( QString & input : user_input_list_) { if (new_inputs.contains(input) == false) { RemoveInput( input); @@ -363,7 +363,7 @@ void ShaderFilterNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeG { resolution_ = globals.vparams().resolution(); - for (QString aInput : user_input_list_) + for (QString & aInput : user_input_list_) { if (HasInputWithID(aInput)) { if (row[aInput].type() == NodeValue::kVec2) { diff --git a/app/node/filter/shader/shaderinputsparser.cpp b/app/node/filter/shader/shaderinputsparser.cpp index 744b3b3d8c..4df8062afb 100644 --- a/app/node/filter/shader/shaderinputsparser.cpp +++ b/app/node/filter/shader/shaderinputsparser.cpp @@ -408,7 +408,7 @@ ShaderInputsParser::parseInputMin(const QRegularExpressionMatch & match) if (ok == false) { reportError(QObject::tr("%1 is not valid as minimum for type %2"). - arg(min_str).arg(currentInput.type_string)); + arg(min_str, currentInput.type_string)); } return PARSING; @@ -432,7 +432,7 @@ ShaderInputsParser::parseInputMax(const QRegularExpressionMatch & match) if (ok == false) { reportError(QObject::tr("%1 is not valid as maximum for type %2"). - arg(max_str).arg(currentInput.type_string)); + arg(max_str, currentInput.type_string)); } return PARSING; diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 114cc990eb..5cac159d9c 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -66,7 +66,8 @@ NodeViewItem::NodeViewItem(Node *node, const QString &input, int element, Node * connect(node_, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); connect(node_, &Node::ColorChanged, this, &NodeViewItem::NodeAppearanceChanged); - connect(node_, &Node::InputListChanged, this, &NodeViewItem::NodeAppearanceChanged); + // a change in input flags (NOT_CONNECTABLE, HIDDEN) may require to show or hide an input of this node + connect(node_, &Node::InputListChanged, this, &NodeViewItem::RepopulateInputs); if (IsOutputItem()) { connect(node_, &Node::InputAdded, this, &NodeViewItem::RepopulateInputs); From b19c55ccca64b00ae6e38f44f932d7adbb758e12 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Wed, 3 May 2023 11:57:42 +0200 Subject: [PATCH 36/49] Node view item update fixed when input property NOT_CONNECTABLE changes. Removed some messages raised by clang-tidy. --- app/dialog/codeeditor/editor.cpp | 37 +------------------ app/node/filter/shader/shader.cpp | 8 ++-- app/node/filter/shader/shaderinputsparser.cpp | 4 +- app/widget/nodeview/nodeviewitem.cpp | 3 +- 4 files changed, 9 insertions(+), 43 deletions(-) diff --git a/app/dialog/codeeditor/editor.cpp b/app/dialog/codeeditor/editor.cpp index 0416506765..a91b10753c 100644 --- a/app/dialog/codeeditor/editor.cpp +++ b/app/dialog/codeeditor/editor.cpp @@ -44,7 +44,6 @@ const Qt::GlobalColor BRACKET_NOT_MATCH_COLOR = Qt::darkRed; const int BRACKET_LAST = -2; // forward declarations -QString regExpEscape( const QStringView &str); QString buildSearchExpression( const QStringView &str, QTextDocument::FindFlags & flags); } @@ -500,40 +499,6 @@ int CodeEditor::countTrailingSpaces(int block_position) namespace { -// This is taken from QT 5.15 source code for "QRegularExpression::escape". -// Olive must compile with QT 5.9 on. -QString regExpEscape(const QStringView &str) -{ - QString result; - const int count = str.size(); - result.reserve(count * 2); - // everything but [a-zA-Z0-9_] gets escaped, - // cf. perldoc -f quotemeta - for (int i = 0; i < count; ++i) { - const QChar current = str.at(i); - if (current == QChar::Null) { - // unlike Perl, a literal NUL must be escaped with - // "\\0" (backslash + 0) and not "\\\0" (backslash + NUL), - // because pcre16_compile uses a NUL-terminated string - result.append(QLatin1Char('\\')); - result.append(QLatin1Char('0')); - } else if ( (current < QLatin1Char('a') || current > QLatin1Char('z')) && - (current < QLatin1Char('A') || current > QLatin1Char('Z')) && - (current < QLatin1Char('0') || current > QLatin1Char('9')) && - current != QLatin1Char('_') ) - { - result.append(QLatin1Char('\\')); - result.append(current); - if (current.isHighSurrogate() && i < (count - 1)) - result.append(str.at(++i)); - } else { - result.append(current); - } - } - result.squeeze(); - return result; -} - // This function is required to search for "whole word". The default QT function considers // underscore "_" as a word breaker, so if you searh for "hello" as whole word it will match // "hello_world". @@ -541,7 +506,7 @@ QString regExpEscape(const QStringView &str) // from 'flags', if present. QString buildSearchExpression( const QStringView &str, QTextDocument::FindFlags & flags) { - QString regExpString = regExpEscape( str); + QString regExpString = QRegularExpression::escape(str); if (flags & QTextDocument::FindWholeWords) { regExpString.prepend("\\b"); diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index 2fd43ec8b1..54aa01873c 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -230,7 +230,7 @@ void ShaderFilterNode::reportErrorList( const ShaderInputsParser & parser) message = QString(tr("There are %1 metadata issues.\n").arg(errors.size())); } - for (ShaderInputsParser::Error e : errors ) { + for (const ShaderInputsParser::Error & e : errors ) { message.append(QString("\"%1\" line %2: %3\n"). arg( parser.ShaderName()).arg(e.line).arg(e.issue)); } @@ -307,7 +307,7 @@ void ShaderFilterNode::updateInputList( const ShaderInputsParser & parser) // Add a point gizmo for each 'kVec2' that does not already have one void ShaderFilterNode::updateGizmoList() { - for (QString aInput : user_input_list_) + for (QString & aInput : user_input_list_) { if (HasInputWithID(aInput)) { if (GetInputDataType(aInput) == NodeValue::kVec2) { @@ -333,7 +333,7 @@ void ShaderFilterNode::updateGizmoList() void ShaderFilterNode::checkDeletedInputs(const QStringList & new_inputs) { // search old inputs that are not present in new inputs - for( const QString & input : user_input_list_) { + for( QString & input : user_input_list_) { if (new_inputs.contains(input) == false) { RemoveInput( input); @@ -363,7 +363,7 @@ void ShaderFilterNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeG { resolution_ = globals.vparams().resolution(); - for (QString aInput : user_input_list_) + for (QString & aInput : user_input_list_) { if (HasInputWithID(aInput)) { if (row[aInput].type() == NodeValue::kVec2) { diff --git a/app/node/filter/shader/shaderinputsparser.cpp b/app/node/filter/shader/shaderinputsparser.cpp index 744b3b3d8c..4df8062afb 100644 --- a/app/node/filter/shader/shaderinputsparser.cpp +++ b/app/node/filter/shader/shaderinputsparser.cpp @@ -408,7 +408,7 @@ ShaderInputsParser::parseInputMin(const QRegularExpressionMatch & match) if (ok == false) { reportError(QObject::tr("%1 is not valid as minimum for type %2"). - arg(min_str).arg(currentInput.type_string)); + arg(min_str, currentInput.type_string)); } return PARSING; @@ -432,7 +432,7 @@ ShaderInputsParser::parseInputMax(const QRegularExpressionMatch & match) if (ok == false) { reportError(QObject::tr("%1 is not valid as maximum for type %2"). - arg(max_str).arg(currentInput.type_string)); + arg(max_str, currentInput.type_string)); } return PARSING; diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index 114cc990eb..5cac159d9c 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -66,7 +66,8 @@ NodeViewItem::NodeViewItem(Node *node, const QString &input, int element, Node * connect(node_, &Node::LabelChanged, this, &NodeViewItem::NodeAppearanceChanged); connect(node_, &Node::ColorChanged, this, &NodeViewItem::NodeAppearanceChanged); - connect(node_, &Node::InputListChanged, this, &NodeViewItem::NodeAppearanceChanged); + // a change in input flags (NOT_CONNECTABLE, HIDDEN) may require to show or hide an input of this node + connect(node_, &Node::InputListChanged, this, &NodeViewItem::RepopulateInputs); if (IsOutputItem()) { connect(node_, &Node::InputAdded, this, &NodeViewItem::RepopulateInputs); From 01f9d273f0053c457ebb794dde9cac7695a6d875 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Wed, 3 May 2023 14:51:38 +0200 Subject: [PATCH 37/49] Removed unused include file --- app/dialog/codeeditor/externaleditorproxy.cpp | 2 +- app/node/filter/shader/shader.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/dialog/codeeditor/externaleditorproxy.cpp b/app/dialog/codeeditor/externaleditorproxy.cpp index 2e30c6d62d..38b4b76963 100644 --- a/app/dialog/codeeditor/externaleditorproxy.cpp +++ b/app/dialog/codeeditor/externaleditorproxy.cpp @@ -56,7 +56,7 @@ void ExternalEditorProxy::launch(const QString &start_text) // create a file whose name is random but unique for each instance. // We can use 'this' pointer as file name. // 'QStandardPaths::TempLocation' is guaranteed not to be empty - file_path_ = QStandardPaths::standardLocations( QStandardPaths::TempLocation).first(); + file_path_ = QStandardPaths::standardLocations( QStandardPaths::TempLocation).at(0); file_path_ += QString("%1%2.frag").arg(QDir::separator()).arg((long long)this); // create file before watching it diff --git a/app/node/filter/shader/shader.h b/app/node/filter/shader/shader.h index af6927045c..5fe4242e28 100644 --- a/app/node/filter/shader/shader.h +++ b/app/node/filter/shader/shader.h @@ -23,7 +23,7 @@ #include "node/node.h" #include "node/gizmo/point.h" -#include "node/inputdragger.h" + namespace olive { From e832a63afd4a9669960b152ab18d139d3fb56e3e Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Wed, 10 May 2023 21:25:06 +0200 Subject: [PATCH 38/49] Improved the external editor feature --- app/dialog/codeeditor/externaleditorproxy.cpp | 90 +++++++++---------- app/dialog/codeeditor/externaleditorproxy.h | 12 ++- .../nodeparamview/nodeparamviewtextedit.cpp | 24 +++-- .../nodeparamview/nodeparamviewtextedit.h | 8 +- .../nodeparamviewwidgetbridge.cpp | 3 +- 5 files changed, 78 insertions(+), 59 deletions(-) diff --git a/app/dialog/codeeditor/externaleditorproxy.cpp b/app/dialog/codeeditor/externaleditorproxy.cpp index 38b4b76963..b5e793b8de 100644 --- a/app/dialog/codeeditor/externaleditorproxy.cpp +++ b/app/dialog/codeeditor/externaleditorproxy.cpp @@ -25,63 +25,47 @@ #include #include #include +#include #include + namespace olive { ExternalEditorProxy::ExternalEditorProxy(QObject *parent) : QObject(parent), - process_(nullptr) + process_(nullptr), + watcher_( this) { - + connect( & watcher_, & QFileSystemWatcher::fileChanged, this, & ExternalEditorProxy::onFileChanged); } ExternalEditorProxy::~ExternalEditorProxy() { - // stop process - if (process_ && process_->isOpen()) { - process_->kill(); - } - - // delete temporary file - if (QFileInfo::exists(file_path_)) { - QFile::remove( file_path_); - } + // Do not delete "file_path_" bacause it might be read from + // another instance of this class. However this leaves a lot + // of files in temporary folder } -void ExternalEditorProxy::launch(const QString &start_text) +void ExternalEditorProxy::Launch(const QString &start_text) { - if (file_path_ == QString()) - { - // create a file whose name is random but unique for each instance. - // We can use 'this' pointer as file name. - // 'QStandardPaths::TempLocation' is guaranteed not to be empty - file_path_ = QStandardPaths::standardLocations( QStandardPaths::TempLocation).at(0); - file_path_ += QString("%1%2.frag").arg(QDir::separator()).arg((long long)this); - - // create file before watching it - QFile out(file_path_); - out.open( QIODevice::WriteOnly); - bool ok = false; - - if (out.isOpen()) - { - ok = watcher_.addPath( file_path_); - out.write( start_text.toLatin1()); - out.close(); - } - if (ok == false) - { - QMessageBox::warning( nullptr, "Olive", - tr("Can't send data to external editor")); - } + // create file before watching it + QFile out(file_path_); + out.open( QIODevice::WriteOnly); + + if (out.isOpen()) { + out.write( start_text.toLatin1()); + out.close(); - connect( & watcher_, & QFileSystemWatcher::fileChanged, this, & ExternalEditorProxy::onFileChanged); + // now the file exists. We can watch for changes + watcher_.addPath( file_path_); + + } else { + QMessageBox::warning( nullptr, "Olive", + tr("Can't send data to external editor")); } - if (process_ == nullptr) - { + if (process_ == nullptr) { process_ = new QProcess(this); connect( process_, static_cast(& QProcess::finished), this, &ExternalEditorProxy::onProcessFinished); @@ -95,6 +79,15 @@ void ExternalEditorProxy::launch(const QString &start_text) } } +void ExternalEditorProxy::SetFilePath(const QString & path) +{ + file_path_ = path; + + // at this point, "file_path_" may or may not exist. + // The following instruction is effective when the file exists. + watcher_.addPath( file_path_); +} + void ExternalEditorProxy::onFileChanged(const QString &path) { // this should always be true @@ -103,16 +96,21 @@ void ExternalEditorProxy::onFileChanged(const QString &path) QFile f(file_path_); f.open( QIODevice::ReadOnly); - if (f.isOpen()) + if (f.size() > 0) { - QString content = QString::fromLatin1( f.readAll()); - emit textChanged( content); + if (f.isOpen()) { + QString content = QString::fromLatin1( f.readAll()); + emit textChanged( content); + f.close(); + } else { + QMessageBox::warning( nullptr, "Olive", tr("Can't update data from external editor")); + } + } else { + // on some platforms, when the file is saved, it is deleted before + // being re-written. In this case, there is a signal of file changed + // but it's size is zero. This must be discarded; a new message will arrive later. f.close(); } - else - { - QMessageBox::warning( nullptr, "Olive", tr("Can't update data from external editor")); - } } void ExternalEditorProxy::onProcessFinished(int /*exitCode*/, QProcess::ExitStatus /*exitStatus*/) diff --git a/app/dialog/codeeditor/externaleditorproxy.h b/app/dialog/codeeditor/externaleditorproxy.h index ff22084069..b4a4489708 100644 --- a/app/dialog/codeeditor/externaleditorproxy.h +++ b/app/dialog/codeeditor/externaleditorproxy.h @@ -22,10 +22,9 @@ #define EXTERNALEDITORPROXY_H -#include #include +#include -class QProcess; namespace olive { @@ -47,7 +46,11 @@ class ExternalEditorProxy : public QObject // launch an external process, if one is not already running. // If a process is running, nothing is done. - void launch( const QString & start_text); + void Launch( const QString & start_text); + + // associate a file path to edit text with an external viewer. + // Olive will listen for changes in that file. + void SetFilePath(const QString & path); signals: // emnitted when temporary file is saved @@ -58,9 +61,10 @@ class ExternalEditorProxy : public QObject void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); private: - QFileSystemWatcher watcher_; + // QFileSystemWatcher watcher_; QString file_path_; QProcess *process_; + QFileSystemWatcher watcher_; }; } // namespace olive diff --git a/app/widget/nodeparamview/nodeparamviewtextedit.cpp b/app/widget/nodeparamview/nodeparamviewtextedit.cpp index 82116c2432..e6a035f550 100644 --- a/app/widget/nodeparamview/nodeparamviewtextedit.cpp +++ b/app/widget/nodeparamview/nodeparamviewtextedit.cpp @@ -21,6 +21,8 @@ #include "nodeparamviewtextedit.h" #include +#include +#include #include "dialog/text/text.h" #include "dialog/codeeditor/codeeditordialog.h" @@ -59,10 +61,12 @@ NodeParamViewTextEdit::NodeParamViewTextEdit(QWidget *parent) : SetEditInViewerOnlyMode(false); ext_editor_proxy_ = new ExternalEditorProxy( this); + connect( ext_editor_proxy_, & ExternalEditorProxy::textChanged, this, & NodeParamViewTextEdit::OnTextChangedExternally); } + void NodeParamViewTextEdit::SetEditInViewerOnlyMode(bool on) { line_edit_->setVisible(!on); @@ -111,7 +115,7 @@ void olive::NodeParamViewTextEdit::launchCodeEditor(QString & text) else { // external editor - ext_editor_proxy_->launch( this->text()); + ext_editor_proxy_->Launch( this->text()); } } @@ -120,15 +124,25 @@ void NodeParamViewTextEdit::InnerWidgetTextChanged() emit textEdited(this->text()); } -void NodeParamViewTextEdit::OnTextChangedExternally(const QString &new_text) +void NodeParamViewTextEdit::OnTextChangedExternally(const QString & content) { - line_edit_->setPlainText( new_text); - emit textEdited( new_text); + line_edit_->setPlainText( content); + emit textEdited( content); } -void NodeParamViewTextEdit::setCodeEditorFlag() +void NodeParamViewTextEdit::setCodeEditorFlag( uint64_t node_id) { code_editor_flag_ = true; + + // create a file whose name is unique for for the node this instance belongs to. + // 'node_id' is based on address of the node this param belongs to + // 'QStandardPaths::TempLocation' is guaranteed not to be empty + QString file_path = QStandardPaths::standardLocations( QStandardPaths::TempLocation).at(0); + file_path += QString("/%2.frag").arg(node_id); + + ext_editor_proxy_->SetFilePath(file_path); + + // no need to draw the stopwatch to keyframe this input setProperty("is_exapandable", QVariant::fromValue(true)); // if the text box is a shader code editor, make it read only so that diff --git a/app/widget/nodeparamview/nodeparamviewtextedit.h b/app/widget/nodeparamview/nodeparamviewtextedit.h index bd98b80058..e7d1a515c9 100644 --- a/app/widget/nodeparamview/nodeparamviewtextedit.h +++ b/app/widget/nodeparamview/nodeparamviewtextedit.h @@ -27,6 +27,7 @@ #include "common/define.h" + namespace olive { class ExternalEditorProxy; @@ -43,8 +44,9 @@ class NodeParamViewTextEdit : public QWidget return line_edit_->toPlainText(); } - // set flag to edit text with code editor - void setCodeEditorFlag(); + // let this instance perform as a code editor. + // This input will be edited with a built-in or external code editor. + void setCodeEditorFlag(uint64_t node_id); // set flag to view text as code issues void setCodeIssuesFlag(); @@ -95,7 +97,7 @@ private slots: void InnerWidgetTextChanged(); - void OnTextChangedExternally( const QString & new_text); + void OnTextChangedExternally(const QString & content); }; diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index 07ced2e956..09c7b67569 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -142,7 +142,8 @@ void NodeParamViewWidgetBridge::CreateWidgets() // check for special type of text QString text_type = GetInnerInput().GetProperty(QStringLiteral("text_type")).toString(); if (text_type == "shader_code") { - line_edit->setCodeEditorFlag(); + const Node * addr = GetInnerInput().node(); + line_edit->setCodeEditorFlag( (uint64_t)addr); } else if (text_type == "shader_issues") { line_edit->setCodeIssuesFlag(); From 524e17d257ad21e83e5a1ea3c7dcea6152635b26 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Thu, 11 May 2023 21:22:00 +0200 Subject: [PATCH 39/49] Fixed an issue when switching from external to internal editor --- app/common/qtutils.h | 12 ++++++------ app/dialog/codeeditor/externaleditorproxy.cpp | 13 +++++++++---- app/dialog/codeeditor/externaleditorproxy.h | 4 ++++ app/node/keyframe.cpp | 2 -- app/widget/nodeparamview/nodeparamviewitem.cpp | 1 - app/widget/nodeparamview/nodeparamviewitem.h | 5 +++-- app/widget/nodeparamview/nodeparamviewtextedit.cpp | 4 ++++ 7 files changed, 26 insertions(+), 15 deletions(-) diff --git a/app/common/qtutils.h b/app/common/qtutils.h index c4eeef0272..6bfad9ee3a 100644 --- a/app/common/qtutils.h +++ b/app/common/qtutils.h @@ -104,11 +104,11 @@ uint qHash(const core::TimeRange& r, uint seed = 0); } -Q_DECLARE_METATYPE(olive::core::rational); -Q_DECLARE_METATYPE(olive::core::Color); -Q_DECLARE_METATYPE(olive::core::TimeRange); -Q_DECLARE_METATYPE(olive::core::Bezier); -Q_DECLARE_METATYPE(olive::core::AudioParams); -Q_DECLARE_METATYPE(olive::core::SampleBuffer); +Q_DECLARE_METATYPE(olive::core::rational) +Q_DECLARE_METATYPE(olive::core::Color) +Q_DECLARE_METATYPE(olive::core::TimeRange) +Q_DECLARE_METATYPE(olive::core::Bezier) +Q_DECLARE_METATYPE(olive::core::AudioParams) +Q_DECLARE_METATYPE(olive::core::SampleBuffer) #endif // QTVERSIONABSTRACTION_H diff --git a/app/dialog/codeeditor/externaleditorproxy.cpp b/app/dialog/codeeditor/externaleditorproxy.cpp index b5e793b8de..3f42bb6f62 100644 --- a/app/dialog/codeeditor/externaleditorproxy.cpp +++ b/app/dialog/codeeditor/externaleditorproxy.cpp @@ -81,11 +81,16 @@ void ExternalEditorProxy::Launch(const QString &start_text) void ExternalEditorProxy::SetFilePath(const QString & path) { - file_path_ = path; + file_path_ = path; - // at this point, "file_path_" may or may not exist. - // The following instruction is effective when the file exists. - watcher_.addPath( file_path_); + // at this point, "file_path_" may or may not exist. + // The following instruction is effective when the file exists. + watcher_.addPath( file_path_); +} + +void ExternalEditorProxy::Detach() +{ + watcher_.removePath( file_path_); } void ExternalEditorProxy::onFileChanged(const QString &path) diff --git a/app/dialog/codeeditor/externaleditorproxy.h b/app/dialog/codeeditor/externaleditorproxy.h index b4a4489708..b100f8f19d 100644 --- a/app/dialog/codeeditor/externaleditorproxy.h +++ b/app/dialog/codeeditor/externaleditorproxy.h @@ -52,6 +52,10 @@ class ExternalEditorProxy : public QObject // Olive will listen for changes in that file. void SetFilePath(const QString & path); + // stop listening to changes of external files. + // Used when user switches from external to internal editor + void Detach(); + signals: // emnitted when temporary file is saved void textChanged( const QString & new_text); diff --git a/app/node/keyframe.cpp b/app/node/keyframe.cpp index 0832c5edff..d329add422 100644 --- a/app/node/keyframe.cpp +++ b/app/node/keyframe.cpp @@ -44,8 +44,6 @@ NodeKeyframe::NodeKeyframe(const rational &time, const QVariant &value, Type typ NodeKeyframe::NodeKeyframe() { type_ = NodeKeyframe::kLinear; - previous_ = nullptr; - next_ = nullptr; } NodeKeyframe::~NodeKeyframe() diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 0de39c18b5..d024b97116 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -619,7 +619,6 @@ void NodeParamViewItemBody::OptionalCheckBoxClicked(bool e) } } - NodeParamViewItemBody::InputUI::InputUI() : main_label(nullptr), widget_bridge(nullptr), diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index f19d9bbabc..56d4fc7bab 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -173,7 +173,6 @@ class NodeParamViewItem : public NodeParamViewItemBase time_target_ = target; body_->SetTimeTarget(target); - time_target_ = target; } void SetTimebase(const rational& timebase) @@ -248,7 +247,9 @@ private slots: NodeParamViewCheckBoxBehavior create_checkboxes_; Node *ctx_; - ViewerOutput * time_target_; + + ViewerOutput *time_target_; + rational timebase_; KeyframeView::NodeConnections keyframe_connections_; diff --git a/app/widget/nodeparamview/nodeparamviewtextedit.cpp b/app/widget/nodeparamview/nodeparamviewtextedit.cpp index e6a035f550..bca46f3b7d 100644 --- a/app/widget/nodeparamview/nodeparamviewtextedit.cpp +++ b/app/widget/nodeparamview/nodeparamviewtextedit.cpp @@ -108,6 +108,10 @@ void olive::NodeParamViewTextEdit::launchCodeEditor(QString & text) // internal editor CodeEditorDialog d(this->text(), this); + // in case external editor was previously opened but + // then user switched to internal editor. + ext_editor_proxy_->Detach(); + if (d.exec() == QDialog::Accepted) { text = d.text(); } From aad09835067f359ff1d6b094beb30e524b6af422 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Mon, 15 May 2023 10:32:28 +0200 Subject: [PATCH 40/49] Fixed default shader to have a minimal functionality --- app/node/filter/shader/shader.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index 54aa01873c..b4b23874a8 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -41,6 +41,7 @@ namespace olive { namespace { // a default shader that replicates to output a texture input. const QString TEMPLATE( + "#version 150\n" "//OVE shader_name: \n" "//OVE shader_description: \n\n" "//OVE main_input_name: Input\n" @@ -51,8 +52,8 @@ const QString TEMPLATE( "// output color\n" "out vec4 frag_color;\n\n" "void main(void) {\n" - " vec4 textureColor = texture2D(tex_in, ove_texcoord);\n" - " frag_color= textureColor.yzxt;\n" + " vec4 textureColor = texture(tex_in, ove_texcoord);\n" + " frag_color= textureColor.brga;\n" "}\n"); } // namespace @@ -196,7 +197,7 @@ void olive::ShaderFilterNode::checkShaderSyntax() // NOTE: this replicates the preamble added in compilation (see openglrenderer.cpp for reference). // This must be aligned if preamble changes ok=shader.compileSourceCode( "#version 150\n\n" - "\n\n" + shader_code_); + "precision highp float;\n\n" + shader_code_); } if (ok == false) From 196c977a18e149f3991e650af744d289a63e7bb4 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Sun, 21 May 2023 21:01:08 +0200 Subject: [PATCH 41/49] Added unit tests for metadata parser. Fixed a bug on end-of-metadata detection --- app/node/filter/shader/shaderinputsparser.cpp | 2 +- tests/CMakeLists.txt | 3 +- tests/shader/CMakeLists.txt | 17 + tests/shader/shader-tests.cpp | 382 ++++++++++++++++++ 4 files changed, 402 insertions(+), 2 deletions(-) create mode 100644 tests/shader/CMakeLists.txt create mode 100644 tests/shader/shader-tests.cpp diff --git a/app/node/filter/shader/shaderinputsparser.cpp b/app/node/filter/shader/shaderinputsparser.cpp index 4df8062afb..01db58ffd6 100644 --- a/app/node/filter/shader/shaderinputsparser.cpp +++ b/app/node/filter/shader/shaderinputsparser.cpp @@ -143,7 +143,7 @@ ShaderInputsParser::ShaderInputsParser( const QString & shader_code) : INPUT_PARAM_PARSE_TABLE.insert( & INPUT_DEFAULT_REGEX, & ShaderInputsParser::parseInputDefault); INPUT_PARAM_PARSE_TABLE.insert( & INPUT_DESCRIPTION_REGEX, & ShaderInputsParser::parseInputDescription); - INPUT_PARAM_PARSE_TABLE.insert( & STOP_PARSE_REGEX, & ShaderInputsParser::parseInputUniform); + INPUT_PARAM_PARSE_TABLE.insert( & STOP_PARSE_REGEX, & ShaderInputsParser::stopParse); } void ShaderInputsParser::Parse() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3dbbd2ccd4..44c0550b98 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,5 +1,5 @@ # Olive - Non-Linear Video Editor -# Copyright (C) 2022 Olive Team +# Copyright (C) 2023 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 @@ -72,3 +72,4 @@ endfunction() add_subdirectory(compositing) add_subdirectory(general) add_subdirectory(timeline) +add_subdirectory(shader) diff --git a/tests/shader/CMakeLists.txt b/tests/shader/CMakeLists.txt new file mode 100644 index 0000000000..9214b568fb --- /dev/null +++ b/tests/shader/CMakeLists.txt @@ -0,0 +1,17 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2023 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 . + +olive_add_test(Filter shader-tests shader-tests.cpp) diff --git a/tests/shader/shader-tests.cpp b/tests/shader/shader-tests.cpp new file mode 100644 index 0000000000..4871ecbe9e --- /dev/null +++ b/tests/shader/shader-tests.cpp @@ -0,0 +1,382 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 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 "testutil.h" + +#include "node/filter/shader/shaderinputsparser.h" + +// shortcut for std::string +#define STR(x) QString(x).toStdString() + +//#include + +namespace olive { + +// Script header information. +OLIVE_ADD_TEST(HeaderTest) +{ + QString code( +"#version 150""\n" +"//OVE shader_name: slide""\n" +"//OVE shader_description: slide transition""\n" +"//OVE shader_version: 0.1""\n" +); + + ShaderInputsParser parser(code); + parser.Parse(); + + OLIVE_ASSERT_EQUAL( parser.ErrorList().size(), 0); + OLIVE_ASSERT_EQUAL( STR(parser.ShaderName()), STR("slide")); + + OLIVE_TEST_END; +} + + +// The main input does not count in the inputs list +OLIVE_ADD_TEST(MainInputTest) +{ + QString code( +"//OVE main_input_name: dummy""\n" +"uniform sampler2D dummy;""\n" +); + + ShaderInputsParser parser(code); + parser.Parse(); + + OLIVE_ASSERT_EQUAL( parser.ErrorList().size(), 0); + OLIVE_ASSERT_EQUAL( parser.InputList().size(), 0); + + OLIVE_TEST_END; +} + +// An input of kind Texture +OLIVE_ADD_TEST(InputTextureTest) +{ + QString code( +"//OVE name: Base image""\n" +"//OVE type: TEXTURE""\n" +"//OVE flag: NOT_KEYFRAMABLE""\n" +"//OVE description: the base image""\n" +"uniform sampler2D tex_base;""\n" +); + + ShaderInputsParser parser(code); + parser.Parse(); + + OLIVE_ASSERT_EQUAL( parser.ErrorList().size(), 0); + OLIVE_ASSERT_EQUAL( parser.InputList().size(), 1); + + const ShaderInputsParser::InputParam & param = parser.InputList().at(0); + + OLIVE_ASSERT_EQUAL( STR(param.human_name), STR("Base image")); + OLIVE_ASSERT_EQUAL( STR(param.description), STR("the base image")); + OLIVE_ASSERT_EQUAL( param.flags, InputFlags(kInputFlagNotKeyframable)); + OLIVE_ASSERT_EQUAL( STR(param.uniform_name), STR("tex_base")); + OLIVE_ASSERT_EQUAL( STR(param.type_string), STR("TEXTURE")); + OLIVE_ASSERT_EQUAL( param.type, NodeValue::kTexture); + + OLIVE_TEST_END; +} + +// An input of kind Float +OLIVE_ADD_TEST(InputFloatTest) +{ + QString code( +"//OVE name: Tolerance %%""\n" +"//OVE type: FLOAT""\n" +"//OVE min: 1.0""\n" +"//OVE default: 5.5""\n" +"//OVE max: 100""\n" +"//OVE description: the tolerance of filter""\n" +"uniform float toler_in;""\n" +); + + ShaderInputsParser parser(code); + parser.Parse(); + + OLIVE_ASSERT_EQUAL( parser.ErrorList().size(), 0); + OLIVE_ASSERT_EQUAL( parser.InputList().size(), 1); + + const ShaderInputsParser::InputParam & param = parser.InputList().at(0); + + OLIVE_ASSERT_EQUAL( STR(param.human_name), STR("Tolerance %%")); + OLIVE_ASSERT_EQUAL( STR(param.description), STR("the tolerance of filter")); + OLIVE_ASSERT_EQUAL( param.flags, InputFlags(0)); + OLIVE_ASSERT_EQUAL( STR(param.uniform_name), STR("toler_in")); + OLIVE_ASSERT_EQUAL( STR(param.type_string), STR("FLOAT")); + OLIVE_ASSERT_EQUAL( param.type, NodeValue::kFloat); + OLIVE_ASSERT_EQUAL( param.min.toFloat(), 1.); + OLIVE_ASSERT_EQUAL( param.max.toFloat(), 100.); + OLIVE_ASSERT_EQUAL( param.default_value.toFloat(), 5.5); + + OLIVE_TEST_END; +} + +bool operator == (const Color & rhs, const Color & lhs) +{ + bool r_ok = (abs(rhs.red() - lhs.red()) < 0.01) ; + bool g_ok = (abs(rhs.green() - lhs.green()) < 0.01) ; + bool b_ok = (abs(rhs.blue() - lhs.blue()) < 0.01) ; + bool a_ok = (abs(rhs.alpha() - lhs.alpha()) < 0.01); + + return r_ok && g_ok && b_ok && a_ok; +} + +// An input of kind Color +OLIVE_ADD_TEST(InputColorTest) +{ + QString code( +"//OVE name: Final tone""\n" +"//OVE type: COLOR""\n" +"//OVE default: RGBA( 0.5,0.4 ,0.1, 1)""\n" +"//OVE description: color applied to the grayscale""\n" +"uniform vec4 tone_in;""\n" +); + + ShaderInputsParser parser(code); + parser.Parse(); + + OLIVE_ASSERT_EQUAL( parser.ErrorList().size(), 0); + OLIVE_ASSERT_EQUAL( parser.InputList().size(), 1); + + const ShaderInputsParser::InputParam & param = parser.InputList().at(0); + + OLIVE_ASSERT_EQUAL( STR(param.human_name), STR("Final tone")); + OLIVE_ASSERT_EQUAL( STR(param.description), STR("color applied to the grayscale")); + OLIVE_ASSERT_EQUAL( STR(param.uniform_name), STR("tone_in")); + OLIVE_ASSERT_EQUAL( STR(param.type_string), STR("COLOR")); + OLIVE_ASSERT_EQUAL( param.type, NodeValue::kColor); + OLIVE_ASSERT( param.default_value.value() == Color(0.5, 0.4, 0.1, 1.0)); + + OLIVE_TEST_END; +} + +// An input of kind Boolean +OLIVE_ADD_TEST(InputBooleanTest) +{ + QString code( +"//OVE name: bypass effect""\n" +"//OVE type: BOOLEAN""\n" +"//OVE flag: NOT_CONNECTABLE""\n" +"//OVE default: false""\n" +"//OVE description: when True, the input is passed to output as is.""\n" +"uniform bool disable_in;""\n" + ); + + ShaderInputsParser parser(code); + parser.Parse(); + + OLIVE_ASSERT_EQUAL( parser.ErrorList().size(), 0); + OLIVE_ASSERT_EQUAL( parser.InputList().size(), 1); + + const ShaderInputsParser::InputParam & param = parser.InputList().at(0); + + OLIVE_ASSERT_EQUAL( STR(param.human_name), STR("bypass effect")); + OLIVE_ASSERT_EQUAL( STR(param.description), STR("when True, the input is passed to output as is.")); + OLIVE_ASSERT_EQUAL( param.flags, InputFlags(kInputFlagNotConnectable)); + OLIVE_ASSERT_EQUAL( STR(param.uniform_name), STR("disable_in")); + OLIVE_ASSERT_EQUAL( STR(param.type_string), STR("BOOLEAN")); + OLIVE_ASSERT_EQUAL( param.type, NodeValue::kBoolean); + OLIVE_ASSERT_EQUAL( param.default_value.toBool(), false); + + OLIVE_TEST_END; +} + +// An input of kind Selection +OLIVE_ADD_TEST(InputSelectionTest) +{ + QString code( +"//OVE name: mode""\n" +"//OVE type: SELECTION""\n" +"//OVE values: \"ADD\", \"AVERAGE\", \"COLOR BURN\"""\n" +"//OVE values: \"COLOR DODGE\" \"DARKEN\" \"DIFFERENCE\"""\n" +"//OVE values: \"EXCLUSION\" \"GLOW\"""\n" +"//OVE default: 3""\n" +"//OVE description: the blend mode""\n" +"uniform int mode_in; ""\n" + ); + + ShaderInputsParser parser(code); + parser.Parse(); + + OLIVE_ASSERT_EQUAL( parser.ErrorList().size(), 0); + OLIVE_ASSERT_EQUAL( parser.InputList().size(), 1); + + const ShaderInputsParser::InputParam & param = parser.InputList().at(0); + + OLIVE_ASSERT_EQUAL( STR(param.human_name), STR("mode")); + OLIVE_ASSERT_EQUAL( STR(param.description), STR("the blend mode")); + OLIVE_ASSERT_EQUAL( STR(param.uniform_name), STR("mode_in")); + OLIVE_ASSERT_EQUAL( STR(param.type_string), STR("SELECTION")); + OLIVE_ASSERT_EQUAL( param.type, NodeValue::kCombo); + OLIVE_ASSERT_EQUAL( param.default_value.toInt(), 3); + + OLIVE_TEST_END; +} + +// An input of kind Point +OLIVE_ADD_TEST(InputPointTest) +{ + QString code( +"//OVE name: From""\n" +"//OVE type: POINT""\n" +"//OVE default: (0.4,0.2)""\n" +"//OVE description: gradient start point""\n" +"uniform vec2 from_in;""\n" + ); + + ShaderInputsParser parser(code); + parser.Parse(); + + OLIVE_ASSERT_EQUAL( parser.ErrorList().size(), 0); + OLIVE_ASSERT_EQUAL( parser.InputList().size(), 1); + + const ShaderInputsParser::InputParam & param = parser.InputList().at(0); + + OLIVE_ASSERT_EQUAL( STR(param.human_name), STR("From")); + OLIVE_ASSERT_EQUAL( STR(param.description), STR("gradient start point")); + OLIVE_ASSERT_EQUAL( param.flags, InputFlags(0)); + OLIVE_ASSERT_EQUAL( STR(param.uniform_name), STR("from_in")); + OLIVE_ASSERT_EQUAL( STR(param.type_string), STR("POINT")); + OLIVE_ASSERT_EQUAL( param.type, NodeValue::kVec2); + + OLIVE_TEST_END; +} + +// An input with multiple flags (with different separators) +OLIVE_ADD_TEST(MultipleFlagsTest) +{ + QString code( +"//OVE name: From""\n" +"//OVE type: POINT""\n" +"//OVE default: (0.4,0.2)""\n" +"//OVE flag: NOT_KEYFRAMABLE, NOT_CONNECTABLE HIDDEN""\n" +"//OVE description: gradient start point""\n" +"uniform vec2 from_in;""\n" + ); + + ShaderInputsParser parser(code); + parser.Parse(); + + OLIVE_ASSERT_EQUAL( parser.ErrorList().size(), 0); + OLIVE_ASSERT_EQUAL( parser.InputList().size(), 1); + + const ShaderInputsParser::InputParam & param = parser.InputList().at(0); + + OLIVE_ASSERT_EQUAL( STR(param.human_name), STR("From")); + OLIVE_ASSERT_EQUAL( param.flags, InputFlags(kInputFlagNotKeyframable | kInputFlagNotConnectable | kInputFlagHidden)); + + OLIVE_TEST_END; +} + + +// input type typo error. Check that errors are detected +OLIVE_ADD_TEST(InputTypoTest) +{ + QString code( +"//OVE name: Tolerance %%""\n" +"//OVE type: FLOA""\n" // <-- typo: "FLOA" for "FLOAT" +"//OVE min: 1.0""\n" +"//OVE default: 5.5""\n" +"//OVE max: 100""\n" +"//OVE description: the tolerance of filter""\n" +"uniform float toler_in;""\n" +); + + ShaderInputsParser parser(code); + parser.Parse(); + + OLIVE_ASSERT_EQUAL( parser.ErrorList().size(), 4); + + OLIVE_ASSERT_EQUAL( STR(parser.ErrorList().at(0).issue), + STR("type FLOA is invalid") ); + OLIVE_ASSERT_EQUAL( parser.ErrorList().at(0).line, 2); + + OLIVE_ASSERT_EQUAL( STR(parser.ErrorList().at(1).issue), + STR("1.0 is not valid as minimum for type FLOA") ); + OLIVE_ASSERT_EQUAL( parser.ErrorList().at(1).line, 3); + + OLIVE_ASSERT_EQUAL( STR(parser.ErrorList().at(2).issue), + STR("type FLOA does not support a default value") ); + OLIVE_ASSERT_EQUAL( parser.ErrorList().at(2).line, 4); + + OLIVE_ASSERT_EQUAL( STR(parser.ErrorList().at(3).issue), + STR("100 is not valid as maximum for type FLOA") ); + OLIVE_ASSERT_EQUAL( parser.ErrorList().at(3).line, 5); + + OLIVE_TEST_END; +} + + +// an input defined after "//OVE end" +// The input is not counted +OLIVE_ADD_TEST(InputAfterEndTest) +{ + QString code( +"//OVE end""\n" // <- end +"//OVE name: Tolerance %%""\n" +"//OVE type: FLOAT""\n" +"//OVE description: the tolerance of filter""\n" +"uniform float toler_in;""\n" +); + + ShaderInputsParser parser(code); + parser.Parse(); + + OLIVE_ASSERT_EQUAL( parser.ErrorList().size(), 0); + OLIVE_ASSERT_EQUAL( parser.InputList().size(), 0); + + OLIVE_TEST_END; +} + + +// presence of multiple inputs +OLIVE_ADD_TEST(InputManyTest) +{ + QString code( +"//OVE name: Base image""\n" +"//OVE type: TEXTURE""\n" +"//OVE flag: NOT_KEYFRAMABLE""\n" +"//OVE description: the base image""\n" +"uniform sampler2D tex_base;""\n" +"\n" +"//OVE name: Tolerance %%""\n" +"//OVE type: FLOAT""\n" +"//OVE description: the tolerance of filter""\n" +"uniform float toler_in;""\n" +"\n" +"//OVE name: base color""\n" +"//OVE type: COLOR""\n" +"//OVE description: the final color""\n" +"uniform vec4 tone_in;""\n" +"\n" +); + + ShaderInputsParser parser(code); + parser.Parse(); + + OLIVE_ASSERT_EQUAL( parser.ErrorList().size(), 0); + OLIVE_ASSERT_EQUAL( parser.InputList().size(), 3); + + OLIVE_TEST_END; +} + +} // olive + From 0bc6fb8820701972ecbd09d32b1e5461bae3ea29 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Tue, 6 Jun 2023 23:26:07 +0200 Subject: [PATCH 42/49] Added shader transition node; Added button to select external editor --- .../preferences/tabs/preferencesedittab.cpp | 24 +- .../preferences/tabs/preferencesedittab.h | 7 +- app/node/block/transition/CMakeLists.txt | 1 + .../block/transition/shader/CMakeLists.txt | 22 ++ .../transition/shader/shadertransition.cpp | 361 ++++++++++++++++++ .../transition/shader/shadertransition.h | 97 +++++ app/node/factory.cpp | 3 + app/node/factory.h | 1 + app/node/filter/shader/shader.cpp | 10 +- app/node/filter/shader/shader.h | 1 - tests/shader/shader-tests.cpp | 4 +- 11 files changed, 516 insertions(+), 15 deletions(-) create mode 100644 app/node/block/transition/shader/CMakeLists.txt create mode 100644 app/node/block/transition/shader/shadertransition.cpp create mode 100644 app/node/block/transition/shader/shadertransition.h diff --git a/app/dialog/preferences/tabs/preferencesedittab.cpp b/app/dialog/preferences/tabs/preferencesedittab.cpp index a9ef9bb250..f569068ac9 100644 --- a/app/dialog/preferences/tabs/preferencesedittab.cpp +++ b/app/dialog/preferences/tabs/preferencesedittab.cpp @@ -26,10 +26,11 @@ #include #include #include +#include +#include +#include -#include "common/filefunctions.h" - namespace olive { PreferencesEditTab::PreferencesEditTab() @@ -87,9 +88,16 @@ PreferencesEditTab::PreferencesEditTab() external_editor_layout->addWidget( new QLabel(tr("Command or full file path of external editor.\n" "Use double quotes if executable path has spaces")) ); + QHBoxLayout * editor_cmd_layout = new QHBoxLayout(); + external_editor_layout->addLayout( editor_cmd_layout); ext_command_ = new QLineEdit(); + ext_select_button = new QPushButton("...", this); + editor_cmd_layout->addWidget( ext_command_); + editor_cmd_layout->addWidget( ext_select_button); + + connect( ext_select_button, & QPushButton::clicked, this, & PreferencesEditTab::onSelectDialogRequest); + ext_params_ = new QLineEdit(); - external_editor_layout->addWidget( ext_command_); external_editor_layout->addWidget( new QLabel(tr("Parameters of external editor.\n" "Use %FILE and %LINE for file path and line number")) ); @@ -129,4 +137,14 @@ void PreferencesEditTab::Accept(MultiUndoCommand *command) Config::Current()["EditorInternalWindowWidth"] = QVariant::fromValue(window_width_->value()); } +void PreferencesEditTab::onSelectDialogRequest() +{ + QString path = QFileDialog::getOpenFileName( this, tr("External editor"), + QStandardPaths::displayName( QStandardPaths::ApplicationsLocation)); + + if (path != QString()) { + ext_command_->setText( path); + } } + +} // olive diff --git a/app/dialog/preferences/tabs/preferencesedittab.h b/app/dialog/preferences/tabs/preferencesedittab.h index b8270125f8..bacaa4b217 100644 --- a/app/dialog/preferences/tabs/preferencesedittab.h +++ b/app/dialog/preferences/tabs/preferencesedittab.h @@ -22,13 +22,14 @@ #define PREFERENCESEDITTAB_H -#include "dialog/configbase/configdialogbase.h" +#include "dialog/configbase/configdialogbasetab.h" class QCheckBox; class QGroupBox; class QRadioButton; class QSpinBox; class QLineEdit; +class QPushButton; namespace olive { @@ -43,6 +44,9 @@ class PreferencesEditTab : public ConfigDialogBaseTab virtual void Accept(MultiUndoCommand* command) override; +private slots: + void onSelectDialogRequest(); + private: QRadioButton * use_internal_editor_; QRadioButton * use_external_editor_; @@ -55,6 +59,7 @@ class PreferencesEditTab : public ConfigDialogBaseTab // for external editor QLineEdit * ext_command_; + QPushButton * ext_select_button; QLineEdit * ext_params_; }; diff --git a/app/node/block/transition/CMakeLists.txt b/app/node/block/transition/CMakeLists.txt index bb1f316a46..9c36ed721a 100644 --- a/app/node/block/transition/CMakeLists.txt +++ b/app/node/block/transition/CMakeLists.txt @@ -16,6 +16,7 @@ add_subdirectory(crossdissolve) add_subdirectory(diptocolor) +add_subdirectory(shader) set(OLIVE_SOURCES ${OLIVE_SOURCES} diff --git a/app/node/block/transition/shader/CMakeLists.txt b/app/node/block/transition/shader/CMakeLists.txt new file mode 100644 index 0000000000..62e233e94b --- /dev/null +++ b/app/node/block/transition/shader/CMakeLists.txt @@ -0,0 +1,22 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2022 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} + node/block/transition/shader/shadertransition.h + node/block/transition/shader/shadertransition.cpp + PARENT_SCOPE +) diff --git a/app/node/block/transition/shader/shadertransition.cpp b/app/node/block/transition/shader/shadertransition.cpp new file mode 100644 index 0000000000..19a628e1c8 --- /dev/null +++ b/app/node/block/transition/shader/shadertransition.cpp @@ -0,0 +1,361 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 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 "shadertransition.h" + +#include + +#include "node/filter/shader/shaderinputsparser.h" + +namespace { +// a default shader transition +const QString TEMPLATE( +"#version 150""\n" +"//OVE end""\n" +"uniform sampler2D out_block_in;""\n" +"uniform sampler2D in_block_in;""\n" +"uniform bool out_block_in_enabled;""\n" +"uniform bool in_block_in_enabled;""\n" +"uniform int curve_in;""\n" +"""\n" +"uniform float ove_tprog_all;""\n" +"""\n" +"in vec2 ove_texcoord;""\n" +"out vec4 frag_color;""\n" +"""\n" +"void main(void) {""\n" +" frag_color = texture(in_block_in, ove_texcoord*step( 1.0 - ove_tprog_all, ove_texcoord.xy)) +""\n" +" texture(out_block_in, ove_texcoord*step( ove_tprog_all, ove_texcoord.xy));""\n" +"}""\n"); + +} + +namespace olive { + +#define super TransitionBlock + +const QString ShaderTransition::kShaderCode = QStringLiteral("source"); +const QString ShaderTransition::kOutputMessages = QStringLiteral("issues"); + +ShaderTransition::ShaderTransition() +{ + // Full code of the shader. Inputs to be exposed are defined within the shader code + // with mark-up comments. + AddInput(kShaderCode, NodeValue::kText, QVariant::fromValue(TEMPLATE), + InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + + // Output messages of shader parser + AddInput(kOutputMessages, NodeValue::kText, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + + // mark this text input as code, so it will be edited with code editor + SetInputProperty( kShaderCode, QStringLiteral("text_type"), QString("shader_code")); + // mark this text input as output messages + SetInputProperty( kOutputMessages, QStringLiteral("text_type"), QString("shader_issues")); +} + +QString ShaderTransition::Name() const +{ + return tr("GLSL Transition"); +} + +QString ShaderTransition::id() const +{ + return QStringLiteral("org.olivevideoeditor.Olive.transitionshader"); +} + +QVector ShaderTransition::Category() const +{ + return {kCategoryTransition}; +} + +void ShaderTransition::InputValueChangedEvent(const QString &input, int element) +{ + Q_UNUSED(element) + + if (input == kShaderCode) { + // for some reason, this function is called more than once for each input + // of each instance. Parse code only if it has changed + QString new_code = GetStandardValue(kShaderCode).value(); + + if (shader_code_ != new_code) { + + shader_code_ = new_code; + output_messages_.clear(); + + // the code of the shader has changed. + // Remove all inputs and re-parse the code + // to fix shader name and input parameters. + parseShaderCode(); + + // check for syntax + checkShaderSyntax(); + + SetStandardValue( kOutputMessages, output_messages_.isEmpty() ? tr("None") : output_messages_); + + invalidate_code_flag_ = true; + } + } +} + +bool ShaderTransition::ShaderCodeInvalidateFlag() const +{ + bool invalid = invalidate_code_flag_; + invalidate_code_flag_ = false; + return invalid; +} + +QString ShaderTransition::Description() const +{ + return tr("GLSL user written transition"); +} + +void ShaderTransition::Retranslate() +{ + super::Retranslate(); + + // Retranslate only fixed inputs. + // Other inputs are read from the shader code + SetInputName( kShaderCode, tr("Shader code")); + SetInputName( kOutputMessages, tr("Issues")); +} + + +ShaderCode ShaderTransition::GetShaderCode(const Node::ShaderRequest &request) const +{ + Q_UNUSED(request); + return ShaderCode(shader_code_); +} + + +void ShaderTransition::ShaderJobEvent(const NodeValueRow &value, ShaderJob *job) const +{ + // add user Inputs + for( const QString & name : user_input_list_) + { + job->Insert( name, value); + } + + // resolution + TexturePtr p = value[QStringLiteral("in_block_in")].toTexture(); + + if (p) { + job->Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, p->virtual_resolution(), this)); + } + + job->SetShaderID(GetLabel()); // TBD: label may not be enough to identify a shader univocally +} + + +void ShaderTransition::checkShaderSyntax() +{ + QOpenGLShader shader(QOpenGLShader::Fragment); + bool ok; + + if (shader_code_.startsWith("#version")) { + + ok=shader.compileSourceCode( shader_code_); + } else { + + // NOTE: this replicates the preamble added in compilation (see openglrenderer.cpp for reference). + // This must be aligned if preamble changes + ok=shader.compileSourceCode( "#version 150\n\n" + "precision highp float;\n\n" + shader_code_); + } + + if (ok == false) + { + output_messages_ += QString("Compilation errors:\n") + shader.log(); + } +} + + +void ShaderTransition::parseShaderCode() +{ + ShaderInputsParser parser(shader_code_); + + parser.Parse(); + + reportErrorList( parser); + updateInputList( parser); + updateGizmoList(); + + // update name, if defined in script; otherwise use a default. + QString label = (parser.ShaderName().isEmpty()) ? "unnamed" : parser.ShaderName(); + SetLabel( label); +} + +void ShaderTransition::reportErrorList( const ShaderInputsParser & parser) +{ + const QList & errors = parser.ErrorList(); + + QString message; + if (errors.size() > 0) { + message = QString(tr("There are %1 metadata issues.\n").arg(errors.size())); + } + + for (const ShaderInputsParser::Error & e : errors ) { + message.append(QString("\"%1\" line %2: %3\n"). + arg( parser.ShaderName()).arg(e.line).arg(e.issue)); + } + + output_messages_ += message; +} + +void ShaderTransition::updateInputList( const ShaderInputsParser & parser) +{ + const QList< ShaderInputsParser::InputParam> & input_list = parser.InputList(); + QList< ShaderInputsParser::InputParam>::const_iterator it; + + QStringList new_input_list; + + for( it = input_list.begin(); it != input_list.end(); ++it) { + + new_input_list.append( it->uniform_name); + + if (HasInputWithID(it->uniform_name) == false) { + // this is a new input + AddInput( it->uniform_name, it->type, it->default_value, it->flags ); + + } + else { + // input already present. Check if some feature has changed + if (GetInputDataType( it->uniform_name) != it->type) { + SetInputDataType( it->uniform_name, it->type); + } + + if (GetInputFlags( it->uniform_name).value() != it->flags.value()) { + + SetInputFlag( it->uniform_name, kInputFlagArray, (it->flags & kInputFlagArray) ? true : false); + SetInputFlag(it->uniform_name, kInputFlagNotKeyframable, (it->flags & kInputFlagNotKeyframable) ? true : false); + SetInputFlag( it->uniform_name, kInputFlagNotConnectable, (it->flags & kInputFlagNotConnectable) ? true : false); + SetInputFlag( it->uniform_name, kInputFlagHidden, (it->flags & kInputFlagHidden) ? true : false); + SetInputFlag( it->uniform_name, kInputFlagIgnoreInvalidations, (it->flags & kInputFlagIgnoreInvalidations) ? true : false); + } + } + + // set other features even if not changed + SetInputName( it->uniform_name, it->human_name); + if (it->min.isValid()) { + SetInputProperty( it->uniform_name, QStringLiteral("min"), it->min); + } + if (it->max.isValid()) { + SetInputProperty( it->uniform_name, QStringLiteral("max"), it->max); + } + + if (it->type == NodeValue::kCombo) { + SetComboBoxStrings(it->uniform_name, it->values); + } + + if (it->type == NodeValue::kVec2) { + handle_shape_table_.insert( it->uniform_name, it->pointShape); + handle_color_table_.insert( it->uniform_name, it->gizmoColor); + } + } + + + // compare 'new_input_list' and 'user_input_list_' to find deleted inputs. + checkDeletedInputs( new_input_list); + + // update inputs + user_input_list_.clear(); + user_input_list_ = new_input_list; + + emit InputListChanged(); +} + + +void ShaderTransition::checkDeletedInputs(const QStringList & new_inputs) +{ + // search old inputs that are not present in new inputs + for( QString & input : user_input_list_) { + if (new_inputs.contains(input) == false) { + RemoveInput( input); + + // remove gizmo, if any + if (handle_table_.contains(input)) { + RemoveGizmo( handle_table_[input]); + handle_table_.remove( input); + handle_shape_table_.remove( input); + handle_color_table_.remove( input); + } + } + } +} + + +// this function assumes that 'updateInputList' has been called already. +// Add a point gizmo for each 'kVec2' that does not already have one +void ShaderTransition::updateGizmoList() +{ + for (QString & aInput : user_input_list_) + { + if (HasInputWithID(aInput)) { + if (GetInputDataType(aInput) == NodeValue::kVec2) { + + // is point new? + if (handle_table_.contains(aInput) == false) { + PointGizmo * g = AddDraggableGizmo(); + g->AddInput(NodeKeyframeTrackReference(NodeInput(this, aInput), 0)); + g->AddInput(NodeKeyframeTrackReference(NodeInput(this, aInput), 1)); + g->SetDragValueBehavior(PointGizmo::kAbsolute); + + handle_table_.insert( aInput, g); + } + + PointGizmo * g = handle_table_[aInput]; + g->SetShape( handle_shape_table_.value( aInput, PointGizmo::kSquare)); + g->setProperty("color", QVariant::fromValue(handle_color_table_.value(aInput, Qt::white))); + } + } + } +} + + +void ShaderTransition::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers & /*modifiers*/) +{ + DraggableGizmo *gizmo = static_cast(sender()); + + Q_ASSERT( resolution_.x() != 0.); + Q_ASSERT( resolution_.y() != 0.); + + gizmo->GetDraggers()[0].Drag( x/resolution_.x()); + gizmo->GetDraggers()[1].Drag( y/resolution_.y()); +} + + +void ShaderTransition::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals & globals) +{ + resolution_ = globals.vparams().resolution(); + + for (QString & aInput : user_input_list_) + { + if (HasInputWithID(aInput)) { + if (row[aInput].type() == NodeValue::kVec2) { + QVector2D pos_vec = row[aInput].value() * resolution_; + QPointF pos( pos_vec.x(), pos_vec.y()); + + handle_table_[aInput]->SetPoint( pos); + } + } + } +} + +} // olive + diff --git a/app/node/block/transition/shader/shadertransition.h b/app/node/block/transition/shader/shadertransition.h new file mode 100644 index 0000000000..3ac77a77d1 --- /dev/null +++ b/app/node/block/transition/shader/shadertransition.h @@ -0,0 +1,97 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2022 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 SHADERTRANSITION_H +#define SHADERTRANSITION_H + +#include "node/block/transition/transition.h" +#include "node/gizmo/point.h" + +namespace olive { + +class ShaderInputsParser; + +// TBD There is a lot of code duplication between 'ShaderFilterNode' and 'ShaderTransition' +// Common code may be factored in a separate class + +class ShaderTransition : public TransitionBlock +{ + Q_OBJECT +public: + ShaderTransition(); + + NODE_DEFAULT_FUNCTIONS(ShaderTransition) + + virtual QString Name() const override; + virtual QString id() const override; + virtual QVector Category() const override; + virtual QString Description() const override; + + virtual void Retranslate() override; + virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; + + virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; + void InputValueChangedEvent(const QString &input, int element) override; + + bool ShaderCodeInvalidateFlag() const override; + + static const QString kShaderCode; + static const QString kOutputMessages; + +protected: + virtual void ShaderJobEvent(const NodeValueRow &value, ShaderJob *job) const override; + +protected slots: + virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override; + +private: + void parseShaderCode(); + void checkShaderSyntax(); + void reportErrorList( const ShaderInputsParser & parser); + void updateInputList( const ShaderInputsParser & parser); + void updateGizmoList(); + void checkDeletedInputs( const QStringList & new_inputs); + +private: + // set to true when shader code changes. Must be mutable because + // it is reset when it is read + mutable bool invalidate_code_flag_; + + // source GLSL code + QString shader_code_; + // error in metadata or shader + QString output_messages_; + // name of default texture input + QString main_input_name_; + // user defined inputs + QStringList user_input_list_; + // input of type vec2 to gizmo map + QMap handle_table_; + // shape for points in 'handle_table_' + QMap handle_shape_table_; + // color for points in 'handle_table_' + QMap handle_color_table_; + + QVector2D resolution_; +}; + +} + +#endif // SHADERTRANSITION_H diff --git a/app/node/factory.cpp b/app/node/factory.cpp index 758a8653ec..1a20fd77ac 100644 --- a/app/node/factory.cpp +++ b/app/node/factory.cpp @@ -29,6 +29,7 @@ #include "block/subtitle/subtitle.h" #include "block/transition/crossdissolve/crossdissolvetransition.h" #include "block/transition/diptocolor/diptocolortransition.h" +#include "block/transition/shader/shadertransition.h" #include "color/displaytransform/displaytransform.h" #include "color/ociogradingtransformlinear/ociogradingtransformlinear.h" #include "distort/cornerpin/cornerpindistortnode.h" @@ -256,6 +257,8 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id) return new CrossDissolveTransition(); case kDipToColorTransition: return new DipToColorTransition(); + case kShaderTransition: + return new ShaderTransition(); case kMosaicFilter: return new MosaicFilterNode(); case kCropDistort: diff --git a/app/node/factory.h b/app/node/factory.h index 46f4591e6f..64b339a725 100644 --- a/app/node/factory.h +++ b/app/node/factory.h @@ -54,6 +54,7 @@ class NodeFactory kTextGeneratorV3, kCrossDissolveTransition, kDipToColorTransition, + kShaderTransition, kMosaicFilter, kCropDistort, kProjectFootage, diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index b4b23874a8..a22b03399f 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -20,15 +20,7 @@ #include "shader.h" -#include -#include -#include - -#include -#include -#include #include -#include #include "shaderinputsparser.h" @@ -184,7 +176,7 @@ void ShaderFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globa -void olive::ShaderFilterNode::checkShaderSyntax() +void ShaderFilterNode::checkShaderSyntax() { QOpenGLShader shader(QOpenGLShader::Fragment); bool ok; diff --git a/app/node/filter/shader/shader.h b/app/node/filter/shader/shader.h index 5fe4242e28..45aa302d3f 100644 --- a/app/node/filter/shader/shader.h +++ b/app/node/filter/shader/shader.h @@ -28,7 +28,6 @@ namespace olive { class ShaderInputsParser; -class MessageDialog; /** @brief diff --git a/tests/shader/shader-tests.cpp b/tests/shader/shader-tests.cpp index 4871ecbe9e..239809d754 100644 --- a/tests/shader/shader-tests.cpp +++ b/tests/shader/shader-tests.cpp @@ -163,7 +163,7 @@ OLIVE_ADD_TEST(InputColorTest) OLIVE_ASSERT_EQUAL( STR(param.uniform_name), STR("tone_in")); OLIVE_ASSERT_EQUAL( STR(param.type_string), STR("COLOR")); OLIVE_ASSERT_EQUAL( param.type, NodeValue::kColor); - OLIVE_ASSERT( param.default_value.value() == Color(0.5, 0.4, 0.1, 1.0)); + OLIVE_ASSERT( param.default_value.value() == Color(0.5f, 0.4f, 0.1f, 1.0f)); OLIVE_TEST_END; } @@ -238,6 +238,7 @@ OLIVE_ADD_TEST(InputPointTest) "//OVE name: From""\n" "//OVE type: POINT""\n" "//OVE default: (0.4,0.2)""\n" +"//OVE color: RGBA( 0.5,0.5 ,0.0, 1)""\n" "//OVE description: gradient start point""\n" "uniform vec2 from_in;""\n" ); @@ -256,6 +257,7 @@ OLIVE_ADD_TEST(InputPointTest) OLIVE_ASSERT_EQUAL( STR(param.uniform_name), STR("from_in")); OLIVE_ASSERT_EQUAL( STR(param.type_string), STR("POINT")); OLIVE_ASSERT_EQUAL( param.type, NodeValue::kVec2); + OLIVE_ASSERT( param.gizmoColor == QColor(127,127,0)); OLIVE_TEST_END; } From 1948ecab2dce7a02a8c17b8ba653ff93fa07549a Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Sat, 10 Jun 2023 23:52:23 +0200 Subject: [PATCH 43/49] Minor change to default transition --- app/node/block/transition/shader/shadertransition.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/node/block/transition/shader/shadertransition.cpp b/app/node/block/transition/shader/shadertransition.cpp index 19a628e1c8..2d8ab43f5c 100644 --- a/app/node/block/transition/shader/shadertransition.cpp +++ b/app/node/block/transition/shader/shadertransition.cpp @@ -28,15 +28,18 @@ namespace { // a default shader transition const QString TEMPLATE( "#version 150""\n" +"//OVE shader_name: transition""\n" "//OVE end""\n" +"\n" "uniform sampler2D out_block_in;""\n" "uniform sampler2D in_block_in;""\n" "uniform bool out_block_in_enabled;""\n" "uniform bool in_block_in_enabled;""\n" +"\n" "uniform int curve_in;""\n" -"""\n" +"\n" "uniform float ove_tprog_all;""\n" -"""\n" +"\n" "in vec2 ove_texcoord;""\n" "out vec4 frag_color;""\n" """\n" From a5752f9a7444c32e61507e4adc6d5e91c8830674 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Fri, 20 Oct 2023 22:09:42 +0200 Subject: [PATCH 44/49] Added vertex shader and number of iterations in shadres. Cosmetic polish. --- app/dialog/codeeditor/glslhighlighter.cpp | 3 +- app/dialog/codeeditor/messagehighlighter.cpp | 17 +- app/dialog/codeeditor/messagehighlighter.h | 6 +- .../transition/shader/shadertransition.cpp | 189 +++++++--- .../transition/shader/shadertransition.h | 42 ++- app/node/filter/shader/shader.cpp | 144 ++++++-- app/node/filter/shader/shader.h | 39 +- app/node/filter/shader/shaderinputsparser.cpp | 180 ++++++---- app/node/filter/shader/shaderinputsparser.h | 23 ++ app/widget/nodeparamview/CMakeLists.txt | 2 + .../nodeparamview/nodeparamviewshader.cpp | 136 +++++++ .../nodeparamview/nodeparamviewshader.h | 78 ++++ .../nodeparamview/nodeparamviewtextedit.cpp | 62 +--- .../nodeparamview/nodeparamviewtextedit.h | 10 +- .../nodeparamviewwidgetbridge.cpp | 4 +- tests/shader/shader-tests.cpp | 332 +++++++++++++----- 16 files changed, 953 insertions(+), 314 deletions(-) create mode 100644 app/widget/nodeparamview/nodeparamviewshader.cpp create mode 100644 app/widget/nodeparamview/nodeparamviewshader.h diff --git a/app/dialog/codeeditor/glslhighlighter.cpp b/app/dialog/codeeditor/glslhighlighter.cpp index e2ba5f653e..48ab400d32 100644 --- a/app/dialog/codeeditor/glslhighlighter.cpp +++ b/app/dialog/codeeditor/glslhighlighter.cpp @@ -94,7 +94,8 @@ const QString functionPatterns[] = { const QString oliveMarkupPatterns[] = { QStringLiteral("//OVE\\s+shader_name:[^\n]*"), QStringLiteral("//OVE\\s+shader_description:[^\n]*"), - QStringLiteral("//OVE\\s+shader_version:[^\n]*"), QStringLiteral("//OVE\\s+main_input_name:[^\n]*"), + QStringLiteral("//OVE\\s+shader_version:[^\n]*"), QStringLiteral("//OVE\\s+number_of_iterations:[^\n]*"), + QStringLiteral("//OVE\\s+main_input_name:[^\n]*"), QStringLiteral("//OVE\\s+name:[^\n]*"), QStringLiteral("//OVE\\s+end\\b[^\n]*"), QStringLiteral("//OVE\\s+type:[^\n]*"), QStringLiteral("//OVE\\s+flag:[^\n]*"), QStringLiteral("//OVE\\s+values:[^\n]*"), QStringLiteral("//OVE\\s+description:[^\n]*"), diff --git a/app/dialog/codeeditor/messagehighlighter.cpp b/app/dialog/codeeditor/messagehighlighter.cpp index 7f8d86cf25..a02a7107d0 100644 --- a/app/dialog/codeeditor/messagehighlighter.cpp +++ b/app/dialog/codeeditor/messagehighlighter.cpp @@ -31,11 +31,13 @@ MessageSyntaxHighlighter::MessageSyntaxHighlighter(QTextDocument *parent) : line_format_.setForeground(QColor(128,128,128)); line_format_.setFontItalic(true); - shader_format_.setForeground(QColor(100,16,16)); + shader_error_format_.setForeground(QColor(255,70,70)); + shader_warning_format_.setForeground(QColor(255,255,40)); group_regexp = QRegularExpression(QStringLiteral("\"[^\"]*\"")); line_regexp = QRegularExpression(QStringLiteral("line\\s\\d+:")); - shader_msg_regexp = QRegularExpression(QStringLiteral("\\d\\(\\d+\\)\\s:\\serror\\sC\\d+:")); + shader_error_regexp = QRegularExpression(QStringLiteral("\\b(error|ERROR|Error)\\b")); + shader_warning_regexp = QRegularExpression(QStringLiteral("\\b(warning|WARNING|Warning)\\b")); } void MessageSyntaxHighlighter::highlightBlock(const QString &text) @@ -54,11 +56,18 @@ void MessageSyntaxHighlighter::highlightBlock(const QString &text) setFormat(match.capturedStart(), match.capturedLength(), line_format_); } - matchIterator = shader_msg_regexp.globalMatch(text); + matchIterator = shader_error_regexp.globalMatch(text); while (matchIterator.hasNext()) { QRegularExpressionMatch match = matchIterator.next(); - setFormat(match.capturedStart(), match.capturedLength(), shader_format_); + setFormat(match.capturedStart(1), match.capturedLength(1), shader_error_format_); + } + + matchIterator = shader_warning_regexp.globalMatch(text); + + while (matchIterator.hasNext()) { + QRegularExpressionMatch match = matchIterator.next(); + setFormat(match.capturedStart(1), match.capturedLength(1), shader_warning_format_); } } diff --git a/app/dialog/codeeditor/messagehighlighter.h b/app/dialog/codeeditor/messagehighlighter.h index 0dfeb70670..fb6e75cfa2 100644 --- a/app/dialog/codeeditor/messagehighlighter.h +++ b/app/dialog/codeeditor/messagehighlighter.h @@ -46,11 +46,13 @@ class MessageSyntaxHighlighter : public QSyntaxHighlighter private: QTextCharFormat group_format_; QTextCharFormat line_format_; - QTextCharFormat shader_format_; + QTextCharFormat shader_error_format_; + QTextCharFormat shader_warning_format_; QRegularExpression group_regexp; QRegularExpression line_regexp; - QRegularExpression shader_msg_regexp; + QRegularExpression shader_error_regexp; + QRegularExpression shader_warning_regexp; }; diff --git a/app/node/block/transition/shader/shadertransition.cpp b/app/node/block/transition/shader/shadertransition.cpp index 2d8ab43f5c..7500e68119 100644 --- a/app/node/block/transition/shader/shadertransition.cpp +++ b/app/node/block/transition/shader/shadertransition.cpp @@ -24,11 +24,21 @@ #include "node/filter/shader/shaderinputsparser.h" + +namespace olive { + namespace { +const QString DEFAULT_NAME = "My Transition"; +const QString DEFAULT_VERSION = "0.1"; +const QString DEFAULT_DESCRIPTION = "a transition that's still to be implemented"; +} + // a default shader transition -const QString TEMPLATE( +const QString olive::ShaderTransition::FRAG_TEMPLATE = QString( "#version 150""\n" -"//OVE shader_name: transition""\n" +"//OVE shader_name: %1""\n" +"//OVE shader_description: %2\n" +"//OVE shader_version: %3\n\n" "//OVE end""\n" "\n" "uniform sampler2D out_block_in;""\n" @@ -46,29 +56,56 @@ const QString TEMPLATE( "void main(void) {""\n" " frag_color = texture(in_block_in, ove_texcoord*step( 1.0 - ove_tprog_all, ove_texcoord.xy)) +""\n" " texture(out_block_in, ove_texcoord*step( ove_tprog_all, ove_texcoord.xy));""\n" -"}""\n"); +"}""\n").arg(DEFAULT_NAME, DEFAULT_DESCRIPTION, DEFAULT_VERSION); + +const QString olive::ShaderTransition::VERTEX_TEMPLATE = QString( + "#version 150\n" + "// No metadata will be parsed from this shader\n" + "// You can decalre 'uniform' inputs defined in fragment shader\n" + "uniform mat4 ove_mvpmat;\n" + "\n" + "in vec4 a_position;\n" + "in vec2 a_texcoord;\n" + "\n" + "out vec2 ove_texcoord;\n" + "\n" + "void main() {\n" + " gl_Position = ove_mvpmat * a_position;\n" + " ove_texcoord = a_texcoord;\n" + "}\n"); -} - -namespace olive { #define super TransitionBlock -const QString ShaderTransition::kShaderCode = QStringLiteral("source"); +const QString ShaderTransition::kFragShaderCode = QStringLiteral("frag_source"); +const QString ShaderTransition::kUseVertexCode = QStringLiteral("use_vertex"); +const QString ShaderTransition::kVertexShaderCode = QStringLiteral("vertex_source"); const QString ShaderTransition::kOutputMessages = QStringLiteral("issues"); -ShaderTransition::ShaderTransition() +ShaderTransition::ShaderTransition() : + invalidate_code_flag_(false), + shader_name_(DEFAULT_NAME), + shader_version_(DEFAULT_VERSION), + shader_description_(DEFAULT_DESCRIPTION), + number_of_iterations_(1), + use_vertex_shader(false) { - // Full code of the shader. Inputs to be exposed are defined within the shader code + // Option to use vertex shader. When false, the default vertex shader is used + AddInput(kUseVertexCode, NodeValue::kBoolean, QVariant::fromValue(false), + InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + AddInput(kVertexShaderCode, NodeValue::kText, QVariant::fromValue(VERTEX_TEMPLATE), + InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagHidden)); + // Full code of fragment shader. Inputs to be exposed are defined within the shader code // with mark-up comments. - AddInput(kShaderCode, NodeValue::kText, QVariant::fromValue(TEMPLATE), + AddInput(kFragShaderCode, NodeValue::kText, QVariant::fromValue(FRAG_TEMPLATE), InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); // Output messages of shader parser AddInput(kOutputMessages, NodeValue::kText, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); // mark this text input as code, so it will be edited with code editor - SetInputProperty( kShaderCode, QStringLiteral("text_type"), QString("shader_code")); + SetInputProperty( kFragShaderCode, QStringLiteral("text_type"), QString("shader_code")); + SetInputProperty( kVertexShaderCode, QStringLiteral("text_type"), QString("shader_code")); // mark this text input as output messages SetInputProperty( kOutputMessages, QStringLiteral("text_type"), QString("shader_issues")); } @@ -92,20 +129,23 @@ void ShaderTransition::InputValueChangedEvent(const QString &input, int element) { Q_UNUSED(element) - if (input == kShaderCode) { + if ((input == kFragShaderCode) || (input == kVertexShaderCode)) { // for some reason, this function is called more than once for each input // of each instance. Parse code only if it has changed - QString new_code = GetStandardValue(kShaderCode).value(); + QString new_frag_code = GetStandardValue(kFragShaderCode).value(); + QString new_vertex_code = GetStandardValue(kVertexShaderCode).value(); - if (shader_code_ != new_code) { + if ((frag_shader_code_ != new_frag_code) || + (vertex_shader_code_ != new_vertex_code)) { - shader_code_ = new_code; + frag_shader_code_ = new_frag_code; + vertex_shader_code_ = new_vertex_code; output_messages_.clear(); // the code of the shader has changed. // Remove all inputs and re-parse the code // to fix shader name and input parameters. - parseShaderCode(); + parseShaderMetadata(); // check for syntax checkShaderSyntax(); @@ -114,6 +154,13 @@ void ShaderTransition::InputValueChangedEvent(const QString &input, int element) invalidate_code_flag_ = true; } + } else if (input == kUseVertexCode) { + bool use_vertex_code = GetStandardValue(kUseVertexCode).toBool(); + // hide or un-hide the widget for vertex shader + SetInputFlag( kVertexShaderCode, kInputFlagHidden, ! use_vertex_code); + + // recalculate shader + invalidate_code_flag_ = true; } } @@ -124,9 +171,16 @@ bool ShaderTransition::ShaderCodeInvalidateFlag() const return invalid; } +void ShaderTransition::getMetadata(QString &name, QString &description, QString &version) const +{ + name = shader_name_; + description = shader_description_; + version = shader_version_; +} + QString ShaderTransition::Description() const { - return tr("GLSL user written transition"); + return tr("a transition made by a GLSL shader code"); } void ShaderTransition::Retranslate() @@ -135,15 +189,18 @@ void ShaderTransition::Retranslate() // Retranslate only fixed inputs. // Other inputs are read from the shader code - SetInputName( kShaderCode, tr("Shader code")); + SetInputName( kUseVertexCode, tr("Use vertex shader")); + SetInputName( kVertexShaderCode, tr("Vertex Shader")); + SetInputName( kFragShaderCode, tr("Fragment Shader")); SetInputName( kOutputMessages, tr("Issues")); } - ShaderCode ShaderTransition::GetShaderCode(const Node::ShaderRequest &request) const { Q_UNUSED(request); - return ShaderCode(shader_code_); + const QString & vertex_code = (GetStandardValue(kUseVertexCode).toBool()) ? vertex_shader_code_ : QString(); + + return ShaderCode(frag_shader_code_, vertex_code); } @@ -163,35 +220,61 @@ void ShaderTransition::ShaderJobEvent(const NodeValueRow &value, ShaderJob *job) } job->SetShaderID(GetLabel()); // TBD: label may not be enough to identify a shader univocally + + if (number_of_iterations_ != 1) { + job->SetIterations( number_of_iterations_, kInBlockInput); + } } void ShaderTransition::checkShaderSyntax() { - QOpenGLShader shader(QOpenGLShader::Fragment); bool ok; - if (shader_code_.startsWith("#version")) { + QOpenGLShader frag_shader(QOpenGLShader::Fragment); + ok = compileShaderCode( frag_shader, frag_shader_code_); - ok=shader.compileSourceCode( shader_code_); + if (ok == false) + { + output_messages_ += QString("Fragment Shader compilation errors:\n") + frag_shader.log(); + } + + bool use_vertex_code = GetStandardValue(kUseVertexCode).toBool(); + + if (use_vertex_code) { + QOpenGLShader vertex_shader(QOpenGLShader::Vertex); + ok = compileShaderCode( vertex_shader, vertex_shader_code_); + + if (ok == false) + { + output_messages_ += QString("Vertex Shader compilation errors:\n") + vertex_shader.log(); + } + } +} + +bool ShaderTransition::compileShaderCode( QOpenGLShader & shader, const QString & souce_code) +{ + bool ok; + + if (souce_code.startsWith("#version")) { + + ok=shader.compileSourceCode( souce_code); } else { // NOTE: this replicates the preamble added in compilation (see openglrenderer.cpp for reference). // This must be aligned if preamble changes ok=shader.compileSourceCode( "#version 150\n\n" - "precision highp float;\n\n" + shader_code_); + "precision highp float;\n\n" + souce_code); } - if (ok == false) - { - output_messages_ += QString("Compilation errors:\n") + shader.log(); - } + return ok; } -void ShaderTransition::parseShaderCode() +void ShaderTransition::parseShaderMetadata() { - ShaderInputsParser parser(shader_code_); + // only fragment shader has metadata + ShaderInputsParser parser(frag_shader_code_); parser.Parse(); @@ -202,6 +285,12 @@ void ShaderTransition::parseShaderCode() // update name, if defined in script; otherwise use a default. QString label = (parser.ShaderName().isEmpty()) ? "unnamed" : parser.ShaderName(); SetLabel( label); + shader_name_ = label; + shader_description_ = parser.ShaderDescription(); + shader_version_ = parser.ShaderVersion(); + number_of_iterations_ = parser.NumberOfIterations(); + + metadataChanged( shader_name_, shader_description_, shader_version_); } void ShaderTransition::reportErrorList( const ShaderInputsParser & parser) @@ -283,26 +372,6 @@ void ShaderTransition::updateInputList( const ShaderInputsParser & parser) emit InputListChanged(); } - -void ShaderTransition::checkDeletedInputs(const QStringList & new_inputs) -{ - // search old inputs that are not present in new inputs - for( QString & input : user_input_list_) { - if (new_inputs.contains(input) == false) { - RemoveInput( input); - - // remove gizmo, if any - if (handle_table_.contains(input)) { - RemoveGizmo( handle_table_[input]); - handle_table_.remove( input); - handle_shape_table_.remove( input); - handle_color_table_.remove( input); - } - } - } -} - - // this function assumes that 'updateInputList' has been called already. // Add a point gizmo for each 'kVec2' that does not already have one void ShaderTransition::updateGizmoList() @@ -318,6 +387,8 @@ void ShaderTransition::updateGizmoList() g->AddInput(NodeKeyframeTrackReference(NodeInput(this, aInput), 0)); g->AddInput(NodeKeyframeTrackReference(NodeInput(this, aInput), 1)); g->SetDragValueBehavior(PointGizmo::kAbsolute); + g->SetVisible( true); + handle_table_.insert( aInput, g); } @@ -330,6 +401,23 @@ void ShaderTransition::updateGizmoList() } } +void ShaderTransition::checkDeletedInputs(const QStringList & new_inputs) +{ + // search old inputs that are not present in new inputs + for( QString & input : user_input_list_) { + if (new_inputs.contains(input) == false) { + RemoveInput( input); + + // remove gizmo, if any + if (handle_table_.contains(input)) { + RemoveGizmo( handle_table_[input]); + handle_table_.remove( input); + handle_shape_table_.remove( input); + handle_color_table_.remove( input); + } + } + } +} void ShaderTransition::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers & /*modifiers*/) { @@ -342,7 +430,6 @@ void ShaderTransition::GizmoDragMove(double x, double y, const Qt::KeyboardModif gizmo->GetDraggers()[1].Drag( y/resolution_.y()); } - void ShaderTransition::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals & globals) { resolution_ = globals.vparams().resolution(); diff --git a/app/node/block/transition/shader/shadertransition.h b/app/node/block/transition/shader/shadertransition.h index 3ac77a77d1..92a2183eed 100644 --- a/app/node/block/transition/shader/shadertransition.h +++ b/app/node/block/transition/shader/shadertransition.h @@ -24,6 +24,8 @@ #include "node/block/transition/transition.h" #include "node/gizmo/point.h" +class QOpenGLShader; + namespace olive { class ShaderInputsParser; @@ -31,6 +33,10 @@ class ShaderInputsParser; // TBD There is a lot of code duplication between 'ShaderFilterNode' and 'ShaderTransition' // Common code may be factored in a separate class +/** @brief + * A node that implements a transition with a GLSL script. The inputs of this node + * are defined in GLSL by markup comments + */ class ShaderTransition : public TransitionBlock { Q_OBJECT @@ -45,6 +51,7 @@ class ShaderTransition : public TransitionBlock virtual QString Description() const override; virtual void Retranslate() override; + virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; @@ -52,34 +59,50 @@ class ShaderTransition : public TransitionBlock bool ShaderCodeInvalidateFlag() const override; - static const QString kShaderCode; + void getMetadata( QString & name, QString & description, QString & version) const; + + static const QString kFragShaderCode; + static const QString kUseVertexCode; + static const QString kVertexShaderCode; static const QString kOutputMessages; protected: virtual void ShaderJobEvent(const NodeValueRow &value, ShaderJob *job) const override; -protected slots: - virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override; +signals: + void metadataChanged( const QString & name, const QString & description, const QString & version); private: - void parseShaderCode(); + void parseShaderMetadata(); void checkShaderSyntax(); + bool compileShaderCode( QOpenGLShader &shader, const QString &souce_code); void reportErrorList( const ShaderInputsParser & parser); void updateInputList( const ShaderInputsParser & parser); void updateGizmoList(); void checkDeletedInputs( const QStringList & new_inputs); + +protected slots: + virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override; + private: // set to true when shader code changes. Must be mutable because // it is reset when it is read mutable bool invalidate_code_flag_; // source GLSL code - QString shader_code_; + QString frag_shader_code_; + QString vertex_shader_code_; + // name of filter declared in metadata + QString shader_name_; + // version decalred in metadata + QString shader_version_; + // description in metadata + QString shader_description_; // error in metadata or shader QString output_messages_; - // name of default texture input - QString main_input_name_; + // number of times fragment shader is invoked + int number_of_iterations_; // user defined inputs QStringList user_input_list_; // input of type vec2 to gizmo map @@ -88,8 +111,13 @@ protected slots: QMap handle_shape_table_; // color for points in 'handle_table_' QMap handle_color_table_; + // when false, a default vertex shader is used + bool use_vertex_shader; QVector2D resolution_; + + static const QString FRAG_TEMPLATE; + static const QString VERTEX_TEMPLATE; }; } diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index a22b03399f..c781f4c67f 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -29,13 +29,15 @@ namespace olive { #define super Node +#define DEFAULT_FILTER_NAME "My Filter" -namespace { -// a default shader that replicates to output a texture input. -const QString TEMPLATE( - "#version 150\n" - "//OVE shader_name: \n" - "//OVE shader_description: \n\n" + +// a default shader filter +const QString ShaderFilterNode::FRAG_TEMPLATE = QString( + "#version 150""\n" + "//OVE shader_name: %1\n" + "//OVE shader_description: a filter that's still to be implemented\n" + "//OVE shader_version: 0.1\n\n" "//OVE main_input_name: Input\n" "uniform sampler2D tex_in;\n\n" "//OVE end\n\n\n" @@ -46,29 +48,53 @@ const QString TEMPLATE( "void main(void) {\n" " vec4 textureColor = texture(tex_in, ove_texcoord);\n" " frag_color= textureColor.brga;\n" - "}\n"); + "}\n").arg(DEFAULT_FILTER_NAME); -} // namespace +const QString ShaderFilterNode::VERTEX_TEMPLATE = QString( + "#version 150\n" + "// No metadata will be parsed from this shader\n" + "// You can decalre 'uniform' inputs defined in fragment shader\n" + "uniform mat4 ove_mvpmat;\n" + "\n" + "in vec4 a_position;\n" + "in vec2 a_texcoord;\n" + "\n" + "out vec2 ove_texcoord;\n" + "\n" + "void main() {\n" + " gl_Position = ove_mvpmat * a_position;\n" + " ove_texcoord = a_texcoord;\n" + "}\n"); const QString ShaderFilterNode::kTextureInput = QStringLiteral("tex_in"); -const QString ShaderFilterNode::kShaderCode = QStringLiteral("source"); +const QString ShaderFilterNode::kFragShaderCode = QStringLiteral("frag_source"); +const QString ShaderFilterNode::kUseVertexCode = QStringLiteral("use_vertex"); +const QString ShaderFilterNode::kVertexShaderCode = QStringLiteral("vertex_source"); const QString ShaderFilterNode::kOutputMessages = QStringLiteral("issues"); ShaderFilterNode::ShaderFilterNode(): invalidate_code_flag_(false), - main_input_name_("Input") + main_input_name_("Input"), + number_of_iterations_(1), + use_vertex_shader(false) { - // Full code of the shader. Inputs to be exposed are defined within the shader code + // Option to use vertex shader. When false, the default vertex shader is used + AddInput(kUseVertexCode, NodeValue::kBoolean, QVariant::fromValue(false), + InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + AddInput(kVertexShaderCode, NodeValue::kText, QVariant::fromValue(VERTEX_TEMPLATE), + InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); + // Full code of fragment shader. Inputs to be exposed are defined within the shader code // with mark-up comments. - AddInput(kShaderCode, NodeValue::kText, QVariant::fromValue(TEMPLATE), + AddInput(kFragShaderCode, NodeValue::kText, QVariant::fromValue(FRAG_TEMPLATE), InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); // Output messages of shader parser AddInput(kOutputMessages, NodeValue::kText, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); // mark this text input as code, so it will be edited with code editor - SetInputProperty( kShaderCode, QStringLiteral("text_type"), QString("shader_code")); + SetInputProperty( kFragShaderCode, QStringLiteral("text_type"), QString("shader_code")); + SetInputProperty( kVertexShaderCode, QStringLiteral("text_type"), QString("shader_code")); // mark this text input as output messages SetInputProperty( kOutputMessages, QStringLiteral("text_type"), QString("shader_issues")); @@ -109,20 +135,23 @@ void ShaderFilterNode::InputValueChangedEvent(const QString &input, int element) { Q_UNUSED(element) - if (input == kShaderCode) { + if ((input == kFragShaderCode) || (input == kVertexShaderCode)) { // for some reason, this function is called more than once for each input // of each instance. Parse code only if it has changed - QString new_code = GetStandardValue(kShaderCode).value(); + QString new_frag_code = GetStandardValue(kFragShaderCode).value(); + QString new_vertex_code = GetStandardValue(kVertexShaderCode).value(); - if (shader_code_ != new_code) { + if ((frag_shader_code_ != new_frag_code) || + (vertex_shader_code_ != new_vertex_code)) { - shader_code_ = new_code; + frag_shader_code_ = new_frag_code; + vertex_shader_code_ = new_vertex_code; output_messages_.clear(); // the code of the shader has changed. // Remove all inputs and re-parse the code // to fix shader name and input parameters. - parseShaderCode(); + parseShaderMetadata(); // check for syntax checkShaderSyntax(); @@ -131,6 +160,13 @@ void ShaderFilterNode::InputValueChangedEvent(const QString &input, int element) invalidate_code_flag_ = true; } + } else if (input == kUseVertexCode) { + bool use_vertex_code = GetStandardValue(kUseVertexCode).toBool(); + // hide or un-hide the widget for vertex shader + SetInputFlag( kVertexShaderCode, kInputFlagHidden, ! use_vertex_code); + + // recalculate shader + invalidate_code_flag_ = true; } } @@ -141,6 +177,13 @@ bool ShaderFilterNode::ShaderCodeInvalidateFlag() const return invalid; } +void ShaderFilterNode::getMetadata(QString &name, QString &description, QString &version) const +{ + name = shader_name_; + description = shader_description_; + version = shader_version_; +} + QString ShaderFilterNode::Description() const { @@ -153,14 +196,18 @@ void ShaderFilterNode::Retranslate() // Retranslate only fixed inputs. // Other inputs are read from the shader code - SetInputName( kShaderCode, tr("Shader code")); + SetInputName( kUseVertexCode, tr("Use vertex shader")); + SetInputName( kVertexShaderCode, tr("Vertex Shader")); + SetInputName( kFragShaderCode, tr("Fragment Shader")); SetInputName( kOutputMessages, tr("Issues")); } ShaderCode ShaderFilterNode::GetShaderCode(const Node::ShaderRequest &request) const { Q_UNUSED(request); - return ShaderCode(shader_code_); + const QString & vertex_code = (GetStandardValue(kUseVertexCode).toBool()) ? vertex_shader_code_ : QString(); + + return ShaderCode(frag_shader_code_, vertex_code); } @@ -170,38 +217,63 @@ void ShaderFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globa ShaderJob job(value); job.SetShaderID(GetLabel()); // TBD: label may not be enough to identify a shader univocally job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, tex->virtual_resolution(), this)); + + if (number_of_iterations_ != 1) { + job.SetIterations( number_of_iterations_, main_input_name_); + } + table->Push(NodeValue::kTexture, tex->toJob(job), this); } } - void ShaderFilterNode::checkShaderSyntax() { - QOpenGLShader shader(QOpenGLShader::Fragment); bool ok; - if (shader_code_.startsWith("#version")) { + QOpenGLShader frag_shader(QOpenGLShader::Fragment); + ok = compileShaderCode( frag_shader, frag_shader_code_); + + if (ok == false) + { + output_messages_ += QString("Fragment Shader compilation errors:\n") + frag_shader.log(); + } + + bool use_vertex_code = GetStandardValue(kUseVertexCode).toBool(); + + if (use_vertex_code) { + QOpenGLShader vertex_shader(QOpenGLShader::Vertex); + ok = compileShaderCode( vertex_shader, vertex_shader_code_); + + if (ok == false) + { + output_messages_ += QString("Vertex Shader compilation errors:\n") + vertex_shader.log(); + } + } +} + +bool ShaderFilterNode::compileShaderCode( QOpenGLShader & shader, const QString & souce_code) +{ + bool ok; + + if (souce_code.startsWith("#version")) { - ok=shader.compileSourceCode( shader_code_); + ok=shader.compileSourceCode( souce_code); } else { // NOTE: this replicates the preamble added in compilation (see openglrenderer.cpp for reference). // This must be aligned if preamble changes ok=shader.compileSourceCode( "#version 150\n\n" - "precision highp float;\n\n" + shader_code_); + "precision highp float;\n\n" + souce_code); } - if (ok == false) - { - output_messages_ += QString("Compilation errors:\n") + shader.log(); - } + return ok; } - -void ShaderFilterNode::parseShaderCode() +void ShaderFilterNode::parseShaderMetadata() { - ShaderInputsParser parser(shader_code_); + // only fragment shader has metadata + ShaderInputsParser parser(frag_shader_code_); parser.Parse(); @@ -212,6 +284,12 @@ void ShaderFilterNode::parseShaderCode() // update name, if defined in script; otherwise use a default. QString label = (parser.ShaderName().isEmpty()) ? "unnamed" : parser.ShaderName(); SetLabel( label); + shader_name_ = label; + shader_description_ = parser.ShaderDescription(); + shader_version_ = parser.ShaderVersion(); + number_of_iterations_ = parser.NumberOfIterations(); + + metadataChanged( shader_name_, shader_description_, shader_version_); } void ShaderFilterNode::reportErrorList( const ShaderInputsParser & parser) @@ -311,6 +389,8 @@ void ShaderFilterNode::updateGizmoList() g->AddInput(NodeKeyframeTrackReference(NodeInput(this, aInput), 0)); g->AddInput(NodeKeyframeTrackReference(NodeInput(this, aInput), 1)); g->SetDragValueBehavior(PointGizmo::kAbsolute); + g->SetVisible( true); + handle_table_.insert( aInput, g); } diff --git a/app/node/filter/shader/shader.h b/app/node/filter/shader/shader.h index 45aa302d3f..93c817bb29 100644 --- a/app/node/filter/shader/shader.h +++ b/app/node/filter/shader/shader.h @@ -18,12 +18,14 @@ ***/ -#ifndef ShaderFilterNode_H -#define ShaderFilterNode_H +#ifndef SHADERFILTERNODE_H +#define SHADERFILTERNODE_H #include "node/node.h" #include "node/gizmo/point.h" +class QOpenGLShader; + namespace olive { @@ -31,7 +33,7 @@ class ShaderInputsParser; /** @brief - * A node that implements a GLSL script. The inputs of this node + * A node that implements a filter with a GLSL script. The inputs of this node * are defined in GLSL by markup comments */ class ShaderFilterNode : public Node @@ -59,13 +61,21 @@ class ShaderFilterNode : public Node bool ShaderCodeInvalidateFlag() const override; + void getMetadata( QString & name, QString & description, QString & version) const; + static const QString kTextureInput; - static const QString kShaderCode; + static const QString kFragShaderCode; + static const QString kUseVertexCode; + static const QString kVertexShaderCode; static const QString kOutputMessages; +signals: + void metadataChanged( const QString & name, const QString & description, const QString & version); + private: - void parseShaderCode(); + void parseShaderMetadata(); void checkShaderSyntax(); + bool compileShaderCode( QOpenGLShader &shader, const QString &souce_code); void reportErrorList( const ShaderInputsParser & parser); void updateInputList( const ShaderInputsParser & parser); void updateGizmoList(); @@ -76,17 +86,25 @@ protected slots: virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override; private: - // set to true when shader code changes. Must be mutable because // it is reset when it is read mutable bool invalidate_code_flag_; // source GLSL code - QString shader_code_; + QString frag_shader_code_; + QString vertex_shader_code_; + // name of filter declared in metadata + QString shader_name_; + // version decalred in metadata + QString shader_version_; + // description in metadata + QString shader_description_; // error in metadata or shader QString output_messages_; // name of default texture input QString main_input_name_; + // number of times fragment shader is invoked + int number_of_iterations_; // user defined inputs QStringList user_input_list_; // input of type vec2 to gizmo map @@ -95,10 +113,15 @@ protected slots: QMap handle_shape_table_; // color for points in 'handle_table_' QMap handle_color_table_; + // when false, a default vertex shader is used + bool use_vertex_shader; QVector2D resolution_; + + static const QString FRAG_TEMPLATE; + static const QString VERTEX_TEMPLATE; }; } -#endif // ShaderFilterNode_H +#endif // SHADERFILTERNODE_H diff --git a/app/node/filter/shader/shaderinputsparser.cpp b/app/node/filter/shader/shaderinputsparser.cpp index 01db58ffd6..f54e004b13 100644 --- a/app/node/filter/shader/shaderinputsparser.cpp +++ b/app/node/filter/shader/shaderinputsparser.cpp @@ -26,7 +26,6 @@ #include #include -// #include "core/util/color.h" // ext\core\include\olive\core\util\color.h namespace olive { @@ -49,12 +48,15 @@ QRegularExpression SHADER_VERSION_REGEX("^\\s*//OVE\\s*shader_version:\\s*(?.*)\\s*$"); +// number of times the fragment shader is invoked +QRegularExpression NUM_OF_ITERATIONS_REGEX("^\\s*//OVE\\s*number_of_iterations:\\s*(?.*)\\s*$"); + // per Input metadata // human name of the input QRegularExpression INPUT_NAME_REGEX("^\\s*//OVE\\s*name:\\s*(?.*)\\s*$"); // name of uniform variable -QRegularExpression INPUT_UNIFORM_REGEX("^\\s*uniform\\s+(.*)\\s+(?[A-Za-z0-9_]+)\\s*;"); +QRegularExpression INPUT_UNIFORM_REGEX("^\\s*uniform\\s+(?[A-Za-z0-9_]+)\\s+(?[A-Za-z0-9_]+)\\s*;"); // kind of input QRegularExpression INPUT_TYPE_REGEX("^\\s*//OVE\\s*type:\\s*(?.*)\\s*$"); // input flags @@ -93,35 +95,17 @@ const QMap MINIMUM_TABLE{{NodeValue::kFloat, QVariant {NodeValue::kInt, QVariant( std::numeric_limits::lowest())} }; -const QMap MAXIMUM_TABLE{{NodeValue::kFloat, QVariant( std::numeric_limits::max())}, +const QMap MAXIMUM_TABLE{{NodeValue::kFloat, QVariant( std::numeric_limits::max())}, {NodeValue::kInt, QVariant( std::numeric_limits::max())} }; -// features of the input that's currently being parsed -ShaderInputsParser::InputParam currentInput; - -void clearCurrentInput() -{ - currentInput.human_name.clear(); - currentInput.uniform_name.clear(); - currentInput.description.clear(); - currentInput.type_string.clear(); - currentInput.type = NodeValue::kNone; - currentInput.flags = InputFlags(kInputFlagNormal); - currentInput.values.clear(); - currentInput.pointShape = PointGizmo::kSquare; - currentInput.gizmoColor = Qt::white; - currentInput.min = QVariant(); - currentInput.max = QVariant(); - currentInput.default_value = QVariant(); -} - -} // namespace +} // namespace ShaderInputsParser::ShaderInputsParser( const QString & shader_code) : shader_code_(shader_code), - line_number_(1) + line_number_(1), + number_of_iterations_(1) { clearCurrentInput(); @@ -130,6 +114,7 @@ ShaderInputsParser::ShaderInputsParser( const QString & shader_code) : INPUT_PARAM_PARSE_TABLE.insert( & SHADER_DESCRIPTION_REGEX, & ShaderInputsParser::parseShaderDescription); INPUT_PARAM_PARSE_TABLE.insert( & SHADER_VERSION_REGEX, & ShaderInputsParser::parseShaderVersion); INPUT_PARAM_PARSE_TABLE.insert( & SHADER_MAIN_INPUT_NAME_REGEX, & ShaderInputsParser::parseMainInputName); + INPUT_PARAM_PARSE_TABLE.insert( & NUM_OF_ITERATIONS_REGEX, & ShaderInputsParser::parseNumberOfIterations); INPUT_PARAM_PARSE_TABLE.insert( & INPUT_NAME_REGEX, & ShaderInputsParser::parseInputName); INPUT_PARAM_PARSE_TABLE.insert( & INPUT_UNIFORM_REGEX, & ShaderInputsParser::parseInputUniform); @@ -159,6 +144,11 @@ void ShaderInputsParser::Parse() error_list_.clear(); line_number_ = 1; + shader_name_ = QString(); + shader_description_ = QString(); + shader_version_ = QString(); + number_of_iterations_ = 1; + // parse line by line while (state != SHADER_COMPLETE) { @@ -183,7 +173,7 @@ void ShaderInputsParser::Parse() state = parseSingleLine( line); if (state == INPUT_COMPLETE) { - input_list_.append( currentInput); + input_list_.append( currentInput_); clearCurrentInput(); } } @@ -210,6 +200,23 @@ void ShaderInputsParser::Parse() } +void ShaderInputsParser::clearCurrentInput() +{ + currentInput_.human_name.clear(); + currentInput_.uniform_name.clear(); + currentInput_.description.clear(); + currentInput_.type_string.clear(); + currentInput_.type = NodeValue::kNone; + currentInput_.flags = InputFlags(kInputFlagNormal); + currentInput_.values.clear(); + currentInput_.pointShape = PointGizmo::kSquare; + currentInput_.gizmoColor = Qt::white; + currentInput_.min = QVariant(); + currentInput_.max = QVariant(); + currentInput_.default_value = QVariant(); +} + + ShaderInputsParser::InputParseState ShaderInputsParser::parseSingleLine( const QString & line) { QRegularExpressionMatch match; @@ -242,16 +249,19 @@ ShaderInputsParser::parseShaderName(const QRegularExpressionMatch & match) } ShaderInputsParser::InputParseState -ShaderInputsParser::parseShaderDescription(const QRegularExpressionMatch & /*match*/) +ShaderInputsParser::parseShaderDescription(const QRegularExpressionMatch & match) { - // nothing to do. So far. + if (shader_description_.size() > 0) { + shader_description_.append(QStringLiteral("\n")); + } + shader_description_ += match.captured("description"); return PARSING; } ShaderInputsParser::InputParseState -ShaderInputsParser::parseShaderVersion(const QRegularExpressionMatch & /*match*/) +ShaderInputsParser::parseShaderVersion(const QRegularExpressionMatch & match) { - // nothing to do. So far. + shader_version_ = match.captured("version"); return PARSING; } @@ -262,10 +272,25 @@ ShaderInputsParser::parseMainInputName(const QRegularExpressionMatch & match) return PARSING; } +ShaderInputsParser::InputParseState ShaderInputsParser::parseNumberOfIterations(const QRegularExpressionMatch & match) +{ + bool ok = false; + int num_iter = match.captured("num").toInt( & ok); + + if (ok && (num_iter >= 1)) { + number_of_iterations_ = num_iter; + } else { + number_of_iterations_ = 1; + reportError(QObject::tr("Number of iterations must be a number greater or equal to 1")); + } + + return PARSING; +} + ShaderInputsParser::InputParseState ShaderInputsParser::parseInputName(const QRegularExpressionMatch & match) { - currentInput.human_name = match.captured("name"); + currentInput_.human_name = match.captured("name"); return PARSING; } @@ -275,8 +300,9 @@ ShaderInputsParser::parseInputUniform(const QRegularExpressionMatch & match) ShaderInputsParser::InputParseState state = PARSING; // not all uniforms are exposed as inputs - if (currentInput.type != NodeValue::kNone) { - currentInput.uniform_name = match.captured("name"); + if (currentInput_.type != NodeValue::kNone) { + currentInput_.uniform_name = match.captured("name"); + checkConsistentType( currentInput_.type, match.captured("type")); state = INPUT_COMPLETE; } @@ -286,16 +312,16 @@ ShaderInputsParser::parseInputUniform(const QRegularExpressionMatch & match) ShaderInputsParser::InputParseState ShaderInputsParser::parseInputType(const QRegularExpressionMatch & match) { - currentInput.type_string = match.captured("type"); - currentInput.type = INPUT_TYPE_TABLE.value( currentInput.type_string, NodeValue::kNone); + currentInput_.type_string = match.captured("type"); + currentInput_.type = INPUT_TYPE_TABLE.value( currentInput_.type_string, NodeValue::kNone); - if (currentInput.type == NodeValue::kNone) { - reportError(QObject::tr("type %1 is invalid").arg(currentInput.type_string)); + if (currentInput_.type == NodeValue::kNone) { + reportError(QObject::tr("type %1 is invalid").arg(currentInput_.type_string)); } // preset 'min' and 'max', in case they are not defined - currentInput.min = MINIMUM_TABLE.value( currentInput.type, QVariant()); - currentInput.max = MAXIMUM_TABLE.value( currentInput.type, QVariant()); + currentInput_.min = MINIMUM_TABLE.value( currentInput_.type, QVariant()); + currentInput_.max = MAXIMUM_TABLE.value( currentInput_.type, QVariant()); return PARSING; } @@ -321,7 +347,7 @@ ShaderInputsParser::parseInputFlags(const QRegularExpressionMatch & line_match) QRegularExpressionMatch flag = flag_match.next(); QString flag_str = flag.captured(1); - currentInput.flags |= InputFlags(FLAG_TABLE.value( flag_str, kInputFlagNormal)); + currentInput_.flags |= InputFlags(FLAG_TABLE.value( flag_str, kInputFlagNormal)); } return PARSING; @@ -342,7 +368,7 @@ ShaderInputsParser::parseInputValueList(const QRegularExpressionMatch & line_mat QRegularExpressionMatch value = value_match.next(); QString value_str = value.captured(1); - currentInput.values << value_str; + currentInput_.values << value_str; } return PARSING; @@ -361,10 +387,10 @@ ShaderInputsParser::InputParseState ShaderInputsParser::parseInputPointShape(con QString shape_str = line_match.captured("shape"); if (INPUT_POINT_SHAPE_TABLE.contains(shape_str)) { - currentInput.pointShape = INPUT_POINT_SHAPE_TABLE[shape_str]; + currentInput_.pointShape = INPUT_POINT_SHAPE_TABLE[shape_str]; } else { - currentInput.pointShape = PointGizmo::kSquare; + currentInput_.pointShape = PointGizmo::kSquare; reportError(QObject::tr("'%1' is not a valid point shape."). arg(shape_str)); } @@ -375,16 +401,16 @@ ShaderInputsParser::InputParseState ShaderInputsParser::parseInputPointShape(con ShaderInputsParser::InputParseState ShaderInputsParser::parseInputGizmoColor(const QRegularExpressionMatch & line_match) { - if (currentInput.type == NodeValue::kVec2) { + if (currentInput_.type == NodeValue::kVec2) { QString color_string = line_match.captured("color").trimmed(); Color ove_color = parseColor( color_string).value(); - currentInput.gizmoColor = QColor( int(ove_color.red()*255.0), - int(ove_color.green()*255.0), - int(ove_color.blue()*255.0), 255); + currentInput_.gizmoColor = QColor( int(ove_color.red()*255.0), + int(ove_color.green()*255.0), + int(ove_color.blue()*255.0), 255); } else { reportError( QObject::tr("Attribute 'color' can be used for type POINT, not for %1"). - arg(currentInput.type_string)); + arg(currentInput_.type_string)); } return PARSING; @@ -396,11 +422,11 @@ ShaderInputsParser::parseInputMin(const QRegularExpressionMatch & match) bool ok = false; QString min_str = match.captured("min"); - if (currentInput.type == NodeValue::kFloat) { - currentInput.min = min_str.toFloat( &ok); + if (currentInput_.type == NodeValue::kFloat) { + currentInput_.min = min_str.toFloat( &ok); } - else if (currentInput.type == NodeValue::kInt) { - currentInput.min = min_str.toInt( &ok); + else if (currentInput_.type == NodeValue::kInt) { + currentInput_.min = min_str.toInt( &ok); } else { // this type does not support "min" property @@ -408,7 +434,7 @@ ShaderInputsParser::parseInputMin(const QRegularExpressionMatch & match) if (ok == false) { reportError(QObject::tr("%1 is not valid as minimum for type %2"). - arg(min_str, currentInput.type_string)); + arg(min_str, currentInput_.type_string)); } return PARSING; @@ -420,11 +446,11 @@ ShaderInputsParser::parseInputMax(const QRegularExpressionMatch & match) bool ok = false; QString max_str = match.captured("max"); - if (currentInput.type == NodeValue::kFloat) { - currentInput.max = max_str.toFloat( &ok); + if (currentInput_.type == NodeValue::kFloat) { + currentInput_.max = max_str.toFloat( &ok); } - else if (currentInput.type == NodeValue::kInt) { - currentInput.max = max_str.toInt( &ok); + else if (currentInput_.type == NodeValue::kInt) { + currentInput_.max = max_str.toInt( &ok); } else { // this type does not support "max" property @@ -432,7 +458,7 @@ ShaderInputsParser::parseInputMax(const QRegularExpressionMatch & match) if (ok == false) { reportError(QObject::tr("%1 is not valid as maximum for type %2"). - arg(max_str, currentInput.type_string)); + arg(max_str, currentInput_.type_string)); } return PARSING; @@ -446,14 +472,14 @@ ShaderInputsParser::parseInputDefault(const QRegularExpressionMatch & match) float float_value; int int_value; - switch (currentInput.type) + switch (currentInput_.type) { case NodeValue::kFloat: float_value = default_string.toFloat( &ok); if (ok) { - currentInput.default_value = QVariant::fromValue(float_value); + currentInput_.default_value = QVariant::fromValue(float_value); } else { @@ -467,7 +493,7 @@ ShaderInputsParser::parseInputDefault(const QRegularExpressionMatch & match) if (ok) { - currentInput.default_value = QVariant::fromValue(int_value); + currentInput_.default_value = QVariant::fromValue(int_value); } else { @@ -477,10 +503,10 @@ ShaderInputsParser::parseInputDefault(const QRegularExpressionMatch & match) case NodeValue::kBoolean: if (default_string == QString::fromLatin1("false")) { - currentInput.default_value = QVariant::fromValue(false); + currentInput_.default_value = QVariant::fromValue(false); } else if (default_string == QString::fromLatin1("true")) { - currentInput.default_value = QVariant::fromValue(true); + currentInput_.default_value = QVariant::fromValue(true); } else { reportError(QObject::tr("value must be 'true' or 'false'")); @@ -488,7 +514,7 @@ ShaderInputsParser::parseInputDefault(const QRegularExpressionMatch & match) break; case NodeValue::kColor: - currentInput.default_value = parseColor( default_string); + currentInput_.default_value = parseColor( default_string); break; case NodeValue::kCombo: @@ -496,7 +522,7 @@ ShaderInputsParser::parseInputDefault(const QRegularExpressionMatch & match) if (ok) { - currentInput.default_value = QVariant::fromValue(int_value); + currentInput_.default_value = QVariant::fromValue(int_value); } else { @@ -505,13 +531,13 @@ ShaderInputsParser::parseInputDefault(const QRegularExpressionMatch & match) break; case NodeValue::kVec2: - currentInput.default_value = parsePoint( default_string); + currentInput_.default_value = parsePoint( default_string); break; default: case NodeValue::kTexture: case NodeValue::kNone: - reportError(QObject::tr("type %1 does not support a default value").arg(currentInput.type_string)); + reportError(QObject::tr("type %1 does not support a default value").arg(currentInput_.type_string)); break; } @@ -521,7 +547,7 @@ ShaderInputsParser::parseInputDefault(const QRegularExpressionMatch & match) ShaderInputsParser::InputParseState ShaderInputsParser::parseInputDescription(const QRegularExpressionMatch & match) { - currentInput.description.append( match.captured("description")); + currentInput_.description.append( match.captured("description")); return PARSING; } @@ -598,6 +624,26 @@ QVariant ShaderInputsParser::parsePoint(const QString &line) return point; } +void ShaderInputsParser::checkConsistentType(const NodeValue::Type &metadata_type, const QString &uniform_type) +{ + static const QMap UNIFORM_STRING_MAP{{"TEXTURE", "sampler2D"}, + {"COLOR", "vec4"}, + {"FLOAT", "float"}, + {"INTEGER", "int"}, + {"BOOLEAN", "bool"}, + {"SELECTION", "int"}, + {"POINT", "vec2"} + }; + + QString meta_type_string = INPUT_TYPE_TABLE.key( metadata_type, "-"); + QString uniform_type_expected = UNIFORM_STRING_MAP.value( meta_type_string, "-"); + + if (uniform_type_expected != uniform_type) { + reportError(QObject::tr("metadata type %1 requires uniform type %2 and not %3"). + arg( meta_type_string, uniform_type_expected, uniform_type)); + } +} + void ShaderInputsParser::reportError(const QString& error) { Error new_err; diff --git a/app/node/filter/shader/shaderinputsparser.h b/app/node/filter/shader/shaderinputsparser.h index b1c5a7a1de..df1e43e0bd 100644 --- a/app/node/filter/shader/shaderinputsparser.h +++ b/app/node/filter/shader/shaderinputsparser.h @@ -90,11 +90,25 @@ class ShaderInputsParser return shader_name_; } + // shader description + const QString & ShaderDescription() const { + return shader_description_; + } + + // shader version + const QString & ShaderVersion() const { + return shader_version_; + } + // name of default input const QString & MainInputName() const { return main_input_name_; } + int NumberOfIterations() const { + return number_of_iterations_; + } + // list of issue found in parsing inputs const QList & ErrorList() const { return error_list_; @@ -111,6 +125,7 @@ class ShaderInputsParser SHADER_COMPLETE }; + void clearCurrentInput(); InputParseState parseSingleLine( const QString & line); // pointer to a function that parse a line that matches an //OVE comment @@ -120,6 +135,7 @@ class ShaderInputsParser InputParseState parseShaderDescription( const QRegularExpressionMatch &); InputParseState parseShaderVersion( const QRegularExpressionMatch &); InputParseState parseMainInputName( const QRegularExpressionMatch &); + InputParseState parseNumberOfIterations( const QRegularExpressionMatch &); InputParseState parseInputName( const QRegularExpressionMatch &); InputParseState parseInputUniform( const QRegularExpressionMatch &); InputParseState parseInputType( const QRegularExpressionMatch &); @@ -135,6 +151,7 @@ class ShaderInputsParser QVariant parseColor( const QString & line); QVariant parsePoint( const QString & line); + void checkConsistentType( const NodeValue::Type & metadata_type, const QString & uniform_type); void reportError( const QString & error); private: @@ -143,8 +160,14 @@ class ShaderInputsParser QList error_list_; int line_number_; + // features of the input that's currently being parsed + InputParam currentInput_; + QString shader_name_; + QString shader_description_; + QString shader_version_; QString main_input_name_; + int number_of_iterations_; QMap< const QRegularExpression *, LineParseFunction> INPUT_PARAM_PARSE_TABLE; }; diff --git a/app/widget/nodeparamview/CMakeLists.txt b/app/widget/nodeparamview/CMakeLists.txt index 1b80edebc2..2496d261c1 100644 --- a/app/widget/nodeparamview/CMakeLists.txt +++ b/app/widget/nodeparamview/CMakeLists.txt @@ -36,6 +36,8 @@ set(OLIVE_SOURCES widget/nodeparamview/nodeparamviewkeyframecontrol.h widget/nodeparamview/nodeparamviewtextedit.cpp widget/nodeparamview/nodeparamviewtextedit.h + widget/nodeparamview/nodeparamviewshader.cpp + widget/nodeparamview/nodeparamviewshader.h widget/nodeparamview/nodeparamviewwidgetbridge.cpp widget/nodeparamview/nodeparamviewwidgetbridge.h PARENT_SCOPE diff --git a/app/widget/nodeparamview/nodeparamviewshader.cpp b/app/widget/nodeparamview/nodeparamviewshader.cpp new file mode 100644 index 0000000000..fc38452c78 --- /dev/null +++ b/app/widget/nodeparamview/nodeparamviewshader.cpp @@ -0,0 +1,136 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 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 +#include +#include + +#include "nodeparamviewshader.h" +#include "node/filter/shader/shader.h" +#include "node/block/transition/shader/shadertransition.h" +#include "dialog/codeeditor/externaleditorproxy.h" +#include "dialog/codeeditor/codeeditordialog.h" +#include "config/config.h" + + +namespace olive { + +NodeParamViewShader::NodeParamViewShader(const QPlainTextEdit &content, + QObject *parent) : + QObject(parent), + full_shader_(content) +{ + summary_ = new QTextEdit(); + summary_->setReadOnly( true); + summary_->setVisible( false); + + ext_editor_proxy_ = new ExternalEditorProxy( this); + + connect( ext_editor_proxy_, & ExternalEditorProxy::textChanged, + this, & NodeParamViewShader::OnTextChangedExternally); +} + + +void NodeParamViewShader::attachOwnerNode(const Node *owner) +{ + summary_->setVisible( true); + + // create a file whose name is unique for for the node this instance belongs to. + // 'node_id' is based on address of the node this param belongs to + // 'QStandardPaths::TempLocation' is guaranteed not to be empty + QString file_path = QStandardPaths::standardLocations( QStandardPaths::TempLocation).at(0); + file_path += QString("/%2.glsl").arg((uint64_t)owner); + + ext_editor_proxy_->SetFilePath(file_path); + + /* list of nodes that use this view: */ + + const ShaderFilterNode * owner_filter = dynamic_cast(owner); + if (owner_filter != nullptr) { + owner_filter->getMetadata( name_, description_, version_); + // initialize summary text ... + onMetadataChanged( name_, description_, version_); + + // ... and track changes + connect( owner_filter, & ShaderFilterNode::metadataChanged, + this, & NodeParamViewShader::onMetadataChanged); + } + + const ShaderTransition * owner_transition = dynamic_cast(owner); + if (owner_transition != nullptr) { + owner_transition->getMetadata( name_, description_, version_); + // initialize summary text ... + onMetadataChanged( name_, description_, version_); + + // ... and track changes + connect( owner_transition, & ShaderTransition::metadataChanged, + this, & NodeParamViewShader::onMetadataChanged); + } + +} + +void NodeParamViewShader::onMetadataChanged(const QString &name, + const QString &description, + const QString &version) +{ + name_ = name; + description_ = description; + version_ = version; + + summary_->setHtml( displayedText()); + +} + +QString NodeParamViewShader::displayedText() const +{ + QString version = version_.isEmpty() ? QString() : QString("(%1)").arg(version_); + + // We don't display the full shader, but only some metadata + return QString("%1" + "        %2" + "

%3

"). + arg( name_, version, description_); +} + +void NodeParamViewShader::launchCodeEditor(QString & text) +{ + if (Config::Current()["EditorUseInternal"].toBool()) { + + // internal editor + CodeEditorDialog d(full_shader_.toPlainText(), nullptr); + + // in case external editor was previously opened but + // then user switched to internal editor. + ext_editor_proxy_->Detach(); + + if (d.exec() == QDialog::Accepted) { + text = d.text(); + } + } + else { + + // external editor + ext_editor_proxy_->Launch( full_shader_.toPlainText()); + } +} + +} + + diff --git a/app/widget/nodeparamview/nodeparamviewshader.h b/app/widget/nodeparamview/nodeparamviewshader.h new file mode 100644 index 0000000000..e2abd6f640 --- /dev/null +++ b/app/widget/nodeparamview/nodeparamviewshader.h @@ -0,0 +1,78 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2023 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 NODEPARAMVIEWSHADER_H +#define NODEPARAMVIEWSHADER_H + +#include +#include +#include + +class QPlainTextEdit; + +namespace olive { + +class ExternalEditorProxy; +class Node; + + +// This is a facility for text-type param view that is used +// as shader code entry (fragment or vertex). +// Instead of showing the full code, a summary is shown +class NodeParamViewShader : public QObject +{ + Q_OBJECT +public: + NodeParamViewShader( const QPlainTextEdit & content, QObject * parent=nullptr); + + QWidget* widget() { + return summary_; + } + + // bind this viewer to a Node that handles one GLSL script, + // so changes in the script will update this widget + void attachOwnerNode( const Node * owner); + + // start editing shader in internal or external editor + void launchCodeEditor(QString & text); + +signals: + void OnTextChangedExternally( const QString & text); + +private slots: + // invoked when the owner Node has parsed a shader script + void onMetadataChanged( const QString & name, const QString & description, const QString & version); + +private: + QString displayedText() const; + +private: + const QPlainTextEdit & full_shader_; + QTextEdit* summary_; + ExternalEditorProxy* ext_editor_proxy_; + + QString name_; + QString description_; + QString version_; +}; + +} + +#endif // NODEPARAMVIEWSHADER_H diff --git a/app/widget/nodeparamview/nodeparamviewtextedit.cpp b/app/widget/nodeparamview/nodeparamviewtextedit.cpp index bca46f3b7d..eafe4b3df6 100644 --- a/app/widget/nodeparamview/nodeparamviewtextedit.cpp +++ b/app/widget/nodeparamview/nodeparamviewtextedit.cpp @@ -21,15 +21,12 @@ #include "nodeparamviewtextedit.h" #include -#include -#include #include "dialog/text/text.h" -#include "dialog/codeeditor/codeeditordialog.h" -#include "dialog/codeeditor/externaleditorproxy.h" + #include "dialog/codeeditor/messagehighlighter.h" #include "ui/icons/icons.h" -#include "config/config.h" +#include "nodeparamviewshader.h" namespace olive { @@ -47,6 +44,11 @@ NodeParamViewTextEdit::NodeParamViewTextEdit(QWidget *parent) : connect(line_edit_, &QPlainTextEdit::textChanged, this, &NodeParamViewTextEdit::InnerWidgetTextChanged); layout->addWidget(line_edit_); + shader_edit_ = new NodeParamViewShader( *line_edit_, this); + layout->addWidget( shader_edit_->widget()); + connect( shader_edit_, & NodeParamViewShader::OnTextChangedExternally, + this, & NodeParamViewTextEdit::OnTextChangedExternally); + edit_btn_ = new QPushButton(); edit_btn_->setIcon(icon::ToolEdit); edit_btn_->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); @@ -59,11 +61,6 @@ NodeParamViewTextEdit::NodeParamViewTextEdit(QWidget *parent) : connect(edit_in_viewer_btn_, &QPushButton::clicked, this, &NodeParamViewTextEdit::RequestEditInViewer); SetEditInViewerOnlyMode(false); - - ext_editor_proxy_ = new ExternalEditorProxy( this); - - connect( ext_editor_proxy_, & ExternalEditorProxy::textChanged, - this, & NodeParamViewTextEdit::OnTextChangedExternally); } @@ -81,9 +78,8 @@ void NodeParamViewTextEdit::ShowTextDialog() if (code_editor_flag_) { - launchCodeEditor(text); - } - else { + shader_edit_->launchCodeEditor(text); + } else { TextDialog d(this->text(), this); if (code_issues_flag_) { @@ -101,27 +97,6 @@ void NodeParamViewTextEdit::ShowTextDialog() } } -void olive::NodeParamViewTextEdit::launchCodeEditor(QString & text) -{ - if (Config::Current()["EditorUseInternal"].toBool()) { - - // internal editor - CodeEditorDialog d(this->text(), this); - - // in case external editor was previously opened but - // then user switched to internal editor. - ext_editor_proxy_->Detach(); - - if (d.exec() == QDialog::Accepted) { - text = d.text(); - } - } - else { - - // external editor - ext_editor_proxy_->Launch( this->text()); - } -} void NodeParamViewTextEdit::InnerWidgetTextChanged() { @@ -134,31 +109,26 @@ void NodeParamViewTextEdit::OnTextChangedExternally(const QString & content) emit textEdited( content); } -void NodeParamViewTextEdit::setCodeEditorFlag( uint64_t node_id) +void NodeParamViewTextEdit::setCodeEditorFlag( const Node * owner) { code_editor_flag_ = true; - // create a file whose name is unique for for the node this instance belongs to. - // 'node_id' is based on address of the node this param belongs to - // 'QStandardPaths::TempLocation' is guaranteed not to be empty - QString file_path = QStandardPaths::standardLocations( QStandardPaths::TempLocation).at(0); - file_path += QString("/%2.frag").arg(node_id); - - ext_editor_proxy_->SetFilePath(file_path); - // no need to draw the stopwatch to keyframe this input setProperty("is_exapandable", QVariant::fromValue(true)); - // if the text box is a shader code editor, make it read only so that - // the shader code is not re-parsed on every key pressed by the user. - // Please use the Text Dialog to edit code. + // as this is a shader code editor, it is edited by a + // separate dialog or external editor line_edit_->setReadOnly( true); + line_edit_->setVisible(false); + shader_edit_->attachOwnerNode( owner); } void NodeParamViewTextEdit::setCodeIssuesFlag() { code_issues_flag_ = true; line_edit_->setReadOnly( true); + + // no need to draw the stopwatch to keyframe this input setProperty("is_exapandable", QVariant::fromValue(true)); new MessageSyntaxHighlighter( line_edit_->document()); diff --git a/app/widget/nodeparamview/nodeparamviewtextedit.h b/app/widget/nodeparamview/nodeparamviewtextedit.h index e7d1a515c9..0209411118 100644 --- a/app/widget/nodeparamview/nodeparamviewtextedit.h +++ b/app/widget/nodeparamview/nodeparamviewtextedit.h @@ -22,6 +22,7 @@ #define NODEPARAMVIEWTEXTEDIT_H #include +#include #include #include @@ -30,7 +31,8 @@ namespace olive { -class ExternalEditorProxy; +class Node; +class NodeParamViewShader; class NodeParamViewTextEdit : public QWidget @@ -46,7 +48,7 @@ class NodeParamViewTextEdit : public QWidget // let this instance perform as a code editor. // This input will be edited with a built-in or external code editor. - void setCodeEditorFlag(uint64_t node_id); + void setCodeEditorFlag(const Node *owner); // set flag to view text as code issues void setCodeIssuesFlag(); @@ -83,11 +85,9 @@ public slots: QPlainTextEdit* line_edit_; bool code_editor_flag_; bool code_issues_flag_; - ExternalEditorProxy * ext_editor_proxy_; + NodeParamViewShader * shader_edit_; private: - void launchCodeEditor(QString & text); - QPushButton* edit_btn_; QPushButton *edit_in_viewer_btn_; diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index 09c7b67569..4ed452f319 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -142,8 +142,8 @@ void NodeParamViewWidgetBridge::CreateWidgets() // check for special type of text QString text_type = GetInnerInput().GetProperty(QStringLiteral("text_type")).toString(); if (text_type == "shader_code") { - const Node * addr = GetInnerInput().node(); - line_edit->setCodeEditorFlag( (uint64_t)addr); + const Node * owner = GetInnerInput().node(); + line_edit->setCodeEditorFlag( owner); } else if (text_type == "shader_issues") { line_edit->setCodeIssuesFlag(); diff --git a/tests/shader/shader-tests.cpp b/tests/shader/shader-tests.cpp index 239809d754..bf24608354 100644 --- a/tests/shader/shader-tests.cpp +++ b/tests/shader/shader-tests.cpp @@ -33,17 +33,43 @@ namespace olive { OLIVE_ADD_TEST(HeaderTest) { QString code( -"#version 150""\n" -"//OVE shader_name: slide""\n" -"//OVE shader_description: slide transition""\n" -"//OVE shader_version: 0.1""\n" -); + "#version 150""\n" + "//OVE shader_name: slide""\n" + "//OVE shader_description: slide transition""\n" + "//OVE shader_version: 0.1""\n" + "//OVE number_of_iterations: 2""\n" + ); ShaderInputsParser parser(code); parser.Parse(); OLIVE_ASSERT_EQUAL( parser.ErrorList().size(), 0); OLIVE_ASSERT_EQUAL( STR(parser.ShaderName()), STR("slide")); + OLIVE_ASSERT_EQUAL( STR(parser.ShaderDescription()), STR("slide transition")); + OLIVE_ASSERT_EQUAL( STR(parser.ShaderVersion()), STR("0.1")); + OLIVE_ASSERT_EQUAL( parser.NumberOfIterations(), 2); + + OLIVE_TEST_END; +} + +// Script description over multiple lines +OLIVE_ADD_TEST(DescriptionTest) +{ + QString code( + "#version 150""\n" + "//OVE shader_name: slide""\n" + "//OVE shader_description: slide transition ""\n" + "//OVE shader_description: that works fine. ""\n" + "//OVE shader_version: 0.1""\n" + ); + + ShaderInputsParser parser(code); + parser.Parse(); + + OLIVE_ASSERT_EQUAL( parser.ErrorList().size(), 0); + OLIVE_ASSERT_EQUAL( STR(parser.ShaderName()), STR("slide")); + OLIVE_ASSERT_EQUAL( STR(parser.ShaderDescription()), STR("slide transition\nthat works fine.")); + OLIVE_ASSERT_EQUAL( STR(parser.ShaderVersion()), STR("0.1")); OLIVE_TEST_END; } @@ -53,9 +79,9 @@ OLIVE_ADD_TEST(HeaderTest) OLIVE_ADD_TEST(MainInputTest) { QString code( -"//OVE main_input_name: dummy""\n" -"uniform sampler2D dummy;""\n" -); + "//OVE main_input_name: dummy""\n" + "uniform sampler2D dummy;""\n" + ); ShaderInputsParser parser(code); parser.Parse(); @@ -70,12 +96,12 @@ OLIVE_ADD_TEST(MainInputTest) OLIVE_ADD_TEST(InputTextureTest) { QString code( -"//OVE name: Base image""\n" -"//OVE type: TEXTURE""\n" -"//OVE flag: NOT_KEYFRAMABLE""\n" -"//OVE description: the base image""\n" -"uniform sampler2D tex_base;""\n" -); + "//OVE name: Base image""\n" + "//OVE type: TEXTURE""\n" + "//OVE flag: NOT_KEYFRAMABLE""\n" + "//OVE description: the base image""\n" + "uniform sampler2D tex_base;""\n" + ); ShaderInputsParser parser(code); parser.Parse(); @@ -99,14 +125,14 @@ OLIVE_ADD_TEST(InputTextureTest) OLIVE_ADD_TEST(InputFloatTest) { QString code( -"//OVE name: Tolerance %%""\n" -"//OVE type: FLOAT""\n" -"//OVE min: 1.0""\n" -"//OVE default: 5.5""\n" -"//OVE max: 100""\n" -"//OVE description: the tolerance of filter""\n" -"uniform float toler_in;""\n" -); + "//OVE name: Tolerance %%""\n" + "//OVE type: FLOAT""\n" + "//OVE min: 1.0""\n" + "//OVE default: 5.5""\n" + "//OVE max: 100""\n" + "//OVE description: the tolerance of filter""\n" + "uniform float toler_in;""\n" + ); ShaderInputsParser parser(code); parser.Parse(); @@ -121,10 +147,43 @@ OLIVE_ADD_TEST(InputFloatTest) OLIVE_ASSERT_EQUAL( param.flags, InputFlags(0)); OLIVE_ASSERT_EQUAL( STR(param.uniform_name), STR("toler_in")); OLIVE_ASSERT_EQUAL( STR(param.type_string), STR("FLOAT")); - OLIVE_ASSERT_EQUAL( param.type, NodeValue::kFloat); - OLIVE_ASSERT_EQUAL( param.min.toFloat(), 1.); - OLIVE_ASSERT_EQUAL( param.max.toFloat(), 100.); - OLIVE_ASSERT_EQUAL( param.default_value.toFloat(), 5.5); + OLIVE_ASSERT( param.type == NodeValue::kFloat); + OLIVE_ASSERT_EQUAL( param.min.toDouble(), 1.); + OLIVE_ASSERT_EQUAL( param.max.toDouble(), 100.); + OLIVE_ASSERT_EQUAL( param.default_value.toDouble(), 5.5); + + OLIVE_TEST_END; +} + +// An input of kind Float with 'min' and 'max' not specified +OLIVE_ADD_TEST(InputFloatTestBoundary) +{ + QString code( + "//OVE name: Tolerance""\n" + "//OVE type: FLOAT""\n" + "uniform float toler_in;""\n" + "//OVE name: Number of bars""\n" + "//OVE type: INTEGER""\n" + "uniform int bars_in;""\n" + ); + + ShaderInputsParser parser(code); + parser.Parse(); + + OLIVE_ASSERT_EQUAL( parser.ErrorList().size(), 0); + OLIVE_ASSERT_EQUAL( parser.InputList().size(), 2); + + const ShaderInputsParser::InputParam & param1 = parser.InputList().at(0); + + OLIVE_ASSERT_EQUAL( STR(param1.human_name), STR("Tolerance")); + OLIVE_ASSERT( param1.min.toDouble() < -1e30); + OLIVE_ASSERT( param1.max.toDouble() > 1e30); + + const ShaderInputsParser::InputParam & param2 = parser.InputList().at(1); + + OLIVE_ASSERT_EQUAL( STR(param2.human_name), STR("Number of bars")); + OLIVE_ASSERT( param2.min.toInt() < -1000000000); + OLIVE_ASSERT( param2.max.toInt() > 1000000000); OLIVE_TEST_END; } @@ -143,12 +202,12 @@ bool operator == (const Color & rhs, const Color & lhs) OLIVE_ADD_TEST(InputColorTest) { QString code( -"//OVE name: Final tone""\n" -"//OVE type: COLOR""\n" -"//OVE default: RGBA( 0.5,0.4 ,0.1, 1)""\n" -"//OVE description: color applied to the grayscale""\n" -"uniform vec4 tone_in;""\n" -); + "//OVE name: Final tone""\n" + "//OVE type: COLOR""\n" + "//OVE default: RGBA( 0.5,0.4 ,0.1, 1)""\n" + "//OVE description: color applied to the grayscale""\n" + "uniform vec4 tone_in;""\n" + ); ShaderInputsParser parser(code); parser.Parse(); @@ -168,16 +227,40 @@ OLIVE_ADD_TEST(InputColorTest) OLIVE_TEST_END; } +// An input of kind Color with components outside [0..1] +OLIVE_ADD_TEST(ColorOutOfRangeTest) +{ + QString code( + "//OVE name: Final tone""\n" + "//OVE type: COLOR""\n" + "//OVE default: RGBA( 2, 0, 0, 1)""\n" // <-- 2 is outisde range + "//OVE description: color applied to the grayscale""\n" + "uniform vec4 tone_in;""\n" + ); + + ShaderInputsParser parser(code); + parser.Parse(); + + OLIVE_ASSERT_EQUAL( parser.ErrorList().size(), 1); + OLIVE_ASSERT_EQUAL( parser.InputList().size(), 1); // input is still present + + OLIVE_ASSERT_EQUAL( STR(parser.ErrorList().at(0).issue), STR("Color must be in format 'RGBA(r,g,b,a)' " + "where r,g,b,a are in range (0,1)")); + OLIVE_ASSERT_EQUAL( parser.ErrorList().at(0).line, 3); + + OLIVE_TEST_END; +} + // An input of kind Boolean OLIVE_ADD_TEST(InputBooleanTest) { QString code( -"//OVE name: bypass effect""\n" -"//OVE type: BOOLEAN""\n" -"//OVE flag: NOT_CONNECTABLE""\n" -"//OVE default: false""\n" -"//OVE description: when True, the input is passed to output as is.""\n" -"uniform bool disable_in;""\n" + "//OVE name: bypass effect""\n" + "//OVE type: BOOLEAN""\n" + "//OVE flag: NOT_CONNECTABLE""\n" + "//OVE default: false""\n" + "//OVE description: when True, the input is passed to output as is.""\n" + "uniform bool disable_in;""\n" ); ShaderInputsParser parser(code); @@ -203,14 +286,14 @@ OLIVE_ADD_TEST(InputBooleanTest) OLIVE_ADD_TEST(InputSelectionTest) { QString code( -"//OVE name: mode""\n" -"//OVE type: SELECTION""\n" -"//OVE values: \"ADD\", \"AVERAGE\", \"COLOR BURN\"""\n" -"//OVE values: \"COLOR DODGE\" \"DARKEN\" \"DIFFERENCE\"""\n" -"//OVE values: \"EXCLUSION\" \"GLOW\"""\n" -"//OVE default: 3""\n" -"//OVE description: the blend mode""\n" -"uniform int mode_in; ""\n" + "//OVE name: mode""\n" + "//OVE type: SELECTION""\n" + "//OVE values: \"ADD\", \"AVERAGE\", \"COLOR BURN\"""\n" + "//OVE values: \"COLOR DODGE\" \"DARKEN\" \"DIFFERENCE\"""\n" + "//OVE values: \"EXCLUSION\" \"GLOW\"""\n" + "//OVE default: 3""\n" + "//OVE description: the blend mode""\n" + "uniform int mode_in; ""\n" ); ShaderInputsParser parser(code); @@ -235,12 +318,12 @@ OLIVE_ADD_TEST(InputSelectionTest) OLIVE_ADD_TEST(InputPointTest) { QString code( -"//OVE name: From""\n" -"//OVE type: POINT""\n" -"//OVE default: (0.4,0.2)""\n" -"//OVE color: RGBA( 0.5,0.5 ,0.0, 1)""\n" -"//OVE description: gradient start point""\n" -"uniform vec2 from_in;""\n" + "//OVE name: From""\n" + "//OVE type: POINT""\n" + "//OVE default: (0.4,0.2)""\n" + "//OVE color: RGBA( 0.5,0.5 ,0.0, 1)""\n" + "//OVE description: gradient start point""\n" + "uniform vec2 from_in;""\n" ); ShaderInputsParser parser(code); @@ -266,12 +349,12 @@ OLIVE_ADD_TEST(InputPointTest) OLIVE_ADD_TEST(MultipleFlagsTest) { QString code( -"//OVE name: From""\n" -"//OVE type: POINT""\n" -"//OVE default: (0.4,0.2)""\n" -"//OVE flag: NOT_KEYFRAMABLE, NOT_CONNECTABLE HIDDEN""\n" -"//OVE description: gradient start point""\n" -"uniform vec2 from_in;""\n" + "//OVE name: From""\n" + "//OVE type: POINT""\n" + "//OVE default: (0.4,0.2)""\n" + "//OVE flag: NOT_KEYFRAMABLE, NOT_CONNECTABLE HIDDEN""\n" + "//OVE description: gradient start point""\n" + "uniform vec2 from_in;""\n" ); ShaderInputsParser parser(code); @@ -293,14 +376,14 @@ OLIVE_ADD_TEST(MultipleFlagsTest) OLIVE_ADD_TEST(InputTypoTest) { QString code( -"//OVE name: Tolerance %%""\n" -"//OVE type: FLOA""\n" // <-- typo: "FLOA" for "FLOAT" -"//OVE min: 1.0""\n" -"//OVE default: 5.5""\n" -"//OVE max: 100""\n" -"//OVE description: the tolerance of filter""\n" -"uniform float toler_in;""\n" -); + "//OVE name: Tolerance %%""\n" + "//OVE type: FLOA""\n" // <-- typo: "FLOA" for "FLOAT" + "//OVE min: 1.0""\n" + "//OVE default: 5.5""\n" + "//OVE max: 100""\n" + "//OVE description: the tolerance of filter""\n" + "uniform float toler_in;""\n" + ); ShaderInputsParser parser(code); parser.Parse(); @@ -332,12 +415,12 @@ OLIVE_ADD_TEST(InputTypoTest) OLIVE_ADD_TEST(InputAfterEndTest) { QString code( -"//OVE end""\n" // <- end -"//OVE name: Tolerance %%""\n" -"//OVE type: FLOAT""\n" -"//OVE description: the tolerance of filter""\n" -"uniform float toler_in;""\n" -); + "//OVE end""\n" // <- end + "//OVE name: Tolerance %%""\n" + "//OVE type: FLOAT""\n" + "//OVE description: the tolerance of filter""\n" + "uniform float toler_in;""\n" + ); ShaderInputsParser parser(code); parser.Parse(); @@ -353,23 +436,23 @@ OLIVE_ADD_TEST(InputAfterEndTest) OLIVE_ADD_TEST(InputManyTest) { QString code( -"//OVE name: Base image""\n" -"//OVE type: TEXTURE""\n" -"//OVE flag: NOT_KEYFRAMABLE""\n" -"//OVE description: the base image""\n" -"uniform sampler2D tex_base;""\n" -"\n" -"//OVE name: Tolerance %%""\n" -"//OVE type: FLOAT""\n" -"//OVE description: the tolerance of filter""\n" -"uniform float toler_in;""\n" -"\n" -"//OVE name: base color""\n" -"//OVE type: COLOR""\n" -"//OVE description: the final color""\n" -"uniform vec4 tone_in;""\n" -"\n" -); + "//OVE name: Base image""\n" + "//OVE type: TEXTURE""\n" + "//OVE flag: NOT_KEYFRAMABLE""\n" + "//OVE description: the base image""\n" + "uniform sampler2D tex_base;""\n" + "\n" + "//OVE name: Tolerance %%""\n" + "//OVE type: FLOAT""\n" + "//OVE description: the tolerance of filter""\n" + "uniform float toler_in;""\n" + "\n" + "//OVE name: base color""\n" + "//OVE type: COLOR""\n" + "//OVE description: the final color""\n" + "uniform vec4 tone_in;""\n" + "\n" + ); ShaderInputsParser parser(code); parser.Parse(); @@ -380,5 +463,76 @@ OLIVE_ADD_TEST(InputManyTest) OLIVE_TEST_END; } +// An input whose type is not consistent with the uniform type +OLIVE_ADD_TEST(InconsistentTypeTest_1) +{ + QString code( + "//OVE name: Base image""\n" + "//OVE type: TEXTURE""\n" + "//OVE description: the base image""\n" + "uniform float tex_base;""\n" // <-- 'float' vs 'TEXTURE' + ); + + ShaderInputsParser parser(code); + parser.Parse(); + + OLIVE_ASSERT_EQUAL( parser.ErrorList().size(), 1); + OLIVE_ASSERT_EQUAL( parser.InputList().size(), 1); // input is still present + + OLIVE_ASSERT_EQUAL( parser.ErrorList().at(0).line, 4); + OLIVE_ASSERT_EQUAL( STR(parser.ErrorList().at(0).issue), + STR("metadata type TEXTURE requires uniform type sampler2D and not float")); + + + OLIVE_TEST_END; +} + +// An input whose type is not consistent with the uniform type +OLIVE_ADD_TEST(InconsistentTypeTest_2) +{ + QString code( + "//OVE name: Gain""\n" + "//OVE type: FLOAT""\n" + "//OVE description: the base image""\n" + "uniform vec2 gain_in;""\n" // <-- 'vec2' vs 'FLOAT' + ); + + ShaderInputsParser parser(code); + parser.Parse(); + + OLIVE_ASSERT_EQUAL( parser.ErrorList().size(), 1); + OLIVE_ASSERT_EQUAL( parser.InputList().size(), 1); // input is still present + + OLIVE_ASSERT_EQUAL( parser.ErrorList().at(0).line, 4); + OLIVE_ASSERT_EQUAL( STR(parser.ErrorList().at(0).issue), + STR("metadata type FLOAT requires uniform type float and not vec2")); + + + OLIVE_TEST_END; +} + +// Illegal number of iterations +OLIVE_ADD_TEST(WrongNumOfIterations) +{ + QString code( + "#version 150""\n" + "//OVE shader_name: slide""\n" + "//OVE shader_description: slide transition""\n" + "//OVE shader_version: 0.1""\n" + "//OVE number_of_iterations: two""\n" + ); + + ShaderInputsParser parser(code); + parser.Parse(); + + OLIVE_ASSERT_EQUAL( parser.ErrorList().size(), 1); + OLIVE_ASSERT_EQUAL( parser.ErrorList().at(0).line, 5); + OLIVE_ASSERT_EQUAL( STR(parser.ErrorList().at(0).issue), + STR("Number of iterations must be a number greater or equal to 1")); + + OLIVE_TEST_END; +} + + } // olive From dc6f2b6318940975d5c575265d7503bf7fb89ea3 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Tue, 24 Oct 2023 20:08:14 +0200 Subject: [PATCH 45/49] Added default description for shaders. --- app/widget/nodeparamview/nodeparamviewshader.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/widget/nodeparamview/nodeparamviewshader.cpp b/app/widget/nodeparamview/nodeparamviewshader.cpp index fc38452c78..e208f1dc26 100644 --- a/app/widget/nodeparamview/nodeparamviewshader.cpp +++ b/app/widget/nodeparamview/nodeparamviewshader.cpp @@ -32,6 +32,8 @@ namespace olive { +const QString DEFAULT_DESCRIPTION = QObject::tr("No description available"); + NodeParamViewShader::NodeParamViewShader(const QPlainTextEdit &content, QObject *parent) : QObject(parent), @@ -101,12 +103,13 @@ void NodeParamViewShader::onMetadataChanged(const QString &name, QString NodeParamViewShader::displayedText() const { QString version = version_.isEmpty() ? QString() : QString("(%1)").arg(version_); + const QString & description = description_.isEmpty() ? DEFAULT_DESCRIPTION : description_; // We don't display the full shader, but only some metadata return QString("%1" "        %2" "

%3

"). - arg( name_, version, description_); + arg( name_, version, description); } void NodeParamViewShader::launchCodeEditor(QString & text) From 7f71bcced02e8a6a454b9a595ae6c088fad5e554 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Sat, 28 Oct 2023 23:53:24 +0200 Subject: [PATCH 46/49] Dedicated folder for olive scripts in temp folder --- app/widget/nodeparamview/nodeparamviewshader.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/widget/nodeparamview/nodeparamviewshader.cpp b/app/widget/nodeparamview/nodeparamviewshader.cpp index e208f1dc26..34e01a0635 100644 --- a/app/widget/nodeparamview/nodeparamviewshader.cpp +++ b/app/widget/nodeparamview/nodeparamviewshader.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include "nodeparamviewshader.h" #include "node/filter/shader/shader.h" @@ -54,11 +55,16 @@ void NodeParamViewShader::attachOwnerNode(const Node *owner) { summary_->setVisible( true); - // create a file whose name is unique for for the node this instance belongs to. - // 'node_id' is based on address of the node this param belongs to + // create a file whose name is unique for the node this instance belongs to. // 'QStandardPaths::TempLocation' is guaranteed not to be empty - QString file_path = QStandardPaths::standardLocations( QStandardPaths::TempLocation).at(0); - file_path += QString("/%2.glsl").arg((uint64_t)owner); + QString base_dir = QStandardPaths::standardLocations( QStandardPaths::TempLocation).at(0); + if ( ! QDir(base_dir + QDir::separator() + "olive").exists()) { + QDir().mkdir(base_dir + QDir::separator() + "olive"); + } + base_dir += QString(QDir::separator()) + "olive"; + + QString file_path = QString("%1%2%3.glsl").arg(base_dir). + arg(QString(QDir::separator())).arg((uint64_t)owner); ext_editor_proxy_->SetFilePath(file_path); From 014a4ff200b54b06faf96cacfe183f7e8976cb0e Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Wed, 1 Nov 2023 18:37:40 +0100 Subject: [PATCH 47/49] Fixed a bug with vertex shader --- app/config/config.cpp | 2 + .../preferences/tabs/preferencesedittab.cpp | 27 +++++++++++++- .../preferences/tabs/preferencesedittab.h | 5 ++- .../transition/shader/shadertransition.cpp | 4 +- app/node/filter/shader/shader.cpp | 4 +- .../nodeparamview/nodeparamviewshader.cpp | 37 ++++++++++++------- .../nodeparamview/nodeparamviewshader.h | 7 +++- .../nodeparamview/nodeparamviewtextedit.cpp | 6 +-- .../nodeparamview/nodeparamviewtextedit.h | 5 ++- .../nodeparamviewwidgetbridge.cpp | 10 +++-- 10 files changed, 76 insertions(+), 31 deletions(-) diff --git a/app/config/config.cpp b/app/config/config.cpp index c8ae0d1594..7f59d391c9 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -170,6 +170,8 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("EditorExternalCommand"), NodeValue::kText, QString()); SetEntryInternal(QStringLiteral("EditorExternalParams"), NodeValue::kText, QString()); + SetEntryInternal(QStringLiteral("EditorExternalTempFolder"), NodeValue::kText, + QStandardPaths::standardLocations( QStandardPaths::TempLocation).at(0)); SetEntryInternal(QStringLiteral("EditorInternalFontSize"), NodeValue::kInt, 14); SetEntryInternal(QStringLiteral("EditorInternalIndentSize"), NodeValue::kInt, 3); SetEntryInternal(QStringLiteral("EditorInternalWindowWidth"), NodeValue::kInt, 800); diff --git a/app/dialog/preferences/tabs/preferencesedittab.cpp b/app/dialog/preferences/tabs/preferencesedittab.cpp index f569068ac9..ea426d4901 100644 --- a/app/dialog/preferences/tabs/preferencesedittab.cpp +++ b/app/dialog/preferences/tabs/preferencesedittab.cpp @@ -95,7 +95,7 @@ PreferencesEditTab::PreferencesEditTab() editor_cmd_layout->addWidget( ext_command_); editor_cmd_layout->addWidget( ext_select_button); - connect( ext_select_button, & QPushButton::clicked, this, & PreferencesEditTab::onSelectDialogRequest); + connect( ext_select_button, & QPushButton::clicked, this, & PreferencesEditTab::onCommandSelectDialogRequest); ext_params_ = new QLineEdit(); external_editor_layout->addWidget( @@ -104,6 +104,17 @@ PreferencesEditTab::PreferencesEditTab() external_editor_layout->addWidget( ext_params_); outer_layout->addWidget(external_editor_box); + external_editor_layout->addWidget( + new QLabel(tr("Folder where temporary shaders are stored")) ); + QHBoxLayout * temp_folder_layout = new QHBoxLayout(); + external_editor_layout->addLayout( temp_folder_layout); + temp_folder_ = new QLineEdit(); + temp_folder_button_ = new QPushButton("...", this); + temp_folder_layout->addWidget( temp_folder_); + temp_folder_layout->addWidget( temp_folder_button_); + + connect( temp_folder_button_, & QPushButton::clicked, this, & PreferencesEditTab::onTempFolderDialogRequest); + outer_layout->addStretch(); bool use_internal = Config::Current()["EditorUseInternal"].toBool(); @@ -115,6 +126,7 @@ PreferencesEditTab::PreferencesEditTab() indent_size_->setValue( Config::Current()["EditorInternalIndentSize"].toInt()); ext_command_->setText( Config::Current()["EditorExternalCommand"].toString()); ext_params_->setText( Config::Current()["EditorExternalParams"].toString()); + temp_folder_->setText( Config::Current()["EditorExternalTempFolder"].toString()); window_heigth_->setValue( Config::Current()["EditorInternalWindowHeight"].toInt()); window_width_->setValue( Config::Current()["EditorInternalWindowWidth"].toInt()); } @@ -131,13 +143,14 @@ void PreferencesEditTab::Accept(MultiUndoCommand *command) Config::Current()["EditorUseInternal"] = QVariant::fromValue( use_internal_editor_->isChecked()); Config::Current()["EditorExternalCommand"] = QVariant::fromValue(ext_command_->text()); Config::Current()["EditorExternalParams"] = QVariant::fromValue(ext_params_->text()); + Config::Current()["EditorExternalTempFolder"] = QVariant::fromValue(temp_folder_->text()); Config::Current()["EditorInternalFontSize"] = QVariant::fromValue(font_size_->value()); Config::Current()["EditorInternalIndentSize"] = QVariant::fromValue(indent_size_->value()); Config::Current()["EditorInternalWindowHeight"] = QVariant::fromValue(window_heigth_->value()); Config::Current()["EditorInternalWindowWidth"] = QVariant::fromValue(window_width_->value()); } -void PreferencesEditTab::onSelectDialogRequest() +void PreferencesEditTab::onCommandSelectDialogRequest() { QString path = QFileDialog::getOpenFileName( this, tr("External editor"), QStandardPaths::displayName( QStandardPaths::ApplicationsLocation)); @@ -147,4 +160,14 @@ void PreferencesEditTab::onSelectDialogRequest() } } +void PreferencesEditTab::onTempFolderDialogRequest() +{ + QString path = QFileDialog::getExistingDirectory( this, tr("Temporary shaders folder"), + Config::Current()["EditorExternalTempFolder"].toString()); + + if (path != QString()) { + temp_folder_->setText( path); + } +} + } // olive diff --git a/app/dialog/preferences/tabs/preferencesedittab.h b/app/dialog/preferences/tabs/preferencesedittab.h index bacaa4b217..d301fd1328 100644 --- a/app/dialog/preferences/tabs/preferencesedittab.h +++ b/app/dialog/preferences/tabs/preferencesedittab.h @@ -45,7 +45,8 @@ class PreferencesEditTab : public ConfigDialogBaseTab virtual void Accept(MultiUndoCommand* command) override; private slots: - void onSelectDialogRequest(); + void onCommandSelectDialogRequest(); + void onTempFolderDialogRequest(); private: QRadioButton * use_internal_editor_; @@ -61,6 +62,8 @@ private slots: QLineEdit * ext_command_; QPushButton * ext_select_button; QLineEdit * ext_params_; + QPushButton * temp_folder_button_; // folder where temporary files are stored + QLineEdit * temp_folder_; }; } diff --git a/app/node/block/transition/shader/shadertransition.cpp b/app/node/block/transition/shader/shadertransition.cpp index 7500e68119..e1d0e6f479 100644 --- a/app/node/block/transition/shader/shadertransition.cpp +++ b/app/node/block/transition/shader/shadertransition.cpp @@ -104,8 +104,8 @@ ShaderTransition::ShaderTransition() : AddInput(kOutputMessages, NodeValue::kText, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); // mark this text input as code, so it will be edited with code editor - SetInputProperty( kFragShaderCode, QStringLiteral("text_type"), QString("shader_code")); - SetInputProperty( kVertexShaderCode, QStringLiteral("text_type"), QString("shader_code")); + SetInputProperty( kFragShaderCode, QStringLiteral("text_type"), QString("shader_code_frag")); + SetInputProperty( kVertexShaderCode, QStringLiteral("text_type"), QString("shader_code_vert")); // mark this text input as output messages SetInputProperty( kOutputMessages, QStringLiteral("text_type"), QString("shader_issues")); } diff --git a/app/node/filter/shader/shader.cpp b/app/node/filter/shader/shader.cpp index c781f4c67f..fabca1807a 100644 --- a/app/node/filter/shader/shader.cpp +++ b/app/node/filter/shader/shader.cpp @@ -93,8 +93,8 @@ ShaderFilterNode::ShaderFilterNode(): AddInput(kOutputMessages, NodeValue::kText, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable)); // mark this text input as code, so it will be edited with code editor - SetInputProperty( kFragShaderCode, QStringLiteral("text_type"), QString("shader_code")); - SetInputProperty( kVertexShaderCode, QStringLiteral("text_type"), QString("shader_code")); + SetInputProperty( kFragShaderCode, QStringLiteral("text_type"), QString("shader_code_frag")); + SetInputProperty( kVertexShaderCode, QStringLiteral("text_type"), QString("shader_code_vert")); // mark this text input as output messages SetInputProperty( kOutputMessages, QStringLiteral("text_type"), QString("shader_issues")); diff --git a/app/widget/nodeparamview/nodeparamviewshader.cpp b/app/widget/nodeparamview/nodeparamviewshader.cpp index 34e01a0635..da54c2ae90 100644 --- a/app/widget/nodeparamview/nodeparamviewshader.cpp +++ b/app/widget/nodeparamview/nodeparamviewshader.cpp @@ -38,7 +38,8 @@ const QString DEFAULT_DESCRIPTION = QObject::tr("No description available"); NodeParamViewShader::NodeParamViewShader(const QPlainTextEdit &content, QObject *parent) : QObject(parent), - full_shader_(content) + full_shader_(content), + is_vertex_(false) { summary_ = new QTextEdit(); summary_->setReadOnly( true); @@ -51,20 +52,21 @@ NodeParamViewShader::NodeParamViewShader(const QPlainTextEdit &content, } -void NodeParamViewShader::attachOwnerNode(const Node *owner) +void NodeParamViewShader::attachOwnerNode(const Node *owner, bool is_vertex) { summary_->setVisible( true); + is_vertex_ = is_vertex; // create a file whose name is unique for the node this instance belongs to. - // 'QStandardPaths::TempLocation' is guaranteed not to be empty - QString base_dir = QStandardPaths::standardLocations( QStandardPaths::TempLocation).at(0); - if ( ! QDir(base_dir + QDir::separator() + "olive").exists()) { - QDir().mkdir(base_dir + QDir::separator() + "olive"); + QString base_dir = Config::Current()["EditorExternalTempFolder"].toString(); + + if (base_dir.isEmpty() || ( ! QDir(base_dir).exists())) { + // 'QStandardPaths::TempLocation' is guaranteed not to be empty + base_dir = QStandardPaths::standardLocations( QStandardPaths::TempLocation).at(0); } - base_dir += QString(QDir::separator()) + "olive"; - QString file_path = QString("%1%2%3.glsl").arg(base_dir). - arg(QString(QDir::separator())).arg((uint64_t)owner); + QString file_path = QString("%1%2%3.%4").arg(base_dir). + arg(QString(QDir::separator())).arg((uint64_t)owner).arg(is_vertex ? "vert" : "frag"); ext_editor_proxy_->SetFilePath(file_path); @@ -91,7 +93,6 @@ void NodeParamViewShader::attachOwnerNode(const Node *owner) connect( owner_transition, & ShaderTransition::metadataChanged, this, & NodeParamViewShader::onMetadataChanged); } - } void NodeParamViewShader::onMetadataChanged(const QString &name, @@ -112,10 +113,18 @@ QString NodeParamViewShader::displayedText() const const QString & description = description_.isEmpty() ? DEFAULT_DESCRIPTION : description_; // We don't display the full shader, but only some metadata - return QString("%1" - "        %2" - "

%3

"). - arg( name_, version, description); + if (is_vertex_) { + return QString("%1 (vertex)" + "        %2" + "

%3

"). + arg( name_, version, description); + } else { + + return QString("%1" + "        %2" + "

%3

"). + arg( name_, version, description); + } } void NodeParamViewShader::launchCodeEditor(QString & text) diff --git a/app/widget/nodeparamview/nodeparamviewshader.h b/app/widget/nodeparamview/nodeparamviewshader.h index e2abd6f640..8310a6fd8e 100644 --- a/app/widget/nodeparamview/nodeparamviewshader.h +++ b/app/widget/nodeparamview/nodeparamviewshader.h @@ -47,8 +47,9 @@ class NodeParamViewShader : public QObject } // bind this viewer to a Node that handles one GLSL script, - // so changes in the script will update this widget - void attachOwnerNode( const Node * owner); + // so changes in the script will update this widget. + // 'is_vertex' is false for Fragment shader, true for vertex shader + void attachOwnerNode(const Node * owner, bool is_vertex); // start editing shader in internal or external editor void launchCodeEditor(QString & text); @@ -71,6 +72,8 @@ private slots: QString name_; QString description_; QString version_; + + bool is_vertex_; }; } diff --git a/app/widget/nodeparamview/nodeparamviewtextedit.cpp b/app/widget/nodeparamview/nodeparamviewtextedit.cpp index eafe4b3df6..f819abeb7d 100644 --- a/app/widget/nodeparamview/nodeparamviewtextedit.cpp +++ b/app/widget/nodeparamview/nodeparamviewtextedit.cpp @@ -109,7 +109,7 @@ void NodeParamViewTextEdit::OnTextChangedExternally(const QString & content) emit textEdited( content); } -void NodeParamViewTextEdit::setCodeEditorFlag( const Node * owner) +void NodeParamViewTextEdit::setShaderCodeEditorFlag( const Node * owner, bool is_vertex) { code_editor_flag_ = true; @@ -120,10 +120,10 @@ void NodeParamViewTextEdit::setCodeEditorFlag( const Node * owner) // separate dialog or external editor line_edit_->setReadOnly( true); line_edit_->setVisible(false); - shader_edit_->attachOwnerNode( owner); + shader_edit_->attachOwnerNode( owner, is_vertex); } -void NodeParamViewTextEdit::setCodeIssuesFlag() +void NodeParamViewTextEdit::setShaderIssuesFlag() { code_issues_flag_ = true; line_edit_->setReadOnly( true); diff --git a/app/widget/nodeparamview/nodeparamviewtextedit.h b/app/widget/nodeparamview/nodeparamviewtextedit.h index 0209411118..9630e4081d 100644 --- a/app/widget/nodeparamview/nodeparamviewtextedit.h +++ b/app/widget/nodeparamview/nodeparamviewtextedit.h @@ -48,9 +48,10 @@ class NodeParamViewTextEdit : public QWidget // let this instance perform as a code editor. // This input will be edited with a built-in or external code editor. - void setCodeEditorFlag(const Node *owner); + // 'is_vertex' is false for Fragment shader, true for vertex shader + void setShaderCodeEditorFlag(const Node *owner, bool is_vertex); // set flag to view text as code issues - void setCodeIssuesFlag(); + void setShaderIssuesFlag(); void SetEditInViewerOnlyMode(bool on); diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index 4ed452f319..def54b867e 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -141,12 +141,16 @@ void NodeParamViewWidgetBridge::CreateWidgets() // check for special type of text QString text_type = GetInnerInput().GetProperty(QStringLiteral("text_type")).toString(); - if (text_type == "shader_code") { + if (text_type == "shader_code_frag") { const Node * owner = GetInnerInput().node(); - line_edit->setCodeEditorFlag( owner); + line_edit->setShaderCodeEditorFlag( owner, false); + } + if (text_type == "shader_code_vert") { + const Node * owner = GetInnerInput().node(); + line_edit->setShaderCodeEditorFlag( owner, true); } else if (text_type == "shader_issues") { - line_edit->setCodeIssuesFlag(); + line_edit->setShaderIssuesFlag(); } connect(line_edit, &NodeParamViewTextEdit::textEdited, this, &NodeParamViewWidgetBridge::WidgetCallback); From bbca74c5256323186f993ec5406385c5a3b1fa18 Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Sat, 11 Nov 2023 20:28:26 +0100 Subject: [PATCH 48/49] Fixed a bug where saving a shader in an external editor while 'issues' panel is open generates a crash --- .../nodeparamview/nodeparamviewtextedit.cpp | 26 ++++++++++++++----- .../nodeparamview/nodeparamviewtextedit.h | 4 +++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/app/widget/nodeparamview/nodeparamviewtextedit.cpp b/app/widget/nodeparamview/nodeparamviewtextedit.cpp index f819abeb7d..d7d7abf106 100644 --- a/app/widget/nodeparamview/nodeparamviewtextedit.cpp +++ b/app/widget/nodeparamview/nodeparamviewtextedit.cpp @@ -22,8 +22,6 @@ #include -#include "dialog/text/text.h" - #include "dialog/codeeditor/messagehighlighter.h" #include "ui/icons/icons.h" #include "nodeparamviewshader.h" @@ -34,7 +32,8 @@ namespace olive { NodeParamViewTextEdit::NodeParamViewTextEdit(QWidget *parent) : QWidget(parent), code_editor_flag_(false), - code_issues_flag_(false) + code_issues_flag_(false), + text_dlg_(nullptr) { QHBoxLayout* layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); @@ -63,6 +62,16 @@ NodeParamViewTextEdit::NodeParamViewTextEdit(QWidget *parent) : SetEditInViewerOnlyMode(false); } +NodeParamViewTextEdit::~NodeParamViewTextEdit() +{ + // This prevents a crash if text changes externally while + // the dialog is open. For example this happens when code issues dialog + // is open and shader code changes externally. + if (text_dlg_ != nullptr) { + text_dlg_->reject(); + } +} + void NodeParamViewTextEdit::SetEditInViewerOnlyMode(bool on) { @@ -80,15 +89,18 @@ void NodeParamViewTextEdit::ShowTextDialog() shader_edit_->launchCodeEditor(text); } else { - TextDialog d(this->text(), this); + text_dlg_ = new TextDialog(this->text(), nullptr); if (code_issues_flag_) { - d.setSyntaxHighlight( new MessageSyntaxHighlighter()); + text_dlg_->setSyntaxHighlight( new MessageSyntaxHighlighter()); } - if (d.exec() == QDialog::Accepted) { - text= d.text(); + if (text_dlg_->exec() == QDialog::Accepted) { + text= text_dlg_->text(); } + + delete text_dlg_; + text_dlg_ = nullptr; } if (text != QString()) { diff --git a/app/widget/nodeparamview/nodeparamviewtextedit.h b/app/widget/nodeparamview/nodeparamviewtextedit.h index 9630e4081d..f110478499 100644 --- a/app/widget/nodeparamview/nodeparamviewtextedit.h +++ b/app/widget/nodeparamview/nodeparamviewtextedit.h @@ -27,6 +27,7 @@ #include #include "common/define.h" +#include "dialog/text/text.h" namespace olive { @@ -40,6 +41,7 @@ class NodeParamViewTextEdit : public QWidget Q_OBJECT public: NodeParamViewTextEdit(QWidget* parent = nullptr); + ~NodeParamViewTextEdit(); QString text() const { @@ -88,6 +90,8 @@ public slots: bool code_issues_flag_; NodeParamViewShader * shader_edit_; + TextDialog * text_dlg_; + private: QPushButton* edit_btn_; From cce542aff894c56b014a1d7c428819fc1ada57ae Mon Sep 17 00:00:00 2001 From: AngeloFrancabandiera Date: Mon, 27 Nov 2023 20:05:38 +0100 Subject: [PATCH 49/49] Remove temporary files when application quits. Improved behavior when multiple shaders are open --- app/dialog/codeeditor/externaleditorproxy.cpp | 43 +++++++++++++++++-- app/dialog/codeeditor/externaleditorproxy.h | 10 +++++ app/window/mainwindow/mainwindow.cpp | 3 ++ 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/app/dialog/codeeditor/externaleditorproxy.cpp b/app/dialog/codeeditor/externaleditorproxy.cpp index 3f42bb6f62..1b3f5b11cf 100644 --- a/app/dialog/codeeditor/externaleditorproxy.cpp +++ b/app/dialog/codeeditor/externaleditorproxy.cpp @@ -27,7 +27,13 @@ #include #include #include +#include +namespace { +// files created in this session. +// They can be deleted when application quits. +QSet CreatedFiles; +} namespace olive { @@ -41,9 +47,11 @@ ExternalEditorProxy::ExternalEditorProxy(QObject *parent) : ExternalEditorProxy::~ExternalEditorProxy() { - // Do not delete "file_path_" bacause it might be read from - // another instance of this class. However this leaves a lot - // of files in temporary folder + // Do not delete "file_path_" now, bacause it might be read from + // another instance of this class. This happens when the clip associated with + // this instance loses focus: when it gets focus back, the file file name is used. + // + // All files will be deleted when application quits. } void ExternalEditorProxy::Launch(const QString &start_text) @@ -57,6 +65,8 @@ void ExternalEditorProxy::Launch(const QString &start_text) out.write( start_text.toLatin1()); out.close(); + CreatedFiles.insert( file_path_); + // now the file exists. We can watch for changes watcher_.addPath( file_path_); @@ -84,6 +94,14 @@ void ExternalEditorProxy::SetFilePath(const QString & path) file_path_ = path; // at this point, "file_path_" may or may not exist. + + // If it exists, align the shader to the content of the file + // as it is possible that user modified the file while the clip was + // not selected and so the shader did not update with file. + if (QFileInfo(file_path_).exists()) { + triggerSynchFromFile(); + } + // The following instruction is effective when the file exists. watcher_.addPath( file_path_); } @@ -93,6 +111,13 @@ void ExternalEditorProxy::Detach() watcher_.removePath( file_path_); } +void ExternalEditorProxy::CleanGeneratedFiles() +{ + foreach (const QString & file, CreatedFiles) { + QFile::remove( file); + } +} + void ExternalEditorProxy::onFileChanged(const QString &path) { // this should always be true @@ -130,5 +155,17 @@ void ExternalEditorProxy::onProcessFinished(int /*exitCode*/, QProcess::ExitStat process_ = nullptr; } +void ExternalEditorProxy::triggerSynchFromFile() +{ + // run a 0 ms timer so that "synch" operation can be done + // immediately but after setting up param-view panel + synch_timer_.singleShot(0, this, & ExternalEditorProxy::synchFromFile); +} + +void ExternalEditorProxy::synchFromFile() +{ + onFileChanged( file_path_); +} + } // namespace olive diff --git a/app/dialog/codeeditor/externaleditorproxy.h b/app/dialog/codeeditor/externaleditorproxy.h index b100f8f19d..b377cdcf42 100644 --- a/app/dialog/codeeditor/externaleditorproxy.h +++ b/app/dialog/codeeditor/externaleditorproxy.h @@ -24,6 +24,7 @@ #include #include +#include namespace olive { @@ -56,6 +57,10 @@ class ExternalEditorProxy : public QObject // Used when user switches from external to internal editor void Detach(); + // to be called when application quits in order to remove + // temporary files. + static void CleanGeneratedFiles(); + signals: // emnitted when temporary file is saved void textChanged( const QString & new_text); @@ -63,12 +68,17 @@ class ExternalEditorProxy : public QObject private: void onFileChanged(const QString& path); void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); + void triggerSynchFromFile(); + +private slots: + void synchFromFile(); private: // QFileSystemWatcher watcher_; QString file_path_; QProcess *process_; QFileSystemWatcher watcher_; + QTimer synch_timer_; }; } // namespace olive diff --git a/app/window/mainwindow/mainwindow.cpp b/app/window/mainwindow/mainwindow.cpp index 7b9927344c..14013eb001 100644 --- a/app/window/mainwindow/mainwindow.cpp +++ b/app/window/mainwindow/mainwindow.cpp @@ -33,6 +33,7 @@ #include "mainmenu.h" #include "mainstatusbar.h" #include "timeline/timelineundoworkarea.h" +#include "dialog/codeeditor/externaleditorproxy.h" namespace olive { @@ -450,6 +451,8 @@ void MainWindow::closeEvent(QCloseEvent *e) return; } + ExternalEditorProxy::CleanGeneratedFiles(); + scope_panel_->SetViewerPanel(nullptr); PanelManager::instance()->DeleteAllPanels();