diff --git a/effects/internal/voideffect.cpp b/effects/internal/voideffect.cpp new file mode 100644 index 000000000..eee3b4e87 --- /dev/null +++ b/effects/internal/voideffect.cpp @@ -0,0 +1,56 @@ +#include "voideffect.h" + +#include +#include + +#include "ui/collapsiblewidget.h" +#include "debug.h" + +VoidEffect::VoidEffect(Clip *c, const QString& n) : Effect(c, NULL) { + name = n; + QString display_name; + if (n.isEmpty()) { + display_name = "(unknown)"; + } else { + display_name = n; + } + EffectRow* row = add_row("Missing Effect", false, false); + row->add_widget(new QLabel(display_name)); + container->setText(display_name); +} + +void VoidEffect::load(QXmlStreamReader &stream) { + QString tag = stream.name().toString(); + qint64 start_index = stream.characterOffset(); + qint64 end_index = start_index; + while (!stream.atEnd() && !(stream.name() == tag && stream.isEndElement())) { + end_index = stream.characterOffset(); + stream.readNext(); + } + qint64 passage_length = end_index - start_index; + if (passage_length > 0) { + // store xml data verbatim + QIODevice* device = stream.device(); + device->seek(start_index); + bytes = device->read(passage_length); + int passage_end = bytes.lastIndexOf('>')+1; + bytes.remove(passage_end, bytes.size()-passage_end); + } +} + +void VoidEffect::save(QXmlStreamWriter &stream) { + if (!name.isEmpty()) { + stream.writeAttribute("name", name); + stream.writeAttribute("enabled", QString::number(is_enabled())); + + // force xml writer to expand tag, ignored when loading + stream.writeStartElement("void"); + stream.writeEndElement(); + + if (!bytes.isEmpty()) { + // write stored data + QIODevice* device = stream.device(); + device->write(bytes); + } + } +} diff --git a/effects/internal/voideffect.h b/effects/internal/voideffect.h new file mode 100644 index 000000000..340030998 --- /dev/null +++ b/effects/internal/voideffect.h @@ -0,0 +1,21 @@ +#ifndef VOIDEFFECT_H +#define VOIDEFFECT_H + +/* VoidEffect is a placeholder used when Olive is unable to find an effect + * requested by a loaded project. It displays a missing effect so the user knows + * an effect is missing, and stores the XML project data verbatim so that it + * isn't lost if the user saves over the project. + */ + +#include "project/effect.h" + +class VoidEffect : public Effect { +public: + VoidEffect(Clip* c, const QString& n); + void load(QXmlStreamReader &stream) override; + void save(QXmlStreamWriter &stream) override; +private: + QByteArray bytes; +}; + +#endif // VOIDEFFECT_H diff --git a/io/loadthread.cpp b/io/loadthread.cpp index e343369d8..71fa7035a 100644 --- a/io/loadthread.cpp +++ b/io/loadthread.cpp @@ -2,6 +2,7 @@ #include "mainwindow.h" #include "panels/panels.h" +#include "panels/effectcontrols.h" #include "panels/project.h" #include "project/footage.h" #include "io/config.h" @@ -13,6 +14,7 @@ #include "io/previewgenerator.h" #include "dialogs/loaddialog.h" #include "project/media.h" +#include "effects/internal/voideffect.h" #include "debug.h" #include @@ -32,7 +34,7 @@ LoadThread::LoadThread(LoadDialog* l, bool a) : ld(l), autorecovery(a), cancelle connect(this, SIGNAL(success()), this, SLOT(success_func())); connect(this, SIGNAL(error()), this, SLOT(error_func())); connect(this, SIGNAL(start_create_dual_transition(const TransitionData*,Clip*,Clip*,const EffectMeta*)), this, SLOT(create_dual_transition(const TransitionData*,Clip*,Clip*,const EffectMeta*))); - connect(this, SIGNAL(start_create_effect_ui(QXmlStreamReader*, Clip*, int, const EffectMeta*, long, bool)), this, SLOT(create_effect_ui(QXmlStreamReader*, Clip*, int, const EffectMeta*, long, bool))); + connect(this, SIGNAL(start_create_effect_ui(QXmlStreamReader*, Clip*, int, const QString*, const EffectMeta*, long, bool)), this, SLOT(create_effect_ui(QXmlStreamReader*, Clip*, int, const QString*, const EffectMeta*, long, bool))); } const EffectMeta* get_meta_from_name(const QString& name) { @@ -60,28 +62,10 @@ void LoadThread::load_effect(QXmlStreamReader& stream, Clip* c) { } else if (attr.name() == "length") { effect_length = attr.value().toLong(); } - } - - // backwards compatibility with 180820 - if (stream.name() == "effect" && effect_id != -1) { - switch (effect_id) { - case 0: effect_name = (c->track < 0) ? "Transform" : "Volume"; break; - case 1: effect_name = (c->track < 0) ? "Shake" : "Pan"; break; - case 2: effect_name = (c->track < 0) ? "Text" : "Noise"; break; - case 3: effect_name = (c->track < 0) ? "Solid" : "Tone"; break; - case 4: effect_name = "Invert"; break; - case 5: effect_name = "Chroma Key"; break; - case 6: effect_name = "Gaussian Blur"; break; - case 7: effect_name = "Crop"; break; - case 8: effect_name = "Flip"; break; - case 9: effect_name = "Box Blur"; break; - case 10: effect_name = "Wave"; break; - case 11: effect_name = "Temperature"; break; - } - } + } // wait for effects to be loaded - effects_loaded.lock(); + panel_effect_controls->effects_loaded.lock(); const EffectMeta* meta = NULL; @@ -90,26 +74,21 @@ void LoadThread::load_effect(QXmlStreamReader& stream, Clip* c) { meta = get_meta_from_name(effect_name); } - effects_loaded.unlock(); + panel_effect_controls->effects_loaded.unlock(); - if (meta == NULL) { - dout << "[WARNING] An effect used by this project is missing. It was not loaded."; - } else { - QString tag = stream.name().toString(); + QString tag = stream.name().toString(); - int type; - if (tag == "opening") { - type = TA_OPENING_TRANSITION; - } else if (tag == "closing") { - type = TA_CLOSING_TRANSITION; - } else { - type = TA_NO_TRANSITION; - } + int type; + if (tag == "opening") { + type = TA_OPENING_TRANSITION; + } else if (tag == "closing") { + type = TA_CLOSING_TRANSITION; + } else { + type = TA_NO_TRANSITION; + } - emit start_create_effect_ui(&stream, c, type, meta, effect_length, effect_enabled); - - waitCond.wait(&mutex); - } + emit start_create_effect_ui(&stream, c, type, &effect_name, meta, effect_length, effect_enabled); + waitCond.wait(&mutex); } void LoadThread::read_next(QXmlStreamReader &stream) { @@ -678,6 +657,7 @@ void LoadThread::create_effect_ui( QXmlStreamReader* stream, Clip* c, int type, + const QString* effect_name, const EffectMeta* meta, long effect_length, bool effect_enabled) @@ -705,11 +685,19 @@ void LoadThread::create_effect_ui( if (cancelled) return; if (type == TA_NO_TRANSITION) { - Effect* e = create_effect(c, meta); - e->set_enabled(effect_enabled); - e->load(*stream); + if (meta == NULL) { + // create void effect + VoidEffect* ve = new VoidEffect(c, *effect_name); + ve->set_enabled(effect_enabled); + ve->load(*stream); + c->effects.append(ve); + } else { + Effect* e = create_effect(c, meta); + e->set_enabled(effect_enabled); + e->load(*stream); - c->effects.append(e); + c->effects.append(e); + } } else { int transition_index = create_transition(c, NULL, meta); Transition* t = c->sequence->transitions.at(transition_index); diff --git a/io/loadthread.h b/io/loadthread.h index 572e7b8d4..e3ae55fe3 100644 --- a/io/loadthread.h +++ b/io/loadthread.h @@ -25,13 +25,13 @@ public: signals: void success(); void error(); - void start_create_effect_ui(QXmlStreamReader* stream, Clip* c, int type, const EffectMeta* meta, long effect_length, bool effect_enabled); + void start_create_effect_ui(QXmlStreamReader* stream, Clip* c, int type, const QString *effect_name, const EffectMeta* meta, long effect_length, bool effect_enabled); void start_create_dual_transition(const TransitionData* td, Clip* primary, Clip* secondary, const EffectMeta* meta); void report_progress(int p); private slots: void error_func(); void success_func(); - void create_effect_ui(QXmlStreamReader* stream, Clip* c, int type, const EffectMeta* meta, long effect_length, bool effect_enabled); + void create_effect_ui(QXmlStreamReader* stream, Clip* c, int type, const QString *effect_name, const EffectMeta* meta, long effect_length, bool effect_enabled); void create_dual_transition(const TransitionData* td, Clip* primary, Clip* secondary, const EffectMeta* meta); private: LoadDialog* ld; diff --git a/olive.pro b/olive.pro index b514c823f..1f8bc78d0 100644 --- a/olive.pro +++ b/olive.pro @@ -119,7 +119,8 @@ SOURCES += \ ui/rectangleselect.cpp \ dialogs/actionsearch.cpp \ ui/embeddedfilechooser.cpp \ - effects/internal/fillleftrighteffect.cpp + effects/internal/fillleftrighteffect.cpp \ + effects/internal/voideffect.cpp HEADERS += \ mainwindow.h \ @@ -208,7 +209,8 @@ HEADERS += \ ui/rectangleselect.h \ dialogs/actionsearch.h \ ui/embeddedfilechooser.h \ - effects/internal/fillleftrighteffect.h + effects/internal/fillleftrighteffect.h \ + effects/internal/voideffect.h FORMS += diff --git a/panels/effectcontrols.cpp b/panels/effectcontrols.cpp index fc64d0083..5e58f3cdf 100644 --- a/panels/effectcontrols.cpp +++ b/panels/effectcontrols.cpp @@ -41,7 +41,6 @@ EffectControls::EffectControls(QWidget *parent) : setup_ui(); - init_effects(); clear_effects(false); headers->viewer = panel_sequence_viewer; headers->snapping = false; diff --git a/panels/effectcontrols.h b/panels/effectcontrols.h index afb9a7e26..1201e8a13 100644 --- a/panels/effectcontrols.h +++ b/panels/effectcontrols.h @@ -3,6 +3,7 @@ #include #include +#include struct Clip; class QMenu; @@ -50,6 +51,8 @@ public: ResizableScrollBar* horizontalScrollBar; QScrollBar* verticalScrollBar; + + QMutex effects_loaded; public slots: void update_keyframes(); private slots: diff --git a/panels/panels.cpp b/panels/panels.cpp index e1f57e94d..c5653a171 100644 --- a/panels/panels.cpp +++ b/panels/panels.cpp @@ -152,6 +152,7 @@ void alloc_panels(QWidget* parent) { panel_project = new Project(parent); panel_project->setObjectName("proj_root"); panel_effect_controls = new EffectControls(parent); + init_effects(); panel_effect_controls->setObjectName("fx_controls"); panel_timeline = new Timeline(parent); panel_timeline->setObjectName("timeline"); diff --git a/project/effect.cpp b/project/effect.cpp index 9a872d57f..05078c77a 100644 --- a/project/effect.cpp +++ b/project/effect.cpp @@ -45,7 +45,6 @@ #include QVector effects; -QMutex effects_loaded; Effect* create_effect(Clip* c, const EffectMeta* em) { if (!em->filename.isEmpty()) { @@ -94,7 +93,7 @@ void load_internal_effects() { em.name = "Volume"; em.internal = EFFECT_INTERNAL_VOLUME; - effects.append(em); + effects.append(em); em.name = "Pan"; em.internal = EFFECT_INTERNAL_PAN; @@ -245,7 +244,7 @@ void init_effects() { } EffectInit::EffectInit() { - effects_loaded.lock(); + panel_effect_controls->effects_loaded.lock(); } void EffectInit::run() { @@ -253,7 +252,7 @@ void EffectInit::run() { load_internal_effects(); load_shader_effects(); load_vst_effects(); - effects_loaded.unlock(); + panel_effect_controls->effects_loaded.unlock(); dout << "[INFO] Finished initializing effects"; } @@ -281,193 +280,195 @@ Effect::Effect(Clip* c, const EffectMeta *em) : connect(container->title_bar, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(show_context_menu(const QPoint&))); - // set up UI from effect file - container->setText(em->name); + if (em != NULL) { + // set up UI from effect file + container->setText(em->name); - if (!em->filename.isEmpty()) { - QFile effect_file(em->filename); - if (effect_file.open(QFile::ReadOnly)) { - QXmlStreamReader reader(&effect_file); + if (!em->filename.isEmpty()) { + QFile effect_file(em->filename); + if (effect_file.open(QFile::ReadOnly)) { + QXmlStreamReader reader(&effect_file); - while (!reader.atEnd()) { - if (reader.name() == "row" && reader.isStartElement()) { - QString row_name; - const QXmlStreamAttributes& attributes = reader.attributes(); - for (int i=0;ifilename << "- ID cannot be empty."; - } else if (type > -1) { - EffectField* field = row->add_field(type, id); - connect(field, SIGNAL(changed()), this, SLOT(field_changed())); - switch (type) { - case EFFECT_FIELD_DOUBLE: - for (int i=0;iset_double_default_value(attr.value().toDouble()); - } else if (attr.name() == "min") { - field->set_double_minimum_value(attr.value().toDouble()); - } else if (attr.name() == "max") { - field->set_double_maximum_value(attr.value().toDouble()); - } - } - break; - case EFFECT_FIELD_COLOR: - { - QColor color; - for (int i=0;iset_color_value(color); - } - break; - case EFFECT_FIELD_STRING: - for (int i=0;iset_string_value(attr.value().toString()); - } - } - break; - case EFFECT_FIELD_BOOL: - for (int i=0;iset_bool_value(attr.value() == "1"); - } - } - break; - case EFFECT_FIELD_COMBO: - { - int combo_index = 0; - for (int i=0;iadd_combo_item(reader.text().toString(), 0); - } - } - field->set_combo_index(combo_index); - } - break; - case EFFECT_FIELD_FONT: - for (int i=0;iset_font_name(attr.value().toString()); - } - } - break; - case EFFECT_FIELD_FILE: - for (int i=0;iset_filename(attr.value().toString()); - } - } - break; - } - } - } - } - } - } else if (reader.name() == "shader" && reader.isStartElement()) { - enable_shader = true; - const QXmlStreamAttributes& attributes = reader.attributes(); - for (int i=0;ifilename; - enable_superimpose = false; - } - break; - } - } - }*/ - reader.readNext(); - } + if (id.isEmpty()) { + dout << "[ERROR] Couldn't load field from" << em->filename << "- ID cannot be empty."; + } else if (type > -1) { + EffectField* field = row->add_field(type, id); + connect(field, SIGNAL(changed()), this, SLOT(field_changed())); + switch (type) { + case EFFECT_FIELD_DOUBLE: + for (int i=0;iset_double_default_value(attr.value().toDouble()); + } else if (attr.name() == "min") { + field->set_double_minimum_value(attr.value().toDouble()); + } else if (attr.name() == "max") { + field->set_double_maximum_value(attr.value().toDouble()); + } + } + break; + case EFFECT_FIELD_COLOR: + { + QColor color; + for (int i=0;iset_color_value(color); + } + break; + case EFFECT_FIELD_STRING: + for (int i=0;iset_string_value(attr.value().toString()); + } + } + break; + case EFFECT_FIELD_BOOL: + for (int i=0;iset_bool_value(attr.value() == "1"); + } + } + break; + case EFFECT_FIELD_COMBO: + { + int combo_index = 0; + for (int i=0;iadd_combo_item(reader.text().toString(), 0); + } + } + field->set_combo_index(combo_index); + } + break; + case EFFECT_FIELD_FONT: + for (int i=0;iset_font_name(attr.value().toString()); + } + } + break; + case EFFECT_FIELD_FILE: + for (int i=0;iset_filename(attr.value().toString()); + } + } + break; + } + } + } + } + } + } else if (reader.name() == "shader" && reader.isStartElement()) { + enable_shader = true; + const QXmlStreamAttributes& attributes = reader.attributes(); + for (int i=0;ifilename; + enable_superimpose = false; + } + break; + } + } + }*/ + reader.readNext(); + } - effect_file.close(); - } else { - dout << "[ERROR] Failed to open effect file" << em->filename; - } - } + effect_file.close(); + } else { + dout << "[ERROR] Failed to open effect file" << em->filename; + } + } + } } Effect::~Effect() { diff --git a/project/effect.h b/project/effect.h index f3f39e7d9..21e0f381b 100644 --- a/project/effect.h +++ b/project/effect.h @@ -41,8 +41,6 @@ void init_effects(); Effect* create_effect(Clip* c, const EffectMeta *em); const EffectMeta* get_internal_meta(int internal_id, int type); -extern QMutex effects_loaded; - #define EFFECT_TYPE_INVALID 0 #define EFFECT_TYPE_VIDEO 1 #define EFFECT_TYPE_AUDIO 2 @@ -135,7 +133,7 @@ public: Effect* copy(Clip* c); void copy_field_keyframes(Effect *e); - void load(QXmlStreamReader& stream); + virtual void load(QXmlStreamReader& stream); virtual void custom_load(QXmlStreamReader& stream); virtual void save(QXmlStreamWriter& stream); diff --git a/project/effectrow.cpp b/project/effectrow.cpp index 7f574d379..4735e8def 100644 --- a/project/effectrow.cpp +++ b/project/effectrow.cpp @@ -31,7 +31,9 @@ EffectRow::EffectRow(Effect *parent, bool save, QGridLayout *uilayout, const QSt column_count = 1; - if (parent_effect->meta->type != EFFECT_TYPE_TRANSITION && keyframable) { + if (parent_effect->meta != NULL + && parent_effect->meta->type != EFFECT_TYPE_TRANSITION + && keyframable) { connect(label, SIGNAL(clicked()), this, SLOT(focus_row())); keyframe_nav = new KeyframeNavigator();