diff --git a/debian/control b/debian/control index 7608033f8..e46c0c253 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: olive-editor Section: video Priority: optional Maintainer: Olive Team -Build-Depends: debhelper (>=9), build-essential, qt5-default, qtmultimedia5-dev, libqt5opengl5-dev, libqt5multimedia5-plugins, libavformat-dev, libavcodec-dev, libavutil-dev, libswscale-dev, libswresample-dev, libavfilter-dev, libpostproc-dev +Build-Depends: debhelper (>=9), build-essential, qt5-default, qtmultimedia5-dev, libqt5opengl5-dev, libqt5multimedia5-plugins, libavformat-dev, libavcodec-dev, libavutil-dev, libswscale-dev, libswresample-dev, libavfilter-dev, libpostproc-dev, git Standards-Version: 3.9.6 Homepage: https://olivevideoeditor.org/ diff --git a/dialogs/actionsearch.cpp b/dialogs/actionsearch.cpp index 5bf7461d8..3f5593dbe 100644 --- a/dialogs/actionsearch.cpp +++ b/dialogs/actionsearch.cpp @@ -21,7 +21,7 @@ ActionSearch::ActionSearch(QWidget *parent) : ActionSearchEntry* entry_field = new ActionSearchEntry(); QFont entry_field_font = entry_field->font(); - entry_field_font.setPointSize(entry_field_font.pointSize()*2); + entry_field_font.setPointSize(entry_field_font.pointSize()*1.2); entry_field->setFont(entry_field_font); entry_field->setPlaceholderText("Search for action..."); connect(entry_field, SIGNAL(textChanged(const QString&)), this, SLOT(search_update(const QString &))); @@ -32,7 +32,7 @@ ActionSearch::ActionSearch(QWidget *parent) : list_widget = new ActionSearchList(); QFont list_widget_font = list_widget->font(); - list_widget_font.setPointSize(list_widget_font.pointSize()*1.5); + list_widget_font.setPointSize(list_widget_font.pointSize()*1.2); list_widget->setFont(list_widget_font); layout->addWidget(list_widget); connect(list_widget, SIGNAL(dbl_click()), this, SLOT(perform_action())); diff --git a/dialogs/preferencesdialog.cpp b/dialogs/preferencesdialog.cpp index 044ffd895..b7df9ddaa 100644 --- a/dialogs/preferencesdialog.cpp +++ b/dialogs/preferencesdialog.cpp @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include "debug.h" @@ -32,6 +34,22 @@ void KeySequenceEditor::set_action_shortcut() { action->setShortcut(keySequence()); } +void KeySequenceEditor::reset_to_default() { + setKeySequence(action->property("default").toString()); +} + +QString KeySequenceEditor::action_name() { + return action->text().replace("&", ""); +} + +QString KeySequenceEditor::export_shortcut() { + QString ks = keySequence().toString(); + if (ks != action->property("default")) { + return action->text().replace("&", "") + "\t" + keySequence().toString(); + } + return 0; +} + PreferencesDialog::PreferencesDialog(QWidget *parent) : QDialog(parent) { @@ -51,7 +69,7 @@ void PreferencesDialog::setup_kbd_shortcut_worker(QMenu* menu, QTreeWidgetItem* for (int i=0;iisSeparator()) { + if (!a->isSeparator() && a->property("keyignore").isNull()) { QTreeWidgetItem* item = new QTreeWidgetItem(); item->setText(0, a->text().replace("&", "")); @@ -61,7 +79,6 @@ void PreferencesDialog::setup_kbd_shortcut_worker(QMenu* menu, QTreeWidgetItem* item->setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator); setup_kbd_shortcut_worker(a->menu(), item); } else { - item->setData(0, Qt::UserRole + 1, reinterpret_cast(a)); key_shortcut_items.append(item); key_shortcut_actions.append(a); } @@ -112,11 +129,14 @@ void PreferencesDialog::reset_default_shortcut() { QList items = keyboard_tree->selectedItems(); for (int i=0;iselectedItems().at(i); - const QVariant& data = item->data(0, Qt::UserRole + 1); - if (!data.isNull()) { - QAction* a = reinterpret_cast(data.value()); - QKeySequence ks(a->property("default").toString()); - static_cast(keyboard_tree->itemWidget(item, 1))->setKeySequence(ks); + static_cast(keyboard_tree->itemWidget(item, 1))->reset_to_default(); + } +} + +void PreferencesDialog::reset_all_shortcuts() { + if (QMessageBox::question(this, "Confirm Reset All Shortcuts", "Are you sure you wish to reset all keyboard shortcuts to their defaults?", QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + for (int i=0;ireset_to_default(); } } } @@ -159,6 +179,58 @@ bool PreferencesDialog::refine_shortcut_list(const QString &s, QTreeWidgetItem* return all_children_are_hidden; } + return true; +} + +void PreferencesDialog::load_shortcut_file() { + QString fn = QFileDialog::getOpenFileName(this, "Import Keyboard Shortcuts"); + if (!fn.isEmpty()) { + QFile f(fn); + if (f.exists() && f.open(QFile::ReadOnly)) { + QByteArray ba = f.readAll(); + f.close(); + for (int i=0;iaction_name()); + if (index == 0 || (index > 0 && ba.at(index-1) == '\n')) { + while (index < ba.size() && ba.at(index) != '\t') index++; + QString ks; + index++; + while (index < ba.size() && ba.at(index) != '\n') { + ks.append(ba.at(index)); + index++; + } + dout << "set" << key_shortcut_fields.at(i)->action_name() << "to" << ks; + key_shortcut_fields.at(i)->setKeySequence(ks); + } else { + key_shortcut_fields.at(i)->reset_to_default(); + } + } + } else { + QMessageBox::critical(this, "Error saving shortcuts", "Failed to open file for reading"); + } + } +} + +void PreferencesDialog::save_shortcut_file() { + QString fn = QFileDialog::getSaveFileName(this, "Export Keyboard Shortcuts"); + if (!fn.isEmpty()) { + QFile f(fn); + if (f.open(QFile::WriteOnly)) { + bool start = true; + for (int i=0;iexport_shortcut(); + if (!s.isEmpty()) { + if (!start) f.write("\n"); + f.write(s.toUtf8()); + start = false; + } + } + QMessageBox::information(this, "Export Shortcuts", "Shortcuts exported successfully"); + f.close(); + } else { + QMessageBox::critical(this, "Error saving shortcuts", "Failed to open file for writing"); + } + } } void PreferencesDialog::setup_ui() { @@ -250,11 +322,24 @@ void PreferencesDialog::setup_ui() { shortcut_layout->addWidget(keyboard_tree); QHBoxLayout* reset_shortcut_layout = new QHBoxLayout(); + + QPushButton* import_shortcut_button = new QPushButton("Import"); + reset_shortcut_layout->addWidget(import_shortcut_button); + connect(import_shortcut_button, SIGNAL(clicked(bool)), this, SLOT(load_shortcut_file())); + + QPushButton* export_shortcut_button = new QPushButton("Export"); + reset_shortcut_layout->addWidget(export_shortcut_button); + connect(export_shortcut_button, SIGNAL(clicked(bool)), this, SLOT(save_shortcut_file())); + reset_shortcut_layout->addStretch(); - reset_shortcut_button = new QPushButton("Reset to Default"); - reset_shortcut_layout->addWidget(reset_shortcut_button); - connect(reset_shortcut_button, SIGNAL(clicked(bool)), this, SLOT(reset_default_shortcut())); + QPushButton* reset_selected_shortcut_button = new QPushButton("Reset Selected"); + reset_shortcut_layout->addWidget(reset_selected_shortcut_button); + connect(reset_selected_shortcut_button, SIGNAL(clicked(bool)), this, SLOT(reset_default_shortcut())); + + QPushButton* reset_all_shortcut_button = new QPushButton("Reset All"); + reset_shortcut_layout->addWidget(reset_all_shortcut_button); + connect(reset_all_shortcut_button, SIGNAL(clicked(bool)), this, SLOT(reset_all_shortcuts())); shortcut_layout->addLayout(reset_shortcut_layout); diff --git a/dialogs/preferencesdialog.h b/dialogs/preferencesdialog.h index 0e6d575d4..5647997f5 100644 --- a/dialogs/preferencesdialog.h +++ b/dialogs/preferencesdialog.h @@ -18,6 +18,9 @@ class KeySequenceEditor : public QKeySequenceEdit { public: KeySequenceEditor(QWidget *parent, QAction* a); void set_action_shortcut(); + void reset_to_default(); + QString action_name(); + QString export_shortcut(); private: QAction* action; }; @@ -35,7 +38,10 @@ public: private slots: void save(); void reset_default_shortcut(); + void reset_all_shortcuts(); bool refine_shortcut_list(const QString &, QTreeWidgetItem* parent = NULL); + void load_shortcut_file(); + void save_shortcut_file(); private: void setup_ui(); @@ -55,8 +61,6 @@ private: QVector key_shortcut_actions; QVector key_shortcut_items; QVector key_shortcut_fields; - - QPushButton* reset_shortcut_button; }; #endif // PREFERENCESDIALOG_H diff --git a/effects/internal/texteffect.cpp b/effects/internal/texteffect.cpp index cb3ea3155..862dca8a9 100644 --- a/effects/internal/texteffect.cpp +++ b/effects/internal/texteffect.cpp @@ -24,7 +24,7 @@ TextEffect::TextEffect(Clip *c, const EffectMeta* em) : Effect(c, em) { enable_superimpose = true; - enable_shader = true; + //enable_shader = true; text_val = add_row("Text")->add_field(EFFECT_FIELD_STRING, "text", 2); @@ -189,6 +189,9 @@ void TextEffect::redraw(double timecode) { } void TextEffect::shadow_enable(bool e) { + enable_shader = e; + close(); + shadow_color->set_enabled(e); shadow_distance->set_enabled(e); shadow_softness->set_enabled(e); diff --git a/effects/internal/voideffect.cpp b/effects/internal/voideffect.cpp new file mode 100644 index 000000000..11a8c25ff --- /dev/null +++ b/effects/internal/voideffect.cpp @@ -0,0 +1,62 @@ +#include "voideffect.h" + +#include +#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 + QFile* device = static_cast(stream.device()); + + QFile passage_get(device->fileName()); + if (passage_get.open(QFile::ReadOnly)) { + passage_get.seek(start_index); + bytes = passage_get.read(passage_length); + int passage_end = bytes.lastIndexOf('>')+1; + bytes.remove(passage_end, bytes.size()-passage_end); + passage_get.close(); + } + } +} + +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/config.cpp b/io/config.cpp index 1b243a0ca..fe5d30a8a 100644 --- a/io/config.cpp +++ b/io/config.cpp @@ -43,7 +43,9 @@ Config::Config() previous_queue_size(3), previous_queue_type(FRAME_QUEUE_TYPE_FRAMES), upcoming_queue_size(0.5), - upcoming_queue_type(FRAME_QUEUE_TYPE_SECONDS) + upcoming_queue_type(FRAME_QUEUE_TYPE_SECONDS), + loop(true), + pause_at_out_point(true) {} void Config::load(QString path) { @@ -150,7 +152,13 @@ void Config::load(QString path) { } else if (stream.name() == "UpcomingFrameQueueType") { stream.readNext(); upcoming_queue_type = stream.text().toInt(); - } + } else if (stream.name() == "Loop") { + stream.readNext(); + loop = (stream.text() == "1"); + } else if (stream.name() == "PauseAtOutPoint") { + stream.readNext(); + pause_at_out_point = (stream.text() == "1"); + } } } if (stream.hasError()) { @@ -206,6 +214,8 @@ void Config::save(QString path) { stream.writeTextElement("PreviousFrameQueueType", QString::number(previous_queue_type)); stream.writeTextElement("UpcomingFrameQueueSize", QString::number(upcoming_queue_size)); stream.writeTextElement("UpcomingFrameQueueType", QString::number(upcoming_queue_type)); + stream.writeTextElement("Loop", QString::number(loop)); + stream.writeTextElement("PauseAtOutPoint", QString::number(pause_at_out_point)); stream.writeEndElement(); // configuration stream.writeEndDocument(); // doc diff --git a/io/config.h b/io/config.h index f1a6e1d48..ce5a323dc 100644 --- a/io/config.h +++ b/io/config.h @@ -58,6 +58,8 @@ struct Config { int previous_queue_type; double upcoming_queue_size; int upcoming_queue_type; + bool loop; + bool pause_at_out_point; void load(QString path); void save(QString path); diff --git a/io/loadthread.cpp b/io/loadthread.cpp index e343369d8..3ae94d103 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) { @@ -62,26 +64,8 @@ void LoadThread::load_effect(QXmlStreamReader& stream, Clip* c) { } } - // 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."; + QString tag = stream.name().toString(); + + int type; + if (tag == "opening") { + type = TA_OPENING_TRANSITION; + } else if (tag == "closing") { + type = TA_CLOSING_TRANSITION; } else { - 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; - } - - emit start_create_effect_ui(&stream, c, type, meta, effect_length, effect_enabled); - - waitCond.wait(&mutex); + type = TA_NO_TRANSITION; } + + emit start_create_effect_ui(&stream, c, type, &effect_name, meta, effect_length, effect_enabled); + waitCond.wait(&mutex); } void LoadThread::read_next(QXmlStreamReader &stream) { @@ -183,7 +162,7 @@ bool LoadThread::load_worker(QFile& f, QXmlStreamReader& stream, int type) { internal_proj_url = stream.readElementText(); internal_proj_dir = QFileInfo(internal_proj_url).absoluteDir(); } else { - while (!cancelled && !(stream.name() == root_search && stream.isEndElement())) { + while (!cancelled && !stream.atEnd() && !(stream.name() == root_search && stream.isEndElement())) { read_next(stream); if (stream.name() == child_search && stream.isStartElement()) { switch (type) { @@ -603,7 +582,7 @@ void LoadThread::run() { xml_error = false; if (show_err) emit error(); } else if (stream.hasError()) { - error_str = stream.errorString(); + error_str = stream.errorString() + " - Line: " + QString::number(stream.lineNumber()) + " Col:" + QString::number(stream.columnNumber()); xml_error = true; emit error(); cont = false; @@ -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/mainwindow.cpp b/mainwindow.cpp index 5b6a695fd..d5186842d 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -248,6 +248,84 @@ void MainWindow::make_inout_menu(QMenu *parent) { parent->addAction("Clear In/Out Point", this, SLOT(clear_inout()), QKeySequence("G")); } +void kbd_shortcut_processor(QByteArray& file, QMenu* menu, bool save, bool first) { + QList actions = menu->actions(); + for (int i=0;imenu() != NULL) { + kbd_shortcut_processor(file, a->menu(), save, first); + } else if (!a->isSeparator()) { + if (save) { + // saving custom shortcuts + if (!a->property("default").isNull()) { + QKeySequence defks(a->property("default").toString()); + if (a->shortcut() != defks) { + // custom shortcut + if (!file.isEmpty()) file.append('\n'); + file.append(a->text().replace("&", "")); + file.append('\t'); + file.append(a->shortcut().toString()); + } + } + } else { + // loading custom shortcuts + if (first) { + // store default shortcut + a->setProperty("default", a->shortcut().toString()); + } else { + // restore default shortcut + a->setShortcut(a->property("default").toString()); + } + QString comp_str = a->text().replace("&", ""); + int shortcut_index = file.indexOf(comp_str); + if (shortcut_index == 0 || (shortcut_index > 0 && file.at(shortcut_index-1) == '\n')) { + shortcut_index += comp_str.size() + 1; + QString shortcut; + while (shortcut_index < file.size() && file.at(shortcut_index) != '\n') { + shortcut.append(file.at(shortcut_index)); + shortcut_index++; + } + QKeySequence ks(shortcut); + if (!ks.isEmpty()) { + a->setShortcut(ks); + } + } + } + } + } +} + +void MainWindow::load_shortcuts(const QString& fn, bool first) { + QByteArray shortcut_bytes; + QFile shortcut_path(fn); + if (shortcut_path.exists() && shortcut_path.open(QFile::ReadOnly)) { + shortcut_bytes = shortcut_path.readAll(); + shortcut_path.close(); + } + QList menus = menuBar()->actions(); + for (int i=0;imenu(); + kbd_shortcut_processor(shortcut_bytes, menu, false, first); + } +} + +void MainWindow::save_shortcuts(const QString& fn) { + // save main menu actions + QList menus = menuBar()->actions(); + QByteArray shortcut_file; + for (int i=0;imenu(); + kbd_shortcut_processor(shortcut_file, menu, true, false); + } + QFile shortcut_file_io(fn); + if (shortcut_file_io.open(QFile::WriteOnly)) { + shortcut_file_io.write(shortcut_file); + shortcut_file_io.close(); + } else { + dout << "[ERROR] Failed to save shortcut file"; + } +} + void MainWindow::show_about() { AboutDialog a(this); a.exec(); @@ -319,7 +397,7 @@ void MainWindow::export_dialog() { } void MainWindow::ripple_delete() { - panel_timeline->delete_selection(sequence->selections, true); + if (sequence != NULL) panel_timeline->delete_selection(sequence->selections, true); } void MainWindow::editMenu_About_To_Be_Shown() { @@ -442,47 +520,6 @@ bool MainWindow::can_close_project() { return true; } -void kbd_shortcut_processor(QByteArray& file, QMenu* menu, bool save) { - QList actions = menu->actions(); - for (int i=0;imenu() != NULL) { - kbd_shortcut_processor(file, a->menu(), save); - } else if (!a->isSeparator()) { - if (save) { - // saving custom shortcuts - if (!a->property("default").isNull()) { - QKeySequence defks(a->property("default").toString()); - if (a->shortcut() != defks) { - // custom shortcut - if (!file.isEmpty()) file.append('\n'); - file.append(a->text().replace("&", "")); - file.append('\t'); - file.append(a->shortcut().toString()); - } - } - } else { - // loading custom shortcuts - a->setProperty("default", a->shortcut().toString()); - QString comp_str = a->text().replace("&", ""); - int shortcut_index = file.indexOf(comp_str); - if (shortcut_index == 0 || (shortcut_index > 0 && file.at(shortcut_index-1) == '\n')) { - shortcut_index += comp_str.size() + 1; - QString shortcut; - while (shortcut_index < file.size() && file.at(shortcut_index) != '\n') { - shortcut.append(file.at(shortcut_index)); - shortcut_index++; - } - QKeySequence ks(shortcut); - if (!ks.isEmpty()) { - a->setShortcut(ks); - } - } - } - } - } -} - void MainWindow::setup_menus() { QMenuBar* menuBar = new QMenuBar(this); setMenuBar(menuBar); @@ -497,8 +534,13 @@ void MainWindow::setup_menus() { file_menu->addAction("&Open Project", this, SLOT(open_project()), QKeySequence("Ctrl+O")); + clear_open_recent_action = new QAction("Clear Recent List"); + connect(clear_open_recent_action, SIGNAL(triggered()), panel_project, SLOT(clear_recent_projects())); + open_recent = file_menu->addMenu("Open Recent"); + open_recent->addAction(clear_open_recent_action); + file_menu->addAction("&Save Project", this, SLOT(save_project()), QKeySequence("Ctrl+S")); file_menu->addAction("Save Project &As", this, SLOT(save_project_as()), QKeySequence("Ctrl+Shift+S")); @@ -642,6 +684,9 @@ void MainWindow::setup_menus() { playback_menu->addSeparator(); playback_menu->addAction("Go to Previous Cut", this, SLOT(prev_cut()), QKeySequence("Up")); playback_menu->addAction("Go to Next Cut", this, SLOT(next_cut()), QKeySequence("Down")); + playback_menu->addSeparator(); + playback_menu->addAction("Go to In Point", this, SLOT(go_to_in()), QKeySequence("Shift+I")); + playback_menu->addAction("Go to Out Point", this, SLOT(go_to_out()), QKeySequence("Shift+O")); // INITIALIZE WINDOW MENU @@ -769,6 +814,14 @@ void MainWindow::setup_menus() { set_name_and_marker->setCheckable(true); set_name_and_marker->setData(reinterpret_cast(&config.set_name_with_marker)); + loop_action = tools_menu->addAction("Loop", this, SLOT(toggle_bool_action())); + loop_action->setCheckable(true); + loop_action->setData(reinterpret_cast(&config.loop)); + + pause_at_out_point_action = tools_menu->addAction("Pause At Out Point", this, SLOT(toggle_bool_action())); + pause_at_out_point_action->setCheckable(true); + pause_at_out_point_action->setData(reinterpret_cast(&config.pause_at_out_point)); + tools_menu->addSeparator(); no_autoscroll = tools_menu->addAction("No Auto-Scroll", this, SLOT(set_autoscroll())); @@ -795,21 +848,13 @@ void MainWindow::setup_menus() { QMenu* help_menu = menuBar->addMenu("&Help"); - help_menu->addAction("A&ction Search", this, SLOT(show_action_search())); + help_menu->addAction("A&ction Search", this, SLOT(show_action_search()), QKeySequence("/")); help_menu->addSeparator(); help_menu->addAction("&About...", this, SLOT(show_about())); - QFile shortcut_path(get_config_path() + "/shortcuts"); - if (shortcut_path.exists() && shortcut_path.open(QFile::ReadOnly)) { - QList menus = menuBar->actions(); - QByteArray shortcut_bytes = shortcut_path.readAll(); - for (int i=0;imenu(); - kbd_shortcut_processor(shortcut_bytes, menu, false); - } - } + load_shortcuts(get_config_path() + "/shortcuts", true); } void MainWindow::set_bool_action_checked(QAction *a) { @@ -862,20 +907,7 @@ void MainWindow::closeEvent(QCloseEvent *e) { dout << "[ERROR] Failed to save layout"; } - // save main menu actions - QList menus = menuBar()->actions(); - QByteArray shortcut_file; - for (int i=0;imenu(); - kbd_shortcut_processor(shortcut_file, menu, true); - } - QFile shortcut_file_io(config_dir + "/shortcuts"); - if (shortcut_file_io.open(QFile::WriteOnly)) { - shortcut_file_io.write(shortcut_file); - shortcut_file_io.close(); - } else { - dout << "[ERROR] Failed to save shortcut file"; - } + save_shortcuts(config_dir + "/shortcuts"); } stop_audio(); @@ -931,6 +963,28 @@ void MainWindow::reset_layout() { setup_layout(true); } +void MainWindow::go_to_in() { + if (panel_timeline->focused() + || panel_sequence_viewer->is_focused() + || panel_effect_controls->keyframe_focus() + || panel_graph_editor->view_is_focused()) { + panel_sequence_viewer->go_to_in(); + } else if (panel_footage_viewer->is_focused()) { + panel_footage_viewer->go_to_in(); + } +} + +void MainWindow::go_to_out() { + if (panel_timeline->focused() + || panel_sequence_viewer->is_focused() + || panel_effect_controls->keyframe_focus() + || panel_graph_editor->view_is_focused()) { + panel_sequence_viewer->go_to_out(); + } else if (panel_footage_viewer->is_focused()) { + panel_footage_viewer->go_to_out(); + } +} + void MainWindow::go_to_start() { if (panel_timeline->focused() || panel_sequence_viewer->is_focused() @@ -1066,6 +1120,8 @@ void MainWindow::toolMenu_About_To_Be_Shown() { set_bool_action_checked(enable_drop_on_media_to_replace); set_bool_action_checked(enable_hover_focus); set_bool_action_checked(set_name_and_marker); + set_bool_action_checked(loop_action); + set_bool_action_checked(pause_at_out_point_action); set_int_action_checked(no_autoscroll, config.autoscroll); set_int_action_checked(page_autoscroll, config.autoscroll); @@ -1103,12 +1159,13 @@ void MainWindow::fileMenu_About_To_Be_Shown() { open_recent->setEnabled(true); for (int i=0;iaddAction(recent_projects.at(i)); + action->setProperty("keyignore", true); action->setData(i); connect(action, SIGNAL(triggered()), this, SLOT(load_recent_project())); } open_recent->addSeparator(); - QAction* clear_action = open_recent->addAction("Clear Recent List"); - connect(clear_action, SIGNAL(triggered()), panel_project, SLOT(clear_recent_projects())); + + open_recent->addAction(clear_open_recent_action); } else { open_recent->setEnabled(false); } diff --git a/mainwindow.h b/mainwindow.h index 557fc23f0..124b862c5 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -20,6 +20,9 @@ public: void make_new_menu(QMenu* parent); void make_inout_menu(QMenu* parent); + void load_shortcuts(const QString &fn, bool first = false); + void save_shortcuts(const QString &fn); + QString appName; public slots: @@ -56,6 +59,8 @@ private slots: bool save_project_as(); bool save_project(); + void go_to_in(); + void go_to_out(); void go_to_start(); void prev_frame(); void playpause(); @@ -131,6 +136,7 @@ private: // file menu actions QMenu* open_recent; + QAction* clear_open_recent_action; // view menu actions QAction* track_lines; @@ -172,6 +178,8 @@ private: QAction* enable_drop_on_media_to_replace; QAction* enable_hover_focus; QAction* set_name_and_marker; + QAction* loop_action; + QAction* pause_at_out_point_action; // edit menu actions QAction* undo_action; 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/packaging/linux/org.olivevideoeditor.Olive.appdata.xml b/packaging/linux/org.olivevideoeditor.Olive.appdata.xml index 62fa50e1b..bcb54e5a6 100644 --- a/packaging/linux/org.olivevideoeditor.Olive.appdata.xml +++ b/packaging/linux/org.olivevideoeditor.Olive.appdata.xml @@ -9,10 +9,14 @@ Editor de vídeo não-linear Editor de video no lineal Нелинейный видеоредактор + Нелінійний відеоредактор + Нелінійний відеоредактор

Olive is a free non-linear video editor aiming to provide a fully-featured alternative to high-end professional video editing software.

Olive é um editor de vídeo não-linear com o objetivo de fornecer uma alternativa completa para softwares profissionais de edição de vídeo.

Olive es un editor de video no lineal libre que apunta a brindar una alternativa completa al software de edición de video profesional.

Olive — свободный нелинейный видеоредактор, задуманный как полноценная замена закрытым коммерческим продуктам.

+

Olive — вільний нелінійний відеоредактор, задуманий як повноцінна заміна закритим комерційним продуктам.

+

Olive — вільний нелінійний відеоредактор, задуманий як повноцінна заміна закритим комерційним продуктам.

https://www.olivevideoeditor.org https://www.patreon.com/olivevideoeditor https://github.com/olive-editor/olive/issues 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/panels/viewer.cpp b/panels/viewer.cpp index af1522dc7..b8fee4edc 100644 --- a/panels/viewer.cpp +++ b/panels/viewer.cpp @@ -248,11 +248,19 @@ void Viewer::seek(long p) { } void Viewer::go_to_start() { + if (seq != NULL) seek(0); +} + +void Viewer::go_to_end() { + if (seq != NULL) seek(seq->getEndFrame()); +} + +void Viewer::go_to_in() { if (seq != NULL) { - if (seq->using_workarea && seq->playhead != seq->workarea_in) { + if (seq->using_workarea) { seek(seq->workarea_in); } else { - seek(0); + go_to_start(); } } } @@ -265,12 +273,12 @@ void Viewer::next_frame() { if (seq != NULL) seek(seq->playhead+1); } -void Viewer::go_to_end() { +void Viewer::go_to_out() { if (seq != NULL) { - if (seq->using_workarea && seq->playhead != seq->workarea_out) { + if (seq->using_workarea) { seek(seq->workarea_out); } else { - seek(seq->getEndFrame()); + go_to_end(); } } } @@ -307,6 +315,12 @@ void Viewer::play() { if (panel_footage_viewer->playing) panel_footage_viewer->pause(); if (seq != NULL) { + if (!is_recording_cued() + && seq->playhead >= get_seq_out() + && (config.loop || !main_sequence)) { + seek(get_seq_in()); + } + reset_all_audio(); if (is_recording_cued() && !start_recording()) { dout << "[ERROR] Failed to record audio"; @@ -455,6 +469,18 @@ void Viewer::set_sb_max() { headers->set_scrollbar_max(horizontal_bar, seq->getEndFrame(), headers->width()); } +long Viewer::get_seq_in() { + return (seq->using_workarea) + ? seq->workarea_in + : 0; +} + +long Viewer::get_seq_out() { + return (seq->using_workarea && previous_playhead < seq->workarea_out) + ? seq->workarea_out + : seq->getEndFrame(); +} + void Viewer::setup_ui() { QWidget* contents = new QWidget(); @@ -499,7 +525,7 @@ void Viewer::setup_ui() { goToStartIcon.addFile(QStringLiteral(":/icons/prev.png"), QSize(), QIcon::Normal, QIcon::Off); goToStartIcon.addFile(QStringLiteral(":/icons/prev-disabled.png"), QSize(), QIcon::Disabled, QIcon::Off); btnSkipToStart->setIcon(goToStartIcon); - connect(btnSkipToStart, SIGNAL(clicked(bool)), this, SLOT(go_to_start())); + connect(btnSkipToStart, SIGNAL(clicked(bool)), this, SLOT(go_to_in())); playback_control_layout->addWidget(btnSkipToStart); btnRewind = new QPushButton(playback_controls); @@ -530,7 +556,7 @@ void Viewer::setup_ui() { nextIcon.addFile(QStringLiteral(":/icons/next.png"), QSize(), QIcon::Normal, QIcon::Off); nextIcon.addFile(QStringLiteral(":/icons/next-disabled.png"), QSize(), QIcon::Disabled, QIcon::Off); btnSkipToEnd->setIcon(nextIcon); - connect(btnSkipToEnd, SIGNAL(clicked(bool)), this, SLOT(go_to_end())); + connect(btnSkipToEnd, SIGNAL(clicked(bool)), this, SLOT(go_to_out())); playback_control_layout->addWidget(btnSkipToEnd); lower_control_layout->addWidget(playback_controls); @@ -637,17 +663,23 @@ void Viewer::update_playhead() { } void Viewer::timer_update() { - long previous_playhead = seq->playhead; + previous_playhead = seq->playhead; seq->playhead = qRound(playhead_start + ((QDateTime::currentMSecsSinceEpoch()-start_msecs) * 0.001 * seq->frame_rate)); update_parents(); - long end_frame = (seq->using_workarea && previous_playhead < seq->workarea_out) ? seq->workarea_out : seq->getEndFrame(); - if ((!recording + long end_frame = get_seq_out(); + if (!recording && playing && seq->playhead >= end_frame - && previous_playhead < end_frame) - || (recording && recording_start != recording_end && seq->playhead >= recording_end)) { + && previous_playhead < end_frame) { + if (!config.pause_at_out_point && config.loop) { + seek(get_seq_in()); + play(); + } else if (config.pause_at_out_point || !main_sequence) { + pause(); + } + } else if (recording && recording_start != recording_end && seq->playhead >= recording_end) { pause(); } } diff --git a/panels/viewer.h b/panels/viewer.h index 624d8df8e..f08b19e30 100644 --- a/panels/viewer.h +++ b/panels/viewer.h @@ -72,9 +72,11 @@ public: public slots: void play_wake(); void go_to_start(); + void go_to_in(); void previous_frame(); void toggle_play(); void next_frame(); + void go_to_out(); void go_to_end(); private slots: @@ -94,6 +96,9 @@ private: void set_zoom_value(double d); void set_sb_max(); + long get_seq_in(); + long get_seq_out(); + QIcon playIcon; void setup_ui(); @@ -112,6 +117,8 @@ private: bool cue_recording_internal; QTimer recording_flasher; + + long previous_playhead; }; #endif // VIEWER_H diff --git a/project/effect.cpp b/project/effect.cpp index 9a872d57f..7468a757d 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()) { @@ -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,191 +280,193 @@ 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()); + 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); } - field->set_color_value(color); - } - break; - case EFFECT_FIELD_STRING: - for (int i=0;iset_string_value(attr.value().toString()); + 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_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); + if (reader.name() == "option" && reader.isStartElement()) { + reader.readNext(); + field->add_combo_item(reader.text().toString(), 0); + } } + field->set_combo_index(combo_index); } - 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_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; + case EFFECT_FIELD_FILE: + for (int i=0;iset_filename(attr.value().toString()); + } } + break; } - break; } } } } - } - } else if (reader.name() == "shader" && reader.isStartElement()) { - enable_shader = true; - const QXmlStreamAttributes& attributes = reader.attributes(); - for (int i=0;ifilename; - enable_superimpose = false; + } 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; + } } } } @@ -793,16 +794,28 @@ void Effect::open() { validate_meta_path(); bool glsl_compiled = true; if (!vertPath.isEmpty()) { - if (!glslProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, meta->path + "/" + vertPath)) { + if (glslProgram->addShaderFromSourceFile(QOpenGLShader::Vertex, meta->path + "/" + vertPath)) { + dout << "[INFO] Vertex shader added successfully"; + } else { glsl_compiled = false; + dout << "[WARNING] Vertex shader could not be added"; } } if (!fragPath.isEmpty()) { - if (!glslProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, meta->path + "/" + fragPath)) { + if (glslProgram->addShaderFromSourceFile(QOpenGLShader::Fragment, meta->path + "/" + fragPath)) { + dout << "[INFO] Fragment shader added successfully"; + } else { glsl_compiled = false; + dout << "[WARNING] Fragment shader could not be added"; + } + } + if (glsl_compiled) { + if (glslProgram->link()) { + dout << "[INFO] Shader program linked successfully"; + } else { + dout << "[WARNING] Shader program failed to link"; } } - if (glsl_compiled) glslProgram->link(); isOpen = true; } } else { 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(); diff --git a/ui/timelinewidget.cpp b/ui/timelinewidget.cpp index d56a36765..34896859a 100644 --- a/ui/timelinewidget.cpp +++ b/ui/timelinewidget.cpp @@ -554,7 +554,10 @@ bool isLiveEditing() { void TimelineWidget::mousePressEvent(QMouseEvent *event) { if (sequence != NULL) { int tool = panel_timeline->tool; - if (event->button() == Qt::RightButton) { + if (event->button() == Qt::MiddleButton) { + tool = TIMELINE_TOOL_HAND; + panel_timeline->creating = false; + } else if (event->button() == Qt::RightButton) { tool = TIMELINE_TOOL_MENU; panel_timeline->creating = false; } @@ -1155,12 +1158,12 @@ void TimelineWidget::mouseReleaseEvent(QMouseEvent *event) { panel_timeline->rect_select_proc = false; panel_timeline->transition_tool_init = false; panel_timeline->transition_tool_proc = false; - panel_timeline->hand_moving = false; pre_clips.clear(); post_clips.clear(); update_ui(true); } + panel_timeline->hand_moving = false; } }