diff --git a/effects/internal/vsthostwin.cpp b/effects/internal/vsthostwin.cpp new file mode 100644 index 000000000..093298907 --- /dev/null +++ b/effects/internal/vsthostwin.cpp @@ -0,0 +1,268 @@ +#include "vsthostwin.h" + +// adapted from http://teragonaudio.com/article/How-to-make-your-own-VST-host.html + +#include + +#include +#include +#include +#include +#include + +#include "playback/audio.h" +#include "mainwindow.h" +#include "debug.h" + +#define BLOCK_SIZE 512 +#define CHANNEL_COUNT 2 + +// C callbacks +extern "C" { + // Main host callback + VstIntPtr VSTCALLBACK hostCallback(AEffect *effect, int opcode, int index, long long value, void *ptr, float opt) { + switch(opcode) { + case audioMasterVersion: + return 2400; + case audioMasterIdle: + effect->dispatcher(effect, effEditIdle, 0, 0, 0, 0); + break; + case 6: // audioMasterWantMidi + return 0; + case audioMasterGetCurrentProcessLevel: + return 0; + // Handle other opcodes here... there will be lots of them + case audioMasterEndEdit: // change made + mainWindow->setWindowModified(true); + break; + default: + dout << "[INFO] Plugin requested unhandled opcode" << opcode; + break; + } + } +} + +// Plugin's entry point +typedef AEffect *(*vstPluginFuncPtr)(audioMasterCallback host); +// Plugin's getParameter() method +typedef float (*getParameterFuncPtr)(AEffect *effect, VstInt32 index); +// Plugin's setParameter() method +typedef void (*setParameterFuncPtr)(AEffect *effect, VstInt32 index, float value); +// Plugin's processEvents() method +typedef VstInt32 (*processEventsFuncPtr)(VstEvents *events); +// Plugin's process() method +typedef void (*processFuncPtr)(AEffect *effect, float **inputs, float **outputs, VstInt32 sampleFrames); + +void VSTHostWin::loadPlugin() { + QString dll_fn = file_field->get_filename(0, true); + LPCWSTR dll_fn_w = reinterpret_cast(dll_fn.utf16()); + + modulePtr = LoadLibrary(dll_fn_w); + if(modulePtr == NULL) { + DWORD dll_err = GetLastError(); + dout << "[ERROR] Failed to load VST" << dll_fn_w << "-" << dll_err; + QString msg_err = "Failed to load VST plugin \"" + dll_fn + "\": " + QString::number(dll_err); + if (dll_err == 193) { +#ifdef _WIN64 + msg_err += "\n\nNOTE: You can't load 32-bit VST plugins into a 64-bit build of Olive. Please find a 64-bit version of this plugin or switch to a 32-bit build of Olive."; +#elif _WIN32 + msg_err += "\n\nNOTE: You can't load 64-bit VST plugins into a 32-bit build of Olive. Please find a 32-bit version of this plugin or switch to a 64-bit build of Olive."; +#endif + } + QMessageBox::critical(mainWindow, "Error loading VST plugin", msg_err); + return; + } + + vstPluginFuncPtr mainEntryPoint = + (vstPluginFuncPtr)GetProcAddress(modulePtr, "VSTPluginMain"); + // Instantiate the plugin + plugin = mainEntryPoint(hostCallback); +} + +void VSTHostWin::freePlugin() { + if (plugin != NULL) { + stopPlugin(); + FreeLibrary(modulePtr); + plugin = NULL; + } +} + +bool VSTHostWin::configurePluginCallbacks() { + // Check plugin's magic number + // If incorrect, then the file either was not loaded properly, is not a + // real VST plugin, or is otherwise corrupt. + if(plugin->magic != kEffectMagic) { + dout << "[ERROR] Plugin's magic number is bad"; + QMessageBox::critical(mainWindow, "VST Error", "Plugin's magic number is invalid"); + return false; + } + + // Create dispatcher handle + dispatcher = (dispatcherFuncPtr)(plugin->dispatcher); + + // Set up plugin callback functions + plugin->getParameter = (getParameterFuncPtr)plugin->getParameter; + plugin->processReplacing = (processFuncPtr)plugin->processReplacing; + plugin->setParameter = (setParameterFuncPtr)plugin->setParameter; + + return true; +} + +void VSTHostWin::startPlugin() { + dispatcher(plugin, effOpen, 0, 0, NULL, 0.0f); + + // Set some default properties + dispatcher(plugin, effSetSampleRate, 0, 0, NULL, current_audio_freq()); + dispatcher(plugin, effSetBlockSize, 0, BLOCK_SIZE, NULL, 0.0f); + + resumePlugin(); +} + +void VSTHostWin::stopPlugin() { + suspendPlugin(); + + dispatcher(plugin, effClose, 0, 0, NULL, 0); +} + +void VSTHostWin::resumePlugin() { + dispatcher(plugin, effMainsChanged, 0, 1, NULL, 0.0f); +} + +void VSTHostWin::suspendPlugin() { + dispatcher(plugin, effMainsChanged, 0, 0, NULL, 0.0f); +} + +bool VSTHostWin::canPluginDo(char *canDoString) { + return (dispatcher(plugin, effCanDo, 0, 0, (void*)canDoString, 0.0f) > 0); +} + +void VSTHostWin::initializeIO() { + // inputs and outputs are assumed to be float** and are declared elsewhere, + // most likely the are fields owned by this class. numChannels and blocksize + // are also fields, both should be size_t (or unsigned int, if you prefer). + inputs = new float* [CHANNEL_COUNT]; + outputs = new float* [CHANNEL_COUNT]; + for(int channel = 0; channel < CHANNEL_COUNT; channel++) { + inputs[channel] = new float[BLOCK_SIZE]; + outputs[channel] = new float[BLOCK_SIZE]; + } +} + +void VSTHostWin::processAudio(long numFrames) { + // Always reset the output array before processing. + for (int i=0;iprocessReplacing(plugin, inputs, outputs, numFrames); +} + +VSTHostWin::VSTHostWin(Clip* c, const EffectMeta *em) : Effect(c, em) { + plugin = NULL; + + initializeIO(); + + file_field = add_row("Plugin", true, false)->add_field(EFFECT_FIELD_FILE, "filename"); + connect(file_field, SIGNAL(changed()), this, SLOT(change_plugin())); + + EffectRow* interface_row = add_row("Interface", false, false); + show_interface_btn = new QPushButton("Show"); + show_interface_btn->setCheckable(true); + show_interface_btn->setEnabled(false); + connect(show_interface_btn, SIGNAL(toggled(bool)), this, SLOT(show_interface(bool))); + interface_row->add_widget(show_interface_btn); + + dialog = new QDialog(mainWindow); + dialog->setWindowTitle("VST Plugin"); + dialog->setAttribute(Qt::WA_NativeWindow, true); + dialog->setWindowFlags(dialog->windowFlags() | Qt::MSWindowsFixedSizeDialogHint); + connect(dialog, SIGNAL(finished(int)), this, SLOT(uncheck_show_button())); +} + +VSTHostWin::~VSTHostWin() { + freePlugin(); +} + +void VSTHostWin::process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int) { + if (plugin != NULL) { + int interval = BLOCK_SIZE*4; + for (int i=0;i>2; + inputs[0][index] = float(left_sample) / float(INT16_MAX); + inputs[1][index] = float(right_sample) / float(INT16_MAX); + } + + // send to VST + processAudio(process_size>>2); + + // convert back to int16 + for (int j=i;j>2; + + qint16 left_sample = qRound(outputs[0][index] * INT16_MAX); + qint16 right_sample = qRound(outputs[1][index] * INT16_MAX); + + samples[j+3] = (quint8) (right_sample >> 8); + samples[j+2] = (quint8) right_sample; + samples[j+1] = (quint8) (left_sample >> 8); + samples[j] = (quint8) left_sample; + } + } + } +} + +void VSTHostWin::custom_load(QXmlStreamReader &stream) { + if (stream.name() == "plugindata") { + stream.readNext(); + QByteArray b = QByteArray::fromBase64(stream.text().toUtf8()); + const char* data = b.constData(); + if (plugin != NULL) { + dispatcher(plugin, effSetChunk, 0, (VstInt32) b.size(), (void*) b.constData(), 0); + } + } +} + +void VSTHostWin::save(QXmlStreamWriter &stream) { + Effect::save(stream); + if (plugin != NULL) { + char* p = NULL; + VstInt32 length = dispatcher(plugin, effGetChunk, 0, 0, &p, 0); + QByteArray b(p, length); + stream.writeTextElement("plugindata", b.toBase64()); + } +} + +void VSTHostWin::show_interface(bool show) { + dialog->setVisible(show); +} + +void VSTHostWin::uncheck_show_button() { + show_interface_btn->setChecked(false); +} + +void VSTHostWin::change_plugin() { + freePlugin(); + loadPlugin(); + if (plugin != NULL) { + if (configurePluginCallbacks()) { + startPlugin(); + dispatcher(plugin, effEditOpen, 0, 0, reinterpret_cast(dialog->winId()), 0); + ERect* eRect = NULL; + plugin->dispatcher(plugin, effEditGetRect, 0, 0, &eRect, 0); + dialog->setFixedWidth(eRect->right); + dialog->setFixedHeight(eRect->bottom); + } else { + FreeLibrary(modulePtr); + plugin = NULL; + } + } + show_interface_btn->setEnabled(plugin != NULL); +} diff --git a/effects/internal/vsthostwin.h b/effects/internal/vsthostwin.h new file mode 100644 index 000000000..cf057cfee --- /dev/null +++ b/effects/internal/vsthostwin.h @@ -0,0 +1,49 @@ +#ifndef VSTHOSTWIN_H +#define VSTHOSTWIN_H + +#include "project/effect.h" + +#include + +// Plugin's dispatcher function +typedef VstIntPtr (*dispatcherFuncPtr)(AEffect *effect, VstInt32 opCode, VstInt32 index, VstInt32 value, void *ptr, float opt); + +struct AEffect; +class QDialog; + +class VSTHostWin : public Effect { + Q_OBJECT +public: + VSTHostWin(Clip* c, const EffectMeta* em); + ~VSTHostWin(); + void process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int channel_count); + + void custom_load(QXmlStreamReader& stream); + void save(QXmlStreamWriter& stream); +private slots: + void show_interface(bool show); + void uncheck_show_button(); + void change_plugin(); +private: + EffectField* file_field; + + void loadPlugin(); + void freePlugin(); + dispatcherFuncPtr dispatcher; + AEffect* plugin; + bool configurePluginCallbacks(); + void startPlugin(); + void stopPlugin(); + void resumePlugin(); + void suspendPlugin(); + bool canPluginDo(char *canDoString); + void initializeIO(); + void processAudio(long numFrames); + float** inputs; + float** outputs; + QDialog* dialog; + QPushButton* show_interface_btn; + HMODULE modulePtr; +}; + +#endif // VSTHOSTWIN_H diff --git a/olive.pro b/olive.pro index 40185c471..b514c823f 100644 --- a/olive.pro +++ b/olive.pro @@ -118,6 +118,7 @@ SOURCES += \ project/keyframe.cpp \ ui/rectangleselect.cpp \ dialogs/actionsearch.cpp \ + ui/embeddedfilechooser.cpp \ effects/internal/fillleftrighteffect.cpp HEADERS += \ @@ -206,13 +207,17 @@ HEADERS += \ project/keyframe.h \ ui/rectangleselect.h \ dialogs/actionsearch.h \ + ui/embeddedfilechooser.h \ effects/internal/fillleftrighteffect.h FORMS += win32 { RC_FILE = packaging/windows/resources.rc - LIBS += -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample -lopengl32 + LIBS += -lavutil -lavformat -lavcodec -lavfilter -lswscale -lswresample -lopengl32 -luser32 + + SOURCES += effects/internal/vsthostwin.cpp + HEADERS += effects/internal/vsthostwin.h } mac { diff --git a/project/effect.cpp b/project/effect.cpp index add2c2a5b..9a872d57f 100644 --- a/project/effect.cpp +++ b/project/effect.cpp @@ -28,6 +28,9 @@ #include "effects/internal/paneffect.h" #include "effects/internal/shakeeffect.h" #include "effects/internal/cornerpineffect.h" +#ifdef _WIN32 +#include "effects/internal/vsthostwin.h" +#endif #include "effects/internal/fillleftrighteffect.h" #include @@ -62,6 +65,9 @@ Effect* create_effect(Clip* c, const EffectMeta* em) { case EFFECT_INTERNAL_SHAKE: return new ShakeEffect(c, em); case EFFECT_INTERNAL_CORNERPIN: return new CornerPinEffect(c, em); case EFFECT_INTERNAL_FILLLEFTRIGHT: return new FillLeftRightEffect(c, em); +#ifdef _WIN32 + case EFFECT_INTERNAL_VST: return new VSTHostWin(c, em); +#endif } } else { dout << "[ERROR] Invalid effect data"; @@ -94,6 +100,12 @@ void load_internal_effects() { em.internal = EFFECT_INTERNAL_PAN; effects.append(em); +#ifdef _WIN32 + em.name = "VST Plugin 2.x"; + em.internal = EFFECT_INTERNAL_VST; + effects.append(em); +#endif + em.name = "Tone"; em.internal = EFFECT_INTERNAL_TONE; effects.append(em); @@ -313,6 +325,8 @@ Effect::Effect(Clip* c, const EffectMeta *em) : type = EFFECT_FIELD_FONT; } else if (comp == "STRING") { type = EFFECT_FIELD_STRING; + } else if (comp == "FILE") { + type = EFFECT_FIELD_FILE; } } else if (attr.name() == "id") { id = attr.value().toString(); @@ -405,6 +419,14 @@ Effect::Effect(Clip* c, const EffectMeta *em) : } } break; + case EFFECT_FIELD_FILE: + for (int i=0;iset_filename(attr.value().toString()); + } + } + break; } } } @@ -477,7 +499,7 @@ void Effect::copy_field_keyframes(Effect* e) { } } -EffectRow* Effect::add_row(const QString& name, bool savable) { +EffectRow* Effect::add_row(const QString& name, bool savable, bool keyframable) { EffectRow* row = new EffectRow(this, savable, ui_layout, name, rows.size()); rows.append(row); return row; @@ -586,10 +608,12 @@ QVariant load_data_from_string(int type, const QString& string) { switch (type) { case EFFECT_FIELD_DOUBLE: return string.toDouble(); case EFFECT_FIELD_COLOR: return QColor(string); - case EFFECT_FIELD_STRING: return string; case EFFECT_FIELD_BOOL: return (string == "1"); case EFFECT_FIELD_COMBO: return string.toInt(); - case EFFECT_FIELD_FONT: return string; + case EFFECT_FIELD_STRING: + case EFFECT_FIELD_FONT: + case EFFECT_FIELD_FILE: + return string; } return QVariant(); } @@ -598,10 +622,12 @@ QString save_data_to_string(int type, const QVariant& data) { switch (type) { case EFFECT_FIELD_DOUBLE: return QString::number(data.toDouble()); case EFFECT_FIELD_COLOR: return data.value().name(); - case EFFECT_FIELD_STRING: return data.toString(); case EFFECT_FIELD_BOOL: return QString::number(data.toBool()); case EFFECT_FIELD_COMBO: return QString::number(data.toInt()); - case EFFECT_FIELD_FONT: return data.toString(); + case EFFECT_FIELD_STRING: + case EFFECT_FIELD_FONT: + case EFFECT_FIELD_FILE: + return data.toString(); } return QString(); } @@ -621,42 +647,6 @@ void Effect::load(QXmlStreamReader& stream) { while (!stream.atEnd() && !(stream.name() == "row" && stream.isEndElement())) { stream.readNext(); - // read keyframes - /*if (stream.name() == "keyframes" && stream.isStartElement()) { - for (int k=0;ksetKeyframing(attr.value() == "1"); - } - } - if (row->isKeyframing()) { - stream.readNext(); - while (!stream.atEnd() && !(stream.name() == "keyframes" && stream.isEndElement())) { - if (stream.name() == "key" && stream.isStartElement()) { - long keyframe_frame; - int keyframe_type; - for (int k=0;kfieldCount();k++) { - EffectField* field = row->field(k); - EffectKeyframe key; - key.time = keyframe_frame; - key.type = keyframe_type; - field->keyframes.append(key); - } - } - stream.readNext(); - } - } - stream.readNext(); - }*/ - // read field if (stream.name() == "field" && stream.isStartElement()) { if (field_count < row->fieldCount()) { @@ -678,9 +668,6 @@ void Effect::load(QXmlStreamReader& stream) { } } - // TODO DEPRECATED, only used for backwards compatibility with 180820 - if (!found_field_by_id) dout << "[INFO] Found field by field number"; - EffectField* field = row->field(field_number); // get current field value @@ -732,10 +719,14 @@ void Effect::load(QXmlStreamReader& stream) { dout << "[ERROR] Too many rows for effect" << id << ". Project might be corrupt. (Got" << row_count << ", expected <" << rows.size()-1 << ")"; } row_count++; + } else if (stream.isStartElement()) { + custom_load(stream); } } } +void Effect::custom_load(QXmlStreamReader &stream) {} + void Effect::save(QXmlStreamWriter& stream) { stream.writeAttribute("name", meta->name); stream.writeAttribute("enabled", QString::number(is_enabled())); @@ -885,6 +876,7 @@ void Effect::process_shader(double timecode, GLTextureCoords&) { glslProgram->setUniformValue(field->id.toUtf8().constData(), field->get_combo_index(timecode)); break; case EFFECT_FIELD_FONT: break; // can you even send a string to a uniform value? + case EFFECT_FIELD_FILE: break; // can you even send a string to a uniform value? } } } @@ -920,27 +912,7 @@ GLuint Effect::process_superimpose(double timecode) { return 0; } -//void Effect::process_audio(double timecode_start, double timecode_end, quint8* samples, int nb_bytes, int channel_count) { -void Effect::process_audio(double, double, quint8*, int, int) { - // only volume/pan, hand off to AU and VST for all other cases - - /*double interval = (timecode_end-timecode_start)/nb_bytes; - - for (int i=0;ifield(0)->get_double_value(timecode_start+(interval*i), true)); - QJSValue result = eval.call(); - samp = result.toInt(); - QJSValueList args; - args << samples << nb_bytes; - - - samples[i+1] = (quint8) (samp >> 8); - samples[i] = (quint8) samp; - }*/ -} +void Effect::process_audio(double, double, quint8*, int, int) {} void Effect::gizmo_draw(double, GLTextureCoords &) {} diff --git a/project/effect.h b/project/effect.h index b11bf8fae..f3f39e7d9 100644 --- a/project/effect.h +++ b/project/effect.h @@ -64,7 +64,7 @@ extern QMutex effects_loaded; #define EFFECT_INTERNAL_TIMECODE 8 #define EFFECT_INTERNAL_MASK 9 #define EFFECT_INTERNAL_FILLLEFTRIGHT 10 - +#define EFFECT_INTERNAL_VST 11 #define EFFECT_INTERNAL_CORNERPIN 12 #define EFFECT_INTERNAL_COUNT 13 @@ -119,7 +119,7 @@ public: QString name; CollapsibleWidget* container; - EffectRow* add_row(const QString &name, bool savable = true); + EffectRow* add_row(const QString &name, bool savable = true, bool keyframable = true); EffectRow* row(int i); int row_count(); @@ -136,7 +136,8 @@ public: void copy_field_keyframes(Effect *e); void load(QXmlStreamReader& stream); - void save(QXmlStreamWriter& stream); + virtual void custom_load(QXmlStreamReader& stream); + virtual void save(QXmlStreamWriter& stream); // glsl handling bool is_open(); diff --git a/project/effectfield.cpp b/project/effectfield.cpp index 6eda75505..f6626dc91 100644 --- a/project/effectfield.cpp +++ b/project/effectfield.cpp @@ -6,6 +6,7 @@ #include "ui/checkboxex.h" #include "ui/comboboxex.h" #include "ui/fontcombobox.h" +#include "ui/embeddedfilechooser.h" #include "effectrow.h" #include "effect.h" @@ -70,6 +71,13 @@ EffectField::EffectField(EffectRow *parent, int t, const QString &i) : connect(fcb, SIGNAL(activated(int)), this, SLOT(ui_element_change())); } break; + case EFFECT_FIELD_FILE: + { + EmbeddedFileChooser* efc = new EmbeddedFileChooser(); + ui_element = efc; + connect(efc, SIGNAL(changed()), this, SLOT(ui_element_change())); + } + break; } } @@ -81,6 +89,7 @@ QVariant EffectField::get_previous_data() { case EFFECT_FIELD_BOOL: return !static_cast(ui_element)->isChecked(); case EFFECT_FIELD_COMBO: return static_cast(ui_element)->getPreviousIndex(); case EFFECT_FIELD_FONT: return static_cast(ui_element)->getPreviousValue(); + case EFFECT_FIELD_FILE: return static_cast(ui_element)->getPreviousValue(); } return QVariant(); } @@ -93,6 +102,7 @@ QVariant EffectField::get_current_data() { case EFFECT_FIELD_BOOL: return static_cast(ui_element)->isChecked(); case EFFECT_FIELD_COMBO: return static_cast(ui_element)->currentIndex(); case EFFECT_FIELD_FONT: return static_cast(ui_element)->currentText(); + case EFFECT_FIELD_FILE: return static_cast(ui_element)->getFilename(); } return QVariant(); } @@ -113,6 +123,7 @@ void EffectField::set_current_data(const QVariant& data) { case EFFECT_FIELD_BOOL: return static_cast(ui_element)->setChecked(data.toBool()); case EFFECT_FIELD_COMBO: return static_cast(ui_element)->setCurrentIndexEx(data.toInt()); case EFFECT_FIELD_FONT: return static_cast(ui_element)->setCurrentTextEx(data.toString()); + case EFFECT_FIELD_FILE: return static_cast(ui_element)->setFilename(data.toString()); } } @@ -246,6 +257,12 @@ QVariant EffectField::validate_keyframe_data(double timecode, bool async) { } static_cast(ui_element)->setCurrentTextEx(before_data.toString()); break; + case EFFECT_FIELD_FILE: + if (async) { + return before_data; + } + static_cast(ui_element)->setFilename(before_data.toString()); + break; } } return QVariant(); @@ -313,12 +330,12 @@ int EffectField::get_combo_index(double timecode, bool async) { return static_cast(ui_element)->currentIndex(); } -const QVariant EffectField::get_combo_data(double timecode) { +QVariant EffectField::get_combo_data(double timecode) { validate_keyframe_data(timecode); return static_cast(ui_element)->currentData(); } -const QString EffectField::get_combo_string(double timecode) { +QString EffectField::get_combo_string(double timecode) { validate_keyframe_data(timecode); return static_cast(ui_element)->currentText(); } @@ -343,7 +360,7 @@ void EffectField::set_bool_value(bool b) { return static_cast(ui_element)->setChecked(b); } -const QString EffectField::get_string_value(double timecode, bool async) { +QString EffectField::get_string_value(double timecode, bool async) { if (async && hasKeyframes()) { return validate_keyframe_data(timecode, true).toString(); } @@ -355,7 +372,7 @@ void EffectField::set_string_value(const QString& s) { static_cast(ui_element)->setPlainTextEx(s); } -const QString EffectField::get_font_name(double timecode, bool async) { +QString EffectField::get_font_name(double timecode, bool async) { if (async && hasKeyframes()) { return validate_keyframe_data(timecode, true).toString(); } @@ -378,3 +395,15 @@ QColor EffectField::get_color_value(double timecode, bool async) { void EffectField::set_color_value(QColor color) { static_cast(ui_element)->set_color(color); } + +QString EffectField::get_filename(double timecode, bool async) { + if (async && hasKeyframes()) { + return validate_keyframe_data(timecode, true).toString(); + } + validate_keyframe_data(timecode); + return static_cast(ui_element)->getFilename(); +} + +void EffectField::set_filename(const QString &s) { + static_cast(ui_element)->setFilename(s); +} diff --git a/project/effectfield.h b/project/effectfield.h index c946c475b..0e836feaf 100644 --- a/project/effectfield.h +++ b/project/effectfield.h @@ -7,6 +7,7 @@ #define EFFECT_FIELD_BOOL 3 #define EFFECT_FIELD_COMBO 4 #define EFFECT_FIELD_FONT 5 +#define EFFECT_FIELD_FILE 6 #include #include @@ -39,25 +40,28 @@ public: void set_double_minimum_value(double v); void set_double_maximum_value(double v); - const QString get_string_value(double timecode, bool async = false); + QString get_string_value(double timecode, bool async = false); void set_string_value(const QString &s); void add_combo_item(const QString& name, const QVariant &data); int get_combo_index(double timecode, bool async = false); - const QVariant get_combo_data(double timecode); - const QString get_combo_string(double timecode); + QVariant get_combo_data(double timecode); + QString get_combo_string(double timecode); void set_combo_index(int index); void set_combo_string(const QString& s); bool get_bool_value(double timecode, bool async = false); void set_bool_value(bool b); - const QString get_font_name(double timecode, bool async = false); + QString get_font_name(double timecode, bool async = false); void set_font_name(const QString& s); QColor get_color_value(double timecode, bool async = false); void set_color_value(QColor color); + QString get_filename(double timecode, bool async = false); + void set_filename(const QString& s); + QWidget* get_ui_element(); void set_enabled(bool e); QVector keyframes; diff --git a/project/effectrow.cpp b/project/effectrow.cpp index 08a5608f5..7f574d379 100644 --- a/project/effectrow.cpp +++ b/project/effectrow.cpp @@ -16,7 +16,7 @@ #include "ui/keyframenavigator.h" #include "ui/clickablelabel.h" -EffectRow::EffectRow(Effect *parent, bool save, QGridLayout *uilayout, const QString &n, int row) : +EffectRow::EffectRow(Effect *parent, bool save, QGridLayout *uilayout, const QString &n, int row, bool keyframable) : parent_effect(parent), savable(save), keyframing(false), @@ -29,7 +29,9 @@ EffectRow::EffectRow(Effect *parent, bool save, QGridLayout *uilayout, const QSt ui->addWidget(label, row, 0); - if (parent_effect->meta->type != EFFECT_TYPE_TRANSITION) { + column_count = 1; + + if (parent_effect->meta->type != EFFECT_TYPE_TRANSITION && keyframable) { connect(label, SIGNAL(clicked()), this, SLOT(focus_row())); keyframe_nav = new KeyframeNavigator(); @@ -145,11 +147,17 @@ EffectField* EffectRow::add_field(int type, const QString& id, int colspan) { if (parent_effect->meta->type != EFFECT_TYPE_TRANSITION) connect(field, SIGNAL(clicked()), this, SLOT(focus_row())); fields.append(field); QWidget* element = field->get_ui_element(); - ui->addWidget(element, ui_row, fields.size(), 1, colspan); + ui->addWidget(element, ui_row, column_count, 1, colspan); + column_count++; connect(field, SIGNAL(changed()), parent_effect, SLOT(field_changed())); return field; } +void EffectRow::add_widget(QWidget* w) { + ui->addWidget(w, ui_row, column_count); + column_count++; +} + EffectRow::~EffectRow() { for (int i=0;i unsafe_old_data; QVector key_is_new; + int column_count; }; #endif // EFFECTROW_H diff --git a/ui/embeddedfilechooser.cpp b/ui/embeddedfilechooser.cpp new file mode 100644 index 000000000..adb1e8484 --- /dev/null +++ b/ui/embeddedfilechooser.cpp @@ -0,0 +1,61 @@ +#include "embeddedfilechooser.h" + +#include +#include +#include +#include +#include + +EmbeddedFileChooser::EmbeddedFileChooser(QWidget* parent) : QWidget(parent) { + QHBoxLayout* layout = new QHBoxLayout(); + layout->setMargin(0); + setLayout(layout); + file_label = new QLabel(); + update_label(); + layout->addWidget(file_label); + QPushButton* browse_button = new QPushButton("..."); + browse_button->setFixedWidth(25); + layout->addWidget(browse_button); + connect(browse_button, SIGNAL(clicked(bool)), this, SLOT(browse())); +} + +const QString &EmbeddedFileChooser::getFilename() { + return filename; +} + +const QString &EmbeddedFileChooser::getPreviousValue() { + return old_filename; +} + +void EmbeddedFileChooser::setFilename(const QString &s) { + old_filename = filename; + filename = s; + update_label(); + emit changed(); +} + +void EmbeddedFileChooser::update_label() { + QString l = "File: "; + if (filename.isEmpty()) { + l += "(none)"; + } else { + bool file_exists = QFileInfo::exists(filename); + if (!file_exists) l += ""; + QString short_fn = filename.mid(filename.lastIndexOf('/')+1); + if (short_fn.size() > 20) { + l += "..." + short_fn.right(20); + } else { + l += short_fn; + } + if (!file_exists) l += ""; + } + l += ""; + file_label->setText(l); +} + +void EmbeddedFileChooser::browse() { + QString fn = QFileDialog::getOpenFileName(this); + if (!fn.isEmpty()) { + setFilename(fn); + } +} diff --git a/ui/embeddedfilechooser.h b/ui/embeddedfilechooser.h new file mode 100644 index 000000000..9a01fa31d --- /dev/null +++ b/ui/embeddedfilechooser.h @@ -0,0 +1,27 @@ +#ifndef EMBEDDEDFILECHOOSER_H +#define EMBEDDEDFILECHOOSER_H + +#include + +class QLabel; + +class EmbeddedFileChooser : public QWidget { + Q_OBJECT +public: + EmbeddedFileChooser(QWidget* parent = 0); + + const QString& getFilename(); + const QString& getPreviousValue(); + void setFilename(const QString& s); +signals: + void changed(); +private: + QLabel* file_label; + QString filename; + QString old_filename; + void update_label(); +private slots: + void browse(); +}; + +#endif // EMBEDDEDFILECHOOSER_H