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 © 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