1 /*
2  * Copyright (c) 2011-2021 Meltytech, LLC
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "mainwindow.h"
19 #include "ui_mainwindow.h"
20 #include "scrubbar.h"
21 #include "openotherdialog.h"
22 #include "player.h"
23 #include "defaultlayouts.h"
24 #include "widgets/alsawidget.h"
25 #include "widgets/colorbarswidget.h"
26 #include "widgets/colorproducerwidget.h"
27 #include "widgets/countproducerwidget.h"
28 #include "widgets/decklinkproducerwidget.h"
29 #include "widgets/directshowvideowidget.h"
30 #include "widgets/isingwidget.h"
31 #include "widgets/jackproducerwidget.h"
32 #include "widgets/toneproducerwidget.h"
33 #include "widgets/lissajouswidget.h"
34 #include "widgets/networkproducerwidget.h"
35 #include "widgets/noisewidget.h"
36 #include "widgets/plasmawidget.h"
37 #include "widgets/pulseaudiowidget.h"
38 #include "widgets/video4linuxwidget.h"
39 #include "widgets/x11grabwidget.h"
40 #include "widgets/avformatproducerwidget.h"
41 #include "widgets/imageproducerwidget.h"
42 #include "widgets/blipproducerwidget.h"
43 #include "widgets/newprojectfolder.h"
44 #include "docks/recentdock.h"
45 #include "docks/encodedock.h"
46 #include "docks/jobsdock.h"
47 #include "jobqueue.h"
48 #include "docks/playlistdock.h"
49 #include "glwidget.h"
50 #include "controllers/filtercontroller.h"
51 #include "controllers/scopecontroller.h"
52 #include "docks/filtersdock.h"
53 #include "dialogs/customprofiledialog.h"
54 #include "settings.h"
55 #include "leapnetworklistener.h"
56 #include "database.h"
57 #include "widgets/gltestwidget.h"
58 #include "docks/timelinedock.h"
59 #include "widgets/lumamixtransition.h"
60 #include "qmltypes/qmlutilities.h"
61 #include "qmltypes/qmlapplication.h"
62 #include "autosavefile.h"
63 #include "commands/playlistcommands.h"
64 #include "shotcut_mlt_properties.h"
65 #include "widgets/avfoundationproducerwidget.h"
66 #include "dialogs/textviewerdialog.h"
67 #include "widgets/gdigrabwidget.h"
68 #include "models/audiolevelstask.h"
69 #include "widgets/trackpropertieswidget.h"
70 #include "widgets/timelinepropertieswidget.h"
71 #include "dialogs/unlinkedfilesdialog.h"
72 #include "docks/keyframesdock.h"
73 #include "util.h"
74 #include "models/keyframesmodel.h"
75 #include "dialogs/listselectiondialog.h"
76 #include "widgets/textproducerwidget.h"
77 #include "qmltypes/qmlprofile.h"
78 #include "dialogs/longuitask.h"
79 #include "dialogs/systemsyncdialog.h"
80 #include "proxymanager.h"
81 #ifdef Q_OS_WIN
82 #include "windowstools.h"
83 #endif
84 
85 #include <QtWidgets>
86 #include <Logger.h>
87 #include <QThreadPool>
88 #include <QtConcurrent/QtConcurrentRun>
89 #include <QMutexLocker>
90 #include <QQuickItem>
91 #include <QtNetwork>
92 #include <QJsonDocument>
93 #include <QJSEngine>
94 #include <QDirIterator>
95 #include <QQuickWindow>
96 #include <QVersionNumber>
97 #include <clocale>
98 
eventDebugCallback(void ** data)99 static bool eventDebugCallback(void **data)
100 {
101     QEvent *event = reinterpret_cast<QEvent*>(data[1]);
102     if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
103         QObject *receiver = reinterpret_cast<QObject*>(data[0]);
104         LOG_DEBUG() << event << "->" << receiver;
105     }
106     else if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) {
107         QObject *receiver = reinterpret_cast<QObject*>(data[0]);
108         LOG_DEBUG() << event << "->" << receiver;
109     }
110     return false;
111 }
112 
113 static const int AUTOSAVE_TIMEOUT_MS = 60000;
114 static const char* kReservedLayoutPrefix = "__%1";
115 static const char* kLayoutSwitcherName("layoutSwitcherGrid");
116 
MainWindow()117 MainWindow::MainWindow()
118     : QMainWindow(0)
119     , ui(new Ui::MainWindow)
120     , m_isKKeyPressed(false)
121     , m_keyerGroup(0)
122     , m_previewScaleGroup(0)
123     , m_keyerMenu(0)
124     , m_isPlaylistLoaded(false)
125     , m_exitCode(EXIT_SUCCESS)
126     , m_navigationPosition(0)
127     , m_upgradeUrl("https://www.shotcut.org/download/")
128     , m_keyframesDock(0)
129 {
130 #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
131     QLibrary libJack("libjack.so.0");
132     if (!libJack.load()) {
133         QMessageBox::critical(this, qApp->applicationName(),
134             tr("Error: This program requires the JACK 1 library.\n\nPlease install it using your package manager. It may be named libjack0, jack-audio-connection-kit, jack, or similar."));
135         ::exit(EXIT_FAILURE);
136     } else {
137         libJack.unload();
138     }
139     QLibrary libSDL("libSDL2-2.0.so.0");
140     if (!libSDL.load()) {
141         QMessageBox::critical(this, qApp->applicationName(),
142             tr("Error: This program requires the SDL 2 library.\n\nPlease install it using your package manager. It may be named libsdl2-2.0-0, SDL2, or similar."));
143         ::exit(EXIT_FAILURE);
144     } else {
145         libSDL.unload();
146     }
147 #endif
148 
149     if (!qgetenv("OBSERVE_FOCUS").isEmpty()) {
150         connect(qApp, &QApplication::focusChanged,
151                 this, &MainWindow::onFocusChanged);
152         connect(qApp, &QGuiApplication::focusObjectChanged,
153                 this, &MainWindow::onFocusObjectChanged);
154         connect(qApp, &QGuiApplication::focusWindowChanged,
155                 this, &MainWindow::onFocusWindowChanged);
156     }
157 
158     if (!qgetenv("EVENT_DEBUG").isEmpty())
159         QInternal::registerCallback(QInternal::EventNotifyCallback, eventDebugCallback);
160 
161     LOG_DEBUG() << "begin";
162     LOG_INFO() << "device pixel ratio =" << devicePixelRatioF();
163 #ifndef Q_OS_WIN
164     new GLTestWidget(this);
165 #endif
166     connect(&m_autosaveTimer, SIGNAL(timeout()), this, SLOT(onAutosaveTimeout()));
167     m_autosaveTimer.start(AUTOSAVE_TIMEOUT_MS);
168 
169     // Initialize all QML types
170     QmlUtilities::registerCommonTypes();
171 
172     // Create the UI.
173     ui->setupUi(this);
174 #ifdef Q_OS_MAC
175     // Qt 5 on OS X supports the standard Full Screen window widget.
176     ui->actionEnter_Full_Screen->setVisible(false);
177     // OS X has a standard Full Screen shortcut we should use.
178     ui->actionEnter_Full_Screen->setShortcut(QKeySequence((Qt::CTRL + Qt::META + Qt::Key_F)));
179 #endif
180     setDockNestingEnabled(true);
181     ui->statusBar->hide();
182 
183     // Connect UI signals.
184     connect(ui->actionOpen, SIGNAL(triggered()), this, SLOT(openVideo()));
185     connect(ui->actionAbout_Qt, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
186     connect(this, &MainWindow::producerOpened, this, &MainWindow::onProducerOpened);
187     connect(ui->mainToolBar, SIGNAL(visibilityChanged(bool)), SLOT(onToolbarVisibilityChanged(bool)));
188 
189     // Accept drag-n-drop of files.
190     this->setAcceptDrops(true);
191 
192     // Setup the undo stack.
193     m_undoStack = new QUndoStack(this);
194     m_undoStack->setUndoLimit(Settings.undoLimit());
195     QAction *undoAction = m_undoStack->createUndoAction(this);
196     QAction *redoAction = m_undoStack->createRedoAction(this);
197     undoAction->setIcon(QIcon::fromTheme("edit-undo", QIcon(":/icons/oxygen/32x32/actions/edit-undo.png")));
198     redoAction->setIcon(QIcon::fromTheme("edit-redo", QIcon(":/icons/oxygen/32x32/actions/edit-redo.png")));
199     undoAction->setShortcut(QString::fromLatin1("Ctrl+Z"));
200 #ifdef Q_OS_WIN
201     redoAction->setShortcut(QString::fromLatin1("Ctrl+Y"));
202 #else
203     redoAction->setShortcut(QString::fromLatin1("Ctrl+Shift+Z"));
204 #endif
205     ui->menuEdit->insertAction(ui->actionCut, undoAction);
206     ui->menuEdit->insertAction(ui->actionCut, redoAction);
207     ui->menuEdit->insertSeparator(ui->actionCut);
208     ui->actionUndo->setIcon(undoAction->icon());
209     ui->actionRedo->setIcon(redoAction->icon());
210     ui->actionUndo->setToolTip(undoAction->toolTip());
211     ui->actionRedo->setToolTip(redoAction->toolTip());
212     connect(m_undoStack, SIGNAL(canUndoChanged(bool)), ui->actionUndo, SLOT(setEnabled(bool)));
213     connect(m_undoStack, SIGNAL(canRedoChanged(bool)), ui->actionRedo, SLOT(setEnabled(bool)));
214 
215     // Add the player widget.
216     m_player = new Player;
217     MLT.videoWidget()->installEventFilter(this);
218     ui->centralWidget->layout()->addWidget(m_player);
219     connect(this, &MainWindow::producerOpened, m_player, &Player::onProducerOpened);
220     connect(m_player, SIGNAL(showStatusMessage(QString)), this, SLOT(showStatusMessage(QString)));
221     connect(m_player, SIGNAL(inChanged(int)), this, SLOT(onCutModified()));
222     connect(m_player, SIGNAL(outChanged(int)), this, SLOT(onCutModified()));
223     connect(m_player, SIGNAL(tabIndexChanged(int)), SLOT(onPlayerTabIndexChanged(int)));
224     connect(MLT.videoWidget(), SIGNAL(started()), SLOT(processMultipleFiles()));
225     connect(MLT.videoWidget(), SIGNAL(paused()), m_player, SLOT(showPaused()));
226     connect(MLT.videoWidget(), SIGNAL(playing()), m_player, SLOT(showPlaying()));
227     connect(MLT.videoWidget(), SIGNAL(toggleZoom(bool)), m_player, SLOT(toggleZoom(bool)));
228 
229     setupSettingsMenu();
230     setupOpenOtherMenu();
231     readPlayerSettings();
232     configureVideoWidget();
233 
234     // setup the layout switcher
235     auto group = new QActionGroup(this);
236     group->addAction(ui->actionLayoutLogging);
237     group->addAction(ui->actionLayoutEditing);
238     group->addAction(ui->actionLayoutEffects);
239     group->addAction(ui->actionLayoutAudio);
240     group->addAction(ui->actionLayoutColor);
241     group->addAction(ui->actionLayoutPlayer);
242     switch (Settings.layoutMode()) {
243     case LayoutMode::Custom:
244         break;
245     case LayoutMode::Logging:
246         ui->actionLayoutLogging->setChecked(true);
247         break;
248     case LayoutMode::Editing:
249         ui->actionLayoutEditing->setChecked(true);
250         break;
251     case LayoutMode::Effects:
252         ui->actionLayoutEffects->setChecked(true);
253         break;
254     case LayoutMode::Color:
255         ui->actionLayoutColor->setChecked(true);
256         break;
257     case LayoutMode::Audio:
258         ui->actionLayoutAudio->setChecked(true);
259         break;
260     case LayoutMode::PlayerOnly:
261         ui->actionLayoutPlayer->setChecked(true);
262         break;
263     default:
264         ui->actionLayoutEditing->setChecked(true);
265         break;
266     }
267     // Center the layout actions in the remaining toolbar space.
268     auto spacer = new QWidget;
269     spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
270     ui->mainToolBar->insertWidget(ui->dummyAction, spacer);
271     spacer = new QWidget;
272     spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
273     ui->mainToolBar->addWidget(spacer);
274     updateLayoutSwitcher();
275 
276 #ifndef SHOTCUT_NOUPGRADE
277     if (Settings.noUpgrade() || qApp->property("noupgrade").toBool())
278 #endif
279         delete ui->actionUpgrade;
280 
281     // Add the docks.
282     m_scopeController = new ScopeController(this, ui->menuView);
283     QDockWidget* audioMeterDock = findChild<QDockWidget*>("AudioPeakMeterDock");
284     if (audioMeterDock) {
285         audioMeterDock->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_1));
286         connect(ui->actionAudioMeter, SIGNAL(triggered()), audioMeterDock->toggleViewAction(), SLOT(trigger()));
287     }
288 
289     m_propertiesDock = new QDockWidget(tr("Properties"), this);
290     m_propertiesDock->hide();
291     m_propertiesDock->setObjectName("propertiesDock");
292     m_propertiesDock->setWindowIcon(ui->actionProperties->icon());
293     m_propertiesDock->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_2));
294     m_propertiesDock->toggleViewAction()->setIcon(ui->actionProperties->icon());
295     m_propertiesDock->setMinimumWidth(300);
296     QScrollArea* scroll = new QScrollArea;
297     scroll->setWidgetResizable(true);
298     m_propertiesDock->setWidget(scroll);
299     ui->menuView->addAction(m_propertiesDock->toggleViewAction());
300     connect(m_propertiesDock->toggleViewAction(), SIGNAL(triggered(bool)), this, SLOT(onPropertiesDockTriggered(bool)));
301     connect(ui->actionProperties, SIGNAL(triggered()), this, SLOT(onPropertiesDockTriggered()));
302 
303     m_recentDock = new RecentDock(this);
304     m_recentDock->hide();
305     m_recentDock->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_3));
306     ui->menuView->addAction(m_recentDock->toggleViewAction());
307     connect(m_recentDock, SIGNAL(itemActivated(QString)), this, SLOT(open(QString)));
308     connect(m_recentDock->toggleViewAction(), SIGNAL(triggered(bool)), this, SLOT(onRecentDockTriggered(bool)));
309     connect(ui->actionRecent, SIGNAL(triggered()), this, SLOT(onRecentDockTriggered()));
310     connect(this, SIGNAL(openFailed(QString)), m_recentDock, SLOT(remove(QString)));
311     connect(m_recentDock, &RecentDock::deleted, m_player->projectWidget(), &NewProjectFolder::updateRecentProjects);
312 
313     m_playlistDock = new PlaylistDock(this);
314     m_playlistDock->hide();
315     m_playlistDock->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_4));
316     ui->menuView->addAction(m_playlistDock->toggleViewAction());
317     connect(m_playlistDock->toggleViewAction(), SIGNAL(triggered(bool)), this, SLOT(onPlaylistDockTriggered(bool)));
318     connect(ui->actionPlaylist, SIGNAL(triggered()), this, SLOT(onPlaylistDockTriggered()));
319     connect(m_playlistDock, SIGNAL(clipOpened(Mlt::Producer*, bool)), this, SLOT(openCut(Mlt::Producer*, bool)));
320     connect(m_playlistDock, SIGNAL(itemActivated(int)), this, SLOT(seekPlaylist(int)));
321     connect(m_playlistDock, SIGNAL(showStatusMessage(QString)), this, SLOT(showStatusMessage(QString)));
322     connect(m_playlistDock->model(), SIGNAL(created()), this, SLOT(onPlaylistCreated()));
323     connect(m_playlistDock->model(), SIGNAL(cleared()), this, SLOT(onPlaylistCleared()));
324     connect(m_playlistDock->model(), SIGNAL(closed()), this, SLOT(onPlaylistClosed()));
325     connect(m_playlistDock->model(), SIGNAL(modified()), this, SLOT(onPlaylistModified()));
326     connect(m_playlistDock->model(), SIGNAL(loaded()), this, SLOT(onPlaylistLoaded()));
327     connect(this, SIGNAL(producerOpened()), m_playlistDock, SLOT(onProducerOpened()));
328     if (!Settings.playerGPU())
329         connect(m_playlistDock->model(), SIGNAL(loaded()), this, SLOT(updateThumbnails()));
330     connect(m_player, &Player::inChanged, m_playlistDock, &PlaylistDock::onInChanged);
331     connect(m_player, &Player::outChanged, m_playlistDock, &PlaylistDock::onOutChanged);
332     connect(m_playlistDock->model(), &PlaylistModel::inChanged, this, &MainWindow::onPlaylistInChanged);
333     connect(m_playlistDock->model(), &PlaylistModel::outChanged, this, &MainWindow::onPlaylistOutChanged);
334 
335     m_timelineDock = new TimelineDock(this);
336     m_timelineDock->hide();
337     m_timelineDock->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_5));
338     ui->menuView->addAction(m_timelineDock->toggleViewAction());
339     connect(m_timelineDock->toggleViewAction(), SIGNAL(triggered(bool)), this, SLOT(onTimelineDockTriggered(bool)));
340     connect(ui->actionTimeline, SIGNAL(triggered()), SLOT(onTimelineDockTriggered()));
341     connect(m_player, SIGNAL(seeked(int)), m_timelineDock, SLOT(onSeeked(int)));
342     connect(m_timelineDock, SIGNAL(seeked(int)), SLOT(seekTimeline(int)));
343     connect(m_timelineDock, SIGNAL(clipClicked()), SLOT(onTimelineClipSelected()));
344     connect(m_timelineDock, SIGNAL(showStatusMessage(QString)), this, SLOT(showStatusMessage(QString)));
345     connect(m_timelineDock->model(), SIGNAL(showStatusMessage(QString)), this, SLOT(showStatusMessage(QString)));
346     connect(m_timelineDock->model(), SIGNAL(created()), SLOT(onMultitrackCreated()));
347     connect(m_timelineDock->model(), SIGNAL(closed()), SLOT(onMultitrackClosed()));
348     connect(m_timelineDock->model(), SIGNAL(modified()), SLOT(onMultitrackModified()));
349     connect(m_timelineDock->model(), SIGNAL(durationChanged()), SLOT(onMultitrackDurationChanged()));
350     connect(m_timelineDock, SIGNAL(clipOpened(Mlt::Producer*)), SLOT(openCut(Mlt::Producer*)));
351     connect(m_timelineDock->model(), &MultitrackModel::seeked, this, &MainWindow::seekTimeline);
352     connect(m_timelineDock->model(), SIGNAL(scaleFactorChanged()), m_player, SLOT(pause()));
353     connect(m_timelineDock, SIGNAL(selected(Mlt::Producer*)), SLOT(loadProducerWidget(Mlt::Producer*)));
354     connect(m_timelineDock, SIGNAL(selectionChanged()), SLOT(onTimelineSelectionChanged()));
355     connect(m_timelineDock, SIGNAL(clipCopied()), SLOT(onClipCopied()));
356     connect(m_timelineDock, SIGNAL(filteredClicked()), SLOT(onFiltersDockTriggered()));
357     connect(m_playlistDock, SIGNAL(addAllTimeline(Mlt::Playlist*)), SLOT(onTimelineDockTriggered()));
358     connect(m_playlistDock, SIGNAL(addAllTimeline(Mlt::Playlist*, bool)), SLOT(onAddAllToTimeline(Mlt::Playlist*, bool)));
359     connect(m_player, SIGNAL(previousSought()), m_timelineDock, SLOT(seekPreviousEdit()));
360     connect(m_player, SIGNAL(nextSought()), m_timelineDock, SLOT(seekNextEdit()));
361 
362     m_filterController = new FilterController(this);
363     m_filtersDock = new FiltersDock(m_filterController->metadataModel(), m_filterController->attachedModel(), this);
364     m_filtersDock->setMinimumSize(400, 300);
365     m_filtersDock->hide();
366     m_filtersDock->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_6));
367     ui->menuView->addAction(m_filtersDock->toggleViewAction());
368     connect(m_filtersDock, SIGNAL(currentFilterRequested(int)), m_filterController, SLOT(setCurrentFilter(int)), Qt::QueuedConnection);
369     connect(m_filtersDock->toggleViewAction(), SIGNAL(triggered(bool)), this, SLOT(onFiltersDockTriggered(bool)));
370     connect(ui->actionFilters, SIGNAL(triggered()), this, SLOT(onFiltersDockTriggered()));
371     connect(m_filterController, SIGNAL(currentFilterChanged(QmlFilter*, QmlMetadata*, int)), m_filtersDock, SLOT(setCurrentFilter(QmlFilter*, QmlMetadata*, int)));
372     connect(this, SIGNAL(producerOpened()), m_filterController, SLOT(setProducer()));
373     connect(m_filterController->attachedModel(), SIGNAL(changed()), SLOT(onFilterModelChanged()));
374     connect(m_filtersDock, SIGNAL(changed()), SLOT(onFilterModelChanged()));
375     connect(m_filterController, SIGNAL(filterChanged(Mlt::Filter*)),
376             m_timelineDock->model(), SLOT(onFilterChanged(Mlt::Filter*)));
377     connect(m_filterController->attachedModel(), SIGNAL(addedOrRemoved(Mlt::Producer*)),
378             m_timelineDock->model(), SLOT(filterAddedOrRemoved(Mlt::Producer*)));
379     connect(&QmlApplication::singleton(), SIGNAL(filtersPasted(Mlt::Producer*)),
380             m_timelineDock->model(), SLOT(filterAddedOrRemoved(Mlt::Producer*)));
381     connect(&QmlApplication::singleton(), &QmlApplication::filtersPasted,
382             this, &MainWindow::onProducerModified);
383     connect(m_filterController, SIGNAL(statusChanged(QString)), this, SLOT(showStatusMessage(QString)));
384     connect(m_timelineDock, SIGNAL(fadeInChanged(int)), m_filterController, SLOT(onFadeInChanged()));
385     connect(m_timelineDock, SIGNAL(fadeOutChanged(int)), m_filterController, SLOT(onFadeOutChanged()));
386     connect(m_timelineDock, SIGNAL(selected(Mlt::Producer*)), m_filterController, SLOT(setProducer(Mlt::Producer*)));
387     connect(m_player, SIGNAL(seeked(int)), m_filtersDock, SLOT(onSeeked(int)), Qt::QueuedConnection);
388     connect(m_filtersDock, SIGNAL(seeked(int)), SLOT(seekKeyframes(int)));
389     connect(MLT.videoWidget(), SIGNAL(frameDisplayed(const SharedFrame&)), m_filtersDock, SLOT(onShowFrame(const SharedFrame&)));
390     connect(m_player, SIGNAL(inChanged(int)), m_filtersDock, SIGNAL(producerInChanged(int)));
391     connect(m_player, SIGNAL(outChanged(int)), m_filtersDock, SIGNAL(producerOutChanged(int)));
392     connect(m_player, SIGNAL(inChanged(int)), m_filterController, SLOT(onFilterInChanged(int)));
393     connect(m_player, SIGNAL(outChanged(int)), m_filterController, SLOT(onFilterOutChanged(int)));
394     connect(m_timelineDock->model(), SIGNAL(filterInChanged(int, Mlt::Filter*)), m_filterController, SLOT(onFilterInChanged(int, Mlt::Filter*)));
395     connect(m_timelineDock->model(), SIGNAL(filterOutChanged(int, Mlt::Filter*)), m_filterController, SLOT(onFilterOutChanged(int, Mlt::Filter*)));
396 
397     m_keyframesDock = new KeyframesDock(m_filtersDock->qmlProducer(), this);
398     m_keyframesDock->hide();
399     m_keyframesDock->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_7));
400     ui->menuView->addAction(m_keyframesDock->toggleViewAction());
401     connect(m_keyframesDock->toggleViewAction(), SIGNAL(triggered(bool)), this, SLOT(onKeyframesDockTriggered(bool)));
402     connect(ui->actionKeyframes, SIGNAL(triggered()), this, SLOT(onKeyframesDockTriggered()));
403     connect(m_filterController, SIGNAL(currentFilterChanged(QmlFilter*, QmlMetadata*, int)), m_keyframesDock, SLOT(setCurrentFilter(QmlFilter*, QmlMetadata*)));
404     connect(m_keyframesDock, SIGNAL(visibilityChanged(bool)), m_filtersDock->qmlProducer(), SLOT(remakeAudioLevels(bool)));
405 
406     m_historyDock = new QDockWidget(tr("History"), this);
407     m_historyDock->hide();
408     m_historyDock->setObjectName("historyDock");
409     m_historyDock->setWindowIcon(ui->actionHistory->icon());
410     m_historyDock->toggleViewAction()->setIcon(ui->actionHistory->icon());
411     m_historyDock->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_8));
412     m_historyDock->setMinimumWidth(150);
413     ui->menuView->addAction(m_historyDock->toggleViewAction());
414     connect(m_historyDock->toggleViewAction(), SIGNAL(triggered(bool)), this, SLOT(onHistoryDockTriggered(bool)));
415     connect(ui->actionHistory, SIGNAL(triggered()), this, SLOT(onHistoryDockTriggered()));
416     QUndoView* undoView = new QUndoView(m_undoStack, m_historyDock);
417     undoView->setObjectName("historyView");
418     undoView->setAlternatingRowColors(true);
419     undoView->setSpacing(2);
420     m_historyDock->setWidget(undoView);
421     ui->actionUndo->setDisabled(true);
422     ui->actionRedo->setDisabled(true);
423 
424     m_encodeDock = new EncodeDock(this);
425     m_encodeDock->hide();
426     m_encodeDock->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_9));
427     ui->menuView->addAction(m_encodeDock->toggleViewAction());
428     connect(this, SIGNAL(producerOpened()), m_encodeDock, SLOT(onProducerOpened()));
429     connect(ui->actionEncode, SIGNAL(triggered()), this, SLOT(onEncodeTriggered()));
430     connect(ui->actionExportVideo, SIGNAL(triggered()), this, SLOT(onEncodeTriggered()));
431     connect(m_encodeDock->toggleViewAction(), SIGNAL(triggered(bool)), this, SLOT(onEncodeTriggered(bool)));
432     connect(m_encodeDock, SIGNAL(captureStateChanged(bool)), m_player, SLOT(onCaptureStateChanged(bool)));
433     connect(m_encodeDock, SIGNAL(captureStateChanged(bool)), m_propertiesDock, SLOT(setDisabled(bool)));
434     connect(m_encodeDock, SIGNAL(captureStateChanged(bool)), m_recentDock, SLOT(setDisabled(bool)));
435     connect(m_encodeDock, SIGNAL(captureStateChanged(bool)), m_filtersDock, SLOT(setDisabled(bool)));
436     connect(m_encodeDock, SIGNAL(captureStateChanged(bool)), m_keyframesDock, SLOT(setDisabled(bool)));
437     connect(m_encodeDock, SIGNAL(captureStateChanged(bool)), ui->actionOpen, SLOT(setDisabled(bool)));
438     connect(m_encodeDock, SIGNAL(captureStateChanged(bool)), ui->actionOpenOther, SLOT(setDisabled(bool)));
439     connect(m_encodeDock, SIGNAL(captureStateChanged(bool)), ui->actionExit, SLOT(setDisabled(bool)));
440     connect(m_encodeDock, SIGNAL(captureStateChanged(bool)), this, SLOT(onCaptureStateChanged(bool)));
441     connect(m_encodeDock, SIGNAL(captureStateChanged(bool)), m_historyDock, SLOT(setDisabled(bool)));
442     connect(this, SIGNAL(profileChanged()), m_encodeDock, SLOT(onProfileChanged()));
443     connect(this, SIGNAL(profileChanged()), SLOT(onProfileChanged()));
444     connect(this, SIGNAL(profileChanged()), &QmlProfile::singleton(), SIGNAL(profileChanged()));
445     connect(this, SIGNAL(audioChannelsChanged()), m_encodeDock, SLOT(onAudioChannelsChanged()));
446     connect(m_playlistDock->model(), SIGNAL(modified()), m_encodeDock, SLOT(onProducerOpened()));
447     connect(m_timelineDock, SIGNAL(clipCopied()), m_encodeDock, SLOT(onProducerOpened()));
448     m_encodeDock->onProfileChanged();
449 
450     m_jobsDock = new JobsDock(this);
451     m_jobsDock->hide();
452     m_jobsDock->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_0));
453     ui->menuView->addAction(m_jobsDock->toggleViewAction());
454     connect(&JOBS, SIGNAL(jobAdded()), m_jobsDock, SLOT(onJobAdded()));
455     connect(m_jobsDock->toggleViewAction(), SIGNAL(triggered(bool)), this, SLOT(onJobsDockTriggered(bool)));
456     connect(ui->actionJobs, SIGNAL(triggered()), this, SLOT(onJobsDockTriggered()));
457 
458     addDockWidget(Qt::LeftDockWidgetArea, m_propertiesDock);
459     addDockWidget(Qt::RightDockWidgetArea, m_recentDock);
460     addDockWidget(Qt::LeftDockWidgetArea, m_playlistDock);
461     addDockWidget(Qt::BottomDockWidgetArea, m_timelineDock);
462     addDockWidget(Qt::LeftDockWidgetArea, m_filtersDock);
463     addDockWidget(Qt::BottomDockWidgetArea, m_keyframesDock);
464     addDockWidget(Qt::RightDockWidgetArea, m_historyDock);
465     addDockWidget(Qt::LeftDockWidgetArea, m_encodeDock);
466     addDockWidget(Qt::RightDockWidgetArea, m_jobsDock);
467     tabifyDockWidget(m_propertiesDock, m_playlistDock);
468     tabifyDockWidget(m_playlistDock, m_filtersDock);
469     tabifyDockWidget(m_filtersDock, m_encodeDock);
470     splitDockWidget(m_recentDock, findChild<QDockWidget*>("AudioWaveformDock"), Qt::Vertical);
471     splitDockWidget(audioMeterDock, m_recentDock, Qt::Horizontal);
472     tabifyDockWidget(m_recentDock, m_historyDock);
473     tabifyDockWidget(m_historyDock, m_jobsDock);
474     tabifyDockWidget(m_keyframesDock, m_timelineDock);
475     m_recentDock->raise();
476     resetDockCorners();
477 
478     // Configure the View menu.
479     ui->menuView->addSeparator();
480     ui->menuView->addAction(ui->actionApplicationLog);
481 
482     // connect video widget signals
483     Mlt::GLWidget* videoWidget = (Mlt::GLWidget*) &(MLT);
484     connect(videoWidget, SIGNAL(dragStarted()), m_playlistDock, SLOT(onPlayerDragStarted()));
485     connect(videoWidget, SIGNAL(seekTo(int)), m_player, SLOT(seek(int)));
486     connect(videoWidget, SIGNAL(gpuNotSupported()), this, SLOT(onGpuNotSupported()));
487     connect(videoWidget->quickWindow(), SIGNAL(sceneGraphInitialized()), SLOT(onSceneGraphInitialized()), Qt::QueuedConnection);
488     connect(videoWidget, SIGNAL(frameDisplayed(const SharedFrame&)), m_scopeController, SIGNAL(newFrame(const SharedFrame&)));
489     connect(m_filterController, SIGNAL(currentFilterChanged(QmlFilter*, QmlMetadata*, int)), videoWidget, SLOT(setCurrentFilter(QmlFilter*, QmlMetadata*)));
490 
491     readWindowSettings();
492 
493     setFocus();
494     setCurrentFile("");
495 
496     LeapNetworkListener* leap = new LeapNetworkListener(this);
497     connect(leap, SIGNAL(shuttle(float)), SLOT(onShuttle(float)));
498     connect(leap, SIGNAL(jogRightFrame()), SLOT(stepRightOneFrame()));
499     connect(leap, SIGNAL(jogRightSecond()), SLOT(stepRightOneSecond()));
500     connect(leap, SIGNAL(jogLeftFrame()), SLOT(stepLeftOneFrame()));
501     connect(leap, SIGNAL(jogLeftSecond()), SLOT(stepLeftOneSecond()));
502 
503     connect(&m_network, SIGNAL(finished(QNetworkReply*)), SLOT(onUpgradeCheckFinished(QNetworkReply*)));
504 
505     QThreadPool::globalInstance()->setMaxThreadCount(qMin(4, QThreadPool::globalInstance()->maxThreadCount()));
506 
507     ProxyManager::removePending();
508 
509     LOG_DEBUG() << "end";
510 }
511 
onFocusWindowChanged(QWindow *) const512 void MainWindow::onFocusWindowChanged(QWindow *) const
513 {
514     LOG_DEBUG() << "Focuswindow changed";
515     LOG_DEBUG() << "Current focusWidget:" << QApplication::focusWidget();
516     LOG_DEBUG() << "Current focusObject:" << QApplication::focusObject();
517     LOG_DEBUG() << "Current focusWindow:" << QApplication::focusWindow();
518 }
519 
onFocusObjectChanged(QObject *) const520 void MainWindow::onFocusObjectChanged(QObject *) const
521 {
522     LOG_DEBUG() << "Focusobject changed";
523     LOG_DEBUG() << "Current focusWidget:" << QApplication::focusWidget();
524     LOG_DEBUG() << "Current focusObject:" << QApplication::focusObject();
525     LOG_DEBUG() << "Current focusWindow:" << QApplication::focusWindow();
526 }
527 
onTimelineClipSelected()528 void MainWindow::onTimelineClipSelected()
529 {
530     // Synchronize navigation position with timeline selection.
531     TimelineDock * t = m_timelineDock;
532 
533     if (t->selection().isEmpty())
534         return;
535 
536     m_navigationPosition = t->centerOfClip(t->selection().first().y(), t->selection().first().x());
537 
538     // Switch to Project player.
539     if (m_player->tabIndex() != Player::ProjectTabIndex) {
540         t->saveAndClearSelection();
541         m_player->onTabBarClicked(Player::ProjectTabIndex);
542     }
543 }
544 
onAddAllToTimeline(Mlt::Playlist * playlist,bool skipProxy)545 void MainWindow::onAddAllToTimeline(Mlt::Playlist* playlist, bool skipProxy)
546 {
547     // We stop the player because of a bug on Windows that results in some
548     // strange memory leak when using Add All To Timeline, more noticeable
549     // with (high res?) still image files.
550     if (MLT.isSeekable())
551         m_player->pause();
552     else
553         m_player->stop();
554     m_timelineDock->appendFromPlaylist(playlist, skipProxy);
555 }
556 
singleton()557 MainWindow& MainWindow::singleton()
558 {
559     static MainWindow* instance = new MainWindow;
560     return *instance;
561 }
562 
~MainWindow()563 MainWindow::~MainWindow()
564 {
565     delete ui;
566     Mlt::Controller::destroy();
567 }
568 
setupSettingsMenu()569 void MainWindow::setupSettingsMenu()
570 {
571     LOG_DEBUG() << "begin";
572     QActionGroup* group = new QActionGroup(this);
573     group->addAction(ui->actionChannels1);
574     group->addAction(ui->actionChannels2);
575     group->addAction(ui->actionChannels6);
576     group = new QActionGroup(this);
577     group->addAction(ui->actionOneField);
578     group->addAction(ui->actionLinearBlend);
579 
580     m_previewScaleGroup = new QActionGroup(this);
581     m_previewScaleGroup->addAction(ui->actionPreviewNone);
582     m_previewScaleGroup->addAction(ui->actionPreview360);
583     m_previewScaleGroup->addAction(ui->actionPreview540);
584     m_previewScaleGroup->addAction(ui->actionPreview720);
585 
586     //XXX workaround yadif crashing with mlt_transition
587 //    group->addAction(ui->actionYadifTemporal);
588 //    group->addAction(ui->actionYadifSpatial);
589     ui->actionYadifTemporal->setVisible(false);
590     ui->actionYadifSpatial->setVisible(false);
591 
592     group = new QActionGroup(this);
593     group->addAction(ui->actionNearest);
594     group->addAction(ui->actionBilinear);
595     group->addAction(ui->actionBicubic);
596     group->addAction(ui->actionHyper);
597     if (Settings.playerGPU()) {
598         group = new QActionGroup(this);
599         group->addAction(ui->actionGammaRec709);
600         group->addAction(ui->actionGammaSRGB);
601     } else {
602         delete ui->menuGamma;
603     }
604     m_profileGroup = new QActionGroup(this);
605     m_profileGroup->addAction(ui->actionProfileAutomatic);
606     ui->actionProfileAutomatic->setData(QString());
607     buildVideoModeMenu(ui->menuProfile, m_customProfileMenu, m_profileGroup, ui->actionAddCustomProfile, ui->actionProfileRemove);
608 
609     // Add the SDI and HDMI devices to the Settings menu.
610     m_externalGroup = new QActionGroup(this);
611     ui->actionExternalNone->setData(QString());
612     m_externalGroup->addAction(ui->actionExternalNone);
613 
614     QList<QScreen*> screens = QGuiApplication::screens();
615     int n = screens.size();
616     for (int i = 0; n > 1 && i < n; i++) {
617         QAction* action = new QAction(tr("Screen %1 (%2 x %3 @ %4 Hz)").arg(i)
618             .arg(screens[i]->size().width() * screens[i]->devicePixelRatio())
619             .arg(screens[i]->size().height() * screens[i]->devicePixelRatio())
620             .arg(screens[i]->refreshRate()), this);
621         action->setCheckable(true);
622         action->setData(i);
623         m_externalGroup->addAction(action);
624     }
625 
626     Mlt::Profile profile;
627     Mlt::Consumer decklink(profile, "decklink:");
628     if (decklink.is_valid()) {
629         decklink.set("list_devices", 1);
630         int n = decklink.get_int("devices");
631         for (int i = 0; i < n; ++i) {
632             QString device(decklink.get(QString("device.%1").arg(i).toLatin1().constData()));
633             if (!device.isEmpty()) {
634                 QAction* action = new QAction(device, this);
635                 action->setCheckable(true);
636                 action->setData(QString("decklink:%1").arg(i));
637                 m_externalGroup->addAction(action);
638 
639                 if (!m_keyerGroup) {
640                     m_keyerGroup = new QActionGroup(this);
641                     action = new QAction(tr("Off"), m_keyerGroup);
642                     action->setData(QVariant(0));
643                     action->setCheckable(true);
644                     action = new QAction(tr("Internal"), m_keyerGroup);
645                     action->setData(QVariant(1));
646                     action->setCheckable(true);
647                     action = new QAction(tr("External"), m_keyerGroup);
648                     action->setData(QVariant(2));
649                     action->setCheckable(true);
650                 }
651             }
652         }
653     }
654     if (m_externalGroup->actions().count() > 1)
655         ui->menuExternal->addActions(m_externalGroup->actions());
656     else {
657         delete ui->menuExternal;
658         ui->menuExternal = 0;
659     }
660     if (m_keyerGroup) {
661         m_keyerMenu = ui->menuExternal->addMenu(tr("DeckLink Keyer"));
662         m_keyerMenu->addActions(m_keyerGroup->actions());
663         m_keyerMenu->setDisabled(true);
664         connect(m_keyerGroup, SIGNAL(triggered(QAction*)), this, SLOT(onKeyerTriggered(QAction*)));
665     }
666     connect(m_externalGroup, SIGNAL(triggered(QAction*)), this, SLOT(onExternalTriggered(QAction*)));
667     connect(m_profileGroup, SIGNAL(triggered(QAction*)), this, SLOT(onProfileTriggered(QAction*)));
668 
669     // Setup the language menu actions
670     m_languagesGroup = new QActionGroup(this);
671     QAction* a;
672     a = new QAction(QLocale::languageToString(QLocale::Arabic), m_languagesGroup);
673     a->setCheckable(true);
674     a->setData("ar");
675     a = new QAction(QLocale::languageToString(QLocale::Catalan), m_languagesGroup);
676     a->setCheckable(true);
677     a->setData("ca");
678     a = new QAction(QLocale::languageToString(QLocale::Chinese).append(" (China)"), m_languagesGroup);
679     a->setCheckable(true);
680     a->setData("zh_CN");
681     a = new QAction(QLocale::languageToString(QLocale::Chinese).append(" (Taiwan)"), m_languagesGroup);
682     a->setCheckable(true);
683     a->setData("zh_TW");
684     a = new QAction(QLocale::languageToString(QLocale::Czech), m_languagesGroup);
685     a->setCheckable(true);
686     a->setData("cs");
687     a = new QAction(QLocale::languageToString(QLocale::Danish), m_languagesGroup);
688     a->setCheckable(true);
689     a->setData("da");
690     a = new QAction(QLocale::languageToString(QLocale::Dutch), m_languagesGroup);
691     a->setCheckable(true);
692     a->setData("nl");
693     a = new QAction(QLocale::languageToString(QLocale::English).append(" (Great Britain)"), m_languagesGroup);
694     a->setCheckable(true);
695     a->setData("en_GB");
696     a = new QAction(QLocale::languageToString(QLocale::English).append(" (United States)"), m_languagesGroup);
697     a->setCheckable(true);
698     a->setData("en_US");
699     a = new QAction(QLocale::languageToString(QLocale::Estonian), m_languagesGroup);
700     a->setCheckable(true);
701     a->setData("et");
702     a = new QAction(QLocale::languageToString(QLocale::Finnish), m_languagesGroup);
703     a->setCheckable(true);
704     a->setData("fi");
705     a = new QAction(QLocale::languageToString(QLocale::French), m_languagesGroup);
706     a->setCheckable(true);
707     a->setData("fr");
708     a = new QAction(QLocale::languageToString(QLocale::Gaelic), m_languagesGroup);
709     a->setCheckable(true);
710     a->setData("gd");
711     a = new QAction(QLocale::languageToString(QLocale::Galician), m_languagesGroup);
712     a->setCheckable(true);
713     a->setData("gl");
714     a = new QAction(QLocale::languageToString(QLocale::German), m_languagesGroup);
715     a->setCheckable(true);
716     a->setData("de");
717     a = new QAction(QLocale::languageToString(QLocale::Greek), m_languagesGroup);
718     a->setCheckable(true);
719     a->setData("el");
720     a = new QAction(QLocale::languageToString(QLocale::Hungarian), m_languagesGroup);
721     a->setCheckable(true);
722     a->setData("hu");
723     a = new QAction(QLocale::languageToString(QLocale::Italian), m_languagesGroup);
724     a->setCheckable(true);
725     a->setData("it");
726     a = new QAction(QLocale::languageToString(QLocale::Japanese), m_languagesGroup);
727     a->setCheckable(true);
728     a->setData("ja");
729     a = new QAction(QLocale::languageToString(QLocale::Korean), m_languagesGroup);
730     a->setCheckable(true);
731     a->setData("ko");
732     a = new QAction(QLocale::languageToString(QLocale::Nepali), m_languagesGroup);
733     a->setCheckable(true);
734     a->setData("ne");
735     a = new QAction(QLocale::languageToString(QLocale::NorwegianBokmal), m_languagesGroup);
736     a->setCheckable(true);
737     a->setData("nb");
738     a = new QAction(QLocale::languageToString(QLocale::NorwegianNynorsk), m_languagesGroup);
739     a->setCheckable(true);
740     a->setData("nn");
741     a = new QAction(QLocale::languageToString(QLocale::Occitan), m_languagesGroup);
742     a->setCheckable(true);
743     a->setData("oc");
744     a = new QAction(QLocale::languageToString(QLocale::Polish), m_languagesGroup);
745     a->setCheckable(true);
746     a->setData("pl");
747     a = new QAction(QLocale::languageToString(QLocale::Portuguese).append(" (Brazil)"), m_languagesGroup);
748     a->setCheckable(true);
749     a->setData("pt_BR");
750     a = new QAction(QLocale::languageToString(QLocale::Portuguese).append(" (Portugal)"), m_languagesGroup);
751     a->setCheckable(true);
752     a->setData("pt_PT");
753     a = new QAction(QLocale::languageToString(QLocale::Russian), m_languagesGroup);
754     a->setCheckable(true);
755     a->setData("ru");
756     a = new QAction(QLocale::languageToString(QLocale::Slovak), m_languagesGroup);
757     a->setCheckable(true);
758     a->setData("sk");
759     a = new QAction(QLocale::languageToString(QLocale::Slovenian), m_languagesGroup);
760     a->setCheckable(true);
761     a->setData("sl");
762     a = new QAction(QLocale::languageToString(QLocale::Spanish), m_languagesGroup);
763     a->setCheckable(true);
764     a->setData("es");
765     a = new QAction(QLocale::languageToString(QLocale::Swedish), m_languagesGroup);
766     a->setCheckable(true);
767     a->setData("sv");
768     a = new QAction(QLocale::languageToString(QLocale::Thai), m_languagesGroup);
769     a->setCheckable(true);
770     a->setData("th");
771     a = new QAction(QLocale::languageToString(QLocale::Turkish), m_languagesGroup);
772     a->setCheckable(true);
773     a->setData("tr");
774     a = new QAction(QLocale::languageToString(QLocale::Ukrainian), m_languagesGroup);
775     a->setCheckable(true);
776     a->setData("uk");
777     ui->menuLanguage->addActions(m_languagesGroup->actions());
778     const QString locale = Settings.language();
779     foreach (QAction* action, m_languagesGroup->actions()) {
780         if (action->data().toString().startsWith(locale)) {
781             action->setChecked(true);
782             break;
783         }
784     }
785     connect(m_languagesGroup, SIGNAL(triggered(QAction*)), this, SLOT(onLanguageTriggered(QAction*)));
786 
787     // Setup the themes actions
788     group = new QActionGroup(this);
789     group->addAction(ui->actionSystemTheme);
790     group->addAction(ui->actionFusionDark);
791     group->addAction(ui->actionFusionLight);
792     if (Settings.theme() == "dark")
793         ui->actionFusionDark->setChecked(true);
794     else if (Settings.theme() == "light")
795         ui->actionFusionLight->setChecked(true);
796     else
797         ui->actionSystemTheme->setChecked(true);
798 
799 #ifdef Q_OS_WIN
800     // On Windows, if there is no JACK or it is not running
801     // then Shotcut crashes inside MLT's call to jack_client_open().
802     // Therefore, the JACK option for Shotcut is banned on Windows.
803     delete ui->actionJack;
804     ui->actionJack = 0;
805 #endif
806 #if !defined(Q_OS_MAC)
807     // Setup the display method actions.
808     if (!Settings.playerGPU()) {
809         group = new QActionGroup(this);
810 #if defined(Q_OS_WIN)
811         ui->actionDrawingAutomatic->setData(0);
812         group->addAction(ui->actionDrawingAutomatic);
813         ui->actionDrawingDirectX->setData(Qt::AA_UseOpenGLES);
814         group->addAction(ui->actionDrawingDirectX);
815 #else
816         delete ui->actionDrawingAutomatic;
817         delete ui->actionDrawingDirectX;
818 #endif
819         ui->actionDrawingOpenGL->setData(Qt::AA_UseDesktopOpenGL);
820         group->addAction(ui->actionDrawingOpenGL);
821         ui->actionDrawingSoftware->setData(Qt::AA_UseSoftwareOpenGL);
822         group->addAction(ui->actionDrawingSoftware);
823         connect(group, SIGNAL(triggered(QAction*)), this, SLOT(onDrawingMethodTriggered(QAction*)));
824         switch (Settings.drawMethod()) {
825         case Qt::AA_UseDesktopOpenGL:
826             ui->actionDrawingOpenGL->setChecked(true);
827             break;
828 #if defined(Q_OS_WIN)
829         case Qt::AA_UseOpenGLES:
830             ui->actionDrawingDirectX->setChecked(true);
831             break;
832 #endif
833         case Qt::AA_UseSoftwareOpenGL:
834             ui->actionDrawingSoftware->setChecked(true);
835             break;
836 #if defined(Q_OS_WIN)
837         default:
838             ui->actionDrawingAutomatic->setChecked(true);
839             break;
840 #else
841         default:
842             ui->actionDrawingOpenGL->setChecked(true);
843             break;
844 #endif
845         }
846     } else {
847         // GPU mode only works with OpenGL.
848         delete ui->menuDrawingMethod;
849         ui->menuDrawingMethod = 0;
850     }
851 #else  // Q_OS_MAC
852     delete ui->menuDrawingMethod;
853     ui->menuDrawingMethod = 0;
854 #endif
855 
856     // Add custom layouts to View > Layout submenu.
857     m_layoutGroup = new QActionGroup(this);
858     connect(m_layoutGroup, SIGNAL(triggered(QAction*)), SLOT(onLayoutTriggered(QAction*)));
859     if (Settings.layouts().size() > 0) {
860         ui->menuLayout->addAction(ui->actionLayoutRemove);
861         ui->menuLayout->addSeparator();
862     }
863     foreach (QString name, Settings.layouts())
864         ui->menuLayout->addAction(addLayout(m_layoutGroup, name));
865 
866     if (qApp->property("clearRecent").toBool()) {
867         ui->actionClearRecentOnExit->setVisible(false);
868         Settings.setRecent(QStringList());
869         Settings.setClearRecent(true);
870     } else {
871         ui->actionClearRecentOnExit->setChecked(Settings.clearRecent());
872     }
873 
874 
875     // Initialize the proxy submenu
876     ui->actionUseProxy->setChecked(Settings.proxyEnabled());
877     ui->actionProxyUseProjectFolder->setChecked(Settings.proxyUseProjectFolder());
878     ui->actionProxyUseHardware->setChecked(Settings.proxyUseHardware());
879 
880     LOG_DEBUG() << "end";
881 }
882 
setupOpenOtherMenu()883 void MainWindow::setupOpenOtherMenu()
884 {
885     // Open Other toolbar menu button
886     QScopedPointer<Mlt::Properties> mltProducers(MLT.repository()->producers());
887     QScopedPointer<Mlt::Properties> mltFilters(MLT.repository()->filters());
888     QMenu* otherMenu = new QMenu(this);
889     ui->actionOpenOther2->setMenu(otherMenu);
890 
891     // populate the generators
892     if (mltProducers->get_data("color")) {
893         otherMenu->addAction(tr("Color"), this, SLOT(onOpenOtherTriggered()))->setObjectName("color");
894         if (!Settings.playerGPU() && mltProducers->get_data("qtext") && mltFilters->get_data("dynamictext"))
895             otherMenu->addAction(tr("Text"), this, SLOT(onOpenOtherTriggered()))->setObjectName("text");
896     }
897     if (mltProducers->get_data("noise"))
898         otherMenu->addAction(tr("Noise"), this, SLOT(onOpenOtherTriggered()))->setObjectName("noise");
899     if (mltProducers->get_data("frei0r.ising0r"))
900         otherMenu->addAction(tr("Ising"), this, SLOT(onOpenOtherTriggered()))->setObjectName("ising0r");
901     if (mltProducers->get_data("frei0r.lissajous0r"))
902         otherMenu->addAction(tr("Lissajous"), this, SLOT(onOpenOtherTriggered()))->setObjectName("lissajous0r");
903     if (mltProducers->get_data("frei0r.plasma"))
904         otherMenu->addAction(tr("Plasma"), this, SLOT(onOpenOtherTriggered()))->setObjectName("plasma");
905     if (mltProducers->get_data("frei0r.test_pat_B"))
906         otherMenu->addAction(tr("Color Bars"), this, SLOT(onOpenOtherTriggered()))->setObjectName("test_pat_B");
907     if (mltProducers->get_data("tone"))
908         otherMenu->addAction(tr("Audio Tone"), this, SLOT(onOpenOtherTriggered()))->setObjectName("tone");
909     if (mltProducers->get_data("count"))
910         otherMenu->addAction(tr("Count"), this, SLOT(onOpenOtherTriggered()))->setObjectName("count");
911     if (mltProducers->get_data("blipflash"))
912         otherMenu->addAction(tr("Blip Flash"), this, SLOT(onOpenOtherTriggered()))->setObjectName("blipflash");
913 
914 #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
915     otherMenu->addAction(tr("Video4Linux"), this, SLOT(onOpenOtherTriggered()))->setObjectName("v4l2");
916     otherMenu->addAction(tr("PulseAudio"), this, SLOT(onOpenOtherTriggered()))->setObjectName("pulse");
917     otherMenu->addAction(tr("JACK Audio"), this, SLOT(onOpenOtherTriggered()))->setObjectName("jack");
918     otherMenu->addAction(tr("ALSA Audio"), this, SLOT(onOpenOtherTriggered()))->setObjectName("alsa");
919 #elif defined(Q_OS_WIN) || defined(Q_OS_MAC)
920     otherMenu->addAction(tr("Audio/Video Device"), this, SLOT(onOpenOtherTriggered()))->setObjectName("device");
921 #endif
922     if (mltProducers->get_data("decklink"))
923         otherMenu->addAction(tr("SDI/HDMI"), this, SLOT(onOpenOtherTriggered()))->setObjectName("decklink");
924 }
925 
addProfile(QActionGroup * actionGroup,const QString & desc,const QString & name)926 QAction* MainWindow::addProfile(QActionGroup* actionGroup, const QString& desc, const QString& name)
927 {
928     QAction* action = new QAction(desc, this);
929     action->setCheckable(true);
930     action->setData(name);
931     actionGroup->addAction(action);
932     return action;
933 }
934 
addLayout(QActionGroup * actionGroup,const QString & name)935 QAction*MainWindow::addLayout(QActionGroup* actionGroup, const QString& name)
936 {
937     QAction* action = new QAction(name, this);
938     actionGroup->addAction(action);
939     return action;
940 }
941 
open(Mlt::Producer * producer)942 void MainWindow::open(Mlt::Producer* producer)
943 {
944     if (!producer->is_valid())
945         showStatusMessage(tr("Failed to open "));
946     else if (producer->get_int("error"))
947         showStatusMessage(tr("Failed to open ") + producer->get("resource"));
948 
949     bool ok = false;
950     int screen = Settings.playerExternal().toInt(&ok);
951     if (ok && screen != QApplication::desktop()->screenNumber(this))
952         m_player->moveVideoToScreen(screen);
953 
954     // no else here because open() will delete the producer if open fails
955     if (!MLT.setProducer(producer)) {
956         emit producerOpened();
957         if (!MLT.profile().is_explicit() || MLT.URL().endsWith(".mlt") || MLT.URL().endsWith(".xml"))
958             emit profileChanged();
959     }
960     m_player->setFocus();
961     m_playlistDock->setUpdateButtonEnabled(false);
962 
963     // Needed on Windows. Upon first file open, window is deactivated, perhaps OpenGL-related.
964     activateWindow();
965 }
966 
isCompatibleWithGpuMode(MltXmlChecker & checker)967 bool MainWindow::isCompatibleWithGpuMode(MltXmlChecker& checker)
968 {
969     if (checker.needsGPU() && !Settings.playerGPU() && Settings.playerWarnGPU()) {
970         LOG_INFO() << "file uses GPU but GPU not enabled";
971         QMessageBox dialog(QMessageBox::Warning,
972            qApp->applicationName(),
973            tr("The file you opened uses GPU effects, but GPU effects are not enabled.\n\n"
974               "GPU effects are EXPERIMENTAL, UNSTABLE and UNSUPPORTED! Unsupported means do not report bugs about it.\n\n"
975               "Do you want to enable GPU effects and restart?"),
976            QMessageBox::No |
977            QMessageBox::Yes,
978            this);
979         dialog.setWindowModality(QmlApplication::dialogModality());
980         dialog.setDefaultButton(QMessageBox::Yes);
981         dialog.setEscapeButton(QMessageBox::No);
982         int r = dialog.exec();
983         if (r == QMessageBox::Yes) {
984             ui->actionGPU->setChecked(true);
985             m_exitCode = EXIT_RESTART;
986             QApplication::closeAllWindows();
987         }
988         return false;
989     }
990     else if (checker.needsCPU() && Settings.playerGPU()) {
991         LOG_INFO() << "file uses GPU incompatible filters but GPU is enabled";
992         QMessageBox dialog(QMessageBox::Question,
993            qApp->applicationName(),
994            tr("The file you opened uses CPU effects that are incompatible with GPU effects, but GPU effects are enabled.\n"
995               "Do you want to disable GPU effects and restart?"),
996            QMessageBox::No |
997            QMessageBox::Yes,
998            this);
999         dialog.setWindowModality(QmlApplication::dialogModality());
1000         dialog.setDefaultButton(QMessageBox::Yes);
1001         dialog.setEscapeButton(QMessageBox::No);
1002         int r = dialog.exec();
1003         if (r == QMessageBox::Yes) {
1004             ui->actionGPU->setChecked(false);
1005             m_exitCode = EXIT_RESTART;
1006             QApplication::closeAllWindows();
1007         }
1008         return false;
1009     }
1010     return true;
1011 }
1012 
saveRepairedXmlFile(MltXmlChecker & checker,QString & fileName)1013 bool MainWindow::saveRepairedXmlFile(MltXmlChecker& checker, QString& fileName)
1014 {
1015     QFileInfo fi(fileName);
1016     QFile repaired(QString("%1/%2 - %3.%4").arg(fi.path())
1017         .arg(fi.completeBaseName()).arg(tr("Repaired")).arg(fi.suffix()));
1018     repaired.open(QIODevice::WriteOnly);
1019     LOG_INFO() << "repaired MLT XML file name" << repaired.fileName();
1020     if (checker.tempFile().exists()) {
1021         checker.tempFile().open();
1022         QByteArray xml = checker.tempFile().readAll();
1023         checker.tempFile().close();
1024 
1025         qint64 n = repaired.write(xml);
1026         while (n > 0 && n < xml.size()) {
1027             qint64 x = repaired.write(xml.right(xml.size() - n));
1028             if (x > 0)
1029                 n += x;
1030             else
1031                 n = x;
1032         }
1033         repaired.close();
1034         if (n == xml.size()) {
1035             fileName = repaired.fileName();
1036             return true;
1037         }
1038     }
1039     QMessageBox::warning(this, qApp->applicationName(), tr("Repairing the project failed."));
1040     LOG_WARNING() << "repairing failed";
1041     return false;
1042 }
1043 
isXmlRepaired(MltXmlChecker & checker,QString & fileName)1044 bool MainWindow::isXmlRepaired(MltXmlChecker& checker, QString& fileName)
1045 {
1046     bool result = true;
1047     if (checker.isCorrected()) {
1048         LOG_WARNING() << fileName;
1049         QMessageBox dialog(QMessageBox::Question,
1050            qApp->applicationName(),
1051            tr("Shotcut noticed some problems in your project.\n"
1052               "Do you want Shotcut to try to repair it?\n\n"
1053               "If you choose Yes, Shotcut will create a copy of your project\n"
1054               "with \"- Repaired\" in the file name and open it."),
1055            QMessageBox::No |
1056            QMessageBox::Yes,
1057            this);
1058         dialog.setWindowModality(QmlApplication::dialogModality());
1059         dialog.setDefaultButton(QMessageBox::Yes);
1060         dialog.setEscapeButton(QMessageBox::No);
1061         int r = dialog.exec();
1062         if (r == QMessageBox::Yes)
1063             result = saveRepairedXmlFile(checker, fileName);
1064     }
1065     else if (checker.unlinkedFilesModel().rowCount() > 0) {
1066         UnlinkedFilesDialog dialog(this);
1067         dialog.setModel(checker.unlinkedFilesModel());
1068         dialog.setWindowModality(QmlApplication::dialogModality());
1069         if (dialog.exec() == QDialog::Accepted) {
1070             if (checker.check(fileName) && checker.isCorrected())
1071                 result = saveRepairedXmlFile(checker, fileName);
1072         } else {
1073             result = false;
1074         }
1075     }
1076     return result;
1077 }
1078 
checkAutoSave(QString & url)1079 bool MainWindow::checkAutoSave(QString &url)
1080 {
1081     QMutexLocker locker(&m_autosaveMutex);
1082 
1083     // check whether autosave files exist:
1084     QSharedPointer<AutoSaveFile> stale(AutoSaveFile::getFile(url));
1085     if (stale) {
1086         QMessageBox dialog(QMessageBox::Question, qApp->applicationName(),
1087            tr("Auto-saved files exist. Do you want to recover them now?"),
1088            QMessageBox::No | QMessageBox::Yes, this);
1089         dialog.setWindowModality(QmlApplication::dialogModality());
1090         dialog.setDefaultButton(QMessageBox::Yes);
1091         dialog.setEscapeButton(QMessageBox::No);
1092         int r = dialog.exec();
1093         if (r == QMessageBox::Yes) {
1094             if (!stale->open(QIODevice::ReadWrite)) {
1095                 LOG_WARNING() << "failed to recover autosave file" << url;
1096             } else {
1097                 m_autosaveFile = stale;
1098                 url = stale->fileName();
1099                 return true;
1100             }
1101         }
1102     }
1103 
1104     // create new autosave object
1105     m_autosaveFile.reset(new AutoSaveFile(url));
1106 
1107     return false;
1108 }
1109 
stepLeftBySeconds(int sec)1110 void MainWindow::stepLeftBySeconds(int sec)
1111 {
1112     m_player->seek(m_player->position() + sec * qRound(MLT.profile().fps()));
1113 }
1114 
doAutosave()1115 void MainWindow::doAutosave()
1116 {
1117     QMutexLocker locker(&m_autosaveMutex);
1118     if (m_autosaveFile) {
1119         bool success = false;
1120         if (m_autosaveFile->isOpen() || m_autosaveFile->open(QIODevice::ReadWrite)) {
1121             m_autosaveFile->close();
1122             success = saveXML(m_autosaveFile->fileName(), false /* without relative paths */);
1123             m_autosaveFile->open(QIODevice::ReadWrite);
1124         }
1125         if (!success) {
1126             LOG_ERROR() << "failed to open autosave file for writing" << m_autosaveFile->fileName();
1127         }
1128     }
1129 }
1130 
setFullScreen(bool isFullScreen)1131 void MainWindow::setFullScreen(bool isFullScreen)
1132 {
1133     if (isFullScreen) {
1134 #ifdef Q_OS_WIN
1135         showMaximized();
1136 #else
1137         showFullScreen();
1138 #endif
1139         ui->actionEnter_Full_Screen->setVisible(false);
1140     }
1141 }
1142 
untitledFileName() const1143 QString MainWindow::untitledFileName() const
1144 {
1145     QDir dir = Settings.appDataLocation();
1146     if (!dir.exists()) dir.mkpath(dir.path());
1147     return dir.filePath("__untitled__.mlt");
1148 }
1149 
setProfile(const QString & profile_name)1150 void MainWindow::setProfile(const QString &profile_name)
1151 {
1152     LOG_DEBUG() << profile_name;
1153     MLT.setProfile(profile_name);
1154     emit profileChanged();
1155 }
1156 
isSourceClipMyProject(QString resource,bool withDialog)1157 bool MainWindow::isSourceClipMyProject(QString resource, bool withDialog)
1158 {
1159     if (m_player->tabIndex() == Player::ProjectTabIndex && MLT.savedProducer() && MLT.savedProducer()->is_valid())
1160         resource = QString::fromUtf8(MLT.savedProducer()->get("resource"));
1161     if (!resource.isEmpty() && QDir(resource) == QDir(fileName())) {
1162         if (withDialog) {
1163             QMessageBox dialog(QMessageBox::Information,
1164                                qApp->applicationName(),
1165                                tr("You cannot add a project to itself!"),
1166                                QMessageBox::Ok,
1167                                this);
1168             dialog.setDefaultButton(QMessageBox::Ok);
1169             dialog.setEscapeButton(QMessageBox::Ok);
1170             dialog.setWindowModality(QmlApplication::dialogModality());
1171             dialog.exec();
1172         }
1173         return true;
1174     }
1175     return false;
1176 }
1177 
keyframesDockIsVisible() const1178 bool MainWindow::keyframesDockIsVisible() const
1179 {
1180     return m_keyframesDock && m_keyframesDock->isVisible();
1181 }
1182 
setAudioChannels(int channels)1183 void MainWindow::setAudioChannels(int channels)
1184 {
1185     LOG_DEBUG() << channels;
1186     MLT.videoWidget()->setProperty("audio_channels", channels);
1187     MLT.setAudioChannels(channels);
1188     if (channels == 1)
1189         ui->actionChannels1->setChecked(true);
1190     else if (channels == 2)
1191         ui->actionChannels2->setChecked(true);
1192     else if (channels == 6)
1193         ui->actionChannels6->setChecked(true);
1194     emit audioChannelsChanged();
1195 }
1196 
showSaveError()1197 void MainWindow::showSaveError()
1198 {
1199     QMessageBox dialog(QMessageBox::Critical,
1200                        qApp->applicationName(),
1201                        tr("There was an error saving. Please try again."),
1202                        QMessageBox::Ok,
1203                        this);
1204     dialog.setDefaultButton(QMessageBox::Ok);
1205     dialog.setEscapeButton(QMessageBox::Ok);
1206     dialog.setWindowModality(QmlApplication::dialogModality());
1207     dialog.exec();
1208 }
1209 
setPreviewScale(int scale)1210 void MainWindow::setPreviewScale(int scale)
1211 {
1212     LOG_DEBUG() << scale;
1213     switch (scale) {
1214     case 360:
1215         ui->actionPreview360->setChecked(true);
1216         break;
1217     case 540:
1218         ui->actionPreview540->setChecked(true);
1219         break;
1220     case 720:
1221         ui->actionPreview720->setChecked(true);
1222         break;
1223     default:
1224         ui->actionPreviewNone->setChecked(true);
1225         break;
1226     }
1227     MLT.setPreviewScale(scale);
1228     MLT.refreshConsumer();
1229 }
1230 
setVideoModeMenu()1231 void MainWindow::setVideoModeMenu()
1232 {
1233     // Find a matching video mode
1234     for (const auto action : m_profileGroup->actions()) {
1235         auto s = action->data().toString();
1236         Mlt::Profile profile(s.toUtf8().constData());
1237         if (MLT.profile().width() == profile.width() &&
1238                 MLT.profile().height() == profile.height() &&
1239                 MLT.profile().sample_aspect_num() == profile.sample_aspect_num() &&
1240                 MLT.profile().sample_aspect_den() == profile.sample_aspect_den() &&
1241                 MLT.profile().frame_rate_num() == profile.frame_rate_num() &&
1242                 MLT.profile().frame_rate_den() == profile.frame_rate_den() &&
1243                 MLT.profile().colorspace() == profile.colorspace() &&
1244                 MLT.profile().progressive() == profile.progressive()) {
1245             // Select it
1246             action->setChecked(true);
1247             return;
1248         }
1249     }
1250     // Choose Automatic if nothing found
1251     m_profileGroup->actions().first()->setChecked(true);
1252 }
1253 
resetVideoModeMenu()1254 void MainWindow::resetVideoModeMenu()
1255 {
1256     // Change selected Video Mode back to Settings
1257     for (const auto action : m_profileGroup->actions()) {
1258         if (action->data().toString() == Settings.playerProfile()) {
1259             action->setChecked(true);
1260             break;
1261         }
1262     }
1263 }
1264 
resetDockCorners()1265 void MainWindow::resetDockCorners()
1266 {
1267     setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
1268     setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
1269     setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea);
1270     setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea);
1271 }
1272 
showIncompatibleProjectMessage(const QString & shotcutVersion)1273 void MainWindow::showIncompatibleProjectMessage(const QString& shotcutVersion)
1274 {
1275     LOG_INFO() << shotcutVersion;
1276     QMessageBox dialog(QMessageBox::Information,
1277                        qApp->applicationName(),
1278                        tr("This project file requires a newer version!\n\n"
1279                            "It was made with version ") + shotcutVersion,
1280                        QMessageBox::Ok,
1281                        this);
1282     dialog.setDefaultButton(QMessageBox::Ok);
1283     dialog.setEscapeButton(QMessageBox::Ok);
1284     dialog.setWindowModality(QmlApplication::dialogModality());
1285     dialog.exec();
1286 }
1287 
autosaveTask(MainWindow * p)1288 static void autosaveTask(MainWindow* p)
1289 {
1290     LOG_DEBUG_TIME();
1291     p->doAutosave();
1292 }
1293 
onAutosaveTimeout()1294 void MainWindow::onAutosaveTimeout()
1295 {
1296     if (isWindowModified()) {
1297         QtConcurrent::run(autosaveTask, this);
1298     }
1299     if (Util::isMemoryLow()) {
1300         MLT.pause();
1301         QMessageBox dialog(QMessageBox::Critical,
1302                            qApp->applicationName(),
1303                            tr("You are running low on available memory!\n\n"
1304                               "Please close other applications or web browser tabs and retry.\n"
1305                               "Or save and restart Shotcut."),
1306                            QMessageBox::Retry | QMessageBox::Save | QMessageBox::Ignore,
1307                            this);
1308         dialog.setDefaultButton(QMessageBox::Retry);
1309         dialog.setEscapeButton(QMessageBox::Ignore);
1310         dialog.setWindowModality(QmlApplication::dialogModality());
1311         switch (dialog.exec()) {
1312         case QMessageBox::Save:
1313             on_actionSave_triggered();
1314             m_exitCode = EXIT_RESTART;
1315             QApplication::closeAllWindows();
1316             break;
1317         case QMessageBox::Retry:
1318             onAutosaveTimeout();
1319             break;
1320         default:
1321             break;
1322         }
1323     }
1324 }
1325 
open(QString url,const Mlt::Properties * properties,bool play)1326 void MainWindow::open(QString url, const Mlt::Properties* properties, bool play)
1327 {
1328     LOG_DEBUG() << url;
1329     bool modified = false;
1330     MltXmlChecker checker;
1331     QFileInfo info(url);
1332 
1333     if (info.isRelative()) {
1334         QDir pwd(QDir::currentPath());
1335         url = pwd.filePath(url);
1336     }
1337     if (url.endsWith(".mlt") || url.endsWith(".xml")) {
1338         if (url != untitledFileName()) {
1339             showStatusMessage(tr("Opening %1").arg(url));
1340             QCoreApplication::processEvents();
1341         }
1342         if (checker.check(url)) {
1343             if (!isCompatibleWithGpuMode(checker))
1344                 return;
1345         } else {
1346             showStatusMessage(tr("Failed to open ").append(url));
1347             showIncompatibleProjectMessage(checker.shotcutVersion());
1348             return;
1349         }
1350         // only check for a modified project when loading a project, not a simple producer
1351         if (!continueModified())
1352             return;
1353         QCoreApplication::processEvents();
1354         // close existing project
1355         if (playlist())
1356             m_playlistDock->model()->close();
1357         if (multitrack())
1358             m_timelineDock->model()->close();
1359         MLT.purgeMemoryPool();
1360         if (!isXmlRepaired(checker, url))
1361             return;
1362         modified = checkAutoSave(url);
1363         if (modified) {
1364             if (checker.check(url)) {
1365                 if (!isCompatibleWithGpuMode(checker))
1366                     return;
1367             } else {
1368                 showStatusMessage(tr("Failed to open ").append(url));
1369                 showIncompatibleProjectMessage(checker.shotcutVersion());
1370                 return;
1371             }
1372             if (!isXmlRepaired(checker, url))
1373                 return;
1374         }
1375         // let the new project change the profile
1376         if (modified || QFile::exists(url)) {
1377             MLT.profile().set_explicit(false);
1378             setWindowModified(modified);
1379         }
1380     }
1381     if (!playlist() && !multitrack()) {
1382         if (!modified && !continueModified())
1383             return;
1384         setCurrentFile("");
1385         setWindowModified(modified);
1386         MLT.resetURL();
1387         // Return to automatic video mode if selected.
1388         if (m_profileGroup->checkedAction() && m_profileGroup->checkedAction()->data().toString().isEmpty())
1389             MLT.profile().set_explicit(false);
1390     }
1391     if (url.endsWith(".mlt") || url.endsWith(".xml")) {
1392         checker.setLocale();
1393         LOG_INFO() << "decimal point" << MLT.decimalPoint();
1394     }
1395     QString urlToOpen = checker.isUpdated()? checker.tempFile().fileName() : url;
1396     if (!MLT.open(QDir::fromNativeSeparators(urlToOpen), QDir::fromNativeSeparators(url))
1397             && MLT.producer() && MLT.producer()->is_valid()) {
1398         Mlt::Properties* props = const_cast<Mlt::Properties*>(properties);
1399         if (props && props->is_valid())
1400             mlt_properties_inherit(MLT.producer()->get_properties(), props->get_properties());
1401         m_player->setPauseAfterOpen(!play || !MLT.isClip());
1402 
1403         setAudioChannels(MLT.audioChannels());
1404         if (url.endsWith(".mlt") || url.endsWith(".xml")) {
1405             setVideoModeMenu();
1406         }
1407 
1408         open(MLT.producer());
1409         if (url.startsWith(AutoSaveFile::path())) {
1410             QMutexLocker locker(&m_autosaveMutex);
1411             if (m_autosaveFile && m_autosaveFile->managedFileName() != untitledFileName()) {
1412                 m_recentDock->add(m_autosaveFile->managedFileName());
1413                 LOG_INFO() << m_autosaveFile->managedFileName();
1414             }
1415         } else {
1416             m_recentDock->add(url);
1417             LOG_INFO() << url;
1418         }
1419     }
1420     else if (url != untitledFileName()) {
1421         showStatusMessage(tr("Failed to open ") + url);
1422         emit openFailed(url);
1423     }
1424 }
1425 
openMultiple(const QStringList & paths)1426 void MainWindow::openMultiple(const QStringList& paths)
1427 {
1428     if (paths.size() > 1) {
1429         QList<QUrl> urls;
1430         foreach (const QString& s, paths)
1431             urls << s;
1432         openMultiple(urls);
1433     } else if (!paths.isEmpty()) {
1434         open(paths.first());
1435     }
1436 }
1437 
openMultiple(const QList<QUrl> & urls)1438 void MainWindow::openMultiple(const QList<QUrl>& urls)
1439 {
1440     if (urls.size() > 1) {
1441         m_multipleFiles = Util::sortedFileList(Util::expandDirectories(urls));
1442         open(m_multipleFiles.first());
1443     } else {
1444         QUrl url = urls.first();
1445         open(Util::removeFileScheme(url));
1446     }
1447 }
1448 
openVideo()1449 void MainWindow::openVideo()
1450 {
1451     QString path = Settings.openPath();
1452 #ifdef Q_OS_MAC
1453     path.append("/*");
1454 #endif
1455     LOG_DEBUG() << Util::getFileDialogOptions();
1456     QStringList filenames = QFileDialog::getOpenFileNames(this, tr("Open File"), path,
1457         tr("All Files (*);;MLT XML (*.mlt)"), nullptr, Util::getFileDialogOptions());
1458 
1459     if (filenames.length() > 0) {
1460         Settings.setOpenPath(QFileInfo(filenames.first()).path());
1461         activateWindow();
1462         if (filenames.length() > 1)
1463             m_multipleFiles = filenames;
1464         open(filenames.first());
1465     }
1466     else {
1467         // If file invalid, then on some platforms the dialog messes up SDL.
1468         MLT.onWindowResize();
1469         activateWindow();
1470     }
1471 }
1472 
openCut(Mlt::Producer * producer,bool play)1473 void MainWindow::openCut(Mlt::Producer* producer, bool play)
1474 {
1475     m_player->setPauseAfterOpen(!play);
1476     open(producer);
1477     MLT.seek(producer->get_in());
1478 }
1479 
hideProducer()1480 void MainWindow::hideProducer()
1481 {
1482     // This is a hack to release references to the old producer, but it
1483     // probably leaves a reference to the new color producer somewhere not
1484     // yet identified (root cause).
1485     openCut(new Mlt::Producer(MLT.profile(), "color:_hide"));
1486     QCoreApplication::processEvents();
1487     openCut(new Mlt::Producer(MLT.profile(), "color:_hide"));
1488     QCoreApplication::processEvents();
1489 
1490     QScrollArea* scrollArea = (QScrollArea*) m_propertiesDock->widget();
1491     delete scrollArea->widget();
1492     scrollArea->setWidget(nullptr);
1493     m_player->reset();
1494 
1495     QCoreApplication::processEvents();
1496 }
1497 
closeProducer()1498 void MainWindow::closeProducer()
1499 {
1500     hideProducer();
1501     MLT.stop();
1502     MLT.close();
1503     MLT.setSavedProducer(0);
1504 }
1505 
showStatusMessage(QAction * action,int timeoutSeconds)1506 void MainWindow::showStatusMessage(QAction* action, int timeoutSeconds)
1507 {
1508     // This object takes ownership of the passed action.
1509     // This version does not currently log its message.
1510     m_statusBarAction.reset(action);
1511     action->setParent(0);
1512     m_player->setStatusLabel(action->text(), timeoutSeconds, action);
1513 }
1514 
showStatusMessage(const QString & message,int timeoutSeconds)1515 void MainWindow::showStatusMessage(const QString& message, int timeoutSeconds)
1516 {
1517     LOG_INFO() << message;
1518     m_player->setStatusLabel(message, timeoutSeconds, 0 /* QAction */);
1519     m_statusBarAction.reset();
1520 }
1521 
seekPlaylist(int start)1522 void MainWindow::seekPlaylist(int start)
1523 {
1524     if (!playlist()) return;
1525     // we bypass this->open() to prevent sending producerOpened signal to self, which causes to reload playlist
1526     if (!MLT.producer() || (void*) MLT.producer()->get_producer() != (void*) playlist()->get_playlist())
1527         MLT.setProducer(new Mlt::Producer(*playlist()));
1528     m_player->setIn(-1);
1529     m_player->setOut(-1);
1530     // since we do not emit producerOpened, these components need updating
1531     on_actionJack_triggered(ui->actionJack && ui->actionJack->isChecked());
1532     m_player->onProducerOpened(false);
1533     m_encodeDock->onProducerOpened();
1534     m_filterController->setProducer();
1535     updateMarkers();
1536     MLT.seek(start);
1537     m_player->setFocus();
1538     m_player->switchToTab(Player::ProjectTabIndex);
1539 }
1540 
seekTimeline(int position,bool seekPlayer)1541 void MainWindow::seekTimeline(int position, bool seekPlayer)
1542 {
1543     if (!multitrack()) return;
1544     // we bypass this->open() to prevent sending producerOpened signal to self, which causes to reload playlist
1545     if (MLT.producer() && (void*) MLT.producer()->get_producer() != (void*) multitrack()->get_producer()) {
1546         MLT.setProducer(new Mlt::Producer(*multitrack()));
1547         m_player->setIn(-1);
1548         m_player->setOut(-1);
1549         // since we do not emit producerOpened, these components need updating
1550         on_actionJack_triggered(ui->actionJack && ui->actionJack->isChecked());
1551         m_player->onProducerOpened(false);
1552         m_encodeDock->onProducerOpened();
1553         m_filterController->setProducer();
1554         updateMarkers();
1555         m_player->setFocus();
1556         m_player->switchToTab(Player::ProjectTabIndex);
1557         m_timelineDock->emitSelectedFromSelection();
1558     }
1559     if (seekPlayer)
1560         m_player->seek(position);
1561     else
1562         m_player->pause();
1563 }
1564 
seekKeyframes(int position)1565 void MainWindow::seekKeyframes(int position)
1566 {
1567     m_player->seek(position);
1568 }
1569 
readPlayerSettings()1570 void MainWindow::readPlayerSettings()
1571 {
1572     LOG_DEBUG() << "begin";
1573     ui->actionRealtime->setChecked(Settings.playerRealtime());
1574     ui->actionProgressive->setChecked(Settings.playerProgressive());
1575     ui->actionScrubAudio->setChecked(Settings.playerScrubAudio());
1576     if (ui->actionJack)
1577         ui->actionJack->setChecked(Settings.playerJACK());
1578     if (ui->actionGPU) {
1579         MLT.videoWidget()->setProperty("gpu", ui->actionGPU->isChecked());
1580         ui->actionGPU->setChecked(Settings.playerGPU());
1581     }
1582 
1583     QString external = Settings.playerExternal();
1584     bool ok = false;
1585     external.toInt(&ok);
1586     auto isExternalPeripheral = !external.isEmpty() && !ok;
1587 
1588     setAudioChannels(Settings.playerAudioChannels());
1589 
1590     if (isExternalPeripheral) {
1591         setPreviewScale(0);
1592         m_previewScaleGroup->setEnabled(false);
1593     } else {
1594         setPreviewScale(Settings.playerPreviewScale());
1595         m_previewScaleGroup->setEnabled(true);
1596     }
1597 
1598     QString deinterlacer = Settings.playerDeinterlacer();
1599     QString interpolation = Settings.playerInterpolation();
1600 
1601     if (deinterlacer == "onefield")
1602         ui->actionOneField->setChecked(true);
1603     else if (deinterlacer == "linearblend")
1604         ui->actionLinearBlend->setChecked(true);
1605     else if (deinterlacer == "yadif-nospatial")
1606         ui->actionYadifTemporal->setChecked(true);
1607     else
1608         ui->actionYadifSpatial->setChecked(true);
1609 
1610     if (interpolation == "nearest")
1611         ui->actionNearest->setChecked(true);
1612     else if (interpolation == "bilinear")
1613         ui->actionBilinear->setChecked(true);
1614     else if (interpolation == "bicubic")
1615         ui->actionBicubic->setChecked(true);
1616     else
1617         ui->actionHyper->setChecked(true);
1618 
1619     foreach (QAction* a, m_externalGroup->actions()) {
1620         if (a->data() == external) {
1621             a->setChecked(true);
1622             if (a->data().toString().startsWith("decklink") && m_keyerMenu)
1623                 m_keyerMenu->setEnabled(true);
1624             break;
1625         }
1626     }
1627 
1628     if (m_keyerGroup) {
1629         int keyer = Settings.playerKeyerMode();
1630         foreach (QAction* a, m_keyerGroup->actions()) {
1631             if (a->data() == keyer) {
1632                 a->setChecked(true);
1633                 break;
1634             }
1635         }
1636     }
1637 
1638     QString profile = Settings.playerProfile();
1639     // Automatic not permitted for SDI/HDMI
1640     if (isExternalPeripheral && profile.isEmpty())
1641         profile = "atsc_720p_50";
1642     foreach (QAction* a, m_profileGroup->actions()) {
1643         // Automatic not permitted for SDI/HDMI
1644         if (a->data().toString().isEmpty() && !external.isEmpty() && !ok)
1645             a->setDisabled(true);
1646         if (a->data().toString() == profile) {
1647             a->setChecked(true);
1648             break;
1649         }
1650     }
1651 
1652     QString gamma = Settings.playerGamma();
1653     if (gamma == "bt709")
1654         ui->actionGammaRec709->setChecked(true);
1655     else
1656         ui->actionGammaSRGB->setChecked(true);
1657 
1658     LOG_DEBUG() << "end";
1659 }
1660 
readWindowSettings()1661 void MainWindow::readWindowSettings()
1662 {
1663     LOG_DEBUG() << "begin";
1664     Settings.setWindowGeometryDefault(saveGeometry());
1665     Settings.setWindowStateDefault(saveState());
1666     Settings.sync();
1667     if (!Settings.windowGeometry().isEmpty()) {
1668         restoreGeometry(Settings.windowGeometry());
1669         restoreState(Settings.windowState());
1670 #ifdef Q_OS_MAC
1671         m_filtersDock->setFloating(false);
1672 #endif
1673     } else {
1674         restoreState(kLayoutEditingDefault);
1675     }
1676     LOG_DEBUG() << "end";
1677 }
1678 
writeSettings()1679 void MainWindow::writeSettings()
1680 {
1681 #ifndef Q_OS_MAC
1682     if (isFullScreen())
1683         showNormal();
1684 #endif
1685     Settings.setPlayerGPU(ui->actionGPU->isChecked());
1686     Settings.setWindowGeometry(saveGeometry());
1687     Settings.setWindowState(saveState());
1688     Settings.sync();
1689 }
1690 
configureVideoWidget()1691 void MainWindow::configureVideoWidget()
1692 {
1693     LOG_DEBUG() << "begin";
1694     if (m_profileGroup->checkedAction())
1695         setProfile(m_profileGroup->checkedAction()->data().toString());
1696     MLT.videoWidget()->setProperty("realtime", ui->actionRealtime->isChecked());
1697     bool ok = false;
1698     m_externalGroup->checkedAction()->data().toInt(&ok);
1699     if (!ui->menuExternal || m_externalGroup->checkedAction()->data().toString().isEmpty() || ok) {
1700         MLT.videoWidget()->setProperty("progressive", ui->actionProgressive->isChecked());
1701     } else {
1702         MLT.videoWidget()->setProperty("mlt_service", m_externalGroup->checkedAction()->data());
1703         MLT.videoWidget()->setProperty("progressive", MLT.profile().progressive());
1704         ui->actionProgressive->setEnabled(false);
1705     }
1706     if (ui->actionChannels1->isChecked())
1707         setAudioChannels(1);
1708     else if (ui->actionChannels2->isChecked())
1709         setAudioChannels(2);
1710     else
1711         setAudioChannels(6);
1712     if (ui->actionOneField->isChecked())
1713         MLT.videoWidget()->setProperty("deinterlace_method", "onefield");
1714     else if (ui->actionLinearBlend->isChecked())
1715         MLT.videoWidget()->setProperty("deinterlace_method", "linearblend");
1716     else if (ui->actionYadifTemporal->isChecked())
1717         MLT.videoWidget()->setProperty("deinterlace_method", "yadif-nospatial");
1718     else
1719         MLT.videoWidget()->setProperty("deinterlace_method", "yadif");
1720     if (ui->actionNearest->isChecked())
1721         MLT.videoWidget()->setProperty("rescale", "nearest");
1722     else if (ui->actionBilinear->isChecked())
1723         MLT.videoWidget()->setProperty("rescale", "bilinear");
1724     else if (ui->actionBicubic->isChecked())
1725         MLT.videoWidget()->setProperty("rescale", "bicubic");
1726     else
1727         MLT.videoWidget()->setProperty("rescale", "hyper");
1728     if (m_keyerGroup)
1729         MLT.videoWidget()->setProperty("keyer", m_keyerGroup->checkedAction()->data());
1730     LOG_DEBUG() << "end";
1731 }
1732 
setCurrentFile(const QString & filename)1733 void MainWindow::setCurrentFile(const QString &filename)
1734 {
1735     QString shownName = tr("Untitled");
1736     if (filename == untitledFileName())
1737         m_currentFile.clear();
1738     else
1739         m_currentFile = filename;
1740     if (!m_currentFile.isEmpty())
1741         shownName = QFileInfo(m_currentFile).fileName();
1742 #ifdef Q_OS_MAC
1743     setWindowTitle(QString("%1 - %2").arg(shownName).arg(qApp->applicationName()));
1744 #else
1745     setWindowTitle(QString("%1[*] - %2").arg(shownName).arg(qApp->applicationName()));
1746 #endif
1747 }
1748 
on_actionAbout_Shotcut_triggered()1749 void MainWindow::on_actionAbout_Shotcut_triggered()
1750 {
1751     const auto copyright = QStringLiteral("Copyright &copy; 2011-2021 <a href=\"https://www.meltytech.com/\">Meltytech</a>, LLC");
1752     const auto license = QStringLiteral("<a href=\"https://www.gnu.org/licenses/gpl.html\">GNU General Public License v3.0</a>");
1753     const auto url = QStringLiteral("https://www.shotcut.org/");
1754     QMessageBox::about(this, tr("About %1"),
1755              tr("<h1>Shotcut version %2</h1>"
1756                 "<p><a href=\"%3\">%1</a> is a free, open source, cross platform video editor.</p>"
1757                 "<small><p>%4</p>"
1758                 "<p>Licensed under the %5</p>"
1759                 "<p>This program proudly uses the following projects:<ul>"
1760                 "<li><a href=\"https://www.qt.io/\">Qt</a> application and UI framework</li>"
1761                 "<li><a href=\"https://www.mltframework.org/\">MLT</a> multimedia authoring framework</li>"
1762                 "<li><a href=\"https://www.ffmpeg.org/\">FFmpeg</a> multimedia format and codec libraries</li>"
1763                 "<li><a href=\"https://www.videolan.org/developers/x264.html\">x264</a> H.264 encoder</li>"
1764                 "<li><a href=\"https://www.webmproject.org/\">WebM</a> VP8 and VP9 encoders</li>"
1765                 "<li><a href=\"http://lame.sourceforge.net/\">LAME</a> MP3 encoder</li>"
1766                 "<li><a href=\"https://www.dyne.org/software/frei0r/\">Frei0r</a> video plugins</li>"
1767                 "<li><a href=\"https://www.ladspa.org/\">LADSPA</a> audio plugins</li>"
1768                 "<li><a href=\"http://www.defaulticon.com/\">DefaultIcon</a> icon collection by <a href=\"http://www.interactivemania.com/\">interactivemania</a></li>"
1769                 "<li><a href=\"http://www.oxygen-icons.org/\">Oxygen</a> icon collection</li>"
1770                 "</ul></p>"
1771                 "<p>The source code used to build this program can be downloaded from "
1772                 "<a href=\"%3\">%3</a>.</p>"
1773                 "This program is distributed in the hope that it will be useful, "
1774                 "but WITHOUT ANY WARRANTY; without even the implied warranty of "
1775                 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.</small>"
1776                 ).arg(qApp->applicationName(), qApp->applicationVersion(), url, copyright, license));
1777 }
1778 
1779 
keyPressEvent(QKeyEvent * event)1780 void MainWindow::keyPressEvent(QKeyEvent* event)
1781 {
1782     if (event->isAccepted() && event->key() != Qt::Key_F12) return;
1783 
1784     bool handled = true;
1785 
1786     switch (event->key()) {
1787     case Qt::Key_Home:
1788         m_player->seek(0);
1789         break;
1790     case Qt::Key_End:
1791         if (MLT.producer())
1792             m_player->seek(MLT.producer()->get_length() - 1);
1793         break;
1794     case Qt::Key_Left:
1795         if ((event->modifiers() & Qt::ControlModifier) && m_timelineDock->isVisible()) {
1796             if (m_timelineDock->selection().isEmpty()) {
1797                 m_timelineDock->selectClipUnderPlayhead();
1798             } else if (m_timelineDock->selection().size() == 1) {
1799                 int newIndex = m_timelineDock->selection().first().x() - 1;
1800                 if (newIndex < 0)
1801                     break;
1802                 m_timelineDock->setSelection(QList<QPoint>() << QPoint(newIndex, m_timelineDock->selection().first().y()));
1803                 m_navigationPosition = m_timelineDock->centerOfClip(m_timelineDock->currentTrack(), newIndex);
1804             }
1805         } else {
1806             stepLeftOneFrame();
1807         }
1808         break;
1809     case Qt::Key_Right:
1810         if ((event->modifiers() & Qt::ControlModifier) && m_timelineDock->isVisible()) {
1811             if (m_timelineDock->selection().isEmpty()) {
1812                 m_timelineDock->selectClipUnderPlayhead();
1813             } else if (m_timelineDock->selection().size() == 1) {
1814                 int newIndex = m_timelineDock->selection().first().x() + 1;
1815                 if (newIndex >= m_timelineDock->clipCount(-1))
1816                     break;
1817                 m_timelineDock->setSelection(QList<QPoint>() << QPoint(newIndex, m_timelineDock->selection().first().y()));
1818                 m_navigationPosition = m_timelineDock->centerOfClip(m_timelineDock->currentTrack(), newIndex);
1819             }
1820         } else {
1821             stepRightOneFrame();
1822         }
1823         break;
1824     case Qt::Key_PageUp:
1825     case Qt::Key_PageDown:
1826         {
1827             int directionMultiplier = event->key() == Qt::Key_PageUp ? -1 : 1;
1828             int seconds = 1;
1829             if (event->modifiers() & Qt::ControlModifier)
1830                 seconds *= 5;
1831             if (event->modifiers() & Qt::ShiftModifier)
1832                 seconds *= 2;
1833             stepLeftBySeconds(seconds * directionMultiplier);
1834         }
1835         break;
1836     case Qt::Key_Space:
1837 #ifdef Q_OS_MAC
1838         // Spotlight defaults to Cmd+Space, so also accept Ctrl+Space.
1839         if ((event->modifiers() == Qt::MetaModifier || (event->modifiers() & Qt::ControlModifier)) && m_timelineDock->isVisible())
1840 #else
1841         if (event->modifiers() == Qt::ControlModifier && m_timelineDock->isVisible())
1842 #endif
1843             m_timelineDock->selectClipUnderPlayhead();
1844         else
1845             handled = false;
1846         break;
1847     case Qt::Key_A:
1848         if (event->modifiers() == Qt::ShiftModifier) {
1849             m_playlistDock->show();
1850             m_playlistDock->raise();
1851             m_playlistDock->on_actionAppendCut_triggered();
1852         } else if ((event->modifiers() & Qt::ControlModifier) && (event->modifiers() & Qt::ShiftModifier)) {
1853             m_playlistDock->show();
1854             m_playlistDock->raise();
1855             m_playlistDock->on_actionSelectAll_triggered();
1856         } else if (event->modifiers() == Qt::ControlModifier) {
1857             m_timelineDock->show();
1858             m_timelineDock->raise();
1859             m_timelineDock->selectAll();
1860         } else if (event->modifiers() == Qt::NoModifier) {
1861             m_timelineDock->show();
1862             m_timelineDock->raise();
1863             m_timelineDock->append(-1);
1864         }
1865         break;
1866     case Qt::Key_C:
1867         if (event->modifiers() == Qt::ShiftModifier && m_playlistDock->model()->rowCount() > 0) {
1868             m_playlistDock->show();
1869             m_playlistDock->raise();
1870             m_playlistDock->on_actionCopy_triggered();
1871         } else if ((event->modifiers() & Qt::ControlModifier) && (event->modifiers() & Qt::AltModifier)) {
1872             m_timelineDock->show();
1873             m_timelineDock->raise();
1874             m_timelineDock->copyToSource();
1875         } else if (isMultitrackValid()) {
1876             m_timelineDock->show();
1877             m_timelineDock->raise();
1878             if (m_timelineDock->selection().isEmpty()) {
1879                 m_timelineDock->copyClip(-1, -1);
1880             } else {
1881                 auto& selected = m_timelineDock->selection().first();
1882                 m_timelineDock->copyClip(selected.y(), selected.x());
1883             }
1884         }
1885         break;
1886     case Qt::Key_D:
1887         if (event->modifiers() == Qt::ControlModifier) {
1888             m_timelineDock->setSelection();
1889             m_timelineDock->model()->reload();
1890         } else if ((event->modifiers() & Qt::ControlModifier) && (event->modifiers() & Qt::ShiftModifier)) {
1891             m_playlistDock->show();
1892             m_playlistDock->raise();
1893             m_playlistDock->on_actionSelectNone_triggered();
1894         } else {
1895             handled = false;
1896         }
1897         break;
1898     case Qt::Key_F:
1899         if (event->modifiers() == Qt::NoModifier || event->modifiers() == Qt::ControlModifier) {
1900             m_filtersDock->show();
1901             m_filtersDock->raise();
1902             m_filtersDock->widget()->setFocus();
1903             m_filtersDock->openFilterMenu();
1904         } else if (event->modifiers() == Qt::ShiftModifier) {
1905             filterController()->removeCurrent();
1906 #ifdef Q_OS_MAC
1907         } else if ((event->modifiers() & Qt::ControlModifier) && (event->modifiers() & Qt::MetaModifier)) {
1908             on_actionEnter_Full_Screen_triggered();
1909 #else
1910         } else if ((event->modifiers() & Qt::ControlModifier) && (event->modifiers() & Qt::ShiftModifier)) {
1911             on_actionEnter_Full_Screen_triggered();
1912 #endif
1913         } else {
1914             handled = false;
1915         }
1916         break;
1917     case Qt::Key_H:
1918 #ifdef Q_OS_MAC
1919         // OS X uses Cmd+H to hide an app.
1920         if (event->modifiers() & Qt::MetaModifier && isMultitrackValid())
1921 #else
1922         if (event->modifiers() & Qt::ControlModifier && isMultitrackValid())
1923 #endif
1924             m_timelineDock->toggleTrackHidden(m_timelineDock->currentTrack());
1925         break;
1926     case Qt::Key_J:
1927         if (m_isKKeyPressed)
1928             m_player->seek(m_player->position() - 1);
1929         else
1930             m_player->rewind(false);
1931         break;
1932     case Qt::Key_K:
1933             m_player->pause();
1934             m_isKKeyPressed = true;
1935         break;
1936     case Qt::Key_L:
1937 #ifdef Q_OS_MAC
1938         // OS X uses Cmd+H to hide an app and Cmd+M to minimize. Therefore, we force
1939         // it to be the apple keyboard control key aka meta. Therefore, to be
1940         // consistent with all track header toggles, we make the lock toggle also use
1941         // meta.
1942         if (event->modifiers() & Qt::MetaModifier && isMultitrackValid())
1943 #else
1944         if (event->modifiers() & Qt::ControlModifier && isMultitrackValid())
1945 #endif
1946             m_timelineDock->setTrackLock(m_timelineDock->currentTrack(), !m_timelineDock->isTrackLocked(m_timelineDock->currentTrack()));
1947         else if (m_isKKeyPressed)
1948             m_player->seek(m_player->position() + 1);
1949         else
1950             m_player->fastForward(false);
1951         break;
1952     case Qt::Key_M:
1953 #ifdef Q_OS_MAC
1954         // OS X uses Cmd+M to minimize an app.
1955         if (event->modifiers() & Qt::MetaModifier && isMultitrackValid())
1956 #else
1957         if (event->modifiers() & Qt::ControlModifier && isMultitrackValid())
1958 #endif
1959             m_timelineDock->toggleTrackMute(m_timelineDock->currentTrack());
1960         break;
1961     case Qt::Key_I:
1962         if (event->modifiers() == Qt::ControlModifier) {
1963             m_timelineDock->show();
1964             m_timelineDock->raise();
1965             m_timelineDock->addVideoTrack();
1966         } else if ((event->modifiers() & Qt::ControlModifier) && (event->modifiers() & Qt::AltModifier)) {
1967             m_timelineDock->show();
1968             m_timelineDock->raise();
1969             m_timelineDock->insertTrack();
1970         } else {
1971             setInToCurrent(event->modifiers() & Qt::ShiftModifier);
1972         }
1973         break;
1974     case Qt::Key_O:
1975         setOutToCurrent(event->modifiers() & Qt::ShiftModifier);
1976         break;
1977     case Qt::Key_P:
1978         if (event->modifiers() == Qt::ControlModifier) {
1979             Settings.setTimelineSnap(!Settings.timelineSnap());
1980         } else if (event->modifiers() & Qt::ControlModifier) {
1981             if (event->modifiers() & Qt::AltModifier) {
1982                 Settings.setTimelineScrollZoom(!Settings.timelineScrollZoom());
1983             }
1984             if (event->modifiers() & Qt::ShiftModifier) {
1985                 Settings.setTimelineCenterPlayhead(!Settings.timelineCenterPlayhead());
1986             }
1987         }
1988         break;
1989     case Qt::Key_R:
1990         if (event->modifiers() & Qt::ControlModifier) {
1991             if (event->modifiers() & Qt::AltModifier) {
1992                 Settings.setTimelineRippleAllTracks(!Settings.timelineRippleAllTracks());
1993             } else if (event->modifiers() & Qt::ShiftModifier) {
1994                 Settings.setTimelineRippleAllTracks(!Settings.timelineRipple());
1995                 Settings.setTimelineRipple(!Settings.timelineRipple());
1996             } else {
1997                 Settings.setTimelineRipple(!Settings.timelineRipple());
1998             }
1999         } else if (isMultitrackValid()) {
2000             m_timelineDock->show();
2001             m_timelineDock->raise();
2002             if (MLT.isClip() || m_timelineDock->selection().isEmpty()) {
2003                 m_timelineDock->replace(-1, -1);
2004             } else {
2005                 auto& selected = m_timelineDock->selection().first();
2006                 m_timelineDock->replace(selected.y(), selected.x());
2007             }
2008         }
2009         break;
2010     case Qt::Key_S:
2011         if (isMultitrackValid())
2012             m_timelineDock->splitClip();
2013         break;
2014     case Qt::Key_U:
2015         if (event->modifiers() == Qt::ControlModifier) {
2016             m_timelineDock->show();
2017             m_timelineDock->raise();
2018             m_timelineDock->addAudioTrack();
2019         } else if ((event->modifiers() & Qt::ControlModifier) && (event->modifiers() & Qt::AltModifier)) {
2020             m_timelineDock->show();
2021             m_timelineDock->raise();
2022             m_timelineDock->removeTrack();
2023         }
2024         break;
2025     case Qt::Key_V: // Avid Splice In
2026         if (event->modifiers() == Qt::ShiftModifier) {
2027             m_playlistDock->show();
2028             m_playlistDock->raise();
2029             m_playlistDock->on_actionInsertCut_triggered();
2030         } else {
2031             m_timelineDock->show();
2032             m_timelineDock->raise();
2033             m_timelineDock->insert(-1);
2034         }
2035         break;
2036     case Qt::Key_B:
2037         if (event->modifiers() & Qt::ControlModifier && event->modifiers() & Qt::AltModifier) {
2038             // Toggle track blending.
2039             int trackIndex = m_timelineDock->currentTrack();
2040             bool isBottomVideo = m_timelineDock->model()->data(m_timelineDock->model()->index(trackIndex), MultitrackModel::IsBottomVideoRole).toBool();
2041             if (!isBottomVideo) {
2042                 bool isComposite = m_timelineDock->model()->data(m_timelineDock->model()->index(trackIndex), MultitrackModel::IsCompositeRole).toBool();
2043                 m_timelineDock->setTrackComposite(trackIndex, !isComposite);
2044             }
2045         } else if (event->modifiers() == Qt::ShiftModifier) {
2046             if (m_playlistDock->model()->rowCount() > 0) {
2047                 // Update playlist item.
2048                 m_playlistDock->show();
2049                 m_playlistDock->raise();
2050                 m_playlistDock->on_actionUpdate_triggered();
2051             }
2052         } else {
2053             // Overwrite on timeline.
2054             m_timelineDock->show();
2055             m_timelineDock->raise();
2056             m_timelineDock->overwrite(-1);
2057         }
2058         break;
2059     case Qt::Key_Escape: // Avid Toggle Active Monitor
2060         if (MLT.isPlaylist()) {
2061             if (isMultitrackValid())
2062                 m_player->onTabBarClicked(Player::ProjectTabIndex);
2063             else if (MLT.savedProducer())
2064                 m_player->onTabBarClicked(Player::SourceTabIndex);
2065             else
2066                 m_playlistDock->on_actionOpen_triggered();
2067         } else if (MLT.isMultitrack()) {
2068             if (MLT.savedProducer())
2069                 m_player->onTabBarClicked(Player::SourceTabIndex);
2070             // TODO else open clip under playhead of current track if available
2071         } else {
2072             if (isMultitrackValid() || (playlist() && playlist()->count() > 0))
2073                 m_player->onTabBarClicked(Player::ProjectTabIndex);
2074         }
2075         break;
2076     case Qt::Key_Up:
2077         if (m_playlistDock->isVisible() && event->modifiers() & Qt::AltModifier && m_playlistDock->model()->rowCount() > 0) {
2078             m_playlistDock->raise();
2079             m_playlistDock->decrementIndex();
2080             m_playlistDock->on_actionOpen_triggered();
2081         } else if ((event->modifiers() & Qt::ControlModifier) && (event->modifiers() & Qt::ShiftModifier)) {
2082             if (m_playlistDock->model()->rowCount() > 0) {
2083                 m_playlistDock->raise();
2084                 m_playlistDock->moveClipUp();
2085                 m_playlistDock->decrementIndex();
2086             }
2087         } else if (isMultitrackValid()) {
2088             int newClipIndex = -1;
2089             int trackIndex = m_timelineDock->currentTrack() - 1;
2090             if ((event->modifiers() & Qt::ControlModifier) &&
2091                     !m_timelineDock->selection().isEmpty() &&
2092                     trackIndex > -1) {
2093 
2094                 newClipIndex = m_timelineDock->clipIndexAtPosition(trackIndex, m_navigationPosition);
2095             }
2096 
2097             m_timelineDock->incrementCurrentTrack(-1);
2098 
2099             if (newClipIndex >= 0) {
2100                 newClipIndex = qMin(newClipIndex, m_timelineDock->clipCount(trackIndex) - 1);
2101                 m_timelineDock->setSelection(QList<QPoint>() << QPoint(newClipIndex, trackIndex));
2102             }
2103 
2104         }
2105         break;
2106     case Qt::Key_Down:
2107         if (m_playlistDock->isVisible() && event->modifiers() & Qt::AltModifier && m_playlistDock->model()->rowCount() > 0) {
2108             m_playlistDock->raise();
2109             m_playlistDock->incrementIndex();
2110             m_playlistDock->on_actionOpen_triggered();
2111         } else if ((event->modifiers() & Qt::ControlModifier) && (event->modifiers() & Qt::ShiftModifier)) {
2112             if (m_playlistDock->model()->rowCount() > 0) {
2113                 m_playlistDock->raise();
2114                 m_playlistDock->moveClipDown();
2115                 m_playlistDock->incrementIndex();
2116             }
2117         } else if (isMultitrackValid()) {
2118             int newClipIndex = -1;
2119             int trackIndex = m_timelineDock->currentTrack() + 1;
2120             if ((event->modifiers() & Qt::ControlModifier) &&
2121                     !m_timelineDock->selection().isEmpty() &&
2122                     trackIndex < m_timelineDock->model()->trackList().count()) {
2123 
2124                 newClipIndex = m_timelineDock->clipIndexAtPosition(trackIndex, m_navigationPosition);
2125             }
2126 
2127             m_timelineDock->incrementCurrentTrack(1);
2128 
2129             if (newClipIndex >= 0) {
2130                 newClipIndex = qMin(newClipIndex, m_timelineDock->clipCount(trackIndex) - 1);
2131                 m_timelineDock->setSelection(QList<QPoint>() << QPoint(newClipIndex, trackIndex));
2132             }
2133 
2134         }
2135         break;
2136     case Qt::Key_1:
2137     case Qt::Key_2:
2138     case Qt::Key_3:
2139     case Qt::Key_4:
2140     case Qt::Key_5:
2141     case Qt::Key_6:
2142     case Qt::Key_7:
2143     case Qt::Key_8:
2144     case Qt::Key_9:
2145         if ((event->modifiers() == Qt::NoModifier || event->modifiers() == Qt::KeypadModifier) &&
2146             m_playlistDock->isVisible() && m_playlistDock->model()->rowCount() > 0) {
2147             m_playlistDock->raise();
2148             m_playlistDock->setIndex(event->key() - Qt::Key_1);
2149         }
2150         break;
2151     case Qt::Key_0:
2152         if ((event->modifiers() == Qt::NoModifier || event->modifiers() == Qt::KeypadModifier)) {
2153             if (m_timelineDock->isVisible()) {
2154                 emit m_timelineDock->zoomToFit();
2155             } else if (m_playlistDock->isVisible() && m_playlistDock->model()->rowCount() > 0) {
2156                 m_playlistDock->raise();
2157                 m_playlistDock->setIndex(9);
2158             }
2159         }
2160         if (m_keyframesDock->isVisible() && (event->modifiers() & Qt::AltModifier)) {
2161             emit m_keyframesDock->zoomToFit();
2162         }
2163         break;
2164     case Qt::Key_X: // Avid Extract
2165         if (event->modifiers() == Qt::ShiftModifier && m_playlistDock->model()->rowCount() > 0) {
2166             m_playlistDock->show();
2167             m_playlistDock->raise();
2168             m_playlistDock->on_removeButton_clicked();
2169         } else if (isMultitrackValid()) {
2170             m_timelineDock->show();
2171             m_timelineDock->raise();
2172             m_timelineDock->removeSelection();
2173         }
2174         break;
2175     case Qt::Key_Backspace:
2176     case Qt::Key_Delete:
2177         if (isMultitrackValid()) {
2178             m_timelineDock->show();
2179             m_timelineDock->raise();
2180             if (event->modifiers() == Qt::ShiftModifier)
2181                 m_timelineDock->removeSelection();
2182             else
2183                 m_timelineDock->liftSelection();
2184         } else if (m_playlistDock->model()->rowCount() > 0) {
2185             m_playlistDock->show();
2186             m_playlistDock->raise();
2187             m_playlistDock->on_removeButton_clicked();
2188         }
2189         break;
2190     case Qt::Key_Z: // Avid Lift
2191         if (event->modifiers() == Qt::ShiftModifier && m_playlistDock->model()->rowCount() > 0) {
2192             m_playlistDock->show();
2193             m_playlistDock->raise();
2194             m_playlistDock->on_removeButton_clicked();
2195         } else if (isMultitrackValid() && event->modifiers() == Qt::NoModifier) {
2196             m_timelineDock->show();
2197             m_timelineDock->raise();
2198             m_timelineDock->liftSelection();
2199         }
2200         break;
2201     case Qt::Key_Minus:
2202         if (m_timelineDock->isVisible() && !(event->modifiers() & Qt::AltModifier)) {
2203             if (event->modifiers() & Qt::ControlModifier)
2204                 emit m_timelineDock->makeTracksShorter();
2205             else
2206                 emit m_timelineDock->zoomOut();
2207         }
2208         if (m_keyframesDock->isVisible() && (event->modifiers() & Qt::AltModifier)) {
2209             emit m_keyframesDock->zoomOut();
2210         }
2211         break;
2212     case Qt::Key_Equal:
2213     case Qt::Key_Plus:
2214         if (m_timelineDock->isVisible() && !(event->modifiers() & Qt::AltModifier)) {
2215             if (event->modifiers() & Qt::ControlModifier)
2216                 emit m_timelineDock->makeTracksTaller();
2217             else
2218                 emit m_timelineDock->zoomIn();
2219         }
2220         if (m_keyframesDock->isVisible() && (event->modifiers() & Qt::AltModifier)) {
2221             emit m_keyframesDock->zoomIn();
2222         }
2223         break;
2224     case Qt::Key_Enter: // Seek to current playlist item
2225     case Qt::Key_Return:
2226         if (m_playlistDock->isVisible() && m_playlistDock->position() >= 0) {
2227             if (event->modifiers() == Qt::ShiftModifier)
2228                 m_playlistDock->on_actionGoto_triggered();
2229             else if (event->modifiers() == Qt::ControlModifier)
2230                 m_playlistDock->on_actionOpen_triggered();
2231         }
2232         break;
2233     case Qt::Key_F2:
2234         onPropertiesDockTriggered(true);
2235         emit renameRequested();
2236         break;
2237     case Qt::Key_F3:
2238         onRecentDockTriggered(true);
2239         m_recentDock->find();
2240         break;
2241     case Qt::Key_F5:
2242         m_timelineDock->model()->reload();
2243         m_keyframesDock->model().reload();
2244         break;
2245     case Qt::Key_F11:
2246         on_actionEnter_Full_Screen_triggered();
2247         break;
2248     case Qt::Key_F12:
2249         LOG_DEBUG() << "event isAccepted:" << event->isAccepted();
2250         LOG_DEBUG() << "Current focusWidget:" << QApplication::focusWidget();
2251         LOG_DEBUG() << "Current focusObject:" << QApplication::focusObject();
2252         LOG_DEBUG() << "Current focusWindow:" << QApplication::focusWindow();
2253         break;
2254     case Qt::Key_BracketLeft:
2255         if (filterController()->currentFilter() && m_filtersDock->qmlProducer()) {
2256             if (event->modifiers() == Qt::AltModifier) {
2257                 emit m_keyframesDock->seekPreviousSimple();
2258             } else {
2259                 int i = m_filtersDock->qmlProducer()->position() + m_filtersDock->qmlProducer()->in();
2260                 filterController()->currentFilter()->setIn(i);
2261             }
2262         }
2263         break;
2264     case Qt::Key_BracketRight:
2265         if (filterController()->currentFilter() && m_filtersDock->qmlProducer()) {
2266             if (event->modifiers() == Qt::AltModifier) {
2267                 emit m_keyframesDock->seekNextSimple();
2268             } else {
2269                 int i = m_filtersDock->qmlProducer()->position() + m_filtersDock->qmlProducer()->in();
2270                 filterController()->currentFilter()->setOut(i);
2271             }
2272         }
2273         break;
2274     case Qt::Key_BraceLeft:
2275         if (filterController()->currentFilter() && m_filtersDock->qmlProducer()) {
2276             int i = m_filtersDock->qmlProducer()->position() + m_filtersDock->qmlProducer()->in() - filterController()->currentFilter()->in();
2277             filterController()->currentFilter()->setAnimateIn(i);
2278         }
2279         break;
2280     case Qt::Key_BraceRight:
2281         if (filterController()->currentFilter() && m_filtersDock->qmlProducer()) {
2282             int i = filterController()->currentFilter()->out() - (m_filtersDock->qmlProducer()->position() + m_filtersDock->qmlProducer()->in());
2283             filterController()->currentFilter()->setAnimateOut(i);
2284         }
2285         break;
2286     case Qt::Key_Semicolon:
2287         if (filterController()->currentFilter() && m_filtersDock->qmlProducer() && m_keyframesDock->currentParameter() >= 0) {
2288             auto position = m_filtersDock->qmlProducer()->position() - (filterController()->currentFilter()->in() - m_filtersDock->qmlProducer()->in());
2289             auto parameterIndex = m_keyframesDock->currentParameter();
2290             if (m_keyframesDock->model().isKeyframe(parameterIndex, position)) {
2291                 auto keyframeIndex = m_keyframesDock->model().keyframeIndex(parameterIndex, position);
2292                 m_keyframesDock->model().remove(parameterIndex, keyframeIndex);
2293             } else {
2294                 m_keyframesDock->model().addKeyframe(parameterIndex, position);
2295             }
2296         }
2297         break;
2298     default:
2299         handled = false;
2300         break;
2301     }
2302 
2303     if (handled)
2304         event->setAccepted(handled);
2305     else
2306         QMainWindow::keyPressEvent(event);
2307 }
2308 
keyReleaseEvent(QKeyEvent * event)2309 void MainWindow::keyReleaseEvent(QKeyEvent* event)
2310 {
2311     if (event->key() == Qt::Key_K) {
2312         m_isKKeyPressed = false;
2313         event->setAccepted(true);
2314     } else {
2315         QMainWindow::keyReleaseEvent(event);
2316     }
2317 }
2318 
hideSetDataDirectory()2319 void MainWindow::hideSetDataDirectory()
2320 {
2321     delete ui->actionAppDataSet;
2322 }
2323 
actionAddCustomProfile() const2324 QAction *MainWindow::actionAddCustomProfile() const
2325 {
2326     return ui->actionAddCustomProfile;
2327 }
2328 
actionProfileRemove() const2329 QAction *MainWindow::actionProfileRemove() const
2330 {
2331     return ui->actionProfileRemove;
2332 }
2333 
buildVideoModeMenu(QMenu * topMenu,QMenu * & customMenu,QActionGroup * group,QAction * addAction,QAction * removeAction)2334 void MainWindow::buildVideoModeMenu(QMenu* topMenu, QMenu*& customMenu, QActionGroup* group, QAction* addAction, QAction* removeAction)
2335 {
2336     topMenu->addAction(addProfile(group, "HD 720p 50 fps", "atsc_720p_50"));
2337     topMenu->addAction(addProfile(group, "HD 720p 59.94 fps", "atsc_720p_5994"));
2338     topMenu->addAction(addProfile(group, "HD 720p 60 fps", "atsc_720p_60"));
2339     topMenu->addAction(addProfile(group, "HD 1080i 25 fps", "atsc_1080i_50"));
2340     topMenu->addAction(addProfile(group, "HD 1080i 29.97 fps", "atsc_1080i_5994"));
2341     topMenu->addAction(addProfile(group, "HD 1080p 23.98 fps", "atsc_1080p_2398"));
2342     topMenu->addAction(addProfile(group, "HD 1080p 24 fps", "atsc_1080p_24"));
2343     topMenu->addAction(addProfile(group, "HD 1080p 25 fps", "atsc_1080p_25"));
2344     topMenu->addAction(addProfile(group, "HD 1080p 29.97 fps", "atsc_1080p_2997"));
2345     topMenu->addAction(addProfile(group, "HD 1080p 30 fps", "atsc_1080p_30"));
2346     topMenu->addAction(addProfile(group, "HD 1080p 50 fps", "atsc_1080p_50"));
2347     topMenu->addAction(addProfile(group, "HD 1080p 59.94 fps", "atsc_1080p_5994"));
2348     topMenu->addAction(addProfile(group, "HD 1080p 60 fps", "atsc_1080p_60"));
2349     topMenu->addAction(addProfile(group, "SD NTSC", "dv_ntsc"));
2350     topMenu->addAction(addProfile(group, "SD PAL", "dv_pal"));
2351     topMenu->addAction(addProfile(group, "UHD 2160p 23.98 fps", "uhd_2160p_2398"));
2352     topMenu->addAction(addProfile(group, "UHD 2160p 24 fps", "uhd_2160p_24"));
2353     topMenu->addAction(addProfile(group, "UHD 2160p 25 fps", "uhd_2160p_25"));
2354     topMenu->addAction(addProfile(group, "UHD 2160p 29.97 fps", "uhd_2160p_2997"));
2355     topMenu->addAction(addProfile(group, "UHD 2160p 30 fps", "uhd_2160p_30"));
2356     topMenu->addAction(addProfile(group, "UHD 2160p 50 fps", "uhd_2160p_50"));
2357     topMenu->addAction(addProfile(group, "UHD 2160p 59.94 fps", "uhd_2160p_5994"));
2358     topMenu->addAction(addProfile(group, "UHD 2160p 60 fps", "uhd_2160p_60"));
2359     QMenu* menu = topMenu->addMenu(tr("Non-Broadcast"));
2360     menu->addAction(addProfile(group, "640x480 4:3 NTSC", "square_ntsc"));
2361     menu->addAction(addProfile(group, "768x576 4:3 PAL", "square_pal"));
2362     menu->addAction(addProfile(group, "854x480 16:9 NTSC", "square_ntsc_wide"));
2363     menu->addAction(addProfile(group, "1024x576 16:9 PAL", "square_pal_wide"));
2364     menu->addAction(addProfile(group, tr("DVD Widescreen NTSC"), "dv_ntsc_wide"));
2365     menu->addAction(addProfile(group, tr("DVD Widescreen PAL"), "dv_pal_wide"));
2366     menu->addAction(addProfile(group, "HD 720p 23.98 fps", "atsc_720p_2398"));
2367     menu->addAction(addProfile(group, "HD 720p 24 fps", "atsc_720p_24"));
2368     menu->addAction(addProfile(group, "HD 720p 25 fps", "atsc_720p_25"));
2369     menu->addAction(addProfile(group, "HD 720p 29.97 fps", "atsc_720p_2997"));
2370     menu->addAction(addProfile(group, "HD 720p 30 fps", "atsc_720p_30"));
2371     menu->addAction(addProfile(group, "HD 1080i 60 fps", "atsc_1080i_60"));
2372     menu->addAction(addProfile(group, "HDV 1080i 25 fps", "hdv_1080_50i"));
2373     menu->addAction(addProfile(group, "HDV 1080i 29.97 fps", "hdv_1080_60i"));
2374     menu->addAction(addProfile(group, "HDV 1080p 25 fps", "hdv_1080_25p"));
2375     menu->addAction(addProfile(group, "HDV 1080p 29.97 fps", "hdv_1080_30p"));
2376     menu->addAction(addProfile(group, tr("Square 1080p 30 fps"), "square_1080p_30"));
2377     menu->addAction(addProfile(group, tr("Square 1080p 60 fps"), "square_1080p_60"));
2378     menu->addAction(addProfile(group, tr("Vertical HD 30 fps"), "vertical_hd_30"));
2379     menu->addAction(addProfile(group, tr("Vertical HD 60 fps"), "vertical_hd_60"));
2380     customMenu = topMenu->addMenu(tr("Custom"));
2381     customMenu->addAction(addAction);
2382     // Load custom profiles
2383     QDir dir(Settings.appDataLocation());
2384     if (dir.cd("profiles")) {
2385         QStringList profiles = dir.entryList(QDir::Files | QDir::NoDotAndDotDot | QDir::Readable);
2386         if (profiles.length() > 0) {
2387             customMenu->addAction(removeAction);
2388             customMenu->addSeparator();
2389         }
2390         foreach (QString name, profiles)
2391             customMenu->addAction(addProfile(group, name, dir.filePath(name)));
2392     }
2393 }
2394 
newProject(const QString & filename,bool isProjectFolder)2395 void MainWindow::newProject(const QString &filename, bool isProjectFolder)
2396 {
2397     if (isProjectFolder) {
2398         QFileInfo info(filename);
2399         MLT.setProjectFolder(info.absolutePath());
2400     }
2401     if (saveXML(filename)) {
2402         QMutexLocker locker(&m_autosaveMutex);
2403         if (m_autosaveFile)
2404             m_autosaveFile->changeManagedFile(filename);
2405         else
2406             m_autosaveFile.reset(new AutoSaveFile(filename));
2407         setCurrentFile(filename);
2408         setWindowModified(false);
2409         if (MLT.producer())
2410             showStatusMessage(tr("Saved %1").arg(m_currentFile));
2411         m_undoStack->setClean();
2412         m_recentDock->add(filename);
2413     } else {
2414         showSaveError();
2415     }
2416 }
2417 
addCustomProfile(const QString & name,QMenu * menu,QAction * action,QActionGroup * group)2418 void MainWindow::addCustomProfile(const QString &name, QMenu *menu, QAction *action, QActionGroup *group)
2419 {
2420     // Add new profile to the menu.
2421     QDir dir(Settings.appDataLocation());
2422     if (dir.cd("profiles")) {
2423         QStringList profiles = dir.entryList(QDir::Files | QDir::NoDotAndDotDot | QDir::Readable);
2424         if (profiles.length() == 1) {
2425             menu->addAction(action);
2426             menu->addSeparator();
2427         }
2428         action = addProfile(group, name, dir.filePath(name));
2429         action->setChecked(true);
2430         menu->addAction(action);
2431         Settings.setPlayerProfile(dir.filePath(name));
2432         Settings.sync();
2433     }
2434 }
2435 
removeCustomProfiles(const QStringList & profiles,QDir & dir,QMenu * menu,QAction * action)2436 void MainWindow::removeCustomProfiles(const QStringList &profiles, QDir& dir, QMenu *menu, QAction *action)
2437 {
2438     foreach(const QString& profile, profiles) {
2439         // Remove the file.
2440         dir.remove(profile);
2441         // Locate the menu item.
2442         foreach (QAction* a, menu->actions()) {
2443             if (a->text() == profile) {
2444                 // Remove the menu item.
2445                 delete a;
2446                 break;
2447             }
2448         }
2449     }
2450     // If no more custom video modes.
2451     if (menu->actions().size() == 3) {
2452         // Remove the Remove action and separator.
2453         menu->removeAction(action);
2454         foreach (QAction* a, menu->actions()) {
2455             if (a->isSeparator()) {
2456                 delete a;
2457                 break;
2458             }
2459         }
2460     }
2461 }
2462 
2463 // Drag-n-drop events
2464 
eventFilter(QObject * target,QEvent * event)2465 bool MainWindow::eventFilter(QObject* target, QEvent* event)
2466 {
2467     if (event->type() == QEvent::DragEnter && target == MLT.videoWidget()) {
2468         dragEnterEvent(static_cast<QDragEnterEvent*>(event));
2469         return true;
2470     } else if (event->type() == QEvent::Drop && target == MLT.videoWidget()) {
2471         dropEvent(static_cast<QDropEvent*>(event));
2472         return true;
2473     } else if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
2474         if (QEvent::KeyPress == event->type()) {
2475             // Let Shift+Escape be a global hook to defocus a widget (assign global player focus).
2476             auto keyEvent = static_cast<QKeyEvent*>(event);
2477             if (Qt::Key_Escape == keyEvent->key() && Qt::ShiftModifier == keyEvent->modifiers()) {
2478                 m_player->setFocus();
2479                 return true;
2480             }
2481         }
2482         QQuickWidget * focusedQuickWidget = qobject_cast<QQuickWidget*>(qApp->focusWidget());
2483         if (focusedQuickWidget && focusedQuickWidget->quickWindow()->activeFocusItem()) {
2484             event->accept();
2485             focusedQuickWidget->quickWindow()->sendEvent(focusedQuickWidget->quickWindow()->activeFocusItem(), event);
2486             QWidget * w = focusedQuickWidget->parentWidget();
2487             if (!event->isAccepted())
2488                 qApp->sendEvent(w, event);
2489             return true;
2490         }
2491     }
2492     return QMainWindow::eventFilter(target, event);
2493 }
2494 
dragEnterEvent(QDragEnterEvent * event)2495 void MainWindow::dragEnterEvent(QDragEnterEvent *event)
2496 {
2497     // Simulate the player firing a dragStarted even to make the playlist close
2498     // its help text view. This lets one drop a clip directly into the playlist
2499     // from a fresh start.
2500     Mlt::GLWidget* videoWidget = (Mlt::GLWidget*) &Mlt::Controller::singleton();
2501     emit videoWidget->dragStarted();
2502 
2503     event->acceptProposedAction();
2504 }
2505 
dropEvent(QDropEvent * event)2506 void MainWindow::dropEvent(QDropEvent *event)
2507 {
2508     const QMimeData *mimeData = event->mimeData();
2509     if (mimeData->hasFormat("application/x-qabstractitemmodeldatalist")) {
2510         QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist");
2511         QDataStream stream(&encoded, QIODevice::ReadOnly);
2512         QMap<int,  QVariant> roleDataMap;
2513         while (!stream.atEnd()) {
2514             int row, col;
2515             stream >> row >> col >> roleDataMap;
2516         }
2517         if (roleDataMap.contains(Qt::ToolTipRole)) {
2518             // DisplayRole is just basename, ToolTipRole contains full path
2519             open(roleDataMap[Qt::ToolTipRole].toString());
2520             event->acceptProposedAction();
2521         }
2522     }
2523     else if (mimeData->hasUrls()) {
2524         openMultiple(mimeData->urls());
2525         event->acceptProposedAction();
2526     }
2527     else if (mimeData->hasFormat(Mlt::XmlMimeType )) {
2528         m_playlistDock->on_actionOpen_triggered();
2529         event->acceptProposedAction();
2530     }
2531 }
2532 
closeEvent(QCloseEvent * event)2533 void MainWindow::closeEvent(QCloseEvent* event)
2534 {
2535     if (continueJobsRunning() && continueModified()) {
2536         LOG_DEBUG() << "begin";
2537         JOBS.cleanup();
2538         writeSettings();
2539         if (m_exitCode == EXIT_SUCCESS) {
2540             MLT.stop();
2541         } else {
2542             if (multitrack())
2543                 m_timelineDock->model()->close();
2544             if (playlist())
2545                 m_playlistDock->model()->close();
2546             else
2547                 onMultitrackClosed();
2548         }
2549         QThreadPool::globalInstance()->clear();
2550         AudioLevelsTask::closeAll();
2551         event->accept();
2552         emit aboutToShutDown();
2553         if (m_exitCode == EXIT_SUCCESS) {
2554             QApplication::quit();
2555             LOG_DEBUG() << "end";
2556             ::_Exit(0);
2557         } else {
2558             QApplication::exit(m_exitCode);
2559             LOG_DEBUG() << "end";
2560         }
2561         return;
2562     }
2563     event->ignore();
2564 }
2565 
showEvent(QShowEvent * event)2566 void MainWindow::showEvent(QShowEvent* event)
2567 {
2568     // This is needed to prevent a crash on windows on startup when timeline
2569     // is visible and dock title bars are hidden.
2570     Q_UNUSED(event)
2571     ui->actionShowTitleBars->setChecked(Settings.showTitleBars());
2572     on_actionShowTitleBars_triggered(Settings.showTitleBars());
2573     ui->actionShowToolbar->setChecked(Settings.showToolBar());
2574     on_actionShowToolbar_triggered(Settings.showToolBar());
2575     ui->actionShowTextUnderIcons->setChecked(Settings.textUnderIcons());
2576     on_actionShowTextUnderIcons_toggled(Settings.textUnderIcons());
2577     ui->actionShowSmallIcons->setChecked(Settings.smallIcons());
2578     on_actionShowSmallIcons_toggled(Settings.smallIcons());
2579 
2580     windowHandle()->installEventFilter(this);
2581     Database::singleton(this);
2582 
2583 #ifndef SHOTCUT_NOUPGRADE
2584     if (!Settings.noUpgrade() && !qApp->property("noupgrade").toBool())
2585         QTimer::singleShot(0, this, SLOT(showUpgradePrompt()));
2586 #endif
2587 
2588 #ifdef Q_OS_WIN
2589     WindowsTaskbarButton::getInstance().setParentWindow(this);
2590 #endif
2591     onAutosaveTimeout();
2592 }
2593 
on_actionOpenOther_triggered()2594 void MainWindow::on_actionOpenOther_triggered()
2595 {
2596     // these static are used to open dialog with previous configuration
2597     OpenOtherDialog dialog(this);
2598 
2599     if (MLT.producer())
2600         dialog.load(MLT.producer());
2601     if (dialog.exec() == QDialog::Accepted) {
2602         closeProducer();
2603         open(dialog.newProducer(MLT.profile()));
2604     }
2605 }
2606 
onProducerOpened(bool withReopen)2607 void MainWindow::onProducerOpened(bool withReopen)
2608 {
2609     QWidget* w = loadProducerWidget(MLT.producer());
2610     if (withReopen && w && !MLT.producer()->get(kMultitrackItemProperty)) {
2611         if (-1 != w->metaObject()->indexOfSignal("producerReopened(bool)"))
2612             connect(w, SIGNAL(producerReopened(bool)), m_player, SLOT(onProducerOpened(bool)));
2613     }
2614     else if (MLT.isPlaylist()) {
2615         m_playlistDock->model()->load();
2616         if (playlist()) {
2617             m_isPlaylistLoaded = true;
2618             m_player->setIn(-1);
2619             m_player->setOut(-1);
2620             m_playlistDock->setVisible(true);
2621             m_playlistDock->raise();
2622             m_player->enableTab(Player::ProjectTabIndex);
2623             m_player->switchToTab(Player::ProjectTabIndex);
2624         }
2625     }
2626     else if (MLT.isMultitrack()) {
2627         m_timelineDock->blockSelection(true);
2628         m_timelineDock->model()->load();
2629         m_timelineDock->blockSelection(false);
2630         if (isMultitrackValid()) {
2631             m_player->setIn(-1);
2632             m_player->setOut(-1);
2633             m_timelineDock->setVisible(true);
2634             m_timelineDock->raise();
2635             m_player->enableTab(Player::ProjectTabIndex);
2636             m_player->switchToTab(Player::ProjectTabIndex);
2637             m_timelineDock->selectMultitrack();
2638             QTimer::singleShot(0, [=]() {
2639                 m_timelineDock->setSelection();
2640             });
2641         }
2642     }
2643     if (MLT.isClip()) {
2644         m_player->enableTab(Player::SourceTabIndex);
2645         m_player->switchToTab(Player::SourceTabIndex);
2646         Util::getHash(*MLT.producer());
2647         ui->actionPaste->setEnabled(true);
2648     }
2649     QMutexLocker locker(&m_autosaveMutex);
2650     if (m_autosaveFile)
2651         setCurrentFile(m_autosaveFile->managedFileName());
2652     else if (!MLT.URL().isEmpty())
2653         setCurrentFile(MLT.URL());
2654     on_actionJack_triggered(ui->actionJack && ui->actionJack->isChecked());
2655 }
2656 
onProducerChanged()2657 void MainWindow::onProducerChanged()
2658 {
2659     MLT.refreshConsumer();
2660     if (playlist() && MLT.producer() && MLT.producer()->is_valid()
2661         && MLT.producer()->get_int(kPlaylistIndexProperty))
2662         m_playlistDock->setUpdateButtonEnabled(true);
2663 }
2664 
on_actionSave_triggered()2665 bool MainWindow::on_actionSave_triggered()
2666 {
2667     if (m_currentFile.isEmpty()) {
2668         return on_actionSave_As_triggered();
2669     } else {
2670         if (Util::warnIfNotWritable(m_currentFile, this, tr("Save XML")))
2671             return false;
2672         bool success = saveXML(m_currentFile);
2673         QMutexLocker locker(&m_autosaveMutex);
2674         m_autosaveFile.reset(new AutoSaveFile(m_currentFile));
2675         setCurrentFile(m_currentFile);
2676         setWindowModified(false);
2677         if (success) {
2678             showStatusMessage(tr("Saved %1").arg(m_currentFile));
2679         } else {
2680             showSaveError();
2681         }
2682         m_undoStack->setClean();
2683         return true;
2684     }
2685 }
2686 
on_actionSave_As_triggered()2687 bool MainWindow::on_actionSave_As_triggered()
2688 {
2689     QString path = Settings.savePath();
2690     if (!m_currentFile.isEmpty())
2691         path = m_currentFile;
2692     QString caption = tr("Save XML");
2693     QString filename = QFileDialog::getSaveFileName(this, caption, path,
2694         tr("MLT XML (*.mlt)"), nullptr, Util::getFileDialogOptions());
2695     if (!filename.isEmpty()) {
2696         QFileInfo fi(filename);
2697         Settings.setSavePath(fi.path());
2698         if (fi.suffix() != "mlt")
2699             filename += ".mlt";
2700 
2701         if (Util::warnIfNotWritable(filename, this, caption))
2702             return false;
2703         newProject(filename);
2704     }
2705     return !filename.isEmpty();
2706 }
2707 
continueModified()2708 bool MainWindow::continueModified()
2709 {
2710     if (isWindowModified()) {
2711         QMessageBox dialog(QMessageBox::Warning,
2712                                      qApp->applicationName(),
2713                                      tr("The project has been modified.\n"
2714                                         "Do you want to save your changes?"),
2715                                      QMessageBox::No |
2716                                      QMessageBox::Cancel |
2717                                      QMessageBox::Yes,
2718                                      this);
2719         dialog.setWindowModality(QmlApplication::dialogModality());
2720         dialog.setDefaultButton(QMessageBox::Yes);
2721         dialog.setEscapeButton(QMessageBox::Cancel);
2722         int r = dialog.exec();
2723         if (r == QMessageBox::Yes || r == QMessageBox::No) {
2724             if (r == QMessageBox::Yes) {
2725                 return on_actionSave_triggered();
2726             } else {
2727                 QMutexLocker locker(&m_autosaveMutex);
2728                 m_autosaveFile.reset();
2729             }
2730         } else if (r == QMessageBox::Cancel) {
2731             return false;
2732         }
2733     }
2734     return true;
2735 }
2736 
continueJobsRunning()2737 bool MainWindow::continueJobsRunning()
2738 {
2739     if (JOBS.hasIncomplete()) {
2740         QMessageBox dialog(QMessageBox::Warning,
2741                                      qApp->applicationName(),
2742                                      tr("There are incomplete jobs.\n"
2743                                         "Do you want to still want to exit?"),
2744                                      QMessageBox::No |
2745                                      QMessageBox::Yes,
2746                                      this);
2747         dialog.setWindowModality(QmlApplication::dialogModality());
2748         dialog.setDefaultButton(QMessageBox::Yes);
2749         dialog.setEscapeButton(QMessageBox::No);
2750         return (dialog.exec() == QMessageBox::Yes);
2751     }
2752     if (m_encodeDock->isExportInProgress()) {
2753         QMessageBox dialog(QMessageBox::Warning,
2754                                      qApp->applicationName(),
2755                                      tr("An export is in progress.\n"
2756                                         "Do you want to still want to exit?"),
2757                                      QMessageBox::No |
2758                                      QMessageBox::Yes,
2759                                      this);
2760         dialog.setWindowModality(QmlApplication::dialogModality());
2761         dialog.setDefaultButton(QMessageBox::Yes);
2762         dialog.setEscapeButton(QMessageBox::No);
2763         return (dialog.exec() == QMessageBox::Yes);
2764     }
2765     return true;
2766 }
2767 
undoStack() const2768 QUndoStack* MainWindow::undoStack() const
2769 {
2770     return m_undoStack;
2771 }
2772 
onEncodeTriggered(bool checked)2773 void MainWindow::onEncodeTriggered(bool checked)
2774 {
2775     if (checked) {
2776         m_encodeDock->show();
2777         m_encodeDock->raise();
2778     }
2779 }
2780 
onCaptureStateChanged(bool started)2781 void MainWindow::onCaptureStateChanged(bool started)
2782 {
2783     if (started && (MLT.resource().startsWith("x11grab:") ||
2784                     MLT.resource().startsWith("gdigrab:") ||
2785                     MLT.resource().startsWith("avfoundation"))
2786                 && !MLT.producer()->get_int(kBackgroundCaptureProperty))
2787         showMinimized();
2788 }
2789 
onJobsDockTriggered(bool checked)2790 void MainWindow::onJobsDockTriggered(bool checked)
2791 {
2792     if (checked) {
2793         m_jobsDock->show();
2794         m_jobsDock->raise();
2795     }
2796 }
2797 
onRecentDockTriggered(bool checked)2798 void MainWindow::onRecentDockTriggered(bool checked)
2799 {
2800     if (checked) {
2801         m_recentDock->show();
2802         m_recentDock->raise();
2803     }
2804 }
2805 
onPropertiesDockTriggered(bool checked)2806 void MainWindow::onPropertiesDockTriggered(bool checked)
2807 {
2808     if (checked) {
2809         m_propertiesDock->show();
2810         m_propertiesDock->raise();
2811     }
2812 }
2813 
onPlaylistDockTriggered(bool checked)2814 void MainWindow::onPlaylistDockTriggered(bool checked)
2815 {
2816     if (checked) {
2817         m_playlistDock->show();
2818         m_playlistDock->raise();
2819     }
2820 }
2821 
onTimelineDockTriggered(bool checked)2822 void MainWindow::onTimelineDockTriggered(bool checked)
2823 {
2824     if (checked) {
2825         m_timelineDock->show();
2826         m_timelineDock->raise();
2827     }
2828 }
2829 
onHistoryDockTriggered(bool checked)2830 void MainWindow::onHistoryDockTriggered(bool checked)
2831 {
2832     if (checked) {
2833         m_historyDock->show();
2834         m_historyDock->raise();
2835     }
2836 }
2837 
onFiltersDockTriggered(bool checked)2838 void MainWindow::onFiltersDockTriggered(bool checked)
2839 {
2840     if (checked) {
2841         m_filtersDock->show();
2842         m_filtersDock->raise();
2843     }
2844 }
2845 
onKeyframesDockTriggered(bool checked)2846 void MainWindow::onKeyframesDockTriggered(bool checked)
2847 {
2848     if (checked) {
2849         m_keyframesDock->show();
2850         m_keyframesDock->raise();
2851     }
2852 }
2853 
onPlaylistCreated()2854 void MainWindow::onPlaylistCreated()
2855 {
2856     if (!playlist() || playlist()->count() == 0) return;
2857     m_player->enableTab(Player::ProjectTabIndex, true);
2858 }
2859 
onPlaylistLoaded()2860 void MainWindow::onPlaylistLoaded()
2861 {
2862     updateMarkers();
2863     m_player->enableTab(Player::ProjectTabIndex, true);
2864 }
2865 
onPlaylistCleared()2866 void MainWindow::onPlaylistCleared()
2867 {
2868     m_player->onTabBarClicked(Player::SourceTabIndex);
2869     setWindowModified(true);
2870 }
2871 
onPlaylistClosed()2872 void MainWindow::onPlaylistClosed()
2873 {
2874     closeProducer();
2875     setProfile(Settings.playerProfile());
2876     resetVideoModeMenu();
2877     setAudioChannels(Settings.playerAudioChannels());
2878     setCurrentFile("");
2879     setWindowModified(false);
2880     m_undoStack->clear();
2881     MLT.resetURL();
2882     QMutexLocker locker(&m_autosaveMutex);
2883     m_autosaveFile.reset(new AutoSaveFile(untitledFileName()));
2884     if (!isMultitrackValid())
2885         m_player->enableTab(Player::ProjectTabIndex, false);
2886 }
2887 
onPlaylistModified()2888 void MainWindow::onPlaylistModified()
2889 {
2890     setWindowModified(true);
2891     if (MLT.producer() && playlist() && (void*) MLT.producer()->get_producer() == (void*) playlist()->get_playlist())
2892         m_player->onDurationChanged();
2893     updateMarkers();
2894     m_player->enableTab(Player::ProjectTabIndex, true);
2895 }
2896 
onMultitrackCreated()2897 void MainWindow::onMultitrackCreated()
2898 {
2899     m_player->enableTab(Player::ProjectTabIndex, true);
2900 }
2901 
onMultitrackClosed()2902 void MainWindow::onMultitrackClosed()
2903 {
2904     setAudioChannels(Settings.playerAudioChannels());
2905     closeProducer();
2906     setProfile(Settings.playerProfile());
2907     resetVideoModeMenu();
2908     setCurrentFile("");
2909     setWindowModified(false);
2910     m_undoStack->clear();
2911     MLT.resetURL();
2912     QMutexLocker locker(&m_autosaveMutex);
2913     m_autosaveFile.reset(new AutoSaveFile(untitledFileName()));
2914     if (!playlist() || playlist()->count() == 0)
2915         m_player->enableTab(Player::ProjectTabIndex, false);
2916 }
2917 
onMultitrackModified()2918 void MainWindow::onMultitrackModified()
2919 {
2920     setWindowModified(true);
2921 
2922     // Reflect this playlist info onto the producer for keyframes dock.
2923     if (!m_timelineDock->selection().isEmpty()) {
2924         int trackIndex = m_timelineDock->selection().first().y();
2925         int clipIndex = m_timelineDock->selection().first().x();
2926         QScopedPointer<Mlt::ClipInfo> info(m_timelineDock->getClipInfo(trackIndex, clipIndex));
2927         if (info && info->producer && info->producer->is_valid()) {
2928             int expected = info->frame_in;
2929             QScopedPointer<Mlt::ClipInfo> info2(m_timelineDock->getClipInfo(trackIndex, clipIndex - 1));
2930             if (info2 && info2->producer && info2->producer->is_valid()
2931                       && info2->producer->get(kShotcutTransitionProperty)) {
2932                 // Factor in a transition left of the clip.
2933                 expected -= info2->frame_count;
2934                 info->producer->set(kPlaylistStartProperty, info2->start);
2935             } else {
2936                 info->producer->set(kPlaylistStartProperty, info->start);
2937             }
2938             if (expected != info->producer->get_int(kFilterInProperty)) {
2939                 int delta = expected - info->producer->get_int(kFilterInProperty);
2940                 info->producer->set(kFilterInProperty, expected);
2941                 emit m_filtersDock->producerInChanged(delta);
2942             }
2943             expected = info->frame_out;
2944             info2.reset(m_timelineDock->getClipInfo(trackIndex, clipIndex + 1));
2945             if (info2 && info2->producer && info2->producer->is_valid()
2946                       && info2->producer->get(kShotcutTransitionProperty)) {
2947                 // Factor in a transition right of the clip.
2948                 expected += info2->frame_count;
2949             }
2950             if (expected != info->producer->get_int(kFilterOutProperty)) {
2951                 int delta = expected - info->producer->get_int(kFilterOutProperty);
2952                 info->producer->set(kFilterOutProperty, expected);
2953                 emit m_filtersDock->producerOutChanged(delta);
2954             }
2955         }
2956     }
2957 }
2958 
onMultitrackDurationChanged()2959 void MainWindow::onMultitrackDurationChanged()
2960 {
2961     if (MLT.producer() && (void*) MLT.producer()->get_producer() == (void*) multitrack()->get_producer())
2962         m_player->onDurationChanged();
2963 }
2964 
onCutModified()2965 void MainWindow::onCutModified()
2966 {
2967     if (!playlist() && !multitrack()) {
2968         setWindowModified(true);
2969     }
2970     if (playlist())
2971         m_playlistDock->setUpdateButtonEnabled(true);
2972 }
2973 
onProducerModified()2974 void MainWindow::onProducerModified()
2975 {
2976     setWindowModified(true);
2977 }
2978 
onFilterModelChanged()2979 void MainWindow::onFilterModelChanged()
2980 {
2981     MLT.refreshConsumer();
2982     setWindowModified(true);
2983     if (playlist())
2984         m_playlistDock->setUpdateButtonEnabled(true);
2985 }
2986 
updateMarkers()2987 void MainWindow::updateMarkers()
2988 {
2989     if (playlist() && MLT.isPlaylist()) {
2990         QList<int> markers;
2991         int n = playlist()->count();
2992         for (int i = 0; i < n; i++)
2993             markers.append(playlist()->clip_start(i));
2994         m_player->setMarkers(markers);
2995     }
2996 }
2997 
updateThumbnails()2998 void MainWindow::updateThumbnails()
2999 {
3000     if (Settings.playlistThumbnails() != "hidden")
3001         m_playlistDock->model()->refreshThumbnails();
3002 }
3003 
on_actionUndo_triggered()3004 void MainWindow::on_actionUndo_triggered()
3005 {
3006     TimelineSelectionBlocker blocker(*m_timelineDock);
3007     m_undoStack->undo();
3008 }
3009 
on_actionRedo_triggered()3010 void MainWindow::on_actionRedo_triggered()
3011 {
3012     TimelineSelectionBlocker blocker(*m_timelineDock);
3013     m_undoStack->redo();
3014 }
3015 
on_actionFAQ_triggered()3016 void MainWindow::on_actionFAQ_triggered()
3017 {
3018     QDesktopServices::openUrl(QUrl("https://www.shotcut.org/FAQ/"));
3019 }
3020 
on_actionForum_triggered()3021 void MainWindow::on_actionForum_triggered()
3022 {
3023     QDesktopServices::openUrl(QUrl("https://forum.shotcut.org/"));
3024 }
3025 
saveXML(const QString & filename,bool withRelativePaths)3026 bool MainWindow::saveXML(const QString &filename, bool withRelativePaths)
3027 {
3028     bool result;
3029     if (m_timelineDock->model()->rowCount() > 0) {
3030         result = MLT.saveXML(filename, multitrack(), withRelativePaths);
3031     } else if (m_playlistDock->model()->rowCount() > 0) {
3032         int in = MLT.producer()->get_in();
3033         int out = MLT.producer()->get_out();
3034         MLT.producer()->set_in_and_out(0, MLT.producer()->get_length() - 1);
3035         result = MLT.saveXML(filename, playlist(), withRelativePaths);
3036         MLT.producer()->set_in_and_out(in, out);
3037     } else if (MLT.producer()) {
3038         result = MLT.saveXML(filename, (MLT.isMultitrack() || MLT.isPlaylist())? MLT.savedProducer() : 0, withRelativePaths);
3039     } else {
3040         // Save an empty playlist, which is accepted by both MLT and Shotcut.
3041         Mlt::Playlist playlist(MLT.profile());
3042         result = MLT.saveXML(filename, &playlist, withRelativePaths);
3043     }
3044     return result;
3045 }
3046 
changeTheme(const QString & theme)3047 void MainWindow::changeTheme(const QString &theme)
3048 {
3049     LOG_DEBUG() << "begin";
3050     if (theme == "dark") {
3051         QApplication::setStyle("Fusion");
3052         QPalette palette;
3053         palette.setColor(QPalette::Window, QColor(50,50,50));
3054         palette.setColor(QPalette::WindowText, QColor(220,220,220));
3055         palette.setColor(QPalette::Base, QColor(30,30,30));
3056         palette.setColor(QPalette::AlternateBase, QColor(40,40,40));
3057         palette.setColor(QPalette::Highlight, QColor(23,92,118));
3058         palette.setColor(QPalette::HighlightedText, Qt::white);
3059         palette.setColor(QPalette::ToolTipBase, palette.color(QPalette::Highlight));
3060         palette.setColor(QPalette::ToolTipText, palette.color(QPalette::WindowText));
3061         palette.setColor(QPalette::Text, palette.color(QPalette::WindowText));
3062         palette.setColor(QPalette::BrightText, Qt::red);
3063         palette.setColor(QPalette::Button, palette.color(QPalette::Window));
3064         palette.setColor(QPalette::ButtonText, palette.color(QPalette::WindowText));
3065         palette.setColor(QPalette::Link, palette.color(QPalette::Highlight).lighter());
3066         palette.setColor(QPalette::LinkVisited, palette.color(QPalette::Highlight));
3067         palette.setColor(QPalette::Disabled, QPalette::Text, Qt::darkGray);
3068         palette.setColor(QPalette::Disabled, QPalette::ButtonText, Qt::darkGray);
3069         QApplication::setPalette(palette);
3070         QIcon::setThemeName("dark");
3071         QMetaObject::invokeMethod(&MAIN, "on_actionShowTextUnderIcons_toggled", Qt::QueuedConnection, Q_ARG(bool, Settings.textUnderIcons()));
3072     } else if (theme == "light") {
3073         QStyle* style = QStyleFactory::create("Fusion");
3074         QApplication::setStyle(style);
3075         QApplication::setPalette(style->standardPalette());
3076         QIcon::setThemeName("light");
3077         QMetaObject::invokeMethod(&MAIN, "on_actionShowTextUnderIcons_toggled", Qt::QueuedConnection, Q_ARG(bool, Settings.textUnderIcons()));
3078     } else {
3079         QApplication::setStyle(qApp->property("system-style").toString());
3080         QIcon::setThemeName("oxygen");
3081     }
3082     emit QmlApplication::singleton().paletteChanged();
3083     LOG_DEBUG() << "end";
3084 }
3085 
playlist() const3086 Mlt::Playlist* MainWindow::playlist() const
3087 {
3088     return m_playlistDock->model()->playlist();
3089 }
3090 
isPlaylistValid() const3091 bool MainWindow::isPlaylistValid() const
3092 {
3093     return m_playlistDock->model()->playlist()
3094         && m_playlistDock->model()->rowCount() > 0;
3095 }
3096 
multitrack() const3097 Mlt::Producer *MainWindow::multitrack() const
3098 {
3099     return m_timelineDock->model()->tractor();
3100 }
3101 
isMultitrackValid() const3102 bool MainWindow::isMultitrackValid() const
3103 {
3104     return m_timelineDock->model()->tractor()
3105        && !m_timelineDock->model()->trackList().empty();
3106 }
3107 
loadProducerWidget(Mlt::Producer * producer)3108 QWidget *MainWindow::loadProducerWidget(Mlt::Producer* producer)
3109 {
3110     QWidget* w = 0;
3111     QScrollArea* scrollArea = (QScrollArea*) m_propertiesDock->widget();
3112 
3113     if (!producer || !producer->is_valid()) {
3114         if (scrollArea->widget())
3115             scrollArea->widget()->deleteLater();
3116         return  w;
3117     } else {
3118         scrollArea->show();
3119     }
3120 
3121     QString service(producer->get("mlt_service"));
3122     QString resource = QString::fromUtf8(producer->get("resource"));
3123     QString shotcutProducer(producer->get(kShotcutProducerProperty));
3124 
3125     if (resource.startsWith("video4linux2:") || QString::fromUtf8(producer->get("resource1")).startsWith("video4linux2:"))
3126         w = new Video4LinuxWidget(this);
3127     else if (resource.startsWith("pulse:"))
3128         w = new PulseAudioWidget(this);
3129     else if (resource.startsWith("jack:"))
3130         w = new JackProducerWidget(this);
3131     else if (resource.startsWith("alsa:"))
3132         w = new AlsaWidget(this);
3133     else if (resource.startsWith("dshow:") || QString::fromUtf8(producer->get("resource1")).startsWith("dshow:"))
3134         w = new DirectShowVideoWidget(this);
3135     else if (resource.startsWith("avfoundation:"))
3136         w = new AvfoundationProducerWidget(this);
3137     else if (resource.startsWith("x11grab:"))
3138         w = new X11grabWidget(this);
3139     else if (resource.startsWith("gdigrab:"))
3140         w = new GDIgrabWidget(this);
3141     else if (service.startsWith("avformat") || shotcutProducer == "avformat")
3142         w = new AvformatProducerWidget(this);
3143     else if (MLT.isImageProducer(producer)) {
3144         w = new ImageProducerWidget(this);
3145         connect(m_player, SIGNAL(outChanged(int)), w, SLOT(updateDuration()));
3146     }
3147     else if (service == "decklink" || resource.contains("decklink"))
3148         w = new DecklinkProducerWidget(this);
3149     else if (service == "color")
3150         w = new ColorProducerWidget(this);
3151     else if (service == "noise")
3152         w = new NoiseWidget(this);
3153     else if (service == "frei0r.ising0r")
3154         w = new IsingWidget(this);
3155     else if (service == "frei0r.lissajous0r")
3156         w = new LissajousWidget(this);
3157     else if (service == "frei0r.plasma")
3158         w = new PlasmaWidget(this);
3159     else if (service == "frei0r.test_pat_B")
3160         w = new ColorBarsWidget(this);
3161     else if (service == "tone")
3162         w = new ToneProducerWidget(this);
3163     else if (service == "count")
3164         w = new CountProducerWidget(this);
3165     else if (service == "blipflash")
3166         w = new BlipProducerWidget(this);
3167     else if (producer->parent().get(kShotcutTransitionProperty)) {
3168         w = new LumaMixTransition(producer->parent(), this);
3169         scrollArea->setWidget(w);
3170         if (-1 != w->metaObject()->indexOfSignal("modified()"))
3171             connect(w, SIGNAL(modified()), SLOT(onProducerModified()));
3172         return w;
3173     } else if (playlist_type == producer->type()) {
3174         int trackIndex = m_timelineDock->currentTrack();
3175         bool isBottomVideo = m_timelineDock->model()->data(m_timelineDock->model()->index(trackIndex), MultitrackModel::IsBottomVideoRole).toBool();
3176         if (!isBottomVideo) {
3177             w = new TrackPropertiesWidget(*producer, this);
3178             scrollArea->setWidget(w);
3179             return w;
3180         }
3181     } else if (tractor_type == producer->type()) {
3182         w = new TimelinePropertiesWidget(*producer, this);
3183         scrollArea->setWidget(w);
3184         return w;
3185     }
3186     if (w) {
3187         dynamic_cast<AbstractProducerWidget*>(w)->setProducer(producer);
3188         if (-1 != w->metaObject()->indexOfSignal("producerChanged(Mlt::Producer*)")) {
3189             connect(w, SIGNAL(producerChanged(Mlt::Producer*)), SLOT(onProducerChanged()));
3190             connect(w, SIGNAL(producerChanged(Mlt::Producer*)), m_filterController, SLOT(setProducer(Mlt::Producer*)));
3191             connect(w, SIGNAL(producerChanged(Mlt::Producer*)), m_playlistDock, SLOT(onProducerChanged(Mlt::Producer*)));
3192             if (producer->get(kMultitrackItemProperty))
3193                 connect(w, SIGNAL(producerChanged(Mlt::Producer*)), m_timelineDock, SLOT(onProducerChanged(Mlt::Producer*)));
3194         }
3195         if (-1 != w->metaObject()->indexOfSignal("modified()")) {
3196             connect(w, SIGNAL(modified()), SLOT(onProducerModified()));
3197             connect(w, SIGNAL(modified()), m_playlistDock, SLOT(onProducerModified()));
3198             connect(w, SIGNAL(modified()), m_timelineDock, SLOT(onProducerModified()));
3199             connect(w, SIGNAL(modified()), m_keyframesDock, SLOT(onProducerModified()));
3200             connect(w, SIGNAL(modified()), m_filterController, SLOT(onProducerChanged()));
3201         }
3202         if (-1 != w->metaObject()->indexOfSlot("updateDuration()")) {
3203             connect(m_timelineDock, SIGNAL(durationChanged()), w, SLOT(updateDuration()));
3204         }
3205         if (-1 != w->metaObject()->indexOfSlot("rename()")) {
3206             connect(this, SIGNAL(renameRequested()), w, SLOT(rename()));
3207         }
3208         scrollArea->setWidget(w);
3209         onProducerChanged();
3210     } else if (scrollArea->widget()) {
3211         scrollArea->widget()->deleteLater();
3212     }
3213     return w;
3214 }
3215 
on_actionEnter_Full_Screen_triggered()3216 void MainWindow::on_actionEnter_Full_Screen_triggered()
3217 {
3218 #ifdef Q_OS_WIN
3219     bool isFull = isMaximized();
3220 #else
3221     bool isFull = isFullScreen();
3222 #endif
3223     if (isFull) {
3224         showNormal();
3225         ui->actionEnter_Full_Screen->setText(tr("Enter Full Screen"));
3226     } else {
3227 #ifdef Q_OS_WIN
3228         showMaximized();
3229 #else
3230         showFullScreen();
3231 #endif
3232         ui->actionEnter_Full_Screen->setText(tr("Exit Full Screen"));
3233     }
3234 }
3235 
onGpuNotSupported()3236 void MainWindow::onGpuNotSupported()
3237 {
3238     Settings.setPlayerGPU(false);
3239     if (ui->actionGPU) {
3240         ui->actionGPU->setChecked(false);
3241         ui->actionGPU->setDisabled(true);
3242     }
3243     LOG_WARNING() << "";
3244     QMessageBox::critical(this, qApp->applicationName(),
3245         tr("GPU effects are not supported"));
3246 }
3247 
stepLeftOneFrame()3248 void MainWindow::stepLeftOneFrame()
3249 {
3250     m_player->seek(m_player->position() - 1);
3251 }
3252 
stepRightOneFrame()3253 void MainWindow::stepRightOneFrame()
3254 {
3255     m_player->seek(m_player->position() + 1);
3256 }
3257 
stepLeftOneSecond()3258 void MainWindow::stepLeftOneSecond()
3259 {
3260     stepLeftBySeconds(-1);
3261 }
3262 
stepRightOneSecond()3263 void MainWindow::stepRightOneSecond()
3264 {
3265     stepLeftBySeconds(1);
3266 }
3267 
setInToCurrent(bool ripple)3268 void MainWindow::setInToCurrent(bool ripple)
3269 {
3270     if (m_player->tabIndex() == Player::ProjectTabIndex && isMultitrackValid()) {
3271         m_timelineDock->trimClipAtPlayhead(TimelineDock::TrimInPoint, ripple);
3272     } else if (MLT.isSeekable() && MLT.isClip()) {
3273         m_player->setIn(m_player->position());
3274         int delta = m_player->position() - MLT.producer()->get_in();
3275         emit m_player->inChanged(delta);
3276     }
3277 }
3278 
setOutToCurrent(bool ripple)3279 void MainWindow::setOutToCurrent(bool ripple)
3280 {
3281     if (m_player->tabIndex() == Player::ProjectTabIndex && isMultitrackValid()) {
3282         m_timelineDock->trimClipAtPlayhead(TimelineDock::TrimOutPoint, ripple);
3283     } else if (MLT.isSeekable() && MLT.isClip()) {
3284         m_player->setOut(m_player->position());
3285         int delta = m_player->position() - MLT.producer()->get_out();
3286         emit m_player->outChanged(delta);
3287     }
3288 }
3289 
onShuttle(float x)3290 void MainWindow::onShuttle(float x)
3291 {
3292     if (x == 0) {
3293         m_player->pause();
3294     } else if (x > 0) {
3295         m_player->play(10.0 * x);
3296     } else {
3297         m_player->play(20.0 * x);
3298     }
3299 }
3300 
showUpgradePrompt()3301 void MainWindow::showUpgradePrompt()
3302 {
3303     if (Settings.checkUpgradeAutomatic()) {
3304         showStatusMessage("Checking for upgrade...");
3305         m_network.get(QNetworkRequest(QUrl("https://check.shotcut.org/version.json")));
3306     } else {
3307         QAction* action = new QAction(tr("Click here to check for a new version of Shotcut."), 0);
3308         connect(action, SIGNAL(triggered(bool)), SLOT(on_actionUpgrade_triggered()));
3309         showStatusMessage(action, 15 /* seconds */);
3310     }
3311 }
3312 
on_actionRealtime_triggered(bool checked)3313 void MainWindow::on_actionRealtime_triggered(bool checked)
3314 {
3315     Settings.setPlayerRealtime(checked);
3316     if (Settings.playerGPU())
3317         MLT.pause();
3318     if (MLT.consumer()) {
3319         MLT.restart();
3320     }
3321 
3322 }
3323 
on_actionProgressive_triggered(bool checked)3324 void MainWindow::on_actionProgressive_triggered(bool checked)
3325 {
3326     MLT.videoWidget()->setProperty("progressive", checked);
3327     if (Settings.playerGPU())
3328         MLT.pause();
3329     if (MLT.consumer()) {
3330         MLT.profile().set_progressive(checked);
3331         MLT.updatePreviewProfile();
3332         MLT.restart();
3333     }
3334     Settings.setPlayerProgressive(checked);
3335 }
3336 
changeAudioChannels(bool checked,int channels)3337 void MainWindow::changeAudioChannels(bool checked, int channels)
3338 {
3339     if( checked ) {
3340         Settings.setPlayerAudioChannels(channels);
3341         setAudioChannels(Settings.playerAudioChannels());
3342     }
3343 }
3344 
on_actionChannels1_triggered(bool checked)3345 void MainWindow::on_actionChannels1_triggered(bool checked)
3346 {
3347     changeAudioChannels(checked, 1);
3348 }
3349 
on_actionChannels2_triggered(bool checked)3350 void MainWindow::on_actionChannels2_triggered(bool checked)
3351 {
3352     changeAudioChannels(checked, 2);
3353 }
3354 
on_actionChannels6_triggered(bool checked)3355 void MainWindow::on_actionChannels6_triggered(bool checked)
3356 {
3357     changeAudioChannels(checked, 6);
3358 }
3359 
changeDeinterlacer(bool checked,const char * method)3360 void MainWindow::changeDeinterlacer(bool checked, const char* method)
3361 {
3362     if (checked) {
3363         MLT.videoWidget()->setProperty("deinterlace_method", method);
3364         if (MLT.consumer()) {
3365             MLT.consumer()->set("deinterlace_method", method);
3366             MLT.refreshConsumer();
3367         }
3368     }
3369     Settings.setPlayerDeinterlacer(method);
3370 }
3371 
on_actionOneField_triggered(bool checked)3372 void MainWindow::on_actionOneField_triggered(bool checked)
3373 {
3374     changeDeinterlacer(checked, "onefield");
3375 }
3376 
on_actionLinearBlend_triggered(bool checked)3377 void MainWindow::on_actionLinearBlend_triggered(bool checked)
3378 {
3379     changeDeinterlacer(checked, "linearblend");
3380 }
3381 
on_actionYadifTemporal_triggered(bool checked)3382 void MainWindow::on_actionYadifTemporal_triggered(bool checked)
3383 {
3384     changeDeinterlacer(checked, "yadif-nospatial");
3385 }
3386 
on_actionYadifSpatial_triggered(bool checked)3387 void MainWindow::on_actionYadifSpatial_triggered(bool checked)
3388 {
3389     changeDeinterlacer(checked, "yadif");
3390 }
3391 
changeInterpolation(bool checked,const char * method)3392 void MainWindow::changeInterpolation(bool checked, const char* method)
3393 {
3394     if (checked) {
3395         MLT.videoWidget()->setProperty("rescale", method);
3396         if (MLT.consumer()) {
3397             MLT.consumer()->set("rescale", method);
3398             MLT.refreshConsumer();
3399         }
3400     }
3401     Settings.setPlayerInterpolation(method);
3402 }
3403 
processMultipleFiles()3404 void MainWindow::processMultipleFiles()
3405 {
3406     if (m_multipleFiles.length() <= 0)
3407         return;
3408     QStringList multipleFiles = m_multipleFiles;
3409     m_multipleFiles.clear();
3410     int count = multipleFiles.length();
3411     if (count > 1) {
3412         LongUiTask longTask(tr("Open Files"));
3413         m_playlistDock->show();
3414         m_playlistDock->raise();
3415         for (int i = 0; i < count; i++) {
3416             QString filename = multipleFiles.takeFirst();
3417             LOG_DEBUG() << filename;
3418             longTask.reportProgress(QFileInfo(filename).fileName(), i, count);
3419             Mlt::Producer p(MLT.profile(), filename.toUtf8().constData());
3420             if (p.is_valid()) {
3421                 // Convert avformat to avformat-novalidate so that XML loads faster.
3422                 if (!qstrcmp(p.get("mlt_service"), "avformat")) {
3423                     p.set("mlt_service", "avformat-novalidate");
3424                     p.set("mute_on_pause", 0);
3425                 }
3426                 if (QDir::toNativeSeparators(filename) == QDir::toNativeSeparators(MAIN.fileName())) {
3427                     MAIN.showStatusMessage(QObject::tr("You cannot add a project to itself!"));
3428                     continue;
3429                 }
3430                 MLT.setImageDurationFromDefault(&p);
3431                 MLT.lockCreationTime(&p);
3432                 p.get_length_time(mlt_time_clock);
3433                 Util::getHash(p);
3434                 ProxyManager::generateIfNotExists(p);
3435                 undoStack()->push(new Playlist::AppendCommand(*m_playlistDock->model(), MLT.XML(&p), false));
3436                 m_recentDock->add(filename.toUtf8().constData());
3437             }
3438         }
3439         emit m_playlistDock->model()->modified();
3440     }
3441     if (m_isPlaylistLoaded && Settings.playerGPU()) {
3442         updateThumbnails();
3443         m_isPlaylistLoaded = false;
3444     }
3445 }
3446 
onLanguageTriggered(QAction * action)3447 void MainWindow::onLanguageTriggered(QAction* action)
3448 {
3449     Settings.setLanguage(action->data().toString());
3450     QMessageBox dialog(QMessageBox::Information,
3451                        qApp->applicationName(),
3452                        tr("You must restart Shotcut to switch to the new language.\n"
3453                           "Do you want to restart now?"),
3454                        QMessageBox::No | QMessageBox::Yes,
3455                        this);
3456     dialog.setDefaultButton(QMessageBox::Yes);
3457     dialog.setEscapeButton(QMessageBox::No);
3458     dialog.setWindowModality(QmlApplication::dialogModality());
3459     if (dialog.exec() == QMessageBox::Yes) {
3460         m_exitCode = EXIT_RESTART;
3461         QApplication::closeAllWindows();
3462     }
3463 }
3464 
on_actionNearest_triggered(bool checked)3465 void MainWindow::on_actionNearest_triggered(bool checked)
3466 {
3467     changeInterpolation(checked, "nearest");
3468 }
3469 
on_actionBilinear_triggered(bool checked)3470 void MainWindow::on_actionBilinear_triggered(bool checked)
3471 {
3472     changeInterpolation(checked, "bilinear");
3473 }
3474 
on_actionBicubic_triggered(bool checked)3475 void MainWindow::on_actionBicubic_triggered(bool checked)
3476 {
3477     changeInterpolation(checked, "bicubic");
3478 }
3479 
on_actionHyper_triggered(bool checked)3480 void MainWindow::on_actionHyper_triggered(bool checked)
3481 {
3482     changeInterpolation(checked, "hyper");
3483 }
3484 
on_actionJack_triggered(bool checked)3485 void MainWindow::on_actionJack_triggered(bool checked)
3486 {
3487     Settings.setPlayerJACK(checked);
3488     if (!MLT.enableJack(checked)) {
3489         if (ui->actionJack)
3490             ui->actionJack->setChecked(false);
3491         Settings.setPlayerJACK(false);
3492         QMessageBox::warning(this, qApp->applicationName(),
3493             tr("Failed to connect to JACK.\nPlease verify that JACK is installed and running."));
3494     }
3495 }
3496 
on_actionGPU_triggered(bool checked)3497 void MainWindow::on_actionGPU_triggered(bool checked)
3498 {
3499     if (checked) {
3500         QMessageBox dialog(QMessageBox::Warning,
3501                            qApp->applicationName(),
3502                            tr("GPU effects are experimental and may cause instability on some systems. "
3503                               "Some CPU effects are incompatible with GPU effects and will be disabled. "
3504                               "A project created with GPU effects can not be converted to a CPU only project later."
3505                               "\n\n"
3506                               "Do you want to enable GPU effects and restart Shotcut?"),
3507                            QMessageBox::No | QMessageBox::Yes,
3508                            this);
3509         dialog.setDefaultButton(QMessageBox::Yes);
3510         dialog.setEscapeButton(QMessageBox::No);
3511         dialog.setWindowModality(QmlApplication::dialogModality());
3512         if (dialog.exec() == QMessageBox::Yes) {
3513             m_exitCode = EXIT_RESTART;
3514             QApplication::closeAllWindows();
3515         }
3516         else {
3517             ui->actionGPU->setChecked(false);
3518         }
3519     }
3520     else
3521     {
3522         QMessageBox dialog(QMessageBox::Information,
3523                            qApp->applicationName(),
3524                            tr("Shotcut must restart to disable GPU effects."
3525                               "\n\n"
3526                               "Disable GPU effects and restart?"),
3527                            QMessageBox::No | QMessageBox::Yes,
3528                            this);
3529         dialog.setDefaultButton(QMessageBox::Yes);
3530         dialog.setEscapeButton(QMessageBox::No);
3531         dialog.setWindowModality(QmlApplication::dialogModality());
3532         if (dialog.exec() == QMessageBox::Yes) {
3533             m_exitCode = EXIT_RESTART;
3534             QApplication::closeAllWindows();
3535         }
3536         else {
3537             ui->actionGPU->setChecked(true);
3538         }
3539     }
3540 }
3541 
onExternalTriggered(QAction * action)3542 void MainWindow::onExternalTriggered(QAction *action)
3543 {
3544     LOG_DEBUG() << action->data().toString();
3545     bool isExternal = !action->data().toString().isEmpty();
3546     Settings.setPlayerExternal(action->data().toString());
3547     MLT.stop();
3548     bool ok = false;
3549     int screen = action->data().toInt(&ok);
3550     if (ok || action->data().toString().isEmpty()) {
3551         m_player->moveVideoToScreen(ok? screen : -2);
3552         isExternal = false;
3553         MLT.videoWidget()->setProperty("mlt_service", QVariant());
3554     } else {
3555         m_player->moveVideoToScreen(-2);
3556         MLT.videoWidget()->setProperty("mlt_service", action->data());
3557     }
3558 
3559     QString profile = Settings.playerProfile();
3560     // Automatic not permitted for SDI/HDMI
3561     if (isExternal && profile.isEmpty()) {
3562         profile = "atsc_720p_50";
3563         Settings.setPlayerProfile(profile);
3564         setProfile(profile);
3565         MLT.restart();
3566         foreach (QAction* a, m_profileGroup->actions()) {
3567             if (a->data() == profile) {
3568                 a->setChecked(true);
3569                 break;
3570             }
3571         }
3572     }
3573     else {
3574         MLT.consumerChanged();
3575     }
3576     // Automatic not permitted for SDI/HDMI
3577     m_profileGroup->actions().at(0)->setEnabled(!isExternal);
3578 
3579     // Disable progressive option when SDI/HDMI
3580     ui->actionProgressive->setEnabled(!isExternal);
3581     bool isProgressive = isExternal
3582             ? MLT.profile().progressive()
3583             : ui->actionProgressive->isChecked();
3584     MLT.videoWidget()->setProperty("progressive", isProgressive);
3585     if (MLT.consumer()) {
3586         MLT.consumer()->set("progressive", isProgressive);
3587         MLT.restart();
3588     }
3589     if (m_keyerMenu)
3590         m_keyerMenu->setEnabled(action->data().toString().startsWith("decklink"));
3591 
3592     // Preview scaling not permitted for SDI/HDMI
3593     if (isExternal) {
3594         setPreviewScale(0);
3595         m_previewScaleGroup->setEnabled(false);
3596     } else {
3597         setPreviewScale(Settings.playerPreviewScale());
3598         m_previewScaleGroup->setEnabled(true);
3599     }
3600 }
3601 
onKeyerTriggered(QAction * action)3602 void MainWindow::onKeyerTriggered(QAction *action)
3603 {
3604     LOG_DEBUG() << action->data().toString();
3605     MLT.videoWidget()->setProperty("keyer", action->data());
3606     MLT.consumerChanged();
3607     Settings.setPlayerKeyerMode(action->data().toInt());
3608 }
3609 
onProfileTriggered(QAction * action)3610 void MainWindow::onProfileTriggered(QAction *action)
3611 {
3612     Settings.setPlayerProfile(action->data().toString());
3613     if (MLT.producer() && MLT.producer()->is_valid()) {
3614         // Save the XML to get correct in/out points before profile is changed.
3615         QString xml = MLT.XML();
3616         setProfile(action->data().toString());
3617         MLT.restart(xml);
3618         emit producerOpened(false);
3619     } else {
3620         setProfile(action->data().toString());
3621     }
3622 }
3623 
onProfileChanged()3624 void MainWindow::onProfileChanged()
3625 {
3626     if (multitrack() && MLT.isMultitrack() &&
3627        (m_timelineDock->selection().isEmpty() || m_timelineDock->currentTrack() == -1)) {
3628         emit m_timelineDock->selected(multitrack());
3629     }
3630 }
3631 
on_actionAddCustomProfile_triggered()3632 void MainWindow::on_actionAddCustomProfile_triggered()
3633 {
3634     QString xml;
3635     if (MLT.producer() && MLT.producer()->is_valid()) {
3636         // Save the XML to get correct in/out points before profile is changed.
3637         xml = MLT.XML();
3638     }
3639     CustomProfileDialog dialog(this);
3640     dialog.setWindowModality(QmlApplication::dialogModality());
3641     if (dialog.exec() == QDialog::Accepted) {
3642         QString name = dialog.profileName();
3643         if (!name.isEmpty()) {
3644             addCustomProfile(name, customProfileMenu(), actionProfileRemove(), profileGroup());
3645         } else if (m_profileGroup->checkedAction()) {
3646             m_profileGroup->checkedAction()->setChecked(false);
3647         }
3648         // Use the new profile.
3649         emit profileChanged();
3650         if (!xml.isEmpty()) {
3651             MLT.restart(xml);
3652             emit producerOpened(false);
3653         }
3654     }
3655 }
3656 
on_actionSystemTheme_triggered()3657 void MainWindow::on_actionSystemTheme_triggered()
3658 {
3659     changeTheme("system");
3660     QApplication::setPalette(QApplication::style()->standardPalette());
3661     Settings.setTheme("system");
3662 }
3663 
on_actionFusionDark_triggered()3664 void MainWindow::on_actionFusionDark_triggered()
3665 {
3666     changeTheme("dark");
3667     Settings.setTheme("dark");
3668     ui->mainToolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
3669 }
3670 
on_actionFusionLight_triggered()3671 void MainWindow::on_actionFusionLight_triggered()
3672 {
3673     changeTheme("light");
3674     Settings.setTheme("light");
3675     ui->mainToolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
3676 }
3677 
on_actionTutorials_triggered()3678 void MainWindow::on_actionTutorials_triggered()
3679 {
3680     QDesktopServices::openUrl(QUrl("https://www.shotcut.org/tutorials/"));
3681 }
3682 
on_actionRestoreLayout_triggered()3683 void MainWindow::on_actionRestoreLayout_triggered()
3684 {
3685     auto mode = Settings.layoutMode();
3686     if (mode != LayoutMode::Custom) {
3687         // Clear the saved layout for this mode
3688         Settings.setLayout(QString(kReservedLayoutPrefix).arg(mode), QByteArray(), QByteArray());
3689         // Reset the layout mode so the current layout is saved as custom when trigger action
3690         Settings.setLayoutMode();
3691     }
3692     switch (mode) {
3693     case LayoutMode::Custom:
3694         ui->actionLayoutEditing->setChecked(true);
3695         Q_FALLTHROUGH();
3696     case LayoutMode::Editing:
3697         on_actionLayoutEditing_triggered();
3698         break;
3699     case LayoutMode::Logging:
3700         on_actionLayoutLogging_triggered();
3701         break;
3702     case LayoutMode::Effects:
3703         on_actionLayoutEffects_triggered();
3704         break;
3705     case LayoutMode::Color:
3706         on_actionLayoutColor_triggered();
3707         break;
3708     case LayoutMode::Audio:
3709         on_actionLayoutAudio_triggered();
3710         break;
3711     case LayoutMode::PlayerOnly:
3712         on_actionLayoutPlayer_triggered();
3713         break;
3714     }
3715 }
3716 
on_actionShowTitleBars_triggered(bool checked)3717 void MainWindow::on_actionShowTitleBars_triggered(bool checked)
3718 {
3719     QList <QDockWidget *> docks = findChildren<QDockWidget *>();
3720     for (int i = 0; i < docks.count(); i++) {
3721         QDockWidget* dock = docks.at(i);
3722         if (checked) {
3723             dock->setTitleBarWidget(0);
3724         } else {
3725             if (!dock->isFloating()) {
3726                 dock->setTitleBarWidget(new QWidget);
3727             }
3728         }
3729     }
3730     Settings.setShowTitleBars(checked);
3731 }
3732 
on_actionShowToolbar_triggered(bool checked)3733 void MainWindow::on_actionShowToolbar_triggered(bool checked)
3734 {
3735     ui->mainToolBar->setVisible(checked);
3736 }
3737 
onToolbarVisibilityChanged(bool visible)3738 void MainWindow::onToolbarVisibilityChanged(bool visible)
3739 {
3740     ui->actionShowToolbar->setChecked(visible);
3741     Settings.setShowToolBar(visible);
3742 }
3743 
on_menuExternal_aboutToShow()3744 void MainWindow::on_menuExternal_aboutToShow()
3745 {
3746     foreach (QAction* action, m_externalGroup->actions()) {
3747         bool ok = false;
3748         int i = action->data().toInt(&ok);
3749         if (ok) {
3750             if (i == QApplication::desktop()->screenNumber(this)) {
3751                 if (action->isChecked()) {
3752                     m_externalGroup->actions().first()->setChecked(true);
3753                     Settings.setPlayerExternal(QString());
3754                 }
3755                 action->setDisabled(true);
3756             }  else {
3757                 action->setEnabled(true);
3758             }
3759         }
3760     }
3761 }
3762 
on_actionUpgrade_triggered()3763 void MainWindow::on_actionUpgrade_triggered()
3764 {
3765     if (Settings.askUpgradeAutomatic()) {
3766         QMessageBox dialog(QMessageBox::Question,
3767            qApp->applicationName(),
3768            tr("Do you want to automatically check for updates in the future?"),
3769            QMessageBox::No |
3770            QMessageBox::Yes,
3771            this);
3772         dialog.setWindowModality(QmlApplication::dialogModality());
3773         dialog.setDefaultButton(QMessageBox::Yes);
3774         dialog.setEscapeButton(QMessageBox::No);
3775         dialog.setCheckBox(new QCheckBox(tr("Do not show this anymore.", "Automatic upgrade check dialog")));
3776         Settings.setCheckUpgradeAutomatic(dialog.exec() == QMessageBox::Yes);
3777         if (dialog.checkBox()->isChecked())
3778             Settings.setAskUpgradeAutomatic(false);
3779     }
3780     showStatusMessage("Checking for upgrade...");
3781     m_network.get(QNetworkRequest(QUrl("https://check.shotcut.org/version.json")));
3782 }
3783 
on_actionOpenXML_triggered()3784 void MainWindow::on_actionOpenXML_triggered()
3785 {
3786     QString path = Settings.openPath();
3787 #ifdef Q_OS_MAC
3788     path.append("/*");
3789 #endif
3790     QStringList filenames = QFileDialog::getOpenFileNames(this, tr("Open File"), path,
3791         tr("MLT XML (*.mlt);;All Files (*)"), nullptr, Util::getFileDialogOptions());
3792     if (filenames.length() > 0) {
3793         QString url = filenames.first();
3794         MltXmlChecker checker;
3795         if (checker.check(url)) {
3796             if (!isCompatibleWithGpuMode(checker))
3797                 return;
3798             isXmlRepaired(checker, url);
3799             // Check if the locale usage differs.
3800             // Get current locale.
3801             QString localeName = QString(::setlocale(MLT_LC_CATEGORY, nullptr)).toUpper();
3802             // Test if it is C or POSIX.
3803             bool currentlyUsingLocale = (localeName != "" && localeName != "C" && localeName != "POSIX");
3804             if (currentlyUsingLocale != checker.usesLocale()) {
3805                 // Show a warning dialog and cancel if requested.
3806                 QMessageBox dialog(QMessageBox::Question,
3807                    qApp->applicationName(),
3808                    tr("The decimal point of the MLT XML file\nyou want to open is incompatible.\n\n"
3809                       "Do you want to continue to open this MLT XML file?"),
3810                    QMessageBox::No |
3811                    QMessageBox::Yes,
3812                    this);
3813                 dialog.setWindowModality(QmlApplication::dialogModality());
3814                 dialog.setDefaultButton(QMessageBox::No);
3815                 dialog.setEscapeButton(QMessageBox::No);
3816                 if (dialog.exec() != QMessageBox::Yes)
3817                     return;
3818             }
3819         } else {
3820             showStatusMessage(tr("Failed to open ").append(url));
3821             showIncompatibleProjectMessage(checker.shotcutVersion());
3822             return;
3823         }
3824         Settings.setOpenPath(QFileInfo(url).path());
3825         activateWindow();
3826         if (filenames.length() > 1)
3827             m_multipleFiles = filenames;
3828         if (!MLT.openXML(url)) {
3829             open(MLT.producer());
3830             m_recentDock->add(url);
3831             LOG_INFO() << url;
3832         }
3833         else {
3834             showStatusMessage(tr("Failed to open ") + url);
3835             emit openFailed(url);
3836         }
3837     }
3838 }
3839 
on_actionGammaSRGB_triggered(bool checked)3840 void MainWindow::on_actionGammaSRGB_triggered(bool checked)
3841 {
3842     Q_UNUSED(checked)
3843     Settings.setPlayerGamma("iec61966_2_1");
3844     MLT.restart();
3845     MLT.refreshConsumer();
3846 }
3847 
on_actionGammaRec709_triggered(bool checked)3848 void MainWindow::on_actionGammaRec709_triggered(bool checked)
3849 {
3850     Q_UNUSED(checked)
3851     Settings.setPlayerGamma("bt709");
3852     MLT.restart();
3853     MLT.refreshConsumer();
3854 }
3855 
onFocusChanged(QWidget *,QWidget *) const3856 void MainWindow::onFocusChanged(QWidget *, QWidget * ) const
3857 {
3858     LOG_DEBUG() << "Focuswidget changed";
3859     LOG_DEBUG() << "Current focusWidget:" << QApplication::focusWidget();
3860     LOG_DEBUG() << "Current focusObject:" << QApplication::focusObject();
3861     LOG_DEBUG() << "Current focusWindow:" << QApplication::focusWindow();
3862 }
3863 
on_actionScrubAudio_triggered(bool checked)3864 void MainWindow::on_actionScrubAudio_triggered(bool checked)
3865 {
3866     Settings.setPlayerScrubAudio(checked);
3867 }
3868 
3869 #if !defined(Q_OS_MAC)
onDrawingMethodTriggered(QAction * action)3870 void MainWindow::onDrawingMethodTriggered(QAction *action)
3871 {
3872     Settings.setDrawMethod(action->data().toInt());
3873     QMessageBox dialog(QMessageBox::Information,
3874                        qApp->applicationName(),
3875                        tr("You must restart Shotcut to change the display method.\n"
3876                           "Do you want to restart now?"),
3877                        QMessageBox::No | QMessageBox::Yes,
3878                        this);
3879     dialog.setDefaultButton(QMessageBox::Yes);
3880     dialog.setEscapeButton(QMessageBox::No);
3881     dialog.setWindowModality(QmlApplication::dialogModality());
3882     if (dialog.exec() == QMessageBox::Yes) {
3883         m_exitCode = EXIT_RESTART;
3884         QApplication::closeAllWindows();
3885     }
3886 }
3887 #endif
3888 
on_actionApplicationLog_triggered()3889 void MainWindow::on_actionApplicationLog_triggered()
3890 {
3891     TextViewerDialog dialog(this);
3892     QDir dir = Settings.appDataLocation();
3893     QFile logFile(dir.filePath("shotcut-log.txt"));
3894     logFile.open(QIODevice::ReadOnly | QIODevice::Text);
3895     dialog.setText(logFile.readAll());
3896     logFile.close();
3897     dialog.setWindowTitle(tr("Application Log"));
3898     dialog.exec();
3899 }
3900 
on_actionClose_triggered()3901 void MainWindow::on_actionClose_triggered()
3902 {
3903     if (continueModified()) {
3904         LOG_DEBUG() << "";
3905         MLT.setProjectFolder(QString());
3906         MLT.stop();
3907         if (multitrack())
3908             m_timelineDock->model()->close();
3909         if (playlist())
3910             m_playlistDock->model()->close();
3911         else
3912             onMultitrackClosed();
3913         m_player->enableTab(Player::SourceTabIndex, false);
3914         MLT.purgeMemoryPool();
3915         MLT.resetLocale();
3916     }
3917 }
3918 
onPlayerTabIndexChanged(int index)3919 void MainWindow::onPlayerTabIndexChanged(int index)
3920 {
3921     if (Player::SourceTabIndex == index)
3922         m_timelineDock->saveAndClearSelection();
3923     else
3924         m_timelineDock->restoreSelection();
3925 }
3926 
onUpgradeCheckFinished(QNetworkReply * reply)3927 void MainWindow::onUpgradeCheckFinished(QNetworkReply* reply)
3928 {
3929     if (!reply->error()) {
3930         QByteArray response = reply->readAll();
3931         LOG_DEBUG() << "response: " << response;
3932         QJsonDocument json = QJsonDocument::fromJson(response);
3933         QString current = qApp->applicationVersion();
3934 
3935         if (!json.isNull() && json.object().value("version_string").type() == QJsonValue::String) {
3936             QString latest = json.object().value("version_string").toString();
3937             if (current != "adhoc" && QVersionNumber::fromString(current) < QVersionNumber::fromString(latest)) {
3938                 QAction* action = new QAction(tr("Shotcut version %1 is available! Click here to get it.").arg(latest), 0);
3939                 connect(action, SIGNAL(triggered(bool)), SLOT(onUpgradeTriggered()));
3940                 if (!json.object().value("url").isUndefined())
3941                     m_upgradeUrl = json.object().value("url").toString();
3942                 showStatusMessage(action, 15 /* seconds */);
3943             } else {
3944                 showStatusMessage(tr("You are running the latest version of Shotcut."));
3945             }
3946             reply->deleteLater();
3947             return;
3948         } else {
3949             LOG_WARNING() << "failed to parse version.json";
3950         }
3951     } else {
3952         LOG_WARNING() << reply->errorString();
3953     }
3954     QAction* action = new QAction(tr("Failed to read version.json when checking. Click here to go to the Web site."), 0);
3955     connect(action, SIGNAL(triggered(bool)), SLOT(onUpgradeTriggered()));
3956     showStatusMessage(action);
3957     reply->deleteLater();
3958 }
3959 
onUpgradeTriggered()3960 void MainWindow::onUpgradeTriggered()
3961 {
3962     QDesktopServices::openUrl(QUrl(m_upgradeUrl));
3963 }
3964 
onTimelineSelectionChanged()3965 void MainWindow::onTimelineSelectionChanged()
3966 {
3967     bool enable = (m_timelineDock->selection().size() > 0);
3968     ui->actionCut->setEnabled(enable);
3969     ui->actionCopy->setEnabled(enable);
3970 }
3971 
on_actionCut_triggered()3972 void MainWindow::on_actionCut_triggered()
3973 {
3974     m_timelineDock->show();
3975     m_timelineDock->raise();
3976     m_timelineDock->removeSelection(true);
3977 }
3978 
on_actionCopy_triggered()3979 void MainWindow::on_actionCopy_triggered()
3980 {
3981     m_timelineDock->show();
3982     m_timelineDock->raise();
3983     if (!m_timelineDock->selection().isEmpty())
3984         m_timelineDock->copyClip(m_timelineDock->selection().first().y(), m_timelineDock->selection().first().x());
3985 }
3986 
on_actionPaste_triggered()3987 void MainWindow::on_actionPaste_triggered()
3988 {
3989     m_timelineDock->show();
3990     m_timelineDock->raise();
3991     m_timelineDock->insert(-1);
3992 }
3993 
onClipCopied()3994 void MainWindow::onClipCopied()
3995 {
3996     m_player->enableTab(Player::SourceTabIndex);
3997 }
3998 
on_actionExportEDL_triggered()3999 void MainWindow::on_actionExportEDL_triggered()
4000 {
4001     // Dialog to get export file name.
4002     QString path = Settings.savePath();
4003     QString caption = tr("Export EDL");
4004     QString saveFileName = QFileDialog::getSaveFileName(this, caption, path,
4005         tr("EDL (*.edl);;All Files (*)"), nullptr, Util::getFileDialogOptions());
4006     if (!saveFileName.isEmpty()) {
4007         QFileInfo fi(saveFileName);
4008         if (fi.suffix() != "edl")
4009             saveFileName += ".edl";
4010 
4011         if (Util::warnIfNotWritable(saveFileName, this, caption))
4012             return;
4013 
4014         // Locate the JavaScript file in the filesystem.
4015         QDir qmlDir = QmlUtilities::qmlDir();
4016         qmlDir.cd("export-edl");
4017         QString jsFileName = qmlDir.absoluteFilePath("export-edl.js");
4018         QFile scriptFile(jsFileName);
4019         if (scriptFile.open(QIODevice::ReadOnly)) {
4020             // Read JavaScript into a string.
4021             QTextStream stream(&scriptFile);
4022             stream.setCodec("UTF-8");
4023             stream.setAutoDetectUnicode(true);
4024             QString contents = stream.readAll();
4025             scriptFile.close();
4026 
4027             // Evaluate JavaScript.
4028             QJSEngine jsEngine;
4029             QJSValue result = jsEngine.evaluate(contents, jsFileName);
4030             if (!result.isError()) {
4031                 // Call the JavaScript main function.
4032                 QJSValue options = jsEngine.newObject();
4033                 options.setProperty("useBaseNameForReelName", true);
4034                 options.setProperty("useBaseNameForClipComment", true);
4035                 options.setProperty("channelsAV", "AA/V");
4036                 QJSValueList args;
4037                 args << MLT.XML(0, true, true) << options;
4038                 result = result.call(args);
4039                 if (!result.isError()) {
4040                     // Save the result with the export file name.
4041                     QFile f(saveFileName);
4042                     f.open(QIODevice::WriteOnly | QIODevice::Text);
4043                     f.write(result.toString().toLatin1());
4044                     f.close();
4045                 }
4046             }
4047             if (result.isError()) {
4048                 LOG_ERROR() << "Uncaught exception at line"
4049                             << result.property("lineNumber").toInt()
4050                             << ":" << result.toString();
4051                 showStatusMessage(tr("A JavaScript error occurred during export."));
4052             }
4053         } else {
4054             showStatusMessage(tr("Failed to open export-edl.js"));
4055         }
4056     }
4057 }
4058 
on_actionExportFrame_triggered()4059 void MainWindow::on_actionExportFrame_triggered()
4060 {
4061     if (!MLT.producer() || !MLT.producer()->is_valid()) return;
4062     filterController()->setCurrentFilter(QmlFilter::DeselectCurrentFilter);
4063     Mlt::GLWidget* glw = qobject_cast<Mlt::GLWidget*>(MLT.videoWidget());
4064     connect(glw, SIGNAL(imageReady()), SLOT(onGLWidgetImageReady()));
4065     MLT.setPreviewScale(0);
4066     glw->requestImage();
4067     MLT.refreshConsumer();
4068 }
4069 
onGLWidgetImageReady()4070 void MainWindow::onGLWidgetImageReady()
4071 {
4072     Mlt::GLWidget* glw = qobject_cast<Mlt::GLWidget*>(MLT.videoWidget());
4073     QImage image = glw->image();
4074     disconnect(glw, SIGNAL(imageReady()), this, nullptr);
4075     if (Settings.playerGPU() || Settings.playerPreviewScale()) {
4076         MLT.setPreviewScale(Settings.playerPreviewScale());
4077     }
4078     if (!image.isNull()) {
4079         QString path = Settings.savePath();
4080         QString caption = tr("Export Frame");
4081         QString nameFilter = tr("PNG (*.png);;BMP (*.bmp);;JPEG (*.jpg *.jpeg);;PPM (*.ppm);;TIFF (*.tif *.tiff);;WebP (*.webp);;All Files (*)");
4082         QString saveFileName = QFileDialog::getSaveFileName(this, caption, path, nameFilter,
4083             nullptr, Util::getFileDialogOptions());
4084         if (!saveFileName.isEmpty()) {
4085             QFileInfo fi(saveFileName);
4086             if (fi.suffix().isEmpty())
4087                 saveFileName += ".png";
4088             if (Util::warnIfNotWritable(saveFileName, this, caption))
4089                 return;
4090             // Convert to square pixels if needed.
4091             qreal aspectRatio = (qreal) image.width() / image.height();
4092             if (qFloor(aspectRatio * 1000) != qFloor(MLT.profile().dar() * 1000)) {
4093                 image = image.scaled(qRound(image.height() * MLT.profile().dar()), image.height(),
4094                                      Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
4095             }
4096             image.save(saveFileName, Q_NULLPTR,
4097                 (QFileInfo(saveFileName).suffix() == "webp")? 80 : -1);
4098             Settings.setSavePath(fi.path());
4099             m_recentDock->add(saveFileName);
4100         }
4101     } else {
4102         showStatusMessage(tr("Unable to export frame."));
4103     }
4104 }
4105 
on_actionAppDataSet_triggered()4106 void MainWindow::on_actionAppDataSet_triggered()
4107 {
4108     QMessageBox dialog(QMessageBox::Information,
4109                        qApp->applicationName(),
4110                        tr("You must restart Shotcut to change the data directory.\n"
4111                           "Do you want to continue?"),
4112                        QMessageBox::No | QMessageBox::Yes,
4113                        this);
4114     dialog.setDefaultButton(QMessageBox::Yes);
4115     dialog.setEscapeButton(QMessageBox::No);
4116     dialog.setWindowModality(QmlApplication::dialogModality());
4117     if (dialog.exec() != QMessageBox::Yes) return;
4118 
4119     QString dirName = QFileDialog::getExistingDirectory(this, tr("Data Directory"), Settings.appDataLocation(),
4120         Util::getFileDialogOptions());
4121     if (!dirName.isEmpty()) {
4122         // Move the data files.
4123         QDirIterator it(Settings.appDataLocation());
4124         while (it.hasNext()) {
4125             if (!it.filePath().isEmpty() && it.fileName() != "." && it.fileName() != "..") {
4126                 if (!QFile::exists(dirName + "/" + it.fileName())) {
4127                     if (it.fileInfo().isDir()) {
4128                         if (!QFile::rename(it.filePath(), dirName + "/" + it.fileName()))
4129                             LOG_WARNING() << "Failed to move" << it.filePath() << "to" << dirName + "/" + it.fileName();
4130                     } else {
4131                         if (!QFile::copy(it.filePath(), dirName + "/" + it.fileName()))
4132                             LOG_WARNING() << "Failed to copy" << it.filePath() << "to" << dirName + "/" + it.fileName();
4133                     }
4134                 }
4135             }
4136             it.next();
4137         }
4138         writeSettings();
4139         Settings.setAppDataLocally(dirName);
4140 
4141         m_exitCode = EXIT_RESTART;
4142         QApplication::closeAllWindows();
4143     }
4144 }
4145 
on_actionAppDataShow_triggered()4146 void MainWindow::on_actionAppDataShow_triggered()
4147 {
4148     Util::showInFolder(Settings.appDataLocation());
4149 }
4150 
on_actionNew_triggered()4151 void MainWindow::on_actionNew_triggered()
4152 {
4153     on_actionClose_triggered();
4154 }
4155 
on_actionKeyboardShortcuts_triggered()4156 void MainWindow::on_actionKeyboardShortcuts_triggered()
4157 {
4158     QDesktopServices::openUrl(QUrl("https://www.shotcut.org/howtos/keyboard-shortcuts/"));
4159 }
4160 
on_actionLayoutLogging_triggered()4161 void MainWindow::on_actionLayoutLogging_triggered()
4162 {
4163     Settings.setLayout(QString(kReservedLayoutPrefix).arg(Settings.layoutMode()), QByteArray(), saveState());
4164     Settings.setLayoutMode(LayoutMode::Logging);
4165     auto state = Settings.layoutState(QString(kReservedLayoutPrefix).arg(LayoutMode::Logging));
4166     if (state.isEmpty()) {
4167         restoreState(kLayoutLoggingDefault);
4168 //        setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
4169 //        setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
4170 //        setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
4171 //        setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
4172 //        resizeDocks({m_playlistDock, m_propertiesDock},
4173 //            {qFloor(width() * 0.25), qFloor(width() * 0.25)}, Qt::Horizontal);
4174     } else {
4175 //        LOG_DEBUG() << state.toBase64();
4176         restoreState(state);
4177     }
4178     Settings.setWindowState(saveState());
4179 }
4180 
on_actionLayoutEditing_triggered()4181 void MainWindow::on_actionLayoutEditing_triggered()
4182 {
4183     Settings.setLayout(QString(kReservedLayoutPrefix).arg(Settings.layoutMode()), QByteArray(), saveState());
4184     Settings.setLayoutMode(LayoutMode::Editing);
4185     auto state = Settings.layoutState(QString(kReservedLayoutPrefix).arg(LayoutMode::Editing));
4186     if (state.isEmpty()) {
4187         restoreState(kLayoutEditingDefault);
4188 //        resetDockCorners();
4189     } else {
4190 //        LOG_DEBUG() << state.toBase64();
4191         restoreState(state);
4192     }
4193     Settings.setWindowState(saveState());
4194 }
4195 
on_actionLayoutEffects_triggered()4196 void MainWindow::on_actionLayoutEffects_triggered()
4197 {
4198     Settings.setLayout(QString(kReservedLayoutPrefix).arg(Settings.layoutMode()), QByteArray(), saveState());
4199     Settings.setLayoutMode(LayoutMode::Effects);
4200     auto state = Settings.layoutState(QString(kReservedLayoutPrefix).arg(LayoutMode::Effects));
4201     if (state.isEmpty()) {
4202         restoreState(kLayoutEffectsDefault);
4203 //        resetDockCorners();
4204     } else {
4205 //        LOG_DEBUG() << state.toBase64();
4206         restoreState(state);
4207     }
4208     Settings.setWindowState(saveState());
4209 }
4210 
on_actionLayoutColor_triggered()4211 void MainWindow::on_actionLayoutColor_triggered()
4212 {
4213     Settings.setLayout(QString(kReservedLayoutPrefix).arg(Settings.layoutMode()), QByteArray(), saveState());
4214     Settings.setLayoutMode(LayoutMode::Color);
4215     auto state = Settings.layoutState(QString(kReservedLayoutPrefix).arg(LayoutMode::Color));
4216     if (state.isEmpty()) {
4217         restoreState(kLayoutColorDefault);
4218 //        setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
4219 //        setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
4220 //        setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea);
4221 //        setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
4222     } else {
4223 //        LOG_DEBUG() << state.toBase64();
4224         restoreState(state);
4225     }
4226     Settings.setWindowState(saveState());
4227 }
4228 
on_actionLayoutAudio_triggered()4229 void MainWindow::on_actionLayoutAudio_triggered()
4230 {
4231     Settings.setLayout(QString(kReservedLayoutPrefix).arg(Settings.layoutMode()), QByteArray(), saveState());
4232     Settings.setLayoutMode(LayoutMode::Audio);
4233     auto state = Settings.layoutState(QString(kReservedLayoutPrefix).arg(LayoutMode::Audio));
4234     if (state.isEmpty()) {
4235         restoreState(kLayoutAudioDefault);
4236 //        setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
4237 //        setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
4238 //        setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea);
4239 //        setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
4240     } else {
4241 //        LOG_DEBUG() << state.toBase64();
4242         restoreState(state);
4243     }
4244     Settings.setWindowState(saveState());
4245 }
4246 
on_actionLayoutPlayer_triggered()4247 void MainWindow::on_actionLayoutPlayer_triggered()
4248 {
4249     Settings.setLayout(QString(kReservedLayoutPrefix).arg(Settings.layoutMode()), QByteArray(), saveState());
4250     Settings.setLayoutMode(LayoutMode::PlayerOnly);
4251     auto state = Settings.layoutState(QString(kReservedLayoutPrefix).arg(LayoutMode::PlayerOnly));
4252     if (state.isEmpty()) {
4253         restoreState(kLayoutPlayerDefault);
4254 //        resetDockCorners();
4255     } else {
4256 //        LOG_DEBUG() << state.toBase64();
4257         restoreState(state);
4258     }
4259     Settings.setWindowState(saveState());
4260 }
4261 
on_actionLayoutPlaylist_triggered()4262 void MainWindow::on_actionLayoutPlaylist_triggered()
4263 {
4264     if (Settings.layoutMode() != LayoutMode::Custom) {
4265         Settings.setLayout(QString(kReservedLayoutPrefix).arg(Settings.layoutMode()), QByteArray(), saveState());
4266         Settings.setLayoutMode(LayoutMode::Custom);
4267     }
4268     clearCurrentLayout();
4269     restoreState(Settings.windowStateDefault());
4270     m_recentDock->show();
4271     m_recentDock->raise();
4272     m_playlistDock->show();
4273     m_playlistDock->raise();
4274     Settings.setWindowState(saveState());
4275 }
4276 
on_actionLayoutClip_triggered()4277 void MainWindow::on_actionLayoutClip_triggered()
4278 {
4279     if (Settings.layoutMode() != LayoutMode::Custom) {
4280         Settings.setLayout(QString(kReservedLayoutPrefix).arg(Settings.layoutMode()), QByteArray(), saveState());
4281         Settings.setLayoutMode(LayoutMode::Custom);
4282     }
4283     clearCurrentLayout();
4284     restoreState(Settings.windowStateDefault());
4285     m_recentDock->show();
4286     m_recentDock->raise();
4287     m_filtersDock->show();
4288     m_filtersDock->raise();
4289     Settings.setWindowState(saveState());
4290 }
4291 
on_actionLayoutAdd_triggered()4292 void MainWindow::on_actionLayoutAdd_triggered()
4293 {
4294     QInputDialog dialog(this);
4295     dialog.setInputMode(QInputDialog::TextInput);
4296     dialog.setWindowTitle(tr("Add Custom Layout"));
4297     dialog.setLabelText(tr("Name"));
4298     dialog.setWindowModality(QmlApplication::dialogModality());
4299     auto result = dialog.exec();
4300     auto name = dialog.textValue();
4301     if (result == QDialog::Accepted && !name.isEmpty()) {
4302         if (Settings.setLayout(name, saveGeometry(), saveState())) {
4303             Settings.setLayoutMode();
4304             clearCurrentLayout();
4305             Settings.sync();
4306             if (Settings.layouts().size() == 1) {
4307                 ui->menuLayout->addAction(ui->actionLayoutRemove);
4308                 ui->menuLayout->addSeparator();
4309             }
4310             ui->menuLayout->addAction(addLayout(m_layoutGroup, name));
4311         }
4312     }
4313 }
4314 
onLayoutTriggered(QAction * action)4315 void MainWindow::onLayoutTriggered(QAction* action)
4316 {
4317     if (Settings.layoutMode() != LayoutMode::Custom) {
4318         Settings.setLayout(QString(kReservedLayoutPrefix).arg(Settings.layoutMode()), QByteArray(), saveState());
4319         Settings.setLayoutMode(LayoutMode::Custom);
4320     }
4321     clearCurrentLayout();
4322     restoreState(Settings.layoutState(action->text()));
4323     Settings.setWindowState(saveState());
4324 }
4325 
on_actionProfileRemove_triggered()4326 void MainWindow::on_actionProfileRemove_triggered()
4327 {
4328     QDir dir(Settings.appDataLocation());
4329     if (dir.cd("profiles")) {
4330         // Setup the dialog.
4331         QStringList profiles = dir.entryList(QDir::Files | QDir::NoDotAndDotDot | QDir::Readable);
4332         ListSelectionDialog dialog(profiles, this);
4333         dialog.setWindowModality(QmlApplication::dialogModality());
4334         dialog.setWindowTitle(tr("Remove Video Mode"));
4335 
4336         // Show the dialog.
4337         if (dialog.exec() == QDialog::Accepted) {
4338             removeCustomProfiles(dialog.selection(), dir, customProfileMenu(), actionProfileRemove());
4339         }
4340     }
4341 }
4342 
on_actionLayoutRemove_triggered()4343 void MainWindow::on_actionLayoutRemove_triggered()
4344 {
4345     // Setup the dialog.
4346     ListSelectionDialog dialog(Settings.layouts(), this);
4347     dialog.setWindowModality(QmlApplication::dialogModality());
4348     dialog.setWindowTitle(tr("Remove Layout"));
4349 
4350     // Show the dialog.
4351     if (dialog.exec() == QDialog::Accepted) {
4352         foreach(const QString& layout, dialog.selection()) {
4353             // Update the configuration.
4354             if (Settings.removeLayout(layout))
4355                 Settings.sync();
4356             // Locate the menu item.
4357             foreach (QAction* action, ui->menuLayout->actions()) {
4358                 if (action->text() == layout) {
4359                     // Remove the menu item.
4360                     delete action;
4361                     break;
4362                 }
4363             }
4364         }
4365         // If no more custom layouts.
4366         if (Settings.layouts().size() == 0) {
4367             // Remove the Remove action and separator.
4368             ui->menuLayout->removeAction(ui->actionLayoutRemove);
4369             bool isSecondSeparator = false;
4370             foreach (QAction* action, ui->menuLayout->actions()) {
4371                 if (action->isSeparator()) {
4372                     if (isSecondSeparator) {
4373                         delete action;
4374                         break;
4375                     } else {
4376                         isSecondSeparator = true;
4377                     }
4378                 }
4379             }
4380         }
4381     }
4382 }
4383 
on_actionOpenOther2_triggered()4384 void MainWindow::on_actionOpenOther2_triggered()
4385 {
4386     ui->actionOpenOther2->menu()->popup(mapToGlobal(ui->mainToolBar->geometry().bottomLeft()) + QPoint(64, 0));
4387 }
4388 
onOpenOtherTriggered(QWidget * widget)4389 void MainWindow::onOpenOtherTriggered(QWidget* widget)
4390 {
4391     QDialog dialog(this);
4392     dialog.resize(426, 288);
4393     QVBoxLayout vlayout(&dialog);
4394     vlayout.addWidget(widget);
4395     QDialogButtonBox buttonBox(&dialog);
4396     buttonBox.setOrientation(Qt::Horizontal);
4397     buttonBox.setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
4398     vlayout.addWidget(&buttonBox);
4399     connect(&buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
4400     connect(&buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject()));
4401     QString name = widget->objectName();
4402     if (name == "NoiseWidget" || dialog.exec() == QDialog::Accepted) {
4403         open(dynamic_cast<AbstractProducerWidget*>(widget)->newProducer(MLT.profile()));
4404         if (name == "TextProducerWidget") {
4405             m_filtersDock->show();
4406             m_filtersDock->raise();
4407         } else {
4408             m_propertiesDock->show();
4409             m_propertiesDock->raise();
4410         }
4411     }
4412     delete widget;
4413 }
4414 
onOpenOtherTriggered()4415 void MainWindow::onOpenOtherTriggered()
4416 {
4417     if (sender()->objectName() == "color")
4418         onOpenOtherTriggered(new ColorProducerWidget(this));
4419     else if (sender()->objectName() == "text")
4420         onOpenOtherTriggered(new TextProducerWidget(this));
4421     else if (sender()->objectName() == "noise")
4422         onOpenOtherTriggered(new NoiseWidget(this));
4423     else if (sender()->objectName() == "ising0r")
4424         onOpenOtherTriggered(new IsingWidget(this));
4425     else if (sender()->objectName() == "lissajous0r")
4426         onOpenOtherTriggered(new LissajousWidget(this));
4427     else if (sender()->objectName() == "plasma")
4428         onOpenOtherTriggered(new PlasmaWidget(this));
4429     else if (sender()->objectName() == "test_pat_B")
4430         onOpenOtherTriggered(new ColorBarsWidget(this));
4431     else if (sender()->objectName() == "tone")
4432         onOpenOtherTriggered(new ToneProducerWidget(this));
4433     else if (sender()->objectName() == "count")
4434         onOpenOtherTriggered(new CountProducerWidget(this));
4435     else if (sender()->objectName() == "blipflash")
4436         onOpenOtherTriggered(new BlipProducerWidget(this));
4437     else if (sender()->objectName() == "v4l2")
4438         onOpenOtherTriggered(new Video4LinuxWidget(this));
4439     else if (sender()->objectName() == "pulse")
4440         onOpenOtherTriggered(new PulseAudioWidget(this));
4441     else if (sender()->objectName() == "jack")
4442         onOpenOtherTriggered(new JackProducerWidget(this));
4443     else if (sender()->objectName() == "alsa")
4444         onOpenOtherTriggered(new AlsaWidget(this));
4445 #if defined(Q_OS_MAC)
4446     else if (sender()->objectName() == "device")
4447         onOpenOtherTriggered(new AvfoundationProducerWidget(this));
4448 #elif defined(Q_OS_WIN)
4449     else if (sender()->objectName() == "device")
4450         onOpenOtherTriggered(new DirectShowVideoWidget(this));
4451 #endif
4452     else if (sender()->objectName() == "decklink")
4453         onOpenOtherTriggered(new DecklinkProducerWidget(this));
4454 }
4455 
on_actionClearRecentOnExit_toggled(bool arg1)4456 void MainWindow::on_actionClearRecentOnExit_toggled(bool arg1)
4457 {
4458     Settings.setClearRecent(arg1);
4459     if (arg1)
4460         Settings.setRecent(QStringList());
4461 }
4462 
onSceneGraphInitialized()4463 void MainWindow::onSceneGraphInitialized()
4464 {
4465     if (Settings.playerGPU() && Settings.playerWarnGPU()) {
4466         QMessageBox dialog(QMessageBox::Warning,
4467                            qApp->applicationName(),
4468                            tr("GPU effects are EXPERIMENTAL, UNSTABLE and UNSUPPORTED! Unsupported means do not report bugs about it.\n\n"
4469                               "Do you want to disable GPU effects and restart Shotcut?"),
4470                            QMessageBox::No | QMessageBox::Yes,
4471                            this);
4472         dialog.setDefaultButton(QMessageBox::Yes);
4473         dialog.setEscapeButton(QMessageBox::No);
4474         dialog.setWindowModality(QmlApplication::dialogModality());
4475         if (dialog.exec() == QMessageBox::Yes) {
4476             ui->actionGPU->setChecked(false);
4477             m_exitCode = EXIT_RESTART;
4478             QApplication::closeAllWindows();
4479         } else {
4480             ui->actionGPU->setVisible(true);
4481         }
4482     } else if (Settings.playerGPU()) {
4483         ui->actionGPU->setVisible(true);
4484     }
4485 }
4486 
on_actionShowTextUnderIcons_toggled(bool b)4487 void MainWindow::on_actionShowTextUnderIcons_toggled(bool b)
4488 {
4489     ui->mainToolBar->setToolButtonStyle(b? Qt::ToolButtonTextUnderIcon : Qt::ToolButtonIconOnly);
4490     Settings.setTextUnderIcons(b);
4491     updateLayoutSwitcher();
4492 }
4493 
on_actionShowSmallIcons_toggled(bool b)4494 void MainWindow::on_actionShowSmallIcons_toggled(bool b)
4495 {
4496     ui->mainToolBar->setIconSize(b? QSize(16, 16) : QSize());
4497     Settings.setSmallIcons(b);
4498     updateLayoutSwitcher();
4499 }
4500 
onPlaylistInChanged(int in)4501 void MainWindow::onPlaylistInChanged(int in)
4502 {
4503     m_player->blockSignals(true);
4504     m_player->setIn(in);
4505     m_player->blockSignals(false);
4506 }
4507 
onPlaylistOutChanged(int out)4508 void MainWindow::onPlaylistOutChanged(int out)
4509 {
4510     m_player->blockSignals(true);
4511     m_player->setOut(out);
4512     m_player->blockSignals(false);
4513 }
4514 
on_actionPreviewNone_triggered(bool checked)4515 void MainWindow::on_actionPreviewNone_triggered(bool checked)
4516 {
4517     if (checked) {
4518         Settings.setPlayerPreviewScale(0);
4519         setPreviewScale(0);
4520         m_player->showIdleStatus();
4521     }
4522 }
4523 
on_actionPreview360_triggered(bool checked)4524 void MainWindow::on_actionPreview360_triggered(bool checked)
4525 {
4526     if (checked) {
4527         Settings.setPlayerPreviewScale(360);
4528         setPreviewScale(360);
4529         m_player->showIdleStatus();
4530     }
4531 }
4532 
on_actionPreview540_triggered(bool checked)4533 void MainWindow::on_actionPreview540_triggered(bool checked)
4534 {
4535     if (checked) {
4536         Settings.setPlayerPreviewScale(540);
4537         setPreviewScale(540);
4538         m_player->showIdleStatus();
4539     }
4540 }
4541 
on_actionPreview720_triggered(bool checked)4542 void MainWindow::on_actionPreview720_triggered(bool checked)
4543 {
4544     if (checked) {
4545         Settings.setPlayerPreviewScale(720);
4546         setPreviewScale(720);
4547         m_player->showIdleStatus();
4548     }
4549 }
4550 
timelineClipUuid(int trackIndex,int clipIndex)4551 QUuid MainWindow::timelineClipUuid(int trackIndex, int clipIndex)
4552 {
4553     QScopedPointer<Mlt::ClipInfo> info(m_timelineDock->getClipInfo(trackIndex, clipIndex));
4554     if (info && info->cut && info->cut->is_valid())
4555         return MLT.ensureHasUuid(*info->cut);
4556     return QUuid();
4557 }
4558 
replaceInTimeline(const QUuid & uuid,Mlt::Producer & producer)4559 void MainWindow::replaceInTimeline(const QUuid& uuid, Mlt::Producer& producer)
4560 {
4561     int trackIndex = -1;
4562     int clipIndex = -1;
4563     // lookup the current track and clip index by UUID
4564     QScopedPointer<Mlt::ClipInfo> info(MAIN.timelineClipInfoByUuid(uuid, trackIndex, clipIndex));
4565 
4566     if (trackIndex >= 0 && clipIndex >= 0) {
4567         Util::getHash(producer);
4568         Util::applyCustomProperties(producer, *info->producer, producer.get_in(), producer.get_out());
4569         m_timelineDock->replace(trackIndex, clipIndex, MLT.XML(&producer));
4570     }
4571 }
4572 
timelineClipInfoByUuid(const QUuid & uuid,int & trackIndex,int & clipIndex)4573 Mlt::ClipInfo* MainWindow::timelineClipInfoByUuid(const QUuid& uuid, int& trackIndex, int& clipIndex)
4574 {
4575     return m_timelineDock->model()->findClipByUuid(uuid, trackIndex, clipIndex);
4576 }
4577 
replaceAllByHash(const QString & hash,Mlt::Producer & producer,bool isProxy)4578 void MainWindow::replaceAllByHash(const QString& hash, Mlt::Producer& producer, bool isProxy)
4579 {
4580     Util::getHash(producer);
4581     if (!isProxy)
4582         m_recentDock->add(producer.get("resource"));
4583     if (MLT.isClip() && MLT.producer() && Util::getHash(*MLT.producer()) == hash) {
4584         Util::applyCustomProperties(producer, *MLT.producer(), MLT.producer()->get_in(), MLT.producer()->get_out());
4585         MLT.copyFilters(*MLT.producer(), producer);
4586         MLT.close();
4587         m_player->setPauseAfterOpen(true);
4588         open(new Mlt::Producer(MLT.profile(), "xml-string", MLT.XML(&producer).toUtf8().constData()));
4589     } else if (MLT.savedProducer() && Util::getHash(*MLT.savedProducer()) == hash) {
4590         Util::applyCustomProperties(producer, *MLT.savedProducer(), MLT.savedProducer()->get_in(), MLT.savedProducer()->get_out());
4591         MLT.copyFilters(*MLT.savedProducer(), producer);
4592         MLT.setSavedProducer(&producer);
4593     }
4594     if (playlist()) {
4595         if (isProxy) {
4596             m_playlistDock->replaceClipsWithHash(hash, producer);
4597         } else {
4598             // Append to playlist
4599             producer.set(kPlaylistIndexProperty, playlist()->count());
4600             MAIN.undoStack()->push(
4601                 new Playlist::AppendCommand(*m_playlistDock->model(), MLT.XML(&producer)));
4602         }
4603     }
4604     if (isMultitrackValid()) {
4605         m_timelineDock->replaceClipsWithHash(hash, producer);
4606     }
4607 }
4608 
on_actionTopics_triggered()4609 void MainWindow::on_actionTopics_triggered()
4610 {
4611     QDesktopServices::openUrl(QUrl("https://www.shotcut.org/howtos/"));
4612 }
4613 
on_actionSync_triggered()4614 void MainWindow::on_actionSync_triggered()
4615 {
4616     auto dialog = new SystemSyncDialog(this);
4617     dialog->show();
4618     dialog->raise();
4619     dialog->activateWindow();
4620 }
4621 
on_actionUseProxy_triggered(bool checked)4622 void MainWindow::on_actionUseProxy_triggered(bool checked)
4623 {
4624     if (MLT.producer()) {
4625         QDir dir(m_currentFile.isEmpty()? QDir::tempPath() : QFileInfo(m_currentFile).dir());
4626         QScopedPointer<QTemporaryFile> tmp(new QTemporaryFile(dir.filePath("shotcut-XXXXXX.mlt")));
4627         tmp->open();
4628         tmp->close();
4629         QString fileName = tmp->fileName();
4630         tmp->remove();
4631         tmp.reset();
4632         LOG_DEBUG() << fileName;
4633 
4634         if (saveXML(fileName)) {
4635             MltXmlChecker checker;
4636 
4637             Settings.setProxyEnabled(checked);
4638             checker.check(fileName);
4639             if (!isXmlRepaired(checker, fileName)) {
4640                 QFile::remove(fileName);
4641                 return;
4642             }
4643             if (checker.isUpdated()) {
4644                 QFile::remove(fileName);
4645                 fileName = checker.tempFile().fileName();
4646             }
4647 
4648             // Open the temporary file
4649             int result = 0;
4650             {
4651                 LongUiTask longTask(checked? tr("Turn Proxy On") : tr("Turn Proxy Off"));
4652                 QFuture<int> future = QtConcurrent::run([=]() {
4653                     return MLT.open(QDir::fromNativeSeparators(fileName), QDir::fromNativeSeparators(m_currentFile));
4654                 });
4655                 result = longTask.wait<int>(tr("Converting"), future);
4656             }
4657             if (!result) {
4658                 auto position = m_player->position();
4659                 m_undoStack->clear();
4660                 m_player->stop();
4661                 m_player->setPauseAfterOpen(true);
4662                 open(MLT.producer());
4663                 MLT.seek(m_player->position());
4664                 m_player->seek(position);
4665 
4666                 if (checked && (isPlaylistValid() || isMultitrackValid())) {
4667                     // Prompt user if they want to create missing proxies
4668                     QMessageBox dialog(QMessageBox::Question, qApp->applicationName(),
4669                        tr("Do you want to create missing proxies for every file in this project?\n\n"
4670                           "You must reopen your project after all proxy jobs are finished."),
4671                        QMessageBox::No | QMessageBox::Yes, this);
4672                     dialog.setWindowModality(QmlApplication::dialogModality());
4673                     dialog.setDefaultButton(QMessageBox::Yes);
4674                     dialog.setEscapeButton(QMessageBox::No);
4675                     if (dialog.exec() == QMessageBox::Yes) {
4676                         Mlt::Producer producer(playlist());
4677                         if (producer.is_valid()) {
4678                             ProxyManager::generateIfNotExistsAll(producer);
4679                         }
4680                         producer = multitrack();
4681                         if (producer.is_valid()) {
4682                             ProxyManager::generateIfNotExistsAll(producer);
4683                         }
4684                     }
4685                 }
4686             } else if (fileName != untitledFileName()) {
4687                 showStatusMessage(tr("Failed to open ") + fileName);
4688                 emit openFailed(fileName);
4689             }
4690         } else {
4691             ui->actionUseProxy->setChecked(!checked);
4692             showSaveError();
4693         }
4694         QFile::remove(fileName);
4695     } else {
4696         Settings.setProxyEnabled(checked);
4697     }
4698     m_player->showIdleStatus();
4699 }
4700 
on_actionProxyStorageSet_triggered()4701 void MainWindow::on_actionProxyStorageSet_triggered()
4702 {
4703     // Present folder dialog just like App Data Directory
4704     QString dirName = QFileDialog::getExistingDirectory(this, tr("Proxy Folder"), Settings.proxyFolder(),
4705         Util::getFileDialogOptions());
4706     if (!dirName.isEmpty() && dirName != Settings.proxyFolder()) {
4707         auto oldFolder = Settings.proxyFolder();
4708         Settings.setProxyFolder(dirName);
4709         Settings.sync();
4710 
4711         // Get a count for the progress dialog
4712         auto oldDir = QDir(oldFolder);
4713         auto dirList = oldDir.entryList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
4714         auto count = dirList.size();
4715 
4716         if (count > 0) {
4717             // Prompt user if they want to create missing proxies
4718             QMessageBox dialog(QMessageBox::Question, qApp->applicationName(),
4719                tr("Do you want to move all files from the old folder to the new folder?"),
4720                QMessageBox::No | QMessageBox::Yes, this);
4721             dialog.setWindowModality(QmlApplication::dialogModality());
4722             dialog.setDefaultButton(QMessageBox::Yes);
4723             dialog.setEscapeButton(QMessageBox::No);
4724             if (dialog.exec() == QMessageBox::Yes) {
4725                 // Move the existing files
4726                 LongUiTask longTask(tr("Moving Files"));
4727                 int i = 0;
4728                 for (const auto& fileName : dirList) {
4729                     if (!fileName.isEmpty() && !QFile::exists(dirName + "/" + fileName)) {
4730                         LOG_DEBUG() << "moving" << oldDir.filePath(fileName) << "to" << dirName + "/" + fileName;
4731                         longTask.reportProgress(fileName, i++, count);
4732                         if (!QFile::rename(oldDir.filePath(fileName), dirName + "/" + fileName))
4733                             LOG_WARNING() << "Failed to move" << oldDir.filePath(fileName);
4734                     }
4735                 }
4736             }
4737         }
4738     }
4739 }
4740 
on_actionProxyStorageShow_triggered()4741 void MainWindow::on_actionProxyStorageShow_triggered()
4742 {
4743     Util::showInFolder(ProxyManager::dir().path());
4744 }
4745 
on_actionProxyUseProjectFolder_triggered(bool checked)4746 void MainWindow::on_actionProxyUseProjectFolder_triggered(bool checked)
4747 {
4748     Settings.setProxyUseProjectFolder(checked);
4749 }
4750 
on_actionProxyUseHardware_triggered(bool checked)4751 void MainWindow::on_actionProxyUseHardware_triggered(bool checked)
4752 {
4753     if (checked && Settings.encodeHardware().isEmpty()) {
4754         if (!m_encodeDock->detectHardwareEncoders())
4755             ui->actionProxyUseHardware->setChecked(false);
4756     }
4757     Settings.setProxyUseHardware(ui->actionProxyUseHardware->isChecked());
4758 }
4759 
on_actionProxyConfigureHardware_triggered()4760 void MainWindow::on_actionProxyConfigureHardware_triggered()
4761 {
4762     m_encodeDock->on_hwencodeButton_clicked();
4763     if (Settings.encodeHardware().isEmpty()) {
4764         ui->actionProxyUseHardware->setChecked(false);
4765         Settings.setProxyUseHardware(false);
4766     }
4767 }
4768 
updateLayoutSwitcher()4769 void MainWindow::updateLayoutSwitcher()
4770 {
4771     if (Settings.textUnderIcons() && !Settings.smallIcons()) {
4772         auto layoutSwitcher = findChild<QWidget*>(kLayoutSwitcherName);
4773         if (layoutSwitcher) {
4774             layoutSwitcher->show();
4775             for (const auto& child : layoutSwitcher->findChildren<QToolButton*>()) {
4776                 child->show();
4777             }
4778         } else {
4779             layoutSwitcher = new QWidget;
4780             layoutSwitcher->setObjectName(kLayoutSwitcherName);
4781             auto layoutGrid = new QGridLayout(layoutSwitcher);
4782             layoutGrid->setContentsMargins(0, 0, 0, 0);
4783             ui->mainToolBar->insertWidget(ui->dummyAction, layoutSwitcher);
4784             auto button = new QToolButton;
4785             button->setAutoRaise(true);
4786             button->setDefaultAction(ui->actionLayoutLogging);
4787             layoutGrid->addWidget(button, 0, 0, Qt::AlignCenter);
4788             button = new QToolButton;
4789             button->setAutoRaise(true);
4790             button->setDefaultAction(ui->actionLayoutEditing);
4791             layoutGrid->addWidget(button, 0, 1, Qt::AlignCenter);
4792             button = new QToolButton;
4793             button->setAutoRaise(true);
4794             button->setDefaultAction(ui->actionLayoutEffects);
4795             layoutGrid->addWidget(button, 0, 2, Qt::AlignCenter);
4796             button = new QToolButton;
4797             button->setAutoRaise(true);
4798             button->setDefaultAction(ui->actionLayoutColor);
4799             layoutGrid->addWidget(button, 1, 0, Qt::AlignCenter);
4800             button = new QToolButton;
4801             button->setAutoRaise(true);
4802             button->setDefaultAction(ui->actionLayoutAudio);
4803             layoutGrid->addWidget(button, 1, 1, Qt::AlignCenter);
4804             button = new QToolButton;
4805             button->setAutoRaise(true);
4806             button->setDefaultAction(ui->actionLayoutPlayer);
4807             layoutGrid->addWidget(button, 1, 2, Qt::AlignCenter);
4808         }
4809         ui->mainToolBar->removeAction(ui->actionLayoutLogging);
4810         ui->mainToolBar->removeAction(ui->actionLayoutEditing);
4811         ui->mainToolBar->removeAction(ui->actionLayoutEffects);
4812         ui->mainToolBar->removeAction(ui->actionLayoutColor);
4813         ui->mainToolBar->removeAction(ui->actionLayoutAudio);
4814         ui->mainToolBar->removeAction(ui->actionLayoutPlayer);
4815     } else {
4816         auto layoutSwitcher = findChild<QWidget*>(kLayoutSwitcherName);
4817         if (layoutSwitcher) {
4818             layoutSwitcher->hide();
4819             for (const auto& child : layoutSwitcher->findChildren<QToolButton*>()) {
4820                 child->hide();
4821             }
4822             ui->mainToolBar->insertAction(ui->dummyAction, ui->actionLayoutLogging);
4823             ui->mainToolBar->insertAction(ui->dummyAction, ui->actionLayoutEditing);
4824             ui->mainToolBar->insertAction(ui->dummyAction, ui->actionLayoutEffects);
4825             ui->mainToolBar->insertAction(ui->dummyAction, ui->actionLayoutColor);
4826             ui->mainToolBar->insertAction(ui->dummyAction, ui->actionLayoutAudio);
4827             ui->mainToolBar->insertAction(ui->dummyAction, ui->actionLayoutPlayer);
4828         }
4829     }
4830 }
4831 
clearCurrentLayout()4832 void MainWindow::clearCurrentLayout()
4833 {
4834     auto currentLayout = ui->actionLayoutLogging->actionGroup()->checkedAction();
4835     if (currentLayout) {
4836         currentLayout->setChecked(false);
4837     }
4838 }
4839