#include "mainwindow.h" #include "io/config.h" #include "io/path.h" #include "project/footage.h" #include "project/sequence.h" #include "project/clip.h" #include "project/undo.h" #include "project/media.h" #include "ui/sourcetable.h" #include "ui/viewerwidget.h" #include "ui/sourceiconview.h" #include "ui/timelineheader.h" #include "panels/panels.h" #include "panels/project.h" #include "panels/effectcontrols.h" #include "panels/viewer.h" #include "panels/timeline.h" #include "panels/grapheditor.h" #include "dialogs/aboutdialog.h" #include "dialogs/newsequencedialog.h" #include "dialogs/exportdialog.h" #include "dialogs/preferencesdialog.h" #include "dialogs/demonotice.h" #include "dialogs/speeddialog.h" #include "dialogs/actionsearch.h" #include "playback/audio.h" #include "playback/playback.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MainWindow* mainWindow; #define OLIVE_FILE_FILTER "Olive Project (*.ove)" QTimer autorecovery_timer; QString config_fn; bool demoNoticeShown = false; void MainWindow::setup_layout(bool reset) { panel_project->show(); panel_effect_controls->show(); panel_footage_viewer->show(); panel_sequence_viewer->show(); panel_timeline->show(); panel_graph_editor->hide(); addDockWidget(Qt::TopDockWidgetArea, panel_project); addDockWidget(Qt::TopDockWidgetArea, panel_footage_viewer); tabifyDockWidget(panel_footage_viewer, panel_effect_controls); panel_footage_viewer->raise(); addDockWidget(Qt::TopDockWidgetArea, panel_sequence_viewer); addDockWidget(Qt::BottomDockWidgetArea, panel_timeline); panel_graph_editor->setFloating(true); // workaround for strange Qt dock bug (see https://bugreports.qt.io/browse/QTBUG-65592) #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) resizeDocks({panel_project}, {40}, Qt::Horizontal); #endif // load panels from file if (!reset) { QFile panel_config(get_config_path() + "/layout"); if (panel_config.exists() && panel_config.open(QFile::ReadOnly)) { restoreState(panel_config.readAll(), 0); panel_config.close(); } } layout()->update(); } MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { enable_launch_with_project = false; setup_debug(); mainWindow = this; // set up style? qApp->setStyle(QStyleFactory::create("Fusion")); setStyleSheet("QPushButton::checked { background: rgb(25, 25, 25); }"); QPalette darkPalette; darkPalette.setColor(QPalette::Window, QColor(53,53,53)); darkPalette.setColor(QPalette::WindowText, Qt::white); darkPalette.setColor(QPalette::Base, QColor(25,25,25)); darkPalette.setColor(QPalette::AlternateBase, QColor(53,53,53)); darkPalette.setColor(QPalette::ToolTipBase, QColor(25,25,25)); darkPalette.setColor(QPalette::ToolTipText, Qt::white); darkPalette.setColor(QPalette::Text, Qt::white); darkPalette.setColor(QPalette::Button, QColor(53,53,53)); darkPalette.setColor(QPalette::ButtonText, Qt::white); darkPalette.setColor(QPalette::BrightText, Qt::red); darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(128, 128, 128)); darkPalette.setColor(QPalette::Link, QColor(42, 130, 218)); darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); darkPalette.setColor(QPalette::HighlightedText, Qt::black); qApp->setPalette(darkPalette); // end style QWidget* centralWidget = new QWidget(this); centralWidget->setMaximumSize(QSize(0, 0)); setCentralWidget(centralWidget); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); setDockNestingEnabled(true); layout()->invalidate(); QString data_dir = get_data_path(); if (!data_dir.isEmpty()) { QDir dir(data_dir); dir.mkpath("."); if (dir.exists()) { qint64 a_month_ago = QDateTime::currentMSecsSinceEpoch() - 2592000000; qint64 a_week_ago = QDateTime::currentMSecsSinceEpoch() - 604800000; // TODO put delete functions in another thread? // delete auto-recoveries older than 7 days QStringList old_autorecoveries = dir.entryList(QStringList("autorecovery.ove.*"), QDir::Files); int deleted_ars = 0; for (int i=0;i 0) dout << "[INFO] Deleted" << deleted_ars << "autorecovery" << ((deleted_ars == 1) ? "file that was" : "files that were") << "older than 7 days"; // delete previews older than 30 days QDir preview_dir = QDir(data_dir + "/previews"); if (preview_dir.exists()) { deleted_ars = 0; QStringList old_prevs = preview_dir.entryList(QDir::Files); for (int i=0;i 0) dout << "[INFO] Deleted" << deleted_ars << "preview" << ((deleted_ars == 1) ? "file that was" : "files that were") << "last read over 30 days ago"; } // search for open recents list recent_proj_file = data_dir + "/recents"; QFile f(recent_proj_file); if (f.exists() && f.open(QFile::ReadOnly | QFile::Text)) { QTextStream text_stream(&f); while (true) { QString line = text_stream.readLine(); if (line.isNull()) { break; } else { recent_projects.append(line); } } f.close(); } } } QString config_path = get_config_path(); if (!config_path.isEmpty()) { QDir config_dir(config_path); config_dir.mkpath("."); config_fn = config_path + "/config.xml"; if (QFileInfo::exists(config_fn)) { config.load(config_fn); } } alloc_panels(this); QStatusBar* statusBar = new QStatusBar(this); statusBar->showMessage("Welcome to " + appName); setStatusBar(statusBar); setup_menus(); if (!data_dir.isEmpty()) { // detect auto-recovery file autorecovery_filename = data_dir + "/autorecovery.ove"; if (QFile::exists(autorecovery_filename)) { if (QMessageBox::question(NULL, "Auto-recovery", "Olive didn't close properly and an autorecovery file was detected. Would you like to open it?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { enable_launch_with_project = false; open_project_worker(autorecovery_filename, true); } } autorecovery_timer.setInterval(60000); QObject::connect(&autorecovery_timer, SIGNAL(timeout()), this, SLOT(autorecover_interval())); autorecovery_timer.start(); } setup_layout(false); init_audio(); } MainWindow::~MainWindow() { free_panels(); close_debug(); } void MainWindow::launch_with_project(const QString& s) { project_url = s; enable_launch_with_project = true; demoNoticeShown = true; } void MainWindow::make_new_menu(QMenu *parent) { parent->addAction("&Project", this, SLOT(new_project()), QKeySequence("Ctrl+N")); parent->addSeparator(); parent->addAction("&Sequence", this, SLOT(new_sequence()), QKeySequence("Ctrl+Shift+N")); parent->addAction("&Folder", this, SLOT(new_folder())); } void MainWindow::make_inout_menu(QMenu *parent) { parent->addAction("Set In Point", this, SLOT(set_in_point()), QKeySequence("I")); parent->addAction("Set Out Point", this, SLOT(set_out_point()), QKeySequence("O")); parent->addAction("Clear In/Out Point", this, SLOT(clear_inout()), QKeySequence("G")); } void MainWindow::show_about() { AboutDialog a(this); a.exec(); } void MainWindow::delete_slot() { if (panel_timeline->headers->hasFocus()) { panel_timeline->headers->delete_markers(); } else if (panel_timeline->focused()) { panel_timeline->delete_selection(sequence->selections, false); } else if (panel_effect_controls->is_focused()) { panel_effect_controls->delete_effects(); } else if (panel_project->is_focused()) { panel_project->delete_selected_media(); } else if (panel_effect_controls->keyframe_focus()) { panel_effect_controls->delete_selected_keyframes(); } else if (panel_graph_editor->view_is_focused()) { panel_graph_editor->delete_selected_keys(); } } void MainWindow::select_all() { if (panel_timeline->focused()) { panel_timeline->select_all(); } else if (panel_graph_editor->view_is_focused()) { panel_graph_editor->select_all(); } } void MainWindow::new_sequence() { NewSequenceDialog nsd(this); nsd.set_sequence_name(panel_project->get_next_sequence_name()); nsd.exec(); } void MainWindow::zoom_in() { QDockWidget* focused_panel = get_focused_panel(); if (focused_panel == panel_timeline) { panel_timeline->set_zoom(true); } else if (focused_panel == panel_effect_controls) { panel_effect_controls->set_zoom(true); } else if (focused_panel == panel_footage_viewer) { panel_footage_viewer->set_zoom(true); } else if (focused_panel == panel_sequence_viewer) { panel_sequence_viewer->set_zoom(true); } } void MainWindow::zoom_out() { QDockWidget* focused_panel = get_focused_panel(); if (focused_panel == panel_timeline) { panel_timeline->set_zoom(false); } else if (focused_panel == panel_effect_controls) { panel_effect_controls->set_zoom(false); } else if (focused_panel == panel_footage_viewer) { panel_footage_viewer->set_zoom(false); } else if (focused_panel == panel_sequence_viewer) { panel_sequence_viewer->set_zoom(false); } } void MainWindow::export_dialog() { if (sequence == NULL) { QMessageBox::information(this, "No active sequence", "Please open the sequence you wish to export.", QMessageBox::Ok); } else { ExportDialog e(this); e.exec(); } } void MainWindow::ripple_delete() { panel_timeline->delete_selection(sequence->selections, true); } void MainWindow::editMenu_About_To_Be_Shown() { undo_action->setEnabled(undo_stack.canUndo()); redo_action->setEnabled(undo_stack.canRedo()); } void MainWindow::undo() { if (!panel_timeline->importing) { // workaround to prevent crash (and also users should never need to do this) undo_stack.undo(); update_ui(true); } } void MainWindow::redo() { undo_stack.redo(); update_ui(true); } void MainWindow::open_speed_dialog() { if (sequence != NULL) { SpeedDialog s(this); for (int i=0;iclips.size();i++) { Clip* c = sequence->clips.at(i); if (c != NULL && panel_timeline->is_clip_selected(c, true)) { s.clips.append(c); } } if (s.clips.size() > 0) s.run(); } } void MainWindow::cut() { if (sequence != NULL) { QDockWidget* focused_panel = get_focused_panel(); if (panel_timeline == focused_panel) { panel_timeline->copy(true); } else if (panel_effect_controls == focused_panel) { panel_effect_controls->copy(true); } } } void MainWindow::copy() { if (sequence != NULL) { QDockWidget* focused_panel = get_focused_panel(); if (panel_timeline == focused_panel) { panel_timeline->copy(false); } else if (panel_effect_controls == focused_panel) { panel_effect_controls->copy(false); } } } void MainWindow::paste() { QDockWidget* focused_panel = get_focused_panel(); if ((panel_timeline == focused_panel || panel_effect_controls == focused_panel) && sequence != NULL) { panel_timeline->paste(false); } } void MainWindow::new_project() { if (can_close_project()) { panel_effect_controls->clear_effects(true); undo_stack.clear(); project_url.clear(); panel_project->new_project(); updateTitle(""); update_ui(false); panel_project->tree_view->update(); } } void MainWindow::autorecover_interval() { if (!rendering && isWindowModified()) { panel_project->save_project(true); dout << "[INFO] Auto-recovery project saved"; } } bool MainWindow::save_project_as() { QString fn = QFileDialog::getSaveFileName(this, "Save Project As...", "", OLIVE_FILE_FILTER); if (!fn.isEmpty()) { if (!fn.endsWith(".ove", Qt::CaseInsensitive)) { fn += ".ove"; } updateTitle(fn); panel_project->save_project(false); return true; } return false; } bool MainWindow::save_project() { if (project_url.isEmpty()) { return save_project_as(); } else { panel_project->save_project(false); return true; } } bool MainWindow::can_close_project() { if (isWindowModified()) { QMessageBox* m = new QMessageBox( QMessageBox::Question, "Unsaved Project", "This project has changed since it was last saved. Would you like to save it before closing?", QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel, this ); m->setWindowModality(Qt::WindowModal); int r = m->exec(); if (r == QMessageBox::Yes) { return save_project(); } else if (r == QMessageBox::Cancel) { return false; } } 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); // INITIALIZE FILE MENU QMenu* file_menu = menuBar->addMenu("&File"); connect(file_menu, SIGNAL(aboutToShow()), this, SLOT(fileMenu_About_To_Be_Shown())); QMenu* new_menu = file_menu->addMenu("&New"); make_new_menu(new_menu); file_menu->addAction("&Open Project", this, SLOT(open_project()), QKeySequence("Ctrl+O")); open_recent = file_menu->addMenu("Open Recent"); 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")); file_menu->addSeparator(); file_menu->addAction("&Import...", panel_project, SLOT(import_dialog()), QKeySequence("Ctrl+I")); file_menu->addSeparator(); file_menu->addAction("&Export...", this, SLOT(export_dialog()), QKeySequence("Ctrl+M")); file_menu->addSeparator(); file_menu->addAction("E&xit", this, SLOT(close())); // INITIALIZE EDIT MENU QMenu* edit_menu = menuBar->addMenu("&Edit"); connect(edit_menu, SIGNAL(aboutToShow()), this, SLOT(editMenu_About_To_Be_Shown())); undo_action = edit_menu->addAction("&Undo", this, SLOT(undo()), QKeySequence("Ctrl+Z")); redo_action = edit_menu->addAction("Redo", this, SLOT(redo()), QKeySequence("Ctrl+Shift+Z")); edit_menu->addSeparator(); edit_menu->addAction("Cu&t", this, SLOT(cut()), QKeySequence("Ctrl+X")); edit_menu->addAction("Cop&y", this, SLOT(copy()), QKeySequence("Ctrl+C")); edit_menu->addAction("&Paste", this, SLOT(paste()), QKeySequence("Ctrl+V")); edit_menu->addAction("Paste Insert", this, SLOT(paste_insert()), QKeySequence("Ctrl+Shift+V")); edit_menu->addAction("Duplicate", this, SLOT(duplicate()), QKeySequence("Ctrl+D")); edit_menu->addAction("Delete", this, SLOT(delete_slot()), QKeySequence("Del")); edit_menu->addAction("Ripple Delete", this, SLOT(ripple_delete()), QKeySequence("Shift+Del")); edit_menu->addAction("Split", panel_timeline, SLOT(split_at_playhead()), QKeySequence("Ctrl+K")); edit_menu->addSeparator(); edit_menu->addAction("Select &All", this, SLOT(select_all()), QKeySequence("Ctrl+A")); edit_menu->addAction("Deselect All", panel_timeline, SLOT(deselect()), QKeySequence("Ctrl+Shift+A")); edit_menu->addSeparator(); edit_menu->addAction("Add Default Transition", this, SLOT(add_default_transition()), QKeySequence("Ctrl+Shift+D")); edit_menu->addAction("Link/Unlink", panel_timeline, SLOT(toggle_links()), QKeySequence("Ctrl+L")); edit_menu->addAction("Enable/Disable", this, SLOT(toggle_enable_clips()), QKeySequence("Shift+E")); edit_menu->addAction("Nest", this, SLOT(nest())); edit_menu->addSeparator(); edit_menu->addAction("Ripple to In Point", this, SLOT(ripple_to_in_point()), QKeySequence("Q")); edit_menu->addAction("Ripple to Out Point", this, SLOT(ripple_to_out_point()), QKeySequence("W")); edit_menu->addAction("Edit to In Point", this, SLOT(edit_to_in_point()), QKeySequence("Ctrl+Alt+Q")); edit_menu->addAction("Edit to Out Point", this, SLOT(edit_to_out_point()), QKeySequence("Ctrl+Alt+W")); edit_menu->addSeparator(); make_inout_menu(edit_menu); edit_menu->addAction("Delete In/Out Point", this, SLOT(delete_inout()), QKeySequence(";")); edit_menu->addAction("Ripple Delete In/Out Point", this, SLOT(ripple_delete_inout()), QKeySequence("'")); edit_menu->addSeparator(); edit_menu->addAction("Set/Edit Marker", this, SLOT(set_marker()), QKeySequence("M")); // INITIALIZE VIEW MENU QMenu* view_menu = menuBar->addMenu("&View"); connect(view_menu, SIGNAL(aboutToShow()), this, SLOT(viewMenu_About_To_Be_Shown())); view_menu->addAction("Zoom In", this, SLOT(zoom_in()), QKeySequence("=")); view_menu->addAction("Zoom Out", this, SLOT(zoom_out()), QKeySequence("-")); view_menu->addAction("Increase Track Height", this, SLOT(zoom_in_tracks()), QKeySequence("Ctrl+=")); view_menu->addAction("Decrease Track Height", this, SLOT(zoom_out_tracks()), QKeySequence("Ctrl+-")); show_all = view_menu->addAction("Toggle Show All", panel_timeline, SLOT(toggle_show_all()), QKeySequence("\\")); show_all->setCheckable(true); view_menu->addSeparator(); track_lines = view_menu->addAction("Track Lines", this, SLOT(toggle_bool_action())); track_lines->setCheckable(true); track_lines->setData(reinterpret_cast(&config.show_track_lines)); rectified_waveforms = view_menu->addAction("Rectified Waveforms", this, SLOT(toggle_bool_action())); rectified_waveforms->setCheckable(true); rectified_waveforms->setData(reinterpret_cast(&config.rectified_waveforms)); view_menu->addSeparator(); frames_action = view_menu->addAction("Frames", this, SLOT(set_timecode_view())); frames_action->setData(TIMECODE_FRAMES); frames_action->setCheckable(true); drop_frame_action = view_menu->addAction("Drop Frame", this, SLOT(set_timecode_view())); drop_frame_action->setData(TIMECODE_DROP); drop_frame_action->setCheckable(true); nondrop_frame_action = view_menu->addAction("Non-Drop Frame", this, SLOT(set_timecode_view())); nondrop_frame_action->setData(TIMECODE_NONDROP); nondrop_frame_action->setCheckable(true); milliseconds_action = view_menu->addAction("Milliseconds", this, SLOT(set_timecode_view())); milliseconds_action->setData(TIMECODE_MILLISECONDS); milliseconds_action->setCheckable(true); view_menu->addSeparator(); QMenu* title_safe_area_menu = view_menu->addMenu("Title/Action Safe Area"); title_safe_off = title_safe_area_menu->addAction("Off"); title_safe_off->setCheckable(true); connect(title_safe_off, SIGNAL(triggered(bool)), this, SLOT(set_tsa_disable())); title_safe_default = title_safe_area_menu->addAction("Default"); title_safe_default->setCheckable(true); connect(title_safe_default, SIGNAL(triggered(bool)), this, SLOT(set_tsa_default())); title_safe_43 = title_safe_area_menu->addAction("4:3"); title_safe_43->setCheckable(true); connect(title_safe_43, SIGNAL(triggered(bool)), this, SLOT(set_tsa_43())); title_safe_169 = title_safe_area_menu->addAction("16:9"); title_safe_169->setCheckable(true); connect(title_safe_169, SIGNAL(triggered(bool)), this, SLOT(set_tsa_169())); title_safe_custom = title_safe_area_menu->addAction("Custom"); title_safe_custom->setCheckable(true); connect(title_safe_custom, SIGNAL(triggered(bool)), this, SLOT(set_tsa_custom())); view_menu->addSeparator(); full_screen = view_menu->addAction("Full Screen", this, SLOT(toggle_full_screen()), QKeySequence("F11")); full_screen->setCheckable(true); // INITIALIZE PLAYBACK MENU QMenu* playback_menu = menuBar->addMenu("&Playback"); playback_menu->addAction("Go to Start", this, SLOT(go_to_start()), QKeySequence("Home")); playback_menu->addAction("Previous Frame", this, SLOT(prev_frame()), QKeySequence("Left")); playback_menu->addAction("Play/Pause", this, SLOT(playpause()), QKeySequence("Space")); playback_menu->addAction("Next Frame", this, SLOT(next_frame()), QKeySequence("Right")); playback_menu->addAction("Go to End", this, SLOT(go_to_end()), QKeySequence("End")); 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")); // INITIALIZE WINDOW MENU window_menu = menuBar->addMenu("&Window"); connect(window_menu, SIGNAL(aboutToShow()), this, SLOT(windowMenu_About_To_Be_Shown())); QAction* window_project_action = window_menu->addAction("Project", this, SLOT(toggle_panel_visibility())); window_project_action->setCheckable(true); window_project_action->setData(reinterpret_cast(panel_project)); QAction* window_effectcontrols_action = window_menu->addAction("Effect Controls", this, SLOT(toggle_panel_visibility())); window_effectcontrols_action->setCheckable(true); window_effectcontrols_action->setData(reinterpret_cast(panel_effect_controls)); QAction* window_timeline_action = window_menu->addAction("Timeline", this, SLOT(toggle_panel_visibility())); window_timeline_action->setCheckable(true); window_timeline_action->setData(reinterpret_cast(panel_timeline)); QAction* window_graph_editor_action = window_menu->addAction("Graph Editor", this, SLOT(toggle_panel_visibility())); window_graph_editor_action->setCheckable(true); window_graph_editor_action->setData(reinterpret_cast(panel_graph_editor)); QAction* window_footageviewer_action = window_menu->addAction("Footage Viewer", this, SLOT(toggle_panel_visibility())); window_footageviewer_action->setCheckable(true); window_footageviewer_action->setData(reinterpret_cast(panel_footage_viewer)); QAction* window_sequenceviewer_action = window_menu->addAction("Sequence Viewer", this, SLOT(toggle_panel_visibility())); window_sequenceviewer_action->setCheckable(true); window_sequenceviewer_action->setData(reinterpret_cast(panel_sequence_viewer)); window_menu->addSeparator(); window_menu->addAction("Reset to Default Layout", this, SLOT(reset_layout())); // INITIALIZE TOOLS MENU QMenu* tools_menu = menuBar->addMenu("&Tools"); connect(tools_menu, SIGNAL(aboutToShow()), this, SLOT(toolMenu_About_To_Be_Shown())); pointer_tool_action = tools_menu->addAction("Pointer Tool", this, SLOT(menu_click_button()), QKeySequence("V")); pointer_tool_action->setCheckable(true); pointer_tool_action->setData(reinterpret_cast(panel_timeline->toolArrowButton)); edit_tool_action = tools_menu->addAction("Edit Tool", this, SLOT(menu_click_button()), QKeySequence("X")); edit_tool_action->setCheckable(true); edit_tool_action->setData(reinterpret_cast(panel_timeline->toolEditButton)); ripple_tool_action = tools_menu->addAction("Ripple Tool", this, SLOT(menu_click_button()), QKeySequence("B")); ripple_tool_action->setCheckable(true); ripple_tool_action->setData(reinterpret_cast(panel_timeline->toolRippleButton)); razor_tool_action = tools_menu->addAction("Razor Tool", this, SLOT(menu_click_button()), QKeySequence("C")); razor_tool_action->setCheckable(true); razor_tool_action->setData(reinterpret_cast(panel_timeline->toolRazorButton)); slip_tool_action = tools_menu->addAction("Slip Tool", this, SLOT(menu_click_button()), QKeySequence("Y")); slip_tool_action->setCheckable(true); slip_tool_action->setData(reinterpret_cast(panel_timeline->toolSlipButton)); slide_tool_action = tools_menu->addAction("Slide Tool", this, SLOT(menu_click_button()), QKeySequence("U")); slide_tool_action->setCheckable(true); slide_tool_action->setData(reinterpret_cast(panel_timeline->toolSlideButton)); hand_tool_action = tools_menu->addAction("Hand Tool", this, SLOT(menu_click_button()), QKeySequence("H")); hand_tool_action->setCheckable(true); hand_tool_action->setData(reinterpret_cast(panel_timeline->toolHandButton)); transition_tool_action = tools_menu->addAction("Transition Tool", this, SLOT(menu_click_button()), QKeySequence("T")); transition_tool_action->setCheckable(true); transition_tool_action->setData(reinterpret_cast(panel_timeline->toolTransitionButton)); tools_menu->addSeparator(); snap_toggle = tools_menu->addAction("Enable Snapping", this, SLOT(menu_click_button()), QKeySequence("S")); snap_toggle->setCheckable(true); snap_toggle->setData(reinterpret_cast(panel_timeline->snappingButton)); tools_menu->addSeparator(); selecting_also_seeks = tools_menu->addAction("Selecting Also Seeks", this, SLOT(toggle_bool_action())); selecting_also_seeks->setCheckable(true); selecting_also_seeks->setData(reinterpret_cast(&config.select_also_seeks)); edit_tool_also_seeks = tools_menu->addAction("Edit Tool Also Seeks", this, SLOT(toggle_bool_action())); edit_tool_also_seeks->setCheckable(true); edit_tool_also_seeks->setData(reinterpret_cast(&config.edit_tool_also_seeks)); edit_tool_selects_links = tools_menu->addAction("Edit Tool Selects Links", this, SLOT(toggle_bool_action())); edit_tool_selects_links->setCheckable(true); edit_tool_selects_links->setData(reinterpret_cast(&config.edit_tool_selects_links)); seek_to_end_of_pastes = tools_menu->addAction("Seek to the End of Pastes", this, SLOT(toggle_bool_action())); seek_to_end_of_pastes->setCheckable(true); seek_to_end_of_pastes->setData(reinterpret_cast(&config.paste_seeks)); scroll_wheel_zooms = tools_menu->addAction("Scroll Wheel Zooms", this, SLOT(toggle_bool_action())); scroll_wheel_zooms->setCheckable(true); scroll_wheel_zooms->setData(reinterpret_cast(&config.scroll_zooms)); enable_drag_files_to_timeline = tools_menu->addAction("Enable Drag Files to Timeline", this, SLOT(toggle_bool_action())); enable_drag_files_to_timeline->setCheckable(true); enable_drag_files_to_timeline->setData(reinterpret_cast(&config.enable_drag_files_to_timeline)); autoscale_by_default = tools_menu->addAction("Auto-Scale By Default", this, SLOT(toggle_bool_action())); autoscale_by_default->setCheckable(true); autoscale_by_default->setData(reinterpret_cast(&config.autoscale_by_default)); enable_seek_to_import = tools_menu->addAction("Enable Seek to Import", this, SLOT(toggle_bool_action())); enable_seek_to_import->setCheckable(true); enable_seek_to_import->setData(reinterpret_cast(&config.enable_seek_to_import)); enable_audio_scrubbing = tools_menu->addAction("Audio Scrubbing", this, SLOT(toggle_bool_action())); enable_audio_scrubbing->setCheckable(true); enable_audio_scrubbing->setData(reinterpret_cast(&config.enable_audio_scrubbing)); enable_drop_on_media_to_replace = tools_menu->addAction("Enable Drop on Media to Replace", this, SLOT(toggle_bool_action())); enable_drop_on_media_to_replace->setCheckable(true); enable_drop_on_media_to_replace->setData(reinterpret_cast(&config.drop_on_media_to_replace)); enable_hover_focus = tools_menu->addAction("Enable Hover Focus", this, SLOT(toggle_bool_action())); enable_hover_focus->setCheckable(true); enable_hover_focus->setData(reinterpret_cast(&config.hover_focus)); set_name_and_marker = tools_menu->addAction("Ask For Name When Setting Marker", this, SLOT(toggle_bool_action())); set_name_and_marker->setCheckable(true); set_name_and_marker->setData(reinterpret_cast(&config.set_name_with_marker)); tools_menu->addSeparator(); no_autoscroll = tools_menu->addAction("No Auto-Scroll", this, SLOT(set_autoscroll())); no_autoscroll->setData(AUTOSCROLL_NO_SCROLL); no_autoscroll->setCheckable(true); page_autoscroll = tools_menu->addAction("Page Auto-Scroll", this, SLOT(set_autoscroll())); page_autoscroll->setData(AUTOSCROLL_PAGE_SCROLL); page_autoscroll->setCheckable(true); smooth_autoscroll = tools_menu->addAction("Smooth Auto-Scroll", this, SLOT(set_autoscroll())); smooth_autoscroll->setData(AUTOSCROLL_SMOOTH_SCROLL); smooth_autoscroll->setCheckable(true); tools_menu->addSeparator(); tools_menu->addAction("Preferences", this, SLOT(preferences()), QKeySequence("Ctrl+.")); #ifdef QT_DEBUG tools_menu->addAction("Clear Undo", this, SLOT(clear_undo_stack())); #endif // INITIALIZE HELP MENU QMenu* help_menu = menuBar->addMenu("&Help"); help_menu->addAction("A&ction Search", this, SLOT(show_action_search())); 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); } } } void MainWindow::set_bool_action_checked(QAction *a) { if (!a->data().isNull()) { bool* variable = reinterpret_cast(a->data().value()); a->setChecked(*variable); } } void MainWindow::set_int_action_checked(QAction *a, const int& i) { if (!a->data().isNull()) { a->setChecked(a->data() == i); } } void MainWindow::set_button_action_checked(QAction *a) { a->setChecked(reinterpret_cast(a->data().value())->isChecked()); } void MainWindow::updateTitle(const QString& url) { project_url = url; setWindowTitle(appName + " - " + ((project_url.isEmpty()) ? "" : project_url) + "[*]"); } void MainWindow::closeEvent(QCloseEvent *e) { if (can_close_project()) { panel_effect_controls->clear_effects(true); set_sequence(NULL); panel_footage_viewer->set_main_sequence(); QString data_dir = get_data_path(); QString config_dir = get_config_path(); if (!data_dir.isEmpty() && !autorecovery_filename.isEmpty()) { if (QFile::exists(autorecovery_filename)) { QFile::rename(autorecovery_filename, autorecovery_filename + "." + QDateTime::currentDateTimeUtc().toString("yyyyMMddHHmmss")); } } if (!config_dir.isEmpty() && !config_fn.isEmpty()) { // save settings config.save(config_fn); // save panel layout QFile panel_config(config_dir + "/layout"); if (panel_config.open(QFile::WriteOnly)) { panel_config.write(saveState(0)); panel_config.close(); } else { 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"; } } stop_audio(); e->accept(); } else { e->ignore(); } } void MainWindow::paintEvent(QPaintEvent *event) { QMainWindow::paintEvent(event); if (enable_launch_with_project) { QTimer::singleShot(10, this, SLOT(load_with_launch())); enable_launch_with_project = false; } #ifndef QT_DEBUG if (!demoNoticeShown) { DemoNotice* d = new DemoNotice(this); d->open(); demoNoticeShown = true; } #endif } void MainWindow::clear_undo_stack() { undo_stack.clear(); } void MainWindow::open_project() { QString fn = QFileDialog::getOpenFileName(this, "Open Project...", "", OLIVE_FILE_FILTER); if (!fn.isEmpty() && can_close_project()) { open_project_worker(fn, false); } } void MainWindow::open_project_worker(const QString& fn, bool autorecovery) { updateTitle(fn); panel_project->load_project(autorecovery); undo_stack.clear(); } void MainWindow::load_with_launch() { open_project_worker(project_url, false); } void MainWindow::show_action_search() { ActionSearch as(this); as.exec(); } void MainWindow::reset_layout() { setup_layout(true); } void MainWindow::go_to_start() { 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_start(); } else if (panel_footage_viewer->is_focused()) { panel_footage_viewer->go_to_start(); } } void MainWindow::prev_frame() { if (panel_timeline->focused() || panel_sequence_viewer->is_focused() || panel_effect_controls->keyframe_focus() || panel_graph_editor->view_is_focused()) { panel_sequence_viewer->previous_frame(); } else if (panel_footage_viewer->is_focused()) { panel_footage_viewer->previous_frame(); } } void MainWindow::next_frame() { if (panel_timeline->focused() || panel_sequence_viewer->is_focused() || panel_effect_controls->keyframe_focus() || panel_graph_editor->view_is_focused()) { panel_sequence_viewer->next_frame(); } else if (panel_footage_viewer->is_focused()) { panel_footage_viewer->next_frame(); } } void MainWindow::go_to_end() { 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_end(); } else if (panel_footage_viewer->is_focused()) { panel_footage_viewer->go_to_end(); } } void MainWindow::playpause() { if (panel_timeline->focused() || panel_sequence_viewer->is_focused() || panel_effect_controls->keyframe_focus() || panel_graph_editor->view_is_focused()) { panel_sequence_viewer->toggle_play(); } else if (panel_footage_viewer->is_focused()) { panel_footage_viewer->toggle_play(); } } void MainWindow::prev_cut() { if (sequence != NULL && (panel_timeline->focused() || panel_sequence_viewer->is_focused())) { panel_timeline->previous_cut(); } } void MainWindow::next_cut() { if (sequence != NULL && (panel_timeline->focused() || panel_sequence_viewer->is_focused())) { panel_timeline->next_cut(); } } void MainWindow::preferences() { PreferencesDialog pd(this); pd.setup_kbd_shortcuts(menuBar()); pd.exec(); } void MainWindow::zoom_in_tracks() { panel_timeline->increase_track_height(); } void MainWindow::zoom_out_tracks() { panel_timeline->decrease_track_height(); } void MainWindow::windowMenu_About_To_Be_Shown() { QList window_actions = window_menu->actions(); for (int i=0;idata().isNull()) { a->setChecked(reinterpret_cast(a->data().value())->isVisible()); } } } void MainWindow::viewMenu_About_To_Be_Shown() { set_bool_action_checked(track_lines); set_int_action_checked(frames_action, config.timecode_view); set_int_action_checked(drop_frame_action, config.timecode_view); set_int_action_checked(nondrop_frame_action, config.timecode_view); set_int_action_checked(milliseconds_action, config.timecode_view); title_safe_off->setChecked(!config.show_title_safe_area); title_safe_default->setChecked(config.show_title_safe_area && !config.use_custom_title_safe_ratio); title_safe_43->setChecked(config.show_title_safe_area && config.use_custom_title_safe_ratio && config.custom_title_safe_ratio == 4.0/3.0); title_safe_169->setChecked(config.show_title_safe_area && config.use_custom_title_safe_ratio && config.custom_title_safe_ratio == 16.0/9.0); title_safe_custom->setChecked(config.show_title_safe_area && config.use_custom_title_safe_ratio && !title_safe_43->isChecked() && !title_safe_169->isChecked()); full_screen->setChecked(windowState() == Qt::WindowFullScreen); show_all->setChecked(panel_timeline->showing_all); } void MainWindow::toolMenu_About_To_Be_Shown() { set_button_action_checked(pointer_tool_action); set_button_action_checked(edit_tool_action); set_button_action_checked(ripple_tool_action); set_button_action_checked(razor_tool_action); set_button_action_checked(slip_tool_action); set_button_action_checked(slide_tool_action); set_button_action_checked(hand_tool_action); set_button_action_checked(transition_tool_action); set_button_action_checked(snap_toggle); set_bool_action_checked(selecting_also_seeks); set_bool_action_checked(edit_tool_also_seeks); set_bool_action_checked(edit_tool_selects_links); set_bool_action_checked(seek_to_end_of_pastes); set_bool_action_checked(scroll_wheel_zooms); set_bool_action_checked(rectified_waveforms); set_bool_action_checked(enable_drag_files_to_timeline); set_bool_action_checked(autoscale_by_default); set_bool_action_checked(enable_seek_to_import); set_bool_action_checked(enable_audio_scrubbing); 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_int_action_checked(no_autoscroll, config.autoscroll); set_int_action_checked(page_autoscroll, config.autoscroll); set_int_action_checked(smooth_autoscroll, config.autoscroll); } void MainWindow::duplicate() { if (panel_project->is_focused()) { panel_project->duplicate_selected(); } } void MainWindow::add_default_transition() { if (panel_timeline->focused()) panel_timeline->add_transition(); } void MainWindow::new_folder() { Media* m = panel_project->new_folder(0); undo_stack.push(new AddMediaCommand(m, panel_project->get_selected_folder())); QModelIndex index = project_model.create_index(m->row(), 0, m); switch (config.project_view_type) { case PROJECT_VIEW_TREE: panel_project->tree_view->edit(panel_project->sorter->mapFromSource(index)); break; case PROJECT_VIEW_ICON: panel_project->icon_view->edit(panel_project->sorter->mapFromSource(index)); break; } } void MainWindow::fileMenu_About_To_Be_Shown() { if (recent_projects.size() > 0) { open_recent->clear(); open_recent->setEnabled(true); for (int i=0;iaddAction(recent_projects.at(i)); 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())); } else { open_recent->setEnabled(false); } } void MainWindow::fileMenu_About_To_Hide() { // open_recent->clear(); } void MainWindow::load_recent_project() { int index = static_cast(sender())->data().toInt(); QString recent_url = recent_projects.at(index); if (!QFile::exists(recent_url)) { if (QMessageBox::question(this, "Missing recent project", "The project '" + recent_url + "' no longer exists. Would you like to remove it from the recent projects list?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { recent_projects.removeAt(index); panel_project->save_recent_projects(); } } else if (can_close_project()) { open_project_worker(recent_url, false); } } void MainWindow::ripple_to_in_point() { if (panel_timeline->focused()) panel_timeline->ripple_to_in_point(true, true); } void MainWindow::ripple_to_out_point() { if (panel_timeline->focused()) panel_timeline->ripple_to_in_point(false, true); } void MainWindow::set_in_point() { if (panel_timeline->focused() || panel_sequence_viewer->is_focused()) { panel_sequence_viewer->set_in_point(); } else if (panel_footage_viewer->is_focused()) { panel_footage_viewer->set_in_point(); } } void MainWindow::set_out_point() { if (panel_timeline->focused() || panel_sequence_viewer->is_focused()) { panel_sequence_viewer->set_out_point(); } else if (panel_footage_viewer->is_focused()) { panel_footage_viewer->set_out_point(); } } void MainWindow::clear_inout() { if (panel_timeline->focused() || panel_sequence_viewer->is_focused()) { panel_sequence_viewer->clear_inout_point(); } else if (panel_footage_viewer->is_focused()) { panel_footage_viewer->clear_inout_point(); } } void MainWindow::toggle_full_screen() { if (windowState() == Qt::WindowFullScreen) { setWindowState(Qt::WindowNoState); // seems to be necessary for it to return to Maximized correctly on Linux setWindowState(Qt::WindowMaximized); } else { setWindowState(Qt::WindowFullScreen); } } void MainWindow::delete_inout() { if (panel_timeline->focused()) { panel_timeline->delete_in_out(false); } } void MainWindow::ripple_delete_inout() { if (panel_timeline->focused()) { panel_timeline->delete_in_out(true); } } void MainWindow::set_tsa_default() { config.show_title_safe_area = true; config.use_custom_title_safe_ratio = false; panel_sequence_viewer->viewer_widget->update(); } void MainWindow::set_tsa_disable() { config.show_title_safe_area = false; panel_sequence_viewer->viewer_widget->update(); } void MainWindow::set_tsa_43() { config.show_title_safe_area = true; config.use_custom_title_safe_ratio = true; config.custom_title_safe_ratio = 4.0/3.0; panel_sequence_viewer->viewer_widget->update(); } void MainWindow::set_tsa_169() { config.show_title_safe_area = true; config.use_custom_title_safe_ratio = true; config.custom_title_safe_ratio = 16.0/9.0; panel_sequence_viewer->viewer_widget->update(); } void MainWindow::set_tsa_custom() { QString input; bool invalid = false; QRegExp arTest("[0-9.]+:[0-9.]+"); do { if (invalid) { QMessageBox::critical(this, "Invalid aspect ratio", "The aspect ratio '" + input + "' is invalid. Please try again."); } input = QInputDialog::getText(this, "Enter custom aspect ratio", "Enter the aspect ratio to use for the title/action safe area (e.g. 16:9):"); invalid = !arTest.exactMatch(input) && !input.isEmpty(); } while (invalid); if (!input.isEmpty()) { QStringList inputList = input.split(':'); config.show_title_safe_area = true; config.use_custom_title_safe_ratio = true; config.custom_title_safe_ratio = inputList.at(0).toDouble()/inputList.at(1).toDouble(); panel_sequence_viewer->viewer_widget->update(); } } void MainWindow::set_marker() { if (sequence != NULL) panel_timeline->set_marker(); } void MainWindow::toggle_enable_clips() { if (sequence != NULL) { ComboAction* ca = new ComboAction(); bool push_undo = false; for (int i=0;iclips.size();i++) { Clip* c = sequence->clips.at(i); if (c != NULL && panel_timeline->is_clip_selected(c, true)) { ca->append(new SetEnableCommand(c, !c->enabled)); push_undo = true; } } if (push_undo) { undo_stack.push(ca); update_ui(true); } else { delete ca; } } } void MainWindow::edit_to_in_point() { if (panel_timeline->focused()) panel_timeline->ripple_to_in_point(true, false); } void MainWindow::edit_to_out_point() { if (panel_timeline->focused()) panel_timeline->ripple_to_in_point(false, false); } void MainWindow::nest() { if (sequence != NULL) { QVector selected_clips; long earliest_point = LONG_MAX; // get selected clips for (int i=0;iclips.size();i++) { Clip* c = sequence->clips.at(i); if (c != NULL && panel_timeline->is_clip_selected(c, true)) { selected_clips.append(i); earliest_point = qMin(c->timeline_in, earliest_point); } } // nest them if (!selected_clips.isEmpty()) { ComboAction* ca = new ComboAction(); Sequence* s = new Sequence(); // create "nest" sequence s->name = panel_project->get_next_sequence_name("Nested Sequence"); s->width = sequence->width; s->height = sequence->height; s->frame_rate = sequence->frame_rate; s->audio_frequency = sequence->audio_frequency; s->audio_layout = sequence->audio_layout; // copy all selected clips to the nest for (int i=0;iappend(new DeleteClipAction(sequence, selected_clips.at(i))); // copy to new Clip* copy = sequence->clips.at(selected_clips.at(i))->copy(s); copy->timeline_in -= earliest_point; copy->timeline_out -= earliest_point; s->clips.append(copy); } // add sequence to project Media* m = panel_project->new_sequence(ca, s, false, NULL); // add nested sequence to active sequence QVector media_list; media_list.append(m); panel_timeline->create_ghosts_from_media(sequence, earliest_point, media_list); panel_timeline->add_clips_from_ghosts(ca, sequence); panel_effect_controls->clear_effects(true); sequence->selections.clear(); undo_stack.push(ca); update_ui(true); } } } void MainWindow::paste_insert() { if (panel_timeline->focused() && sequence != NULL) { panel_timeline->paste(true); } } void MainWindow::toggle_bool_action() { QAction* action = static_cast(sender()); bool* variable = reinterpret_cast(action->data().value()); *variable = !(*variable); update_ui(false); } void MainWindow::set_autoscroll() { QAction* action = static_cast(sender()); config.autoscroll = action->data().toInt(); } void MainWindow::menu_click_button() { if (panel_timeline->focused() || panel_effect_controls->keyframe_focus() || panel_footage_viewer->is_focused() || panel_sequence_viewer->is_focused()) reinterpret_cast(static_cast(sender())->data().value())->click(); } void MainWindow::toggle_panel_visibility() { QAction* action = static_cast(sender()); QDockWidget* w = reinterpret_cast(action->data().value()); w->setVisible(!w->isVisible()); } void MainWindow::set_timecode_view() { QAction* action = static_cast(sender()); config.timecode_view = action->data().toInt(); update_ui(false); }