/*
* Copyright (c) 2011-2022 Meltytech, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "scrubbar.h"
#include "openotherdialog.h"
#include "player.h"
#include "defaultlayouts.h"
#include "widgets/alsawidget.h"
#include "widgets/colorbarswidget.h"
#include "widgets/colorproducerwidget.h"
#include "widgets/countproducerwidget.h"
#include "widgets/decklinkproducerwidget.h"
#include "widgets/directshowvideowidget.h"
#include "widgets/glaxnimateproducerwidget.h"
#include "widgets/isingwidget.h"
#include "widgets/jackproducerwidget.h"
#include "widgets/toneproducerwidget.h"
#include "widgets/lissajouswidget.h"
#include "widgets/networkproducerwidget.h"
#include "widgets/noisewidget.h"
#include "widgets/plasmawidget.h"
#include "widgets/pulseaudiowidget.h"
#include "widgets/video4linuxwidget.h"
#include "widgets/x11grabwidget.h"
#include "widgets/avformatproducerwidget.h"
#include "widgets/imageproducerwidget.h"
#include "widgets/blipproducerwidget.h"
#include "widgets/newprojectfolder.h"
#include "docks/recentdock.h"
#include "docks/encodedock.h"
#include "docks/jobsdock.h"
#include "jobqueue.h"
#include "docks/playlistdock.h"
#include "glwidget.h"
#include "controllers/filtercontroller.h"
#include "controllers/scopecontroller.h"
#include "docks/filtersdock.h"
#include "dialogs/customprofiledialog.h"
#include "dialogs/saveimagedialog.h"
#include "settings.h"
#include "leapnetworklistener.h"
#include "database.h"
#include "widgets/gltestwidget.h"
#include "docks/timelinedock.h"
#include "widgets/lumamixtransition.h"
#include "qmltypes/qmlutilities.h"
#include "qmltypes/qmlapplication.h"
#include "autosavefile.h"
#include "commands/playlistcommands.h"
#include "shotcut_mlt_properties.h"
#include "widgets/avfoundationproducerwidget.h"
#include "dialogs/textviewerdialog.h"
#include "widgets/gdigrabwidget.h"
#include "models/audiolevelstask.h"
#include "widgets/trackpropertieswidget.h"
#include "widgets/timelinepropertieswidget.h"
#include "dialogs/unlinkedfilesdialog.h"
#include "docks/keyframesdock.h"
#include "docks/markersdock.h"
#include "docks/notesdock.h"
#include "util.h"
#include "models/keyframesmodel.h"
#include "dialogs/listselectiondialog.h"
#include "widgets/textproducerwidget.h"
#include "qmltypes/qmlprofile.h"
#include "dialogs/longuitask.h"
#include "dialogs/systemsyncdialog.h"
#include "proxymanager.h"
#ifdef Q_OS_WIN
#include "windowstools.h"
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static bool eventDebugCallback(void **data)
{
QEvent *event = reinterpret_cast(data[1]);
if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
QObject *receiver = reinterpret_cast(data[0]);
LOG_DEBUG() << event << "->" << receiver;
} else if (event->type() == QEvent::MouseButtonPress
|| event->type() == QEvent::MouseButtonRelease) {
QObject *receiver = reinterpret_cast(data[0]);
LOG_DEBUG() << event << "->" << receiver;
}
return false;
}
static const int AUTOSAVE_TIMEOUT_MS = 60000;
static const char *kReservedLayoutPrefix = "__%1";
static const char *kLayoutSwitcherName("layoutSwitcherGrid");
MainWindow::MainWindow()
: QMainWindow(0)
, ui(new Ui::MainWindow)
, m_isKKeyPressed(false)
, m_keyerGroup(0)
, m_previewScaleGroup(0)
, m_keyerMenu(0)
, m_isPlaylistLoaded(false)
, m_exitCode(EXIT_SUCCESS)
, m_navigationPosition(0)
, m_upgradeUrl("https://www.shotcut.org/download/")
, m_keyframesDock(0)
{
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
QLibrary libJack("libjack.so.0");
if (!libJack.load()) {
QMessageBox::critical(this, qApp->applicationName(),
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."));
::exit(EXIT_FAILURE);
} else {
libJack.unload();
}
QLibrary libSDL("libSDL2-2.0.so.0");
if (!libSDL.load()) {
QMessageBox::critical(this, qApp->applicationName(),
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."));
::exit(EXIT_FAILURE);
} else {
libSDL.unload();
}
#endif
connectFocusSignals();
registerDebugCallback();
LOG_DEBUG() << "begin";
LOG_INFO() << "device pixel ratio =" << devicePixelRatioF();
#ifndef Q_OS_WIN
new GLTestWidget(this);
#endif
connect(&m_autosaveTimer, SIGNAL(timeout()), this, SLOT(onAutosaveTimeout()));
m_autosaveTimer.start(AUTOSAVE_TIMEOUT_MS);
// Initialize all QML types
QmlUtilities::registerCommonTypes();
// Create the UI.
ui->setupUi(this);
#ifdef Q_OS_MAC
// Qt 5 on OS X supports the standard Full Screen window widget.
ui->actionEnter_Full_Screen->setVisible(false);
// OS X has a standard Full Screen shortcut we should use.
ui->actionEnter_Full_Screen->setShortcut(QKeySequence((Qt::CTRL + Qt::META + Qt::Key_F)));
#endif
setDockNestingEnabled(true);
ui->statusBar->hide();
connectUISignals();
// Accept drag-n-drop of files.
this->setAcceptDrops(true);
setupAndConnectUndoStack();
// Add the player widget.
setupAndConnectPlayerWidget();
setupSettingsMenu();
setupOpenOtherMenu();
readPlayerSettings();
configureVideoWidget();
setupLayoutSwitcher();
centerLayoutInRemainingToolbarSpace();
#ifndef SHOTCUT_NOUPGRADE
if (Settings.noUpgrade() || qApp->property("noupgrade").toBool())
#endif
delete ui->actionUpgrade;
setupAndConnectDocks();
setupMenuView();
connectVideoWidgetSignals();
readWindowSettings();
setFocus();
setCurrentFile("");
setupAndConnectLeapNetworkListener();
connect(&m_network, SIGNAL(finished(QNetworkReply *)),
SLOT(onUpgradeCheckFinished(QNetworkReply *)));
resetSourceUpdated();
m_clipboardUpdatedAt.setSecsSinceEpoch(0);
onClipboardChanged();
connect(QGuiApplication::clipboard(), SIGNAL(dataChanged()), this, SLOT(onClipboardChanged()));
QThreadPool::globalInstance()->setMaxThreadCount(qMin(4,
QThreadPool::globalInstance()->maxThreadCount()));
ProxyManager::removePending();
LOG_DEBUG() << "end";
}
void MainWindow::connectFocusSignals()
{
if (!qgetenv("OBSERVE_FOCUS").isEmpty()) {
connect(qApp, &QApplication::focusChanged,
this, &MainWindow::onFocusChanged);
connect(qApp, &QGuiApplication::focusObjectChanged,
this, &MainWindow::onFocusObjectChanged);
connect(qApp, &QGuiApplication::focusWindowChanged,
this, &MainWindow::onFocusWindowChanged);
}
}
void MainWindow::registerDebugCallback()
{
if (!qgetenv("EVENT_DEBUG").isEmpty())
QInternal::registerCallback(QInternal::EventNotifyCallback, eventDebugCallback);
}
void MainWindow::connectUISignals()
{
connect(ui->actionOpen, SIGNAL(triggered()), this, SLOT(openVideo()));
connect(ui->actionAbout_Qt, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
connect(this, &MainWindow::producerOpened, this, &MainWindow::onProducerOpened);
connect(ui->mainToolBar, SIGNAL(visibilityChanged(bool)), SLOT(onToolbarVisibilityChanged(bool)));
ui->actionSave->setEnabled(false);
}
void MainWindow::setupAndConnectUndoStack()
{
m_undoStack = new QUndoStack(this);
m_undoStack->setUndoLimit(Settings.undoLimit());
QAction *undoAction = m_undoStack->createUndoAction(this);
QAction *redoAction = m_undoStack->createRedoAction(this);
undoAction->setIcon(QIcon::fromTheme("edit-undo",
QIcon(":/icons/oxygen/32x32/actions/edit-undo.png")));
redoAction->setIcon(QIcon::fromTheme("edit-redo",
QIcon(":/icons/oxygen/32x32/actions/edit-redo.png")));
undoAction->setShortcut(QString::fromLatin1("Ctrl+Z"));
#ifdef Q_OS_WIN
redoAction->setShortcut(QString::fromLatin1("Ctrl+Y"));
#else
redoAction->setShortcut(QString::fromLatin1("Ctrl+Shift+Z"));
#endif
ui->menuEdit->insertAction(ui->actionCut, undoAction);
ui->menuEdit->insertAction(ui->actionCut, redoAction);
ui->menuEdit->insertSeparator(ui->actionCut);
ui->actionUndo->setIcon(undoAction->icon());
ui->actionRedo->setIcon(redoAction->icon());
ui->actionUndo->setToolTip(undoAction->toolTip());
ui->actionRedo->setToolTip(redoAction->toolTip());
connect(m_undoStack, SIGNAL(canUndoChanged(bool)), ui->actionUndo, SLOT(setEnabled(bool)));
connect(m_undoStack, SIGNAL(canRedoChanged(bool)), ui->actionRedo, SLOT(setEnabled(bool)));
}
void MainWindow::setupAndConnectPlayerWidget()
{
m_player = new Player;
MLT.videoWidget()->installEventFilter(this);
ui->centralWidget->layout()->addWidget(m_player);
connect(this, &MainWindow::producerOpened, m_player, &Player::onProducerOpened);
connect(m_player, SIGNAL(showStatusMessage(QString)), this, SLOT(showStatusMessage(QString)));
connect(m_player, SIGNAL(inChanged(int)), this, SLOT(onCutModified()));
connect(m_player, SIGNAL(outChanged(int)), this, SLOT(onCutModified()));
connect(m_player, SIGNAL(tabIndexChanged(int)), SLOT(onPlayerTabIndexChanged(int)));
connect(MLT.videoWidget(), SIGNAL(started()), SLOT(processMultipleFiles()));
connect(MLT.videoWidget(), SIGNAL(paused()), m_player, SLOT(showPaused()));
connect(MLT.videoWidget(), SIGNAL(playing()), m_player, SLOT(showPlaying()));
connect(MLT.videoWidget(), SIGNAL(toggleZoom(bool)), m_player, SLOT(toggleZoom(bool)));
}
void MainWindow::setupLayoutSwitcher()
{
auto group = new QActionGroup(this);
group->addAction(ui->actionLayoutLogging);
group->addAction(ui->actionLayoutEditing);
group->addAction(ui->actionLayoutEffects);
group->addAction(ui->actionLayoutAudio);
group->addAction(ui->actionLayoutColor);
group->addAction(ui->actionLayoutPlayer);
switch (Settings.layoutMode()) {
case LayoutMode::Custom:
break;
case LayoutMode::Logging:
ui->actionLayoutLogging->setChecked(true);
break;
case LayoutMode::Editing:
ui->actionLayoutEditing->setChecked(true);
break;
case LayoutMode::Effects:
ui->actionLayoutEffects->setChecked(true);
break;
case LayoutMode::Color:
ui->actionLayoutColor->setChecked(true);
break;
case LayoutMode::Audio:
ui->actionLayoutAudio->setChecked(true);
break;
case LayoutMode::PlayerOnly:
ui->actionLayoutPlayer->setChecked(true);
break;
default:
ui->actionLayoutEditing->setChecked(true);
break;
}
}
void MainWindow::centerLayoutInRemainingToolbarSpace()
{
auto spacer = new QWidget;
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
ui->mainToolBar->insertWidget(ui->dummyAction, spacer);
spacer = new QWidget;
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
ui->mainToolBar->addWidget(spacer);
updateLayoutSwitcher();
}
void MainWindow::setupAndConnectDocks()
{
m_scopeController = new ScopeController(this, ui->menuView);
QDockWidget *audioMeterDock = findChild("AudioPeakMeterDock");
if (audioMeterDock) {
audioMeterDock->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_1));
connect(ui->actionAudioMeter, SIGNAL(triggered()), audioMeterDock->toggleViewAction(),
SLOT(trigger()));
}
m_propertiesDock = new QDockWidget(tr("Properties"), this);
m_propertiesDock->hide();
m_propertiesDock->setObjectName("propertiesDock");
m_propertiesDock->setWindowIcon(ui->actionProperties->icon());
m_propertiesDock->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_2));
m_propertiesDock->toggleViewAction()->setIcon(ui->actionProperties->icon());
m_propertiesDock->setMinimumWidth(300);
QScrollArea *scroll = new QScrollArea;
scroll->setWidgetResizable(true);
m_propertiesDock->setWidget(scroll);
ui->menuView->addAction(m_propertiesDock->toggleViewAction());
connect(m_propertiesDock->toggleViewAction(), SIGNAL(triggered(bool)), this,
SLOT(onPropertiesDockTriggered(bool)));
connect(ui->actionProperties, SIGNAL(triggered()), this, SLOT(onPropertiesDockTriggered()));
m_recentDock = new RecentDock(this);
m_recentDock->hide();
m_recentDock->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_3));
ui->menuView->addAction(m_recentDock->toggleViewAction());
connect(m_recentDock, SIGNAL(itemActivated(QString)), this, SLOT(open(QString)));
connect(m_recentDock->toggleViewAction(), SIGNAL(triggered(bool)), this,
SLOT(onRecentDockTriggered(bool)));
connect(ui->actionRecent, SIGNAL(triggered()), this, SLOT(onRecentDockTriggered()));
connect(this, SIGNAL(openFailed(QString)), m_recentDock, SLOT(remove(QString)));
connect(m_recentDock, &RecentDock::deleted, m_player->projectWidget(),
&NewProjectFolder::updateRecentProjects);
m_playlistDock = new PlaylistDock(this);
m_playlistDock->hide();
m_playlistDock->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_4));
ui->menuView->addAction(m_playlistDock->toggleViewAction());
connect(m_playlistDock->toggleViewAction(), SIGNAL(triggered(bool)), this,
SLOT(onPlaylistDockTriggered(bool)));
connect(ui->actionPlaylist, SIGNAL(triggered()), this, SLOT(onPlaylistDockTriggered()));
connect(m_playlistDock, SIGNAL(clipOpened(Mlt::Producer *, bool)), this,
SLOT(openCut(Mlt::Producer *, bool)));
connect(m_playlistDock, SIGNAL(itemActivated(int)), this, SLOT(seekPlaylist(int)));
connect(m_playlistDock, SIGNAL(showStatusMessage(QString)), this, SLOT(showStatusMessage(QString)));
connect(m_playlistDock->model(), SIGNAL(created()), this, SLOT(onPlaylistCreated()));
connect(m_playlistDock->model(), SIGNAL(cleared()), this, SLOT(onPlaylistCleared()));
connect(m_playlistDock->model(), SIGNAL(closed()), this, SLOT(onPlaylistClosed()));
connect(m_playlistDock->model(), SIGNAL(modified()), this, SLOT(onPlaylistModified()));
connect(m_playlistDock->model(), SIGNAL(loaded()), this, SLOT(onPlaylistLoaded()));
connect(this, SIGNAL(producerOpened()), m_playlistDock, SLOT(onProducerOpened()));
if (!Settings.playerGPU())
connect(m_playlistDock->model(), SIGNAL(loaded()), this, SLOT(updateThumbnails()));
connect(m_player, &Player::inChanged, m_playlistDock, &PlaylistDock::onInChanged);
connect(m_player, &Player::outChanged, m_playlistDock, &PlaylistDock::onOutChanged);
connect(m_playlistDock->model(), &PlaylistModel::inChanged, this, &MainWindow::onPlaylistInChanged);
connect(m_playlistDock->model(), &PlaylistModel::outChanged, this,
&MainWindow::onPlaylistOutChanged);
m_timelineDock = new TimelineDock(this);
m_timelineDock->hide();
m_timelineDock->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_5));
ui->menuView->addAction(m_timelineDock->toggleViewAction());
connect(m_timelineDock->toggleViewAction(), SIGNAL(triggered(bool)), this,
SLOT(onTimelineDockTriggered(bool)));
connect(ui->actionTimeline, SIGNAL(triggered()), SLOT(onTimelineDockTriggered()));
connect(m_player, SIGNAL(seeked(int)), m_timelineDock, SLOT(onSeeked(int)));
connect(m_timelineDock, SIGNAL(seeked(int)), SLOT(seekTimeline(int)));
connect(m_timelineDock, SIGNAL(clipClicked()), SLOT(onTimelineClipSelected()));
connect(m_timelineDock, SIGNAL(showStatusMessage(QString)), this, SLOT(showStatusMessage(QString)));
connect(m_timelineDock->model(), SIGNAL(showStatusMessage(QString)), this,
SLOT(showStatusMessage(QString)));
connect(m_timelineDock->model(), SIGNAL(created()), SLOT(onMultitrackCreated()));
connect(m_timelineDock->model(), SIGNAL(closed()), SLOT(onMultitrackClosed()));
connect(m_timelineDock->model(), SIGNAL(modified()), SLOT(onMultitrackModified()));
connect(m_timelineDock->model(), SIGNAL(durationChanged()), SLOT(onMultitrackDurationChanged()));
connect(m_timelineDock, SIGNAL(clipOpened(Mlt::Producer *)), SLOT(openCut(Mlt::Producer *)));
connect(m_timelineDock->model(), &MultitrackModel::seeked, this, &MainWindow::seekTimeline);
connect(m_timelineDock->model(), SIGNAL(scaleFactorChanged()), m_player, SLOT(pause()));
connect(m_timelineDock->markersModel(), SIGNAL(modified()), SLOT(onMultitrackModified()));
connect(m_timelineDock, SIGNAL(selected(Mlt::Producer *)),
SLOT(loadProducerWidget(Mlt::Producer *)));
connect(m_timelineDock, SIGNAL(selectionChanged()), SLOT(onTimelineSelectionChanged()));
connect(m_timelineDock, SIGNAL(clipCopied()), SLOT(onClipCopied()));
connect(m_timelineDock, SIGNAL(filteredClicked()), SLOT(onFiltersDockTriggered()));
connect(m_playlistDock, SIGNAL(addAllTimeline(Mlt::Playlist *)), SLOT(onTimelineDockTriggered()));
connect(m_playlistDock, SIGNAL(addAllTimeline(Mlt::Playlist *, bool)),
SLOT(onAddAllToTimeline(Mlt::Playlist *, bool)));
connect(m_player, SIGNAL(previousSought()), m_timelineDock, SLOT(seekPreviousEdit()));
connect(m_player, SIGNAL(nextSought()), m_timelineDock, SLOT(seekNextEdit()));
connect(m_timelineDock, SIGNAL(isRecordingChanged(bool)), m_player,
SLOT(onMuteButtonToggled(bool)));
m_filterController = new FilterController(this);
m_filtersDock = new FiltersDock(m_filterController->metadataModel(),
m_filterController->attachedModel(), this);
m_filtersDock->setMinimumSize(400, 300);
m_filtersDock->hide();
m_filtersDock->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_6));
ui->menuView->addAction(m_filtersDock->toggleViewAction());
connect(m_filtersDock, SIGNAL(currentFilterRequested(int)), m_filterController,
SLOT(setCurrentFilter(int)), Qt::QueuedConnection);
connect(m_filtersDock->toggleViewAction(), SIGNAL(triggered(bool)), this,
SLOT(onFiltersDockTriggered(bool)));
connect(ui->actionFilters, SIGNAL(triggered()), this, SLOT(onFiltersDockTriggered()));
connect(m_filterController, SIGNAL(currentFilterChanged(QmlFilter *, QmlMetadata *, int)),
m_filtersDock, SLOT(setCurrentFilter(QmlFilter *, QmlMetadata *, int)));
connect(this, SIGNAL(producerOpened()), m_filterController, SLOT(setProducer()));
connect(m_filterController->attachedModel(), SIGNAL(changed()), SLOT(onFilterModelChanged()));
connect(m_filtersDock, SIGNAL(changed()), SLOT(onFilterModelChanged()));
connect(m_filterController, SIGNAL(filterChanged(Mlt::Service *)),
m_timelineDock->model(), SLOT(onFilterChanged(Mlt::Service *)));
connect(m_filterController->attachedModel(), SIGNAL(addedOrRemoved(Mlt::Producer *)),
m_timelineDock->model(), SLOT(filterAddedOrRemoved(Mlt::Producer *)));
connect(&QmlApplication::singleton(), SIGNAL(filtersPasted(Mlt::Producer *)),
m_timelineDock->model(), SLOT(filterAddedOrRemoved(Mlt::Producer *)));
connect(&QmlApplication::singleton(), &QmlApplication::filtersPasted,
this, &MainWindow::onProducerModified);
connect(m_filterController, SIGNAL(statusChanged(QString)), this, SLOT(showStatusMessage(QString)));
connect(m_timelineDock, SIGNAL(fadeInChanged(int)), m_filterController, SLOT(onFadeInChanged()));
connect(m_timelineDock, SIGNAL(fadeOutChanged(int)), m_filterController, SLOT(onFadeOutChanged()));
connect(m_timelineDock, SIGNAL(selected(Mlt::Producer *)), m_filterController,
SLOT(setProducer(Mlt::Producer *)));
connect(m_player, SIGNAL(seeked(int)), m_filtersDock, SLOT(onSeeked(int)), Qt::QueuedConnection);
connect(m_filtersDock, SIGNAL(seeked(int)), SLOT(seekKeyframes(int)));
connect(MLT.videoWidget(), SIGNAL(frameDisplayed(const SharedFrame &)), m_filtersDock,
SLOT(onShowFrame(const SharedFrame &)));
connect(m_player, SIGNAL(inChanged(int)), m_filtersDock, SIGNAL(producerInChanged(int)));
connect(m_player, SIGNAL(outChanged(int)), m_filtersDock, SIGNAL(producerOutChanged(int)));
connect(m_player, SIGNAL(inChanged(int)), m_filterController, SLOT(onServiceInChanged(int)));
connect(m_player, SIGNAL(outChanged(int)), m_filterController, SLOT(onServiceOutChanged(int)));
connect(this, SIGNAL(serviceInChanged(int, Mlt::Service *)), m_filterController,
SLOT(onServiceInChanged(int, Mlt::Service *)));
connect(this, SIGNAL(serviceOutChanged(int, Mlt::Service *)), m_filterController,
SLOT(onServiceOutChanged(int, Mlt::Service *)));
m_markersDock = new MarkersDock(this);
m_markersDock->hide();
m_markersDock->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_6));
m_markersDock->setModel(m_timelineDock->markersModel());
ui->menuView->addAction(m_markersDock->toggleViewAction());
connect(m_markersDock->toggleViewAction(), SIGNAL(triggered(bool)), this,
SLOT(onMarkersDockTriggered(bool)));
connect(ui->actionMarkers, SIGNAL(triggered()), this, SLOT(onMarkersDockTriggered()));
connect(m_markersDock, SIGNAL(seekRequested(int)), SLOT(seekTimeline(int)));
connect(m_markersDock, SIGNAL(addRequested()), m_timelineDock, SLOT(createMarker()));
connect(m_markersDock, SIGNAL(addAroundSelectionRequested()), m_timelineDock,
SLOT(createOrEditSelectionMarker()));
connect(m_timelineDock, SIGNAL(markerSeeked(int)), m_markersDock,
SLOT(onMarkerSelectionRequest(int)));
m_keyframesDock = new KeyframesDock(m_filtersDock->qmlProducer(), this);
m_keyframesDock->hide();
m_keyframesDock->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_7));
ui->menuView->addAction(m_keyframesDock->toggleViewAction());
connect(m_keyframesDock->toggleViewAction(), SIGNAL(triggered(bool)), this,
SLOT(onKeyframesDockTriggered(bool)));
connect(ui->actionKeyframes, SIGNAL(triggered()), this, SLOT(onKeyframesDockTriggered()));
connect(m_filterController, SIGNAL(currentFilterChanged(QmlFilter *, QmlMetadata *, int)),
m_keyframesDock, SLOT(setCurrentFilter(QmlFilter *, QmlMetadata *)));
connect(m_keyframesDock, SIGNAL(visibilityChanged(bool)), m_filtersDock->qmlProducer(),
SLOT(remakeAudioLevels(bool)));
m_historyDock = new QDockWidget(tr("History"), this);
m_historyDock->hide();
m_historyDock->setObjectName("historyDock");
m_historyDock->setWindowIcon(ui->actionHistory->icon());
m_historyDock->toggleViewAction()->setIcon(ui->actionHistory->icon());
m_historyDock->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_8));
m_historyDock->setMinimumWidth(150);
ui->menuView->addAction(m_historyDock->toggleViewAction());
connect(m_historyDock->toggleViewAction(), SIGNAL(triggered(bool)), this,
SLOT(onHistoryDockTriggered(bool)));
connect(ui->actionHistory, SIGNAL(triggered()), this, SLOT(onHistoryDockTriggered()));
QUndoView *undoView = new QUndoView(m_undoStack, m_historyDock);
undoView->setObjectName("historyView");
undoView->setAlternatingRowColors(true);
undoView->setSpacing(2);
m_historyDock->setWidget(undoView);
ui->actionUndo->setDisabled(true);
ui->actionRedo->setDisabled(true);
m_encodeDock = new EncodeDock(this);
m_encodeDock->hide();
m_encodeDock->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_9));
ui->menuView->addAction(m_encodeDock->toggleViewAction());
connect(this, SIGNAL(producerOpened()), m_encodeDock, SLOT(onProducerOpened()));
connect(ui->actionEncode, SIGNAL(triggered()), this, SLOT(onEncodeTriggered()));
connect(ui->actionExportVideo, SIGNAL(triggered()), this, SLOT(onEncodeTriggered()));
connect(m_encodeDock->toggleViewAction(), SIGNAL(triggered(bool)), this,
SLOT(onEncodeTriggered(bool)));
connect(m_encodeDock, SIGNAL(captureStateChanged(bool)), m_player,
SLOT(onCaptureStateChanged(bool)));
connect(m_encodeDock, SIGNAL(captureStateChanged(bool)), m_propertiesDock, SLOT(setDisabled(bool)));
connect(m_encodeDock, SIGNAL(captureStateChanged(bool)), m_recentDock, SLOT(setDisabled(bool)));
connect(m_encodeDock, SIGNAL(captureStateChanged(bool)), m_filtersDock, SLOT(setDisabled(bool)));
connect(m_encodeDock, SIGNAL(captureStateChanged(bool)), m_keyframesDock, SLOT(setDisabled(bool)));
connect(m_encodeDock, SIGNAL(captureStateChanged(bool)), ui->actionOpen, SLOT(setDisabled(bool)));
connect(m_encodeDock, SIGNAL(captureStateChanged(bool)), ui->actionOpenOther,
SLOT(setDisabled(bool)));
connect(m_encodeDock, SIGNAL(captureStateChanged(bool)), ui->actionExit, SLOT(setDisabled(bool)));
connect(m_encodeDock, SIGNAL(captureStateChanged(bool)), this, SLOT(onCaptureStateChanged(bool)));
connect(m_encodeDock, SIGNAL(captureStateChanged(bool)), m_historyDock, SLOT(setDisabled(bool)));
connect(this, SIGNAL(profileChanged()), m_encodeDock, SLOT(onProfileChanged()));
connect(this, SIGNAL(profileChanged()), SLOT(onProfileChanged()));
connect(this, SIGNAL(profileChanged()), &QmlProfile::singleton(), SIGNAL(profileChanged()));
connect(this, SIGNAL(audioChannelsChanged()), m_encodeDock, SLOT(onAudioChannelsChanged()));
connect(m_playlistDock->model(), SIGNAL(modified()), m_encodeDock, SLOT(onProducerOpened()));
connect(m_timelineDock, SIGNAL(clipCopied()), m_encodeDock, SLOT(onProducerOpened()));
connect(m_timelineDock, SIGNAL(markerRangesChanged()), m_encodeDock, SLOT(onProducerOpened()));
m_encodeDock->onProfileChanged();
m_jobsDock = new JobsDock(this);
m_jobsDock->hide();
m_jobsDock->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_0));
ui->menuView->addAction(m_jobsDock->toggleViewAction());
connect(&JOBS, SIGNAL(jobAdded()), m_jobsDock, SLOT(onJobAdded()));
connect(m_jobsDock->toggleViewAction(), SIGNAL(triggered(bool)), this,
SLOT(onJobsDockTriggered(bool)));
connect(ui->actionJobs, SIGNAL(triggered()), this, SLOT(onJobsDockTriggered()));
m_notesDock = new NotesDock(this);
m_notesDock->hide();
m_notesDock->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_3));
ui->menuView->insertAction(m_playlistDock->toggleViewAction(), m_notesDock->toggleViewAction());
connect(m_notesDock->toggleViewAction(), SIGNAL(triggered(bool)), this,
SLOT(onNotesDockTriggered(bool)));
connect(ui->actionNotes, SIGNAL(triggered()), this, SLOT(onNotesDockTriggered()));
connect(m_notesDock, SIGNAL(modified()), this, SLOT(onNoteModified()));
addDockWidget(Qt::LeftDockWidgetArea, m_propertiesDock);
addDockWidget(Qt::RightDockWidgetArea, m_recentDock);
addDockWidget(Qt::LeftDockWidgetArea, m_playlistDock);
addDockWidget(Qt::BottomDockWidgetArea, m_timelineDock);
addDockWidget(Qt::LeftDockWidgetArea, m_filtersDock);
addDockWidget(Qt::BottomDockWidgetArea, m_keyframesDock);
addDockWidget(Qt::RightDockWidgetArea, m_historyDock);
addDockWidget(Qt::LeftDockWidgetArea, m_encodeDock);
addDockWidget(Qt::RightDockWidgetArea, m_jobsDock);
addDockWidget(Qt::LeftDockWidgetArea, m_notesDock);
splitDockWidget(m_timelineDock, m_markersDock, Qt::Horizontal);
tabifyDockWidget(m_propertiesDock, m_playlistDock);
tabifyDockWidget(m_playlistDock, m_filtersDock);
tabifyDockWidget(m_filtersDock, m_encodeDock);
tabifyDockWidget(m_encodeDock, m_notesDock);
splitDockWidget(m_recentDock, findChild("AudioWaveformDock"), Qt::Vertical);
splitDockWidget(audioMeterDock, m_recentDock, Qt::Horizontal);
tabifyDockWidget(m_recentDock, m_historyDock);
tabifyDockWidget(m_historyDock, m_jobsDock);
tabifyDockWidget(m_keyframesDock, m_timelineDock);
m_recentDock->raise();
resetDockCorners();
}
void MainWindow::setupMenuView()
{
ui->menuView->addSeparator();
ui->menuView->addAction(ui->actionApplicationLog);
}
void MainWindow::connectVideoWidgetSignals()
{
Mlt::GLWidget *videoWidget = (Mlt::GLWidget *) & (MLT);
connect(videoWidget, SIGNAL(dragStarted()), m_playlistDock, SLOT(onPlayerDragStarted()));
connect(videoWidget, SIGNAL(seekTo(int)), m_player, SLOT(seek(int)));
connect(videoWidget, SIGNAL(gpuNotSupported()), this, SLOT(onGpuNotSupported()));
connect(videoWidget->quickWindow(), SIGNAL(sceneGraphInitialized()),
SLOT(onSceneGraphInitialized()), Qt::QueuedConnection);
connect(videoWidget, SIGNAL(frameDisplayed(const SharedFrame &)), m_scopeController,
SIGNAL(newFrame(const SharedFrame &)));
connect(m_filterController, SIGNAL(currentFilterChanged(QmlFilter *, QmlMetadata *, int)),
videoWidget, SLOT(setCurrentFilter(QmlFilter *, QmlMetadata *)));
}
void MainWindow::setupAndConnectLeapNetworkListener()
{
LeapNetworkListener *leap = new LeapNetworkListener(this);
connect(leap, SIGNAL(shuttle(float)), SLOT(onShuttle(float)));
connect(leap, SIGNAL(jogRightFrame()), SLOT(stepRightOneFrame()));
connect(leap, SIGNAL(jogRightSecond()), SLOT(stepRightOneSecond()));
connect(leap, SIGNAL(jogLeftFrame()), SLOT(stepLeftOneFrame()));
connect(leap, SIGNAL(jogLeftSecond()), SLOT(stepLeftOneSecond()));
}
void MainWindow::onFocusWindowChanged(QWindow *) const
{
LOG_DEBUG() << "Focuswindow changed";
LOG_DEBUG() << "Current focusWidget:" << QApplication::focusWidget();
LOG_DEBUG() << "Current focusObject:" << QApplication::focusObject();
LOG_DEBUG() << "Current focusWindow:" << QApplication::focusWindow();
}
void MainWindow::onFocusObjectChanged(QObject *) const
{
LOG_DEBUG() << "Focusobject changed";
LOG_DEBUG() << "Current focusWidget:" << QApplication::focusWidget();
LOG_DEBUG() << "Current focusObject:" << QApplication::focusObject();
LOG_DEBUG() << "Current focusWindow:" << QApplication::focusWindow();
}
void MainWindow::onTimelineClipSelected()
{
// Synchronize navigation position with timeline selection.
TimelineDock *t = m_timelineDock;
if (t->selection().isEmpty())
return;
m_navigationPosition = t->centerOfClip(t->selection().first().y(), t->selection().first().x());
// Switch to Project player.
if (m_player->tabIndex() != Player::ProjectTabIndex) {
t->saveAndClearSelection();
m_player->onTabBarClicked(Player::ProjectTabIndex);
}
}
void MainWindow::onAddAllToTimeline(Mlt::Playlist *playlist, bool skipProxy)
{
// We stop the player because of a bug on Windows that results in some
// strange memory leak when using Add All To Timeline, more noticeable
// with (high res?) still image files.
if (MLT.isSeekable())
m_player->pause();
else
m_player->stop();
m_timelineDock->appendFromPlaylist(playlist, skipProxy);
}
MainWindow &MainWindow::singleton()
{
static MainWindow *instance = new MainWindow;
return *instance;
}
MainWindow::~MainWindow()
{
delete ui;
Mlt::Controller::destroy();
}
void MainWindow::setupSettingsMenu()
{
LOG_DEBUG() << "begin";
QActionGroup *group = new QActionGroup(this);
group->addAction(ui->actionChannels1);
group->addAction(ui->actionChannels2);
group->addAction(ui->actionChannels6);
group = new QActionGroup(this);
group->addAction(ui->actionOneField);
group->addAction(ui->actionLinearBlend);
m_previewScaleGroup = new QActionGroup(this);
m_previewScaleGroup->addAction(ui->actionPreviewNone);
m_previewScaleGroup->addAction(ui->actionPreview360);
m_previewScaleGroup->addAction(ui->actionPreview540);
m_previewScaleGroup->addAction(ui->actionPreview720);
//XXX workaround yadif crashing with mlt_transition
// group->addAction(ui->actionYadifTemporal);
// group->addAction(ui->actionYadifSpatial);
ui->actionYadifTemporal->setVisible(false);
ui->actionYadifSpatial->setVisible(false);
group = new QActionGroup(this);
group->addAction(ui->actionNearest);
group->addAction(ui->actionBilinear);
group->addAction(ui->actionBicubic);
group->addAction(ui->actionHyper);
if (Settings.playerGPU()) {
group = new QActionGroup(this);
group->addAction(ui->actionGammaRec709);
group->addAction(ui->actionGammaSRGB);
} else {
delete ui->menuGamma;
}
m_profileGroup = new QActionGroup(this);
m_profileGroup->addAction(ui->actionProfileAutomatic);
ui->actionProfileAutomatic->setData(QString());
buildVideoModeMenu(ui->menuProfile, m_customProfileMenu, m_profileGroup, ui->actionAddCustomProfile,
ui->actionProfileRemove);
// Add the SDI and HDMI devices to the Settings menu.
m_externalGroup = new QActionGroup(this);
ui->actionExternalNone->setData(QString());
m_externalGroup->addAction(ui->actionExternalNone);
QList screens = QGuiApplication::screens();
int n = screens.size();
for (int i = 0; n > 1 && i < n; i++) {
QAction *action = new QAction(tr("Screen %1 (%2 x %3 @ %4 Hz)").arg(i)
.arg(screens[i]->size().width() * screens[i]->devicePixelRatio())
.arg(screens[i]->size().height() * screens[i]->devicePixelRatio())
.arg(screens[i]->refreshRate()), this);
action->setCheckable(true);
action->setData(i);
m_externalGroup->addAction(action);
}
Mlt::Profile profile;
Mlt::Consumer decklink(profile, "decklink:");
if (decklink.is_valid()) {
decklink.set("list_devices", 1);
int n = decklink.get_int("devices");
for (int i = 0; i < n; ++i) {
QString device(decklink.get(QString("device.%1").arg(i).toLatin1().constData()));
if (!device.isEmpty()) {
QAction *action = new QAction(device, this);
action->setCheckable(true);
action->setData(QString("decklink:%1").arg(i));
m_externalGroup->addAction(action);
if (!m_keyerGroup) {
m_keyerGroup = new QActionGroup(this);
action = new QAction(tr("Off"), m_keyerGroup);
action->setData(QVariant(0));
action->setCheckable(true);
action = new QAction(tr("Internal"), m_keyerGroup);
action->setData(QVariant(1));
action->setCheckable(true);
action = new QAction(tr("External"), m_keyerGroup);
action->setData(QVariant(2));
action->setCheckable(true);
}
}
}
}
if (m_externalGroup->actions().count() > 1)
ui->menuExternal->addActions(m_externalGroup->actions());
else {
delete ui->menuExternal;
ui->menuExternal = 0;
}
if (m_keyerGroup) {
m_keyerMenu = ui->menuExternal->addMenu(tr("DeckLink Keyer"));
m_keyerMenu->addActions(m_keyerGroup->actions());
m_keyerMenu->setDisabled(true);
connect(m_keyerGroup, SIGNAL(triggered(QAction *)), this, SLOT(onKeyerTriggered(QAction *)));
}
connect(m_externalGroup, SIGNAL(triggered(QAction *)), this, SLOT(onExternalTriggered(QAction *)));
connect(m_profileGroup, SIGNAL(triggered(QAction *)), this, SLOT(onProfileTriggered(QAction *)));
// Setup the language menu actions
m_languagesGroup = new QActionGroup(this);
QAction *a;
a = new QAction(QLocale::languageToString(QLocale::Arabic), m_languagesGroup);
a->setCheckable(true);
a->setData("ar");
a = new QAction(QLocale::languageToString(QLocale::Catalan), m_languagesGroup);
a->setCheckable(true);
a->setData("ca");
a = new QAction(QLocale::languageToString(QLocale::Chinese).append(" (China)"), m_languagesGroup);
a->setCheckable(true);
a->setData("zh_CN");
a = new QAction(QLocale::languageToString(QLocale::Chinese).append(" (Taiwan)"), m_languagesGroup);
a->setCheckable(true);
a->setData("zh_TW");
a = new QAction(QLocale::languageToString(QLocale::Czech), m_languagesGroup);
a->setCheckable(true);
a->setData("cs");
a = new QAction(QLocale::languageToString(QLocale::Danish), m_languagesGroup);
a->setCheckable(true);
a->setData("da");
a = new QAction(QLocale::languageToString(QLocale::Dutch), m_languagesGroup);
a->setCheckable(true);
a->setData("nl");
a = new QAction(QLocale::languageToString(QLocale::English).append(" (Great Britain)"),
m_languagesGroup);
a->setCheckable(true);
a->setData("en_GB");
a = new QAction(QLocale::languageToString(QLocale::English).append(" (United States)"),
m_languagesGroup);
a->setCheckable(true);
a->setData("en_US");
a = new QAction(QLocale::languageToString(QLocale::Estonian), m_languagesGroup);
a->setCheckable(true);
a->setData("et");
a = new QAction(QLocale::languageToString(QLocale::Finnish), m_languagesGroup);
a->setCheckable(true);
a->setData("fi");
a = new QAction(QLocale::languageToString(QLocale::French), m_languagesGroup);
a->setCheckable(true);
a->setData("fr");
a = new QAction(QLocale::languageToString(QLocale::Gaelic), m_languagesGroup);
a->setCheckable(true);
a->setData("gd");
a = new QAction(QLocale::languageToString(QLocale::Galician), m_languagesGroup);
a->setCheckable(true);
a->setData("gl");
a = new QAction(QLocale::languageToString(QLocale::German), m_languagesGroup);
a->setCheckable(true);
a->setData("de");
a = new QAction(QLocale::languageToString(QLocale::Greek), m_languagesGroup);
a->setCheckable(true);
a->setData("el");
a = new QAction(QLocale::languageToString(QLocale::Hungarian), m_languagesGroup);
a->setCheckable(true);
a->setData("hu");
a = new QAction(QLocale::languageToString(QLocale::Italian), m_languagesGroup);
a->setCheckable(true);
a->setData("it");
a = new QAction(QLocale::languageToString(QLocale::Japanese), m_languagesGroup);
a->setCheckable(true);
a->setData("ja");
a = new QAction(QLocale::languageToString(QLocale::Korean), m_languagesGroup);
a->setCheckable(true);
a->setData("ko");
a = new QAction(QLocale::languageToString(QLocale::Nepali), m_languagesGroup);
a->setCheckable(true);
a->setData("ne");
a = new QAction(QLocale::languageToString(QLocale::NorwegianBokmal), m_languagesGroup);
a->setCheckable(true);
a->setData("nb");
a = new QAction(QLocale::languageToString(QLocale::NorwegianNynorsk), m_languagesGroup);
a->setCheckable(true);
a->setData("nn");
a = new QAction(QLocale::languageToString(QLocale::Occitan), m_languagesGroup);
a->setCheckable(true);
a->setData("oc");
a = new QAction(QLocale::languageToString(QLocale::Polish), m_languagesGroup);
a->setCheckable(true);
a->setData("pl");
a = new QAction(QLocale::languageToString(QLocale::Portuguese).append(" (Brazil)"),
m_languagesGroup);
a->setCheckable(true);
a->setData("pt_BR");
a = new QAction(QLocale::languageToString(QLocale::Portuguese).append(" (Portugal)"),
m_languagesGroup);
a->setCheckable(true);
a->setData("pt_PT");
a = new QAction(QLocale::languageToString(QLocale::Romanian), m_languagesGroup);
a->setCheckable(true);
a->setData("ro");
a = new QAction(QLocale::languageToString(QLocale::Russian), m_languagesGroup);
a->setCheckable(true);
a->setData("ru");
a = new QAction(QLocale::languageToString(QLocale::Slovak), m_languagesGroup);
a->setCheckable(true);
a->setData("sk");
a = new QAction(QLocale::languageToString(QLocale::Slovenian), m_languagesGroup);
a->setCheckable(true);
a->setData("sl");
a = new QAction(QLocale::languageToString(QLocale::Spanish), m_languagesGroup);
a->setCheckable(true);
a->setData("es");
a = new QAction(QLocale::languageToString(QLocale::Swedish), m_languagesGroup);
a->setCheckable(true);
a->setData("sv");
a = new QAction(QLocale::languageToString(QLocale::Thai), m_languagesGroup);
a->setCheckable(true);
a->setData("th");
a = new QAction(QLocale::languageToString(QLocale::Turkish), m_languagesGroup);
a->setCheckable(true);
a->setData("tr");
a = new QAction(QLocale::languageToString(QLocale::Ukrainian), m_languagesGroup);
a->setCheckable(true);
a->setData("uk");
ui->menuLanguage->addActions(m_languagesGroup->actions());
const QString locale = Settings.language();
foreach (QAction *action, m_languagesGroup->actions()) {
if (action->data().toString().startsWith(locale)) {
action->setChecked(true);
break;
}
}
connect(m_languagesGroup, SIGNAL(triggered(QAction *)), this, SLOT(onLanguageTriggered(QAction *)));
// Setup the themes actions
group = new QActionGroup(this);
group->addAction(ui->actionSystemTheme);
group->addAction(ui->actionFusionDark);
group->addAction(ui->actionFusionLight);
if (Settings.theme() == "dark")
ui->actionFusionDark->setChecked(true);
else if (Settings.theme() == "light")
ui->actionFusionLight->setChecked(true);
else
ui->actionSystemTheme->setChecked(true);
#if defined(Q_OS_WIN) || (defined(Q_OS_MAC) && defined(Q_PROCESSOR_ARM))
// On Windows, if there is no JACK or it is not running
// then Shotcut crashes inside MLT's call to jack_client_open().
// Therefore, the JACK option for Shotcut is banned on Windows.
delete ui->actionJack;
ui->actionJack = 0;
#endif
#if !defined(Q_OS_MAC)
// Setup the display method actions.
if (!Settings.playerGPU()) {
group = new QActionGroup(this);
#if defined(Q_OS_WIN)
ui->actionDrawingAutomatic->setData(0);
group->addAction(ui->actionDrawingAutomatic);
ui->actionDrawingDirectX->setData(Qt::AA_UseOpenGLES);
group->addAction(ui->actionDrawingDirectX);
#else
delete ui->actionDrawingAutomatic;
delete ui->actionDrawingDirectX;
#endif
ui->actionDrawingOpenGL->setData(Qt::AA_UseDesktopOpenGL);
group->addAction(ui->actionDrawingOpenGL);
ui->actionDrawingSoftware->setData(Qt::AA_UseSoftwareOpenGL);
group->addAction(ui->actionDrawingSoftware);
connect(group, SIGNAL(triggered(QAction *)), this, SLOT(onDrawingMethodTriggered(QAction *)));
switch (Settings.drawMethod()) {
case Qt::AA_UseDesktopOpenGL:
ui->actionDrawingOpenGL->setChecked(true);
break;
#if defined(Q_OS_WIN)
case Qt::AA_UseOpenGLES:
ui->actionDrawingDirectX->setChecked(true);
break;
#endif
case Qt::AA_UseSoftwareOpenGL:
ui->actionDrawingSoftware->setChecked(true);
break;
#if defined(Q_OS_WIN)
default:
ui->actionDrawingAutomatic->setChecked(true);
break;
#else
default:
ui->actionDrawingOpenGL->setChecked(true);
break;
#endif
}
} else {
// GPU mode only works with OpenGL.
delete ui->menuDrawingMethod;
ui->menuDrawingMethod = 0;
}
#else // Q_OS_MAC
delete ui->menuDrawingMethod;
ui->menuDrawingMethod = 0;
#endif
// Setup the job priority actions
group = new QActionGroup(this);
group->addAction(ui->actionJobPriorityLow);
group->addAction(ui->actionJobPriorityNormal);
if (Settings.jobPriority() == QThread::LowPriority)
ui->actionJobPriorityLow->setChecked(true);
else
ui->actionJobPriorityNormal->setChecked(true);
// Add custom layouts to View > Layout submenu.
m_layoutGroup = new QActionGroup(this);
connect(m_layoutGroup, SIGNAL(triggered(QAction *)), SLOT(onLayoutTriggered(QAction *)));
if (Settings.layouts().size() > 0) {
ui->menuLayout->addAction(ui->actionLayoutRemove);
ui->menuLayout->addSeparator();
}
foreach (QString name, Settings.layouts())
ui->menuLayout->addAction(addLayout(m_layoutGroup, name));
if (qApp->property("clearRecent").toBool()) {
ui->actionClearRecentOnExit->setVisible(false);
Settings.setRecent(QStringList());
Settings.setClearRecent(true);
} else {
ui->actionClearRecentOnExit->setChecked(Settings.clearRecent());
}
// Initialize the proxy submenu
ui->actionUseProxy->setChecked(Settings.proxyEnabled());
ui->actionProxyUseProjectFolder->setChecked(Settings.proxyUseProjectFolder());
ui->actionProxyUseHardware->setChecked(Settings.proxyUseHardware());
LOG_DEBUG() << "end";
}
void MainWindow::setupOpenOtherMenu()
{
// Open Other toolbar menu button
QScopedPointer mltProducers(MLT.repository()->producers());
QScopedPointer mltFilters(MLT.repository()->filters());
QMenu *otherMenu = new QMenu(this);
ui->actionOpenOther2->setMenu(otherMenu);
// populate the generators
if (mltProducers->get_data("color")) {
otherMenu->addAction(tr("Color"), this, SLOT(onOpenOtherTriggered()))->setObjectName("color");
if (!Settings.playerGPU() && mltProducers->get_data("qtext") && mltFilters->get_data("dynamictext"))
otherMenu->addAction(tr("Text"), this, SLOT(onOpenOtherTriggered()))->setObjectName("text");
}
if (mltProducers->get_data("glaxnimate"))
otherMenu->addAction(tr("Animation"), this,
SLOT(onOpenOtherTriggered()))->setObjectName("glaxnimate");
if (mltProducers->get_data("noise"))
otherMenu->addAction(tr("Noise"), this, SLOT(onOpenOtherTriggered()))->setObjectName("noise");
if (mltProducers->get_data("frei0r.ising0r"))
otherMenu->addAction(tr("Ising"), this, SLOT(onOpenOtherTriggered()))->setObjectName("ising0r");
if (mltProducers->get_data("frei0r.lissajous0r"))
otherMenu->addAction(tr("Lissajous"), this,
SLOT(onOpenOtherTriggered()))->setObjectName("lissajous0r");
if (mltProducers->get_data("frei0r.plasma"))
otherMenu->addAction(tr("Plasma"), this, SLOT(onOpenOtherTriggered()))->setObjectName("plasma");
if (mltProducers->get_data("frei0r.test_pat_B"))
otherMenu->addAction(tr("Color Bars"), this,
SLOT(onOpenOtherTriggered()))->setObjectName("test_pat_B");
if (mltProducers->get_data("tone"))
otherMenu->addAction(tr("Audio Tone"), this, SLOT(onOpenOtherTriggered()))->setObjectName("tone");
if (mltProducers->get_data("count"))
otherMenu->addAction(tr("Count"), this, SLOT(onOpenOtherTriggered()))->setObjectName("count");
if (mltProducers->get_data("blipflash"))
otherMenu->addAction(tr("Blip Flash"), this,
SLOT(onOpenOtherTriggered()))->setObjectName("blipflash");
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
otherMenu->addAction(tr("Video4Linux"), this, SLOT(onOpenOtherTriggered()))->setObjectName("v4l2");
otherMenu->addAction(tr("PulseAudio"), this, SLOT(onOpenOtherTriggered()))->setObjectName("pulse");
otherMenu->addAction(tr("JACK Audio"), this, SLOT(onOpenOtherTriggered()))->setObjectName("jack");
otherMenu->addAction(tr("ALSA Audio"), this, SLOT(onOpenOtherTriggered()))->setObjectName("alsa");
#elif defined(Q_OS_WIN) || defined(Q_OS_MAC)
otherMenu->addAction(tr("Audio/Video Device"), this,
SLOT(onOpenOtherTriggered()))->setObjectName("device");
#endif
if (mltProducers->get_data("decklink"))
otherMenu->addAction(tr("SDI/HDMI"), this, SLOT(onOpenOtherTriggered()))->setObjectName("decklink");
}
QAction *MainWindow::addProfile(QActionGroup *actionGroup, const QString &desc, const QString &name)
{
QAction *action = new QAction(desc, this);
action->setCheckable(true);
action->setData(name);
actionGroup->addAction(action);
return action;
}
QAction *MainWindow::addLayout(QActionGroup *actionGroup, const QString &name)
{
QAction *action = new QAction(name, this);
actionGroup->addAction(action);
return action;
}
void MainWindow::open(Mlt::Producer *producer)
{
if (!producer->is_valid())
showStatusMessage(tr("Failed to open "));
else if (producer->get_int("error"))
showStatusMessage(tr("Failed to open ") + producer->get("resource"));
bool ok = false;
int screen = Settings.playerExternal().toInt(&ok);
if (ok && screen != QApplication::desktop()->screenNumber(this))
m_player->moveVideoToScreen(screen);
// no else here because open() will delete the producer if open fails
if (!MLT.setProducer(producer)) {
emit producerOpened();
if (!MLT.profile().is_explicit() || MLT.URL().endsWith(".mlt") || MLT.URL().endsWith(".xml"))
emit profileChanged();
}
m_player->setFocus();
m_playlistDock->setUpdateButtonEnabled(false);
// Needed on Windows. Upon first file open, window is deactivated, perhaps OpenGL-related.
activateWindow();
}
bool MainWindow::isCompatibleWithGpuMode(MltXmlChecker &checker)
{
if (checker.needsGPU() && !Settings.playerGPU()) {
LOG_INFO() << "file uses GPU but GPU not enabled";
QMessageBox dialog(QMessageBox::Warning,
qApp->applicationName(),
tr("The file you opened uses GPU effects, but GPU effects are not enabled.\n\n"
"GPU effects are EXPERIMENTAL, UNSTABLE and UNSUPPORTED! Unsupported means do not report bugs about it."),
QMessageBox::Ok,
this);
dialog.setWindowModality(QmlApplication::dialogModality());
dialog.setDefaultButton(QMessageBox::Ok);
dialog.setEscapeButton(QMessageBox::Ok);
dialog.exec();
return false;
} else if (checker.needsCPU() && Settings.playerGPU()) {
LOG_INFO() << "file uses GPU incompatible filters but GPU is enabled";
QMessageBox dialog(QMessageBox::Question,
qApp->applicationName(),
tr("The file you opened uses CPU effects that are incompatible with GPU effects, but GPU effects are enabled.\n"
"Do you want to disable GPU effects and restart?"),
QMessageBox::No |
QMessageBox::Yes,
this);
dialog.setWindowModality(QmlApplication::dialogModality());
dialog.setDefaultButton(QMessageBox::Yes);
dialog.setEscapeButton(QMessageBox::No);
int r = dialog.exec();
if (r == QMessageBox::Yes) {
ui->actionGPU->setChecked(false);
m_exitCode = EXIT_RESTART;
QApplication::closeAllWindows();
}
return false;
}
return true;
}
bool MainWindow::saveRepairedXmlFile(MltXmlChecker &checker, QString &fileName)
{
QFileInfo fi(fileName);
auto filename = QString("%1/%2 - %3.%4").arg(fi.path())
.arg(fi.completeBaseName()).arg(tr("Repaired")).arg(fi.suffix());
auto caption = tr("Save Repaired XML");
filename = QFileDialog::getSaveFileName(this, caption, filename,
tr("MLT XML (*.mlt)"), nullptr, Util::getFileDialogOptions());
if (!filename.isEmpty()) {
QFile repaired(filename);
repaired.open(QIODevice::WriteOnly);
LOG_INFO() << "repaired MLT XML file name" << repaired.fileName();
if (checker.tempFile().exists()) {
checker.tempFile().open();
QByteArray xml = checker.tempFile().readAll();
checker.tempFile().close();
qint64 n = repaired.write(xml);
while (n > 0 && n < xml.size()) {
qint64 x = repaired.write(xml.right(xml.size() - n));
if (x > 0)
n += x;
else
n = x;
}
repaired.close();
if (n == xml.size()) {
fileName = repaired.fileName();
return true;
}
}
QMessageBox::warning(this, qApp->applicationName(), tr("Repairing the project failed."));
LOG_WARNING() << "repairing failed";
}
return false;
}
bool MainWindow::isXmlRepaired(MltXmlChecker &checker, QString &fileName)
{
bool result = true;
if (checker.isCorrected()) {
LOG_WARNING() << fileName;
QMessageBox dialog(QMessageBox::Question,
qApp->applicationName(),
tr("Shotcut noticed some problems in your project.\n"
"Do you want Shotcut to try to repair it?\n\n"
"If you choose Yes, Shotcut will create a copy of your project\n"
"with \"- Repaired\" in the file name and open it."),
QMessageBox::No |
QMessageBox::Yes,
this);
dialog.setWindowModality(QmlApplication::dialogModality());
dialog.setDefaultButton(QMessageBox::Yes);
dialog.setEscapeButton(QMessageBox::No);
int r = dialog.exec();
if (r == QMessageBox::Yes)
result = saveRepairedXmlFile(checker, fileName);
} else if (checker.unlinkedFilesModel().rowCount() > 0) {
UnlinkedFilesDialog dialog(this);
dialog.setModel(checker.unlinkedFilesModel());
dialog.setWindowModality(QmlApplication::dialogModality());
if (dialog.exec() == QDialog::Accepted) {
if (checker.check(fileName) == QXmlStreamReader::NoError && checker.isCorrected())
result = saveRepairedXmlFile(checker, fileName);
} else {
result = false;
}
}
return result;
}
bool MainWindow::checkAutoSave(QString &url)
{
QMutexLocker locker(&m_autosaveMutex);
// check whether autosave files exist:
QSharedPointer stale(AutoSaveFile::getFile(url));
if (stale) {
QMessageBox dialog(QMessageBox::Question, qApp->applicationName(),
tr("Auto-saved files exist. Do you want to recover them now?"),
QMessageBox::No | QMessageBox::Yes, this);
dialog.setWindowModality(QmlApplication::dialogModality());
dialog.setDefaultButton(QMessageBox::Yes);
dialog.setEscapeButton(QMessageBox::No);
int r = dialog.exec();
if (r == QMessageBox::Yes) {
if (!stale->open(QIODevice::ReadWrite)) {
LOG_WARNING() << "failed to recover autosave file" << url;
} else {
m_autosaveFile = stale;
url = stale->fileName();
return true;
}
}
}
// create new autosave object
m_autosaveFile.reset(new AutoSaveFile(url));
return false;
}
void MainWindow::stepLeftBySeconds(int sec)
{
m_player->seek(m_player->position() + sec * qRound(MLT.profile().fps()));
}
void MainWindow::doAutosave()
{
QMutexLocker locker(&m_autosaveMutex);
if (m_autosaveFile) {
bool success = false;
if (m_autosaveFile->isOpen() || m_autosaveFile->open(QIODevice::ReadWrite)) {
m_autosaveFile->close();
success = saveXML(m_autosaveFile->fileName(), false /* without relative paths */);
m_autosaveFile->open(QIODevice::ReadWrite);
}
if (!success) {
LOG_ERROR() << "failed to open autosave file for writing" << m_autosaveFile->fileName();
}
}
}
void MainWindow::setFullScreen(bool isFullScreen)
{
if (isFullScreen) {
#ifdef Q_OS_WIN
showMaximized();
#else
showFullScreen();
#endif
ui->actionEnter_Full_Screen->setVisible(false);
}
}
QString MainWindow::untitledFileName() const
{
QDir dir = Settings.appDataLocation();
if (!dir.exists()) dir.mkpath(dir.path());
return dir.filePath("__untitled__.mlt");
}
void MainWindow::setProfile(const QString &profile_name)
{
LOG_DEBUG() << profile_name;
MLT.setProfile(profile_name);
emit profileChanged();
}
bool MainWindow::isSourceClipMyProject(QString resource, bool withDialog)
{
if (m_player->tabIndex() == Player::ProjectTabIndex && MLT.savedProducer()
&& MLT.savedProducer()->is_valid())
resource = QString::fromUtf8(MLT.savedProducer()->get("resource"));
if (!resource.isEmpty() && QDir(resource) == QDir(fileName())) {
if (withDialog) {
QMessageBox dialog(QMessageBox::Information,
qApp->applicationName(),
tr("You cannot add a project to itself!"),
QMessageBox::Ok,
this);
dialog.setDefaultButton(QMessageBox::Ok);
dialog.setEscapeButton(QMessageBox::Ok);
dialog.setWindowModality(QmlApplication::dialogModality());
dialog.exec();
}
return true;
}
return false;
}
bool MainWindow::keyframesDockIsVisible() const
{
return m_keyframesDock && m_keyframesDock->isVisible();
}
void MainWindow::setAudioChannels(int channels)
{
LOG_DEBUG() << channels;
MLT.videoWidget()->setProperty("audio_channels", channels);
MLT.setAudioChannels(channels);
if (channels == 1)
ui->actionChannels1->setChecked(true);
else if (channels == 2)
ui->actionChannels2->setChecked(true);
else if (channels == 6)
ui->actionChannels6->setChecked(true);
emit audioChannelsChanged();
}
void MainWindow::showSaveError()
{
QMessageBox dialog(QMessageBox::Critical,
qApp->applicationName(),
tr("There was an error saving. Please try again."),
QMessageBox::Ok,
this);
dialog.setDefaultButton(QMessageBox::Ok);
dialog.setEscapeButton(QMessageBox::Ok);
dialog.setWindowModality(QmlApplication::dialogModality());
dialog.exec();
}
void MainWindow::setPreviewScale(int scale)
{
LOG_DEBUG() << scale;
switch (scale) {
case 360:
ui->actionPreview360->setChecked(true);
break;
case 540:
ui->actionPreview540->setChecked(true);
break;
case 720:
ui->actionPreview720->setChecked(true);
break;
default:
ui->actionPreviewNone->setChecked(true);
break;
}
MLT.setPreviewScale(scale);
MLT.refreshConsumer();
}
void MainWindow::setVideoModeMenu()
{
// Find a matching video mode
for (const auto action : m_profileGroup->actions()) {
auto s = action->data().toString();
Mlt::Profile profile(s.toUtf8().constData());
if (MLT.profile().width() == profile.width() &&
MLT.profile().height() == profile.height() &&
MLT.profile().sample_aspect_num() == profile.sample_aspect_num() &&
MLT.profile().sample_aspect_den() == profile.sample_aspect_den() &&
MLT.profile().frame_rate_num() == profile.frame_rate_num() &&
MLT.profile().frame_rate_den() == profile.frame_rate_den() &&
MLT.profile().colorspace() == profile.colorspace() &&
MLT.profile().progressive() == profile.progressive()) {
// Select it
action->setChecked(true);
return;
}
}
// Choose Automatic if nothing found
m_profileGroup->actions().first()->setChecked(true);
}
void MainWindow::resetVideoModeMenu()
{
// Change selected Video Mode back to Settings
for (const auto action : m_profileGroup->actions()) {
if (action->data().toString() == Settings.playerProfile()) {
action->setChecked(true);
break;
}
}
}
void MainWindow::resetDockCorners()
{
setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea);
setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea);
}
void MainWindow::showIncompatibleProjectMessage(const QString &shotcutVersion)
{
LOG_INFO() << shotcutVersion;
QMessageBox dialog(QMessageBox::Information,
qApp->applicationName(),
tr("This project file requires a newer version!\n\n"
"It was made with version ") + shotcutVersion,
QMessageBox::Ok,
this);
dialog.setDefaultButton(QMessageBox::Ok);
dialog.setEscapeButton(QMessageBox::Ok);
dialog.setWindowModality(QmlApplication::dialogModality());
dialog.exec();
}
static void autosaveTask(MainWindow *p)
{
LOG_DEBUG_TIME();
p->doAutosave();
}
void MainWindow::onAutosaveTimeout()
{
if (isWindowModified()) {
QtConcurrent::run(autosaveTask, this);
}
if (Util::isMemoryLow()) {
MLT.pause();
QMessageBox dialog(QMessageBox::Critical,
qApp->applicationName(),
tr("You are running low on available memory!\n\n"
"Please close other applications or web browser tabs and retry.\n"
"Or save and restart Shotcut."),
QMessageBox::Retry | QMessageBox::Save | QMessageBox::Ignore,
this);
dialog.setDefaultButton(QMessageBox::Retry);
dialog.setEscapeButton(QMessageBox::Ignore);
dialog.setWindowModality(QmlApplication::dialogModality());
switch (dialog.exec()) {
case QMessageBox::Save:
on_actionSave_triggered();
m_exitCode = EXIT_RESTART;
QApplication::closeAllWindows();
break;
case QMessageBox::Retry:
onAutosaveTimeout();
break;
default:
break;
}
}
}
void MainWindow::open(QString url, const Mlt::Properties *properties, bool play)
{
LOG_DEBUG() << url;
bool modified = false;
MltXmlChecker checker;
QFileInfo info(url);
if (info.isRelative()) {
QDir pwd(QDir::currentPath());
url = pwd.filePath(url);
}
if (url.endsWith(".mlt") || url.endsWith(".xml")) {
if (url != untitledFileName()) {
showStatusMessage(tr("Opening %1").arg(url));
QCoreApplication::processEvents();
}
switch (checker.check(url)) {
case QXmlStreamReader::NoError:
if (!isCompatibleWithGpuMode(checker)) {
showStatusMessage(tr("Failed to open ").append(url));
return;
}
break;
case QXmlStreamReader::CustomError:
showIncompatibleProjectMessage(checker.shotcutVersion());
return;
default:
showStatusMessage(tr("Failed to open ").append(url));
return;
}
// only check for a modified project when loading a project, not a simple producer
if (!continueModified())
return;
QCoreApplication::processEvents();
// close existing project
if (playlist())
m_playlistDock->model()->close();
if (multitrack())
m_timelineDock->model()->close();
MLT.purgeMemoryPool();
if (!isXmlRepaired(checker, url))
return;
modified = checkAutoSave(url);
if (modified) {
if (checker.check(url) == QXmlStreamReader::NoError) {
if (!isCompatibleWithGpuMode(checker))
return;
} else {
showStatusMessage(tr("Failed to open ").append(url));
showIncompatibleProjectMessage(checker.shotcutVersion());
return;
}
if (!isXmlRepaired(checker, url))
return;
}
// let the new project change the profile
if (modified || QFile::exists(url)) {
MLT.profile().set_explicit(false);
setWindowModified(modified);
resetSourceUpdated();
}
}
if (!playlist() && !multitrack()) {
if (!modified && !continueModified())
return;
setCurrentFile("");
setWindowModified(modified);
sourceUpdated();
MLT.resetURL();
// Return to automatic video mode if selected.
if (m_profileGroup->checkedAction() && m_profileGroup->checkedAction()->data().toString().isEmpty())
MLT.profile().set_explicit(false);
}
if (url.endsWith(".mlt") || url.endsWith(".xml")) {
checker.setLocale();
LOG_INFO() << "decimal point" << MLT.decimalPoint();
}
QString urlToOpen = checker.isUpdated() ? checker.tempFile().fileName() : url;
if (!MLT.open(QDir::fromNativeSeparators(urlToOpen), QDir::fromNativeSeparators(url))
&& MLT.producer() && MLT.producer()->is_valid()) {
Mlt::Properties *props = const_cast(properties);
if (props && props->is_valid())
mlt_properties_inherit(MLT.producer()->get_properties(), props->get_properties());
m_player->setPauseAfterOpen(!play || !MLT.isClip());
setAudioChannels(MLT.audioChannels());
if (url.endsWith(".mlt") || url.endsWith(".xml")) {
setVideoModeMenu();
m_notesDock->setText(MLT.producer()->get(kShotcutProjectNote));
}
open(MLT.producer());
if (url.startsWith(AutoSaveFile::path())) {
QMutexLocker locker(&m_autosaveMutex);
if (m_autosaveFile && m_autosaveFile->managedFileName() != untitledFileName()) {
m_recentDock->add(m_autosaveFile->managedFileName());
LOG_INFO() << m_autosaveFile->managedFileName();
}
} else {
m_recentDock->add(url);
LOG_INFO() << url;
}
} else if (url != untitledFileName()) {
showStatusMessage(tr("Failed to open ") + url);
emit openFailed(url);
}
}
void MainWindow::openMultiple(const QStringList &paths)
{
if (paths.size() > 1) {
QList urls;
foreach (const QString &s, paths)
urls << s;
openMultiple(urls);
} else if (!paths.isEmpty()) {
open(paths.first());
}
}
void MainWindow::openMultiple(const QList &urls)
{
if (urls.size() > 1) {
m_multipleFiles = Util::sortedFileList(Util::expandDirectories(urls));
open(m_multipleFiles.first());
} else {
QUrl url = urls.first();
open(Util::removeFileScheme(url));
}
}
void MainWindow::openVideo()
{
QString path = Settings.openPath();
#ifdef Q_OS_MAC
path.append("/*");
#endif
LOG_DEBUG() << Util::getFileDialogOptions();
QStringList filenames = QFileDialog::getOpenFileNames(this, tr("Open File"), path,
tr("All Files (*);;MLT XML (*.mlt)"), nullptr, Util::getFileDialogOptions());
if (filenames.length() > 0) {
Settings.setOpenPath(QFileInfo(filenames.first()).path());
activateWindow();
if (filenames.length() > 1)
m_multipleFiles = filenames;
open(filenames.first());
} else {
// If file invalid, then on some platforms the dialog messes up SDL.
MLT.onWindowResize();
activateWindow();
}
}
void MainWindow::openCut(Mlt::Producer *producer, bool play)
{
m_player->setPauseAfterOpen(!play);
open(producer);
MLT.seek(producer->get_in());
}
void MainWindow::hideProducer()
{
// This is a hack to release references to the old producer, but it
// probably leaves a reference to the new color producer somewhere not
// yet identified (root cause).
openCut(new Mlt::Producer(MLT.profile(), "color:_hide"));
QCoreApplication::processEvents();
openCut(new Mlt::Producer(MLT.profile(), "color:_hide"));
QCoreApplication::processEvents();
QScrollArea *scrollArea = (QScrollArea *) m_propertiesDock->widget();
delete scrollArea->widget();
scrollArea->setWidget(nullptr);
m_player->reset();
QCoreApplication::processEvents();
}
void MainWindow::closeProducer()
{
hideProducer();
MLT.stop();
MLT.close();
MLT.setSavedProducer(0);
}
void MainWindow::showStatusMessage(QAction *action, int timeoutSeconds)
{
// This object takes ownership of the passed action.
// This version does not currently log its message.
m_statusBarAction.reset(action);
action->setParent(nullptr);
m_player->setStatusLabel(action->text(), timeoutSeconds, action);
}
void MainWindow::showStatusMessage(const QString &message, int timeoutSeconds,
QPalette::ColorRole role)
{
LOG_INFO() << message;
auto action = new QAction;
connect(action, SIGNAL(triggered()), this, SLOT(onStatusMessageClicked()));
m_statusBarAction.reset(action);
m_player->setStatusLabel(message, timeoutSeconds, action, role);
}
void MainWindow::onStatusMessageClicked()
{
showStatusMessage(QString(), 0);
}
void MainWindow::seekPlaylist(int start)
{
if (!playlist()) return;
// we bypass this->open() to prevent sending producerOpened signal to self, which causes to reload playlist
if (!MLT.producer()
|| (void *) MLT.producer()->get_producer() != (void *) playlist()->get_playlist())
MLT.setProducer(new Mlt::Producer(*playlist()));
m_player->setIn(-1);
m_player->setOut(-1);
// since we do not emit producerOpened, these components need updating
on_actionJack_triggered(ui->actionJack && ui->actionJack->isChecked());
m_player->onProducerOpened(false);
m_encodeDock->onProducerOpened();
m_filterController->setProducer();
updateMarkers();
MLT.seek(start);
m_player->setFocus();
m_player->switchToTab(Player::ProjectTabIndex);
}
void MainWindow::seekTimeline(int position, bool seekPlayer)
{
if (!multitrack()) return;
// we bypass this->open() to prevent sending producerOpened signal to self, which causes to reload playlist
if (MLT.producer()
&& (void *) MLT.producer()->get_producer() != (void *) multitrack()->get_producer()) {
MLT.setProducer(new Mlt::Producer(*multitrack()));
m_player->setIn(-1);
m_player->setOut(-1);
// since we do not emit producerOpened, these components need updating
on_actionJack_triggered(ui->actionJack && ui->actionJack->isChecked());
m_player->onProducerOpened(false);
m_encodeDock->onProducerOpened();
m_filterController->setProducer();
updateMarkers();
m_player->setFocus();
m_player->switchToTab(Player::ProjectTabIndex);
m_timelineDock->emitSelectedFromSelection();
}
if (seekPlayer)
m_player->seek(position);
else
m_player->pause();
}
void MainWindow::seekKeyframes(int position)
{
m_player->seek(position);
}
void MainWindow::readPlayerSettings()
{
LOG_DEBUG() << "begin";
ui->actionRealtime->setChecked(Settings.playerRealtime());
ui->actionProgressive->setChecked(Settings.playerProgressive());
ui->actionScrubAudio->setChecked(Settings.playerScrubAudio());
if (ui->actionJack)
ui->actionJack->setChecked(Settings.playerJACK());
if (ui->actionGPU) {
MLT.videoWidget()->setProperty("gpu", ui->actionGPU->isChecked());
ui->actionGPU->setChecked(Settings.playerGPU());
}
QString external = Settings.playerExternal();
bool ok = false;
external.toInt(&ok);
auto isExternalPeripheral = !external.isEmpty() && !ok;
setAudioChannels(Settings.playerAudioChannels());
if (isExternalPeripheral) {
setPreviewScale(0);
m_previewScaleGroup->setEnabled(false);
} else {
setPreviewScale(Settings.playerPreviewScale());
m_previewScaleGroup->setEnabled(true);
}
QString deinterlacer = Settings.playerDeinterlacer();
QString interpolation = Settings.playerInterpolation();
if (deinterlacer == "onefield")
ui->actionOneField->setChecked(true);
else if (deinterlacer == "linearblend")
ui->actionLinearBlend->setChecked(true);
else if (deinterlacer == "yadif-nospatial")
ui->actionYadifTemporal->setChecked(true);
else
ui->actionYadifSpatial->setChecked(true);
if (interpolation == "nearest")
ui->actionNearest->setChecked(true);
else if (interpolation == "bilinear")
ui->actionBilinear->setChecked(true);
else if (interpolation == "bicubic")
ui->actionBicubic->setChecked(true);
else
ui->actionHyper->setChecked(true);
foreach (QAction *a, m_externalGroup->actions()) {
if (a->data() == external) {
a->setChecked(true);
if (a->data().toString().startsWith("decklink") && m_keyerMenu)
m_keyerMenu->setEnabled(true);
break;
}
}
if (m_keyerGroup) {
int keyer = Settings.playerKeyerMode();
foreach (QAction *a, m_keyerGroup->actions()) {
if (a->data() == keyer) {
a->setChecked(true);
break;
}
}
}
QString profile = Settings.playerProfile();
// Automatic not permitted for SDI/HDMI
if (isExternalPeripheral && profile.isEmpty())
profile = "atsc_720p_50";
foreach (QAction *a, m_profileGroup->actions()) {
// Automatic not permitted for SDI/HDMI
if (a->data().toString().isEmpty() && !external.isEmpty() && !ok)
a->setDisabled(true);
if (a->data().toString() == profile) {
a->setChecked(true);
break;
}
}
QString gamma = Settings.playerGamma();
if (gamma == "bt709")
ui->actionGammaRec709->setChecked(true);
else
ui->actionGammaSRGB->setChecked(true);
LOG_DEBUG() << "end";
}
void MainWindow::readWindowSettings()
{
LOG_DEBUG() << "begin";
Settings.setWindowGeometryDefault(saveGeometry());
Settings.setWindowStateDefault(saveState());
Settings.sync();
if (!Settings.windowGeometry().isEmpty()) {
restoreGeometry(Settings.windowGeometry());
restoreState(Settings.windowState());
#ifdef Q_OS_MAC
m_filtersDock->setFloating(false);
#endif
} else {
restoreState(kLayoutEditingDefault);
}
#ifdef Q_OS_WIN
if (isMaximized()) {
ui->actionEnter_Full_Screen->setText(tr("Exit Full Screen"));
} else {
ui->actionEnter_Full_Screen->setText(tr("Enter Full Screen"));
}
#endif
LOG_DEBUG() << "end";
}
void MainWindow::writeSettings()
{
#ifndef Q_OS_MAC
if (isFullScreen())
showNormal();
#endif
Settings.setPlayerGPU(ui->actionGPU->isChecked());
Settings.setWindowGeometry(saveGeometry());
Settings.setWindowState(saveState());
Settings.sync();
}
void MainWindow::configureVideoWidget()
{
LOG_DEBUG() << "begin";
if (m_profileGroup->checkedAction())
setProfile(m_profileGroup->checkedAction()->data().toString());
MLT.videoWidget()->setProperty("realtime", ui->actionRealtime->isChecked());
bool ok = false;
m_externalGroup->checkedAction()->data().toInt(&ok);
if (!ui->menuExternal || m_externalGroup->checkedAction()->data().toString().isEmpty() || ok) {
MLT.videoWidget()->setProperty("progressive", ui->actionProgressive->isChecked());
} else {
MLT.videoWidget()->setProperty("mlt_service", m_externalGroup->checkedAction()->data());
MLT.videoWidget()->setProperty("progressive", MLT.profile().progressive());
ui->actionProgressive->setEnabled(false);
}
if (ui->actionChannels1->isChecked())
setAudioChannels(1);
else if (ui->actionChannels2->isChecked())
setAudioChannels(2);
else
setAudioChannels(6);
if (ui->actionOneField->isChecked())
MLT.videoWidget()->setProperty("deinterlace_method", "onefield");
else if (ui->actionLinearBlend->isChecked())
MLT.videoWidget()->setProperty("deinterlace_method", "linearblend");
else if (ui->actionYadifTemporal->isChecked())
MLT.videoWidget()->setProperty("deinterlace_method", "yadif-nospatial");
else
MLT.videoWidget()->setProperty("deinterlace_method", "yadif");
if (ui->actionNearest->isChecked())
MLT.videoWidget()->setProperty("rescale", "nearest");
else if (ui->actionBilinear->isChecked())
MLT.videoWidget()->setProperty("rescale", "bilinear");
else if (ui->actionBicubic->isChecked())
MLT.videoWidget()->setProperty("rescale", "bicubic");
else
MLT.videoWidget()->setProperty("rescale", "hyper");
if (m_keyerGroup)
MLT.videoWidget()->setProperty("keyer", m_keyerGroup->checkedAction()->data());
LOG_DEBUG() << "end";
}
void MainWindow::setCurrentFile(const QString &filename)
{
QString shownName = tr("Untitled");
if (filename == untitledFileName())
m_currentFile.clear();
else
m_currentFile = filename;
if (!m_currentFile.isEmpty())
shownName = QFileInfo(m_currentFile).fileName();
#ifdef Q_OS_MAC
setWindowTitle(QString("%1 - %2").arg(shownName).arg(qApp->applicationName()));
#else
setWindowTitle(QString("%1[*] - %2").arg(shownName).arg(qApp->applicationName()));
#endif
}
void MainWindow::on_actionAbout_Shotcut_triggered()
{
const auto copyright =
QStringLiteral("Copyright © 2011-2022 Meltytech, LLC");
const auto license =
QStringLiteral("GNU General Public License v3.0");
const auto url = QStringLiteral("https://www.shotcut.org/");
QMessageBox::about(this, tr("About %1").arg(qApp->applicationName()),
tr("Shotcut version %2
"
"%1 is a free, open source, cross platform video editor.
"
"%4
"
"Licensed under the %5
"
"This program proudly uses the following projects:
"
"The source code used to build this program can be downloaded from "
"%3.
"
"This program is distributed in the hope that it will be useful, "
"but WITHOUT ANY WARRANTY; without even the implied warranty of "
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
).arg(qApp->applicationName(), qApp->applicationVersion(), url, copyright, license));
}
void MainWindow::keyPressEvent(QKeyEvent *event)
{
if (event->isAccepted() && event->key() != Qt::Key_F12) return;
bool handled = true;
switch (event->key()) {
case Qt::Key_Home:
m_player->seek(0);
break;
case Qt::Key_End:
if (MLT.producer())
m_player->seek(MLT.producer()->get_length());
break;
case Qt::Key_Left:
if ((event->modifiers() & Qt::ControlModifier) && m_timelineDock->isVisible()) {
if (m_timelineDock->selection().isEmpty()) {
m_timelineDock->selectClipUnderPlayhead();
} else if (m_timelineDock->selection().size() == 1) {
int newIndex = m_timelineDock->selection().first().x() - 1;
if (newIndex < 0)
break;
m_timelineDock->setSelection(QList() << QPoint(newIndex,
m_timelineDock->selection().first().y()));
m_navigationPosition = m_timelineDock->centerOfClip(m_timelineDock->currentTrack(), newIndex);
}
} else {
stepLeftOneFrame();
}
break;
case Qt::Key_Right:
if ((event->modifiers() & Qt::ControlModifier) && m_timelineDock->isVisible()) {
if (m_timelineDock->selection().isEmpty()) {
m_timelineDock->selectClipUnderPlayhead();
} else if (m_timelineDock->selection().size() == 1) {
int newIndex = m_timelineDock->selection().first().x() + 1;
if (newIndex >= m_timelineDock->clipCount(-1))
break;
m_timelineDock->setSelection(QList() << QPoint(newIndex,
m_timelineDock->selection().first().y()));
m_navigationPosition = m_timelineDock->centerOfClip(m_timelineDock->currentTrack(), newIndex);
}
} else {
stepRightOneFrame();
}
break;
case Qt::Key_PageUp:
case Qt::Key_PageDown: {
int directionMultiplier = event->key() == Qt::Key_PageUp ? -1 : 1;
int seconds = 1;
if (event->modifiers() & Qt::ControlModifier)
seconds *= 5;
if (event->modifiers() & Qt::ShiftModifier)
seconds *= 2;
stepLeftBySeconds(seconds * directionMultiplier);
}
break;
case Qt::Key_Space:
#ifdef Q_OS_MAC
// Spotlight defaults to Cmd+Space, so also accept Ctrl+Space.
if ((event->modifiers() == Qt::MetaModifier || (event->modifiers() & Qt::ControlModifier))
&& m_timelineDock->isVisible())
#else
if (event->modifiers() == Qt::ControlModifier && m_timelineDock->isVisible())
#endif
m_timelineDock->selectClipUnderPlayhead();
else
handled = false;
break;
case Qt::Key_A:
if (event->modifiers() == Qt::ShiftModifier) {
m_playlistDock->show();
m_playlistDock->raise();
m_playlistDock->on_actionAppendCut_triggered();
} else if ((event->modifiers() & Qt::ControlModifier) && (event->modifiers() & Qt::ShiftModifier)) {
m_playlistDock->show();
m_playlistDock->raise();
m_playlistDock->on_actionSelectAll_triggered();
} else if (event->modifiers() == Qt::ControlModifier) {
m_timelineDock->show();
m_timelineDock->raise();
m_timelineDock->selectAll();
} else if (event->modifiers() == Qt::NoModifier) {
m_timelineDock->show();
m_timelineDock->raise();
m_timelineDock->append(-1);
}
break;
case Qt::Key_C:
if (event->modifiers() == Qt::ShiftModifier && m_playlistDock->model()->rowCount() > 0) {
m_playlistDock->show();
m_playlistDock->raise();
m_playlistDock->on_actionCopy_triggered();
} else if (isMultitrackValid()) {
m_timelineDock->show();
m_timelineDock->raise();
if (m_timelineDock->selection().isEmpty()) {
m_timelineDock->copy(-1, -1);
} else {
auto &selected = m_timelineDock->selection().first();
m_timelineDock->copy(selected.y(), selected.x());
}
}
break;
case Qt::Key_D:
if (event->modifiers() == Qt::ControlModifier) {
m_timelineDock->setSelection();
m_timelineDock->model()->reload();
} else if ((event->modifiers() & Qt::ControlModifier) && (event->modifiers() & Qt::ShiftModifier)) {
m_playlistDock->show();
m_playlistDock->raise();
m_playlistDock->on_actionSelectNone_triggered();
} else {
handled = false;
}
break;
case Qt::Key_F:
if (event->modifiers() == Qt::NoModifier || event->modifiers() == Qt::ControlModifier) {
m_filtersDock->show();
m_filtersDock->raise();
m_filtersDock->widget()->setFocus();
m_filtersDock->openFilterMenu();
} else if (event->modifiers() == Qt::ShiftModifier) {
filterController()->removeCurrent();
#ifdef Q_OS_MAC
} else if ((event->modifiers() & Qt::ControlModifier) && (event->modifiers() & Qt::MetaModifier)) {
on_actionEnter_Full_Screen_triggered();
#else
} else if ((event->modifiers() & Qt::ControlModifier) && (event->modifiers() & Qt::ShiftModifier)) {
on_actionEnter_Full_Screen_triggered();
#endif
} else {
handled = false;
}
break;
case Qt::Key_H:
#ifdef Q_OS_MAC
// OS X uses Cmd+H to hide an app.
if (event->modifiers() & Qt::MetaModifier && isMultitrackValid())
#else
if (event->modifiers() & Qt::ControlModifier && isMultitrackValid())
#endif
m_timelineDock->toggleTrackHidden(m_timelineDock->currentTrack());
break;
case Qt::Key_J:
if (m_isKKeyPressed)
m_player->seek(m_player->position() - 1);
else
m_player->rewind(false);
break;
case Qt::Key_K:
m_player->pause();
m_isKKeyPressed = true;
break;
case Qt::Key_L:
#ifdef Q_OS_MAC
// OS X uses Cmd+H to hide an app and Cmd+M to minimize. Therefore, we force
// it to be the apple keyboard control key aka meta. Therefore, to be
// consistent with all track header toggles, we make the lock toggle also use
// meta.
if (event->modifiers() & Qt::MetaModifier && isMultitrackValid())
#else
if (event->modifiers() & Qt::ControlModifier && isMultitrackValid())
#endif
m_timelineDock->setTrackLock(m_timelineDock->currentTrack(),
!m_timelineDock->isTrackLocked(m_timelineDock->currentTrack()));
else if (m_isKKeyPressed)
m_player->seek(m_player->position() + 1);
else
m_player->fastForward(false);
break;
case Qt::Key_M:
#ifdef Q_OS_MAC
// OS X uses Cmd+M to minimize an app.
if (event->modifiers() & Qt::MetaModifier && isMultitrackValid()) {
#else
if (event->modifiers() & Qt::ControlModifier && isMultitrackValid()) {
#endif
if (event->modifiers() & Qt::ShiftModifier) {
m_timelineDock->deleteMarker();
} else {
m_timelineDock->toggleTrackMute(m_timelineDock->currentTrack());
}
} else if (event->modifiers() == Qt::NoModifier && isMultitrackValid()) {
m_timelineDock->createOrEditMarker();
} else if (event->modifiers() == Qt::AltModifier && isMultitrackValid()) {
m_timelineDock->createOrEditSelectionMarker();
}
break;
case Qt::Key_I:
if (event->modifiers() == Qt::ControlModifier) {
m_timelineDock->show();
m_timelineDock->raise();
m_timelineDock->addVideoTrack();
} else if ((event->modifiers() & Qt::ControlModifier) && (event->modifiers() & Qt::AltModifier)) {
m_timelineDock->show();
m_timelineDock->raise();
m_timelineDock->insertTrack();
} else {
setInToCurrent(event->modifiers() & Qt::ShiftModifier);
}
break;
case Qt::Key_O:
setOutToCurrent(event->modifiers() & Qt::ShiftModifier);
break;
case Qt::Key_P:
if (event->modifiers() == Qt::ControlModifier) {
Settings.setTimelineSnap(!Settings.timelineSnap());
} else if (event->modifiers() & Qt::ControlModifier) {
if (event->modifiers() & Qt::AltModifier) {
Settings.setTimelineScrollZoom(!Settings.timelineScrollZoom());
}
if (event->modifiers() & Qt::ShiftModifier) {
Settings.setTimelineCenterPlayhead(!Settings.timelineCenterPlayhead());
}
}
break;
case Qt::Key_R:
if (event->modifiers() & Qt::AltModifier && event->modifiers() & Qt::ShiftModifier) {
Settings.setTimelineRippleAllTracks(!Settings.timelineRipple());
Settings.setTimelineRipple(!Settings.timelineRipple());
Settings.setTimelineRippleMarkers(!Settings.timelineRippleMarkers());
} else if (event->modifiers() & Qt::AltModifier && !(event->modifiers() & Qt::ControlModifier)
&& !(event->modifiers() & Qt::ShiftModifier)) {
Settings.setTimelineRippleMarkers(!Settings.timelineRippleMarkers());
} else if (event->modifiers() & Qt::ControlModifier) {
if (event->modifiers() & Qt::AltModifier) {
Settings.setTimelineRippleAllTracks(!Settings.timelineRippleAllTracks());
} else if (event->modifiers() & Qt::ShiftModifier) {
Settings.setTimelineRippleAllTracks(!Settings.timelineRipple());
Settings.setTimelineRipple(!Settings.timelineRipple());
} else {
Settings.setTimelineRipple(!Settings.timelineRipple());
}
} else if (isMultitrackValid()) {
m_timelineDock->show();
m_timelineDock->raise();
if (MLT.isClip() || m_timelineDock->selection().isEmpty()) {
m_timelineDock->replace(-1, -1);
} else {
auto &selected = m_timelineDock->selection().first();
m_timelineDock->replace(selected.y(), selected.x());
}
}
break;
case Qt::Key_S:
if (isMultitrackValid())
m_timelineDock->splitClip();
break;
case Qt::Key_T:
m_player->focusPositionSpinner();
break;
case Qt::Key_U:
if (event->modifiers() == Qt::ControlModifier) {
m_timelineDock->show();
m_timelineDock->raise();
m_timelineDock->addAudioTrack();
} else if ((event->modifiers() & Qt::ControlModifier) && (event->modifiers() & Qt::AltModifier)) {
m_timelineDock->show();
m_timelineDock->raise();
m_timelineDock->removeTrack();
}
break;
case Qt::Key_V: // Avid Splice In
if (event->modifiers() == Qt::ShiftModifier) {
m_playlistDock->show();
m_playlistDock->raise();
m_playlistDock->on_actionInsertCut_triggered();
} else {
m_timelineDock->show();
m_timelineDock->raise();
m_timelineDock->insert(-1);
}
break;
case Qt::Key_B:
if (event->modifiers() & Qt::ControlModifier && event->modifiers() & Qt::AltModifier) {
// Toggle track blending.
int trackIndex = m_timelineDock->currentTrack();
bool isBottomVideo = m_timelineDock->model()->data(m_timelineDock->model()->index(trackIndex),
MultitrackModel::IsBottomVideoRole).toBool();
if (!isBottomVideo) {
bool isComposite = m_timelineDock->model()->data(m_timelineDock->model()->index(trackIndex),
MultitrackModel::IsCompositeRole).toBool();
m_timelineDock->setTrackComposite(trackIndex, !isComposite);
}
} else if (event->modifiers() == Qt::ShiftModifier) {
if (m_playlistDock->model()->rowCount() > 0) {
// Update playlist item.
m_playlistDock->show();
m_playlistDock->raise();
m_playlistDock->on_actionUpdate_triggered();
}
} else {
// Overwrite on timeline.
m_timelineDock->show();
m_timelineDock->raise();
m_timelineDock->overwrite(-1);
}
break;
case Qt::Key_Escape: // Avid Toggle Active Monitor
if (MLT.isPlaylist()) {
if (isMultitrackValid())
m_player->onTabBarClicked(Player::ProjectTabIndex);
else if (MLT.savedProducer())
m_player->onTabBarClicked(Player::SourceTabIndex);
else
m_playlistDock->on_actionOpen_triggered();
} else if (MLT.isMultitrack()) {
if (MLT.savedProducer())
m_player->onTabBarClicked(Player::SourceTabIndex);
// TODO else open clip under playhead of current track if available
} else {
if (isMultitrackValid() || (playlist() && playlist()->count() > 0))
m_player->onTabBarClicked(Player::ProjectTabIndex);
}
break;
case Qt::Key_Up:
if (isMultitrackValid() && (event->modifiers() & Qt::AltModifier)
&& (event->modifiers() & Qt::AltModifier) && m_timelineDock->isVisible()) {
m_timelineDock->moveTrackUp();
} else if (m_playlistDock->isVisible() && event->modifiers() & Qt::AltModifier
&& m_playlistDock->model()->rowCount() > 0) {
m_playlistDock->raise();
m_playlistDock->decrementIndex();
m_playlistDock->on_actionOpen_triggered();
} else if ((event->modifiers() & Qt::ControlModifier) && (event->modifiers() & Qt::ShiftModifier)) {
if (m_playlistDock->model()->rowCount() > 0) {
m_playlistDock->raise();
m_playlistDock->moveClipUp();
m_playlistDock->decrementIndex();
}
} else if (isMultitrackValid()) {
int newClipIndex = -1;
int trackIndex = m_timelineDock->currentTrack() - 1;
if ((event->modifiers() & Qt::ControlModifier) &&
!m_timelineDock->selection().isEmpty() &&
trackIndex > -1) {
newClipIndex = m_timelineDock->clipIndexAtPosition(trackIndex, m_navigationPosition);
}
m_timelineDock->incrementCurrentTrack(-1);
if (newClipIndex >= 0) {
newClipIndex = qMin(newClipIndex, m_timelineDock->clipCount(trackIndex) - 1);
m_timelineDock->setSelection(QList() << QPoint(newClipIndex, trackIndex));
}
}
break;
case Qt::Key_Down:
if (isMultitrackValid() && (event->modifiers() & Qt::AltModifier)
&& (event->modifiers() & Qt::ShiftModifier) && m_timelineDock->isVisible()) {
m_timelineDock->moveTrackDown();
} else if (m_playlistDock->isVisible() && event->modifiers() & Qt::AltModifier
&& m_playlistDock->model()->rowCount() > 0) {
m_playlistDock->raise();
m_playlistDock->incrementIndex();
m_playlistDock->on_actionOpen_triggered();
} else if ((event->modifiers() & Qt::ControlModifier) && (event->modifiers() & Qt::ShiftModifier)) {
if (m_playlistDock->model()->rowCount() > 0) {
m_playlistDock->raise();
m_playlistDock->moveClipDown();
m_playlistDock->incrementIndex();
}
} else if (isMultitrackValid()) {
int newClipIndex = -1;
int trackIndex = m_timelineDock->currentTrack() + 1;
if ((event->modifiers() & Qt::ControlModifier) &&
!m_timelineDock->selection().isEmpty() &&
trackIndex < m_timelineDock->model()->trackList().count()) {
newClipIndex = m_timelineDock->clipIndexAtPosition(trackIndex, m_navigationPosition);
}
m_timelineDock->incrementCurrentTrack(1);
if (newClipIndex >= 0) {
newClipIndex = qMin(newClipIndex, m_timelineDock->clipCount(trackIndex) - 1);
m_timelineDock->setSelection(QList() << QPoint(newClipIndex, trackIndex));
}
}
break;
case Qt::Key_1:
case Qt::Key_2:
case Qt::Key_3:
case Qt::Key_4:
case Qt::Key_5:
case Qt::Key_6:
case Qt::Key_7:
case Qt::Key_8:
case Qt::Key_9:
if ((event->modifiers() == Qt::NoModifier || event->modifiers() == Qt::KeypadModifier) &&
m_playlistDock->isVisible() && m_playlistDock->model()->rowCount() > 0) {
m_playlistDock->raise();
m_playlistDock->setIndex(event->key() - Qt::Key_1);
}
break;
case Qt::Key_0:
if ((event->modifiers() == Qt::NoModifier || event->modifiers() == Qt::KeypadModifier)) {
if (m_timelineDock->isVisible()) {
emit m_timelineDock->zoomToFit();
} else if (m_playlistDock->isVisible() && m_playlistDock->model()->rowCount() > 0) {
m_playlistDock->raise();
m_playlistDock->setIndex(9);
}
}
if (m_keyframesDock->isVisible() && (event->modifiers() & Qt::AltModifier)) {
emit m_keyframesDock->zoomToFit();
}
break;
case Qt::Key_X: // Avid Extract
if (event->modifiers() == Qt::ShiftModifier && m_playlistDock->model()->rowCount() > 0) {
m_playlistDock->show();
m_playlistDock->raise();
m_playlistDock->on_removeButton_clicked();
} else if (isMultitrackValid()) {
m_timelineDock->show();
m_timelineDock->raise();
m_timelineDock->removeSelection();
}
break;
case Qt::Key_Backspace:
case Qt::Key_Delete:
if (isMultitrackValid()) {
m_timelineDock->show();
m_timelineDock->raise();
if (event->modifiers() == Qt::ShiftModifier)
m_timelineDock->removeSelection();
else
m_timelineDock->liftSelection();
} else if (m_playlistDock->model()->rowCount() > 0) {
m_playlistDock->show();
m_playlistDock->raise();
m_playlistDock->on_removeButton_clicked();
}
break;
case Qt::Key_Z: // Avid Lift
if (event->modifiers() == Qt::ShiftModifier && m_playlistDock->model()->rowCount() > 0) {
m_playlistDock->show();
m_playlistDock->raise();
m_playlistDock->on_removeButton_clicked();
} else if (isMultitrackValid() && event->modifiers() == Qt::NoModifier) {
m_timelineDock->show();
m_timelineDock->raise();
m_timelineDock->liftSelection();
}
break;
case Qt::Key_Minus:
if (m_timelineDock->isVisible() && !(event->modifiers() & Qt::AltModifier)) {
if (event->modifiers() & Qt::ControlModifier)
emit m_timelineDock->makeTracksShorter();
else
emit m_timelineDock->zoomOut();
}
if (m_keyframesDock->isVisible() && (event->modifiers() & Qt::AltModifier)) {
emit m_keyframesDock->zoomOut();
}
break;
case Qt::Key_Equal:
case Qt::Key_Plus:
if (m_timelineDock->isVisible() && !(event->modifiers() & Qt::AltModifier)) {
if (event->modifiers() & Qt::ControlModifier)
emit m_timelineDock->makeTracksTaller();
else
emit m_timelineDock->zoomIn();
}
if (m_keyframesDock->isVisible() && (event->modifiers() & Qt::AltModifier)) {
emit m_keyframesDock->zoomIn();
}
break;
case Qt::Key_Enter: // Seek to current playlist item
case Qt::Key_Return:
if (m_playlistDock->isVisible() && m_playlistDock->position() >= 0) {
if (event->modifiers() == Qt::ShiftModifier)
m_playlistDock->on_actionGoto_triggered();
else if (event->modifiers() == Qt::ControlModifier)
m_playlistDock->on_actionOpen_triggered();
}
break;
case Qt::Key_F2:
onPropertiesDockTriggered(true);
emit renameRequested();
break;
case Qt::Key_F3:
onRecentDockTriggered(true);
m_recentDock->find();
break;
case Qt::Key_F5:
m_timelineDock->model()->reload();
m_keyframesDock->model().reload();
break;
case Qt::Key_F11:
on_actionEnter_Full_Screen_triggered();
break;
case Qt::Key_F12:
LOG_DEBUG() << "event isAccepted:" << event->isAccepted();
LOG_DEBUG() << "Current focusWidget:" << QApplication::focusWidget();
LOG_DEBUG() << "Current focusObject:" << QApplication::focusObject();
LOG_DEBUG() << "Current focusWindow:" << QApplication::focusWindow();
break;
case Qt::Key_BracketLeft:
if (filterController()->currentFilter() && m_filtersDock->qmlProducer()) {
if (event->modifiers() == Qt::AltModifier) {
emit m_keyframesDock->seekPreviousSimple();
} else {
int i = m_filtersDock->qmlProducer()->position() + m_filtersDock->qmlProducer()->in();
if (filterController()->currentFilter()->allowTrim()) {
m_keyframesDock->model().trimFilterIn(i);
}
}
}
break;
case Qt::Key_BracketRight:
if (filterController()->currentFilter() && m_filtersDock->qmlProducer()) {
if (event->modifiers() == Qt::AltModifier) {
emit m_keyframesDock->seekNextSimple();
} else {
int i = m_filtersDock->qmlProducer()->position() + m_filtersDock->qmlProducer()->in();
if (filterController()->currentFilter()->allowTrim()) {
m_keyframesDock->model().trimFilterOut(i);
}
}
}
break;
case Qt::Key_BraceLeft:
if (filterController()->currentFilter() && m_filtersDock->qmlProducer()) {
int i = m_filtersDock->qmlProducer()->position() + m_filtersDock->qmlProducer()->in() -
filterController()->currentFilter()->in();
if (filterController()->currentFilter()->allowAnimateIn()) {
filterController()->currentFilter()->setAnimateIn(i);
}
}
break;
case Qt::Key_BraceRight:
if (filterController()->currentFilter() && m_filtersDock->qmlProducer()) {
int i = filterController()->currentFilter()->out() - (m_filtersDock->qmlProducer()->position() +
m_filtersDock->qmlProducer()->in());
if (filterController()->currentFilter()->allowAnimateOut()) {
filterController()->currentFilter()->setAnimateOut(i);
}
}
break;
case Qt::Key_Semicolon:
if (filterController()->currentFilter() && m_filtersDock->qmlProducer()
&& m_keyframesDock->currentParameter() >= 0) {
auto position = m_filtersDock->qmlProducer()->position() -
(filterController()->currentFilter()->in() - m_filtersDock->qmlProducer()->in());
auto parameterIndex = m_keyframesDock->currentParameter();
if (m_keyframesDock->model().isKeyframe(parameterIndex, position)) {
auto keyframeIndex = m_keyframesDock->model().keyframeIndex(parameterIndex, position);
m_keyframesDock->model().remove(parameterIndex, keyframeIndex);
} else {
m_keyframesDock->model().addKeyframe(parameterIndex, position);
}
}
break;
case Qt::Key_Less:
m_timelineDock->seekPrevMarker();
break;
case Qt::Key_Greater:
m_timelineDock->seekNextMarker();
break;
default:
handled = false;
break;
}
if (handled)
event->setAccepted(handled);
else
QMainWindow::keyPressEvent(event);
}
void MainWindow::keyReleaseEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_K) {
m_isKKeyPressed = false;
event->setAccepted(true);
} else {
QMainWindow::keyReleaseEvent(event);
}
}
void MainWindow::hideSetDataDirectory()
{
delete ui->actionAppDataSet;
}
QAction *MainWindow::actionAddCustomProfile() const
{
return ui->actionAddCustomProfile;
}
QAction *MainWindow::actionProfileRemove() const
{
return ui->actionProfileRemove;
}
void MainWindow::buildVideoModeMenu(QMenu *topMenu, QMenu *&customMenu, QActionGroup *group,
QAction *addAction, QAction *removeAction)
{
topMenu->addAction(addProfile(group, "HD 720p 50 fps", "atsc_720p_50"));
topMenu->addAction(addProfile(group, "HD 720p 59.94 fps", "atsc_720p_5994"));
topMenu->addAction(addProfile(group, "HD 720p 60 fps", "atsc_720p_60"));
topMenu->addAction(addProfile(group, "HD 1080i 25 fps", "atsc_1080i_50"));
topMenu->addAction(addProfile(group, "HD 1080i 29.97 fps", "atsc_1080i_5994"));
topMenu->addAction(addProfile(group, "HD 1080p 23.98 fps", "atsc_1080p_2398"));
topMenu->addAction(addProfile(group, "HD 1080p 24 fps", "atsc_1080p_24"));
topMenu->addAction(addProfile(group, "HD 1080p 25 fps", "atsc_1080p_25"));
topMenu->addAction(addProfile(group, "HD 1080p 29.97 fps", "atsc_1080p_2997"));
topMenu->addAction(addProfile(group, "HD 1080p 30 fps", "atsc_1080p_30"));
topMenu->addAction(addProfile(group, "HD 1080p 50 fps", "atsc_1080p_50"));
topMenu->addAction(addProfile(group, "HD 1080p 59.94 fps", "atsc_1080p_5994"));
topMenu->addAction(addProfile(group, "HD 1080p 60 fps", "atsc_1080p_60"));
topMenu->addAction(addProfile(group, "SD NTSC", "dv_ntsc"));
topMenu->addAction(addProfile(group, "SD PAL", "dv_pal"));
topMenu->addAction(addProfile(group, "UHD 2160p 23.98 fps", "uhd_2160p_2398"));
topMenu->addAction(addProfile(group, "UHD 2160p 24 fps", "uhd_2160p_24"));
topMenu->addAction(addProfile(group, "UHD 2160p 25 fps", "uhd_2160p_25"));
topMenu->addAction(addProfile(group, "UHD 2160p 29.97 fps", "uhd_2160p_2997"));
topMenu->addAction(addProfile(group, "UHD 2160p 30 fps", "uhd_2160p_30"));
topMenu->addAction(addProfile(group, "UHD 2160p 50 fps", "uhd_2160p_50"));
topMenu->addAction(addProfile(group, "UHD 2160p 59.94 fps", "uhd_2160p_5994"));
topMenu->addAction(addProfile(group, "UHD 2160p 60 fps", "uhd_2160p_60"));
QMenu *menu = topMenu->addMenu(tr("Non-Broadcast"));
menu->addAction(addProfile(group, "640x480 4:3 NTSC", "square_ntsc"));
menu->addAction(addProfile(group, "768x576 4:3 PAL", "square_pal"));
menu->addAction(addProfile(group, "854x480 16:9 NTSC", "square_ntsc_wide"));
menu->addAction(addProfile(group, "1024x576 16:9 PAL", "square_pal_wide"));
menu->addAction(addProfile(group, tr("DVD Widescreen NTSC"), "dv_ntsc_wide"));
menu->addAction(addProfile(group, tr("DVD Widescreen PAL"), "dv_pal_wide"));
menu->addAction(addProfile(group, "HD 720p 23.98 fps", "atsc_720p_2398"));
menu->addAction(addProfile(group, "HD 720p 24 fps", "atsc_720p_24"));
menu->addAction(addProfile(group, "HD 720p 25 fps", "atsc_720p_25"));
menu->addAction(addProfile(group, "HD 720p 29.97 fps", "atsc_720p_2997"));
menu->addAction(addProfile(group, "HD 720p 30 fps", "atsc_720p_30"));
menu->addAction(addProfile(group, "HD 1080i 60 fps", "atsc_1080i_60"));
menu->addAction(addProfile(group, "HDV 1080i 25 fps", "hdv_1080_50i"));
menu->addAction(addProfile(group, "HDV 1080i 29.97 fps", "hdv_1080_60i"));
menu->addAction(addProfile(group, "HDV 1080p 25 fps", "hdv_1080_25p"));
menu->addAction(addProfile(group, "HDV 1080p 29.97 fps", "hdv_1080_30p"));
menu->addAction(addProfile(group, tr("Square 1080p 30 fps"), "square_1080p_30"));
menu->addAction(addProfile(group, tr("Square 1080p 60 fps"), "square_1080p_60"));
menu->addAction(addProfile(group, tr("Vertical HD 30 fps"), "vertical_hd_30"));
menu->addAction(addProfile(group, tr("Vertical HD 60 fps"), "vertical_hd_60"));
customMenu = topMenu->addMenu(tr("Custom"));
customMenu->addAction(addAction);
// Load custom profiles
QDir dir(Settings.appDataLocation());
if (dir.cd("profiles")) {
QStringList profiles = dir.entryList(QDir::Files | QDir::NoDotAndDotDot | QDir::Readable);
if (profiles.length() > 0) {
customMenu->addAction(removeAction);
customMenu->addSeparator();
}
foreach (QString name, profiles)
customMenu->addAction(addProfile(group, name, dir.filePath(name)));
}
}
void MainWindow::newProject(const QString &filename, bool isProjectFolder)
{
if (isProjectFolder) {
QFileInfo info(filename);
MLT.setProjectFolder(info.absolutePath());
}
if (saveXML(filename)) {
QMutexLocker locker(&m_autosaveMutex);
if (m_autosaveFile)
m_autosaveFile->changeManagedFile(filename);
else
m_autosaveFile.reset(new AutoSaveFile(filename));
setCurrentFile(filename);
setWindowModified(false);
resetSourceUpdated();
if (MLT.producer())
showStatusMessage(tr("Saved %1").arg(m_currentFile));
m_undoStack->setClean();
m_recentDock->add(filename);
} else {
showSaveError();
}
}
void MainWindow::addCustomProfile(const QString &name, QMenu *menu, QAction *action,
QActionGroup *group)
{
// Add new profile to the menu.
QDir dir(Settings.appDataLocation());
if (dir.cd("profiles")) {
QStringList profiles = dir.entryList(QDir::Files | QDir::NoDotAndDotDot | QDir::Readable);
if (profiles.length() == 1) {
menu->addAction(action);
menu->addSeparator();
}
action = addProfile(group, name, dir.filePath(name));
action->setChecked(true);
menu->addAction(action);
Settings.setPlayerProfile(dir.filePath(name));
Settings.sync();
}
}
void MainWindow::removeCustomProfiles(const QStringList &profiles, QDir &dir, QMenu *menu,
QAction *action)
{
foreach (const QString &profile, profiles) {
// Remove the file.
dir.remove(profile);
// Locate the menu item.
foreach (QAction *a, menu->actions()) {
if (a->text() == profile) {
// Remove the menu item.
delete a;
break;
}
}
}
// If no more custom video modes.
if (menu->actions().size() == 3) {
// Remove the Remove action and separator.
menu->removeAction(action);
foreach (QAction *a, menu->actions()) {
if (a->isSeparator()) {
delete a;
break;
}
}
}
}
// Drag-n-drop events
bool MainWindow::eventFilter(QObject *target, QEvent *event)
{
if (event->type() == QEvent::DragEnter && target == MLT.videoWidget()) {
dragEnterEvent(static_cast(event));
return true;
} else if (event->type() == QEvent::Drop && target == MLT.videoWidget()) {
dropEvent(static_cast(event));
return true;
} else if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
if (QEvent::KeyPress == event->type()) {
// Let Shift+Escape be a global hook to defocus a widget (assign global player focus).
auto keyEvent = static_cast(event);
if (Qt::Key_Escape == keyEvent->key() && Qt::ShiftModifier == keyEvent->modifiers()) {
m_player->setFocus();
return true;
}
}
QQuickWidget *focusedQuickWidget = qobject_cast(qApp->focusWidget());
if (focusedQuickWidget && focusedQuickWidget->quickWindow()->activeFocusItem()) {
event->accept();
qApp->sendEvent(focusedQuickWidget->quickWindow()->activeFocusItem(), event);
QWidget *w = focusedQuickWidget->parentWidget();
if (!event->isAccepted())
qApp->sendEvent(w, event);
return true;
}
}
return QMainWindow::eventFilter(target, event);
}
void MainWindow::dragEnterEvent(QDragEnterEvent *event)
{
// Simulate the player firing a dragStarted even to make the playlist close
// its help text view. This lets one drop a clip directly into the playlist
// from a fresh start.
Mlt::GLWidget *videoWidget = (Mlt::GLWidget *) &Mlt::Controller::singleton();
emit videoWidget->dragStarted();
event->acceptProposedAction();
}
void MainWindow::dropEvent(QDropEvent *event)
{
const QMimeData *mimeData = event->mimeData();
if (mimeData->hasFormat("application/x-qabstractitemmodeldatalist")) {
QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist");
QDataStream stream(&encoded, QIODevice::ReadOnly);
QMap roleDataMap;
while (!stream.atEnd()) {
int row, col;
stream >> row >> col >> roleDataMap;
}
if (roleDataMap.contains(Qt::ToolTipRole)) {
// DisplayRole is just basename, ToolTipRole contains full path
open(roleDataMap[Qt::ToolTipRole].toString());
event->acceptProposedAction();
}
} else if (mimeData->hasUrls()) {
openMultiple(mimeData->urls());
event->acceptProposedAction();
} else if (mimeData->hasFormat(Mlt::XmlMimeType )) {
m_playlistDock->on_actionOpen_triggered();
event->acceptProposedAction();
}
}
void MainWindow::closeEvent(QCloseEvent *event)
{
m_timelineDock->stopRecording();
if (continueJobsRunning() && continueModified()) {
LOG_DEBUG() << "begin";
JOBS.cleanup();
writeSettings();
if (m_exitCode == EXIT_SUCCESS) {
MLT.stop();
} else {
if (multitrack())
m_timelineDock->model()->close();
if (playlist())
m_playlistDock->model()->close();
else
onMultitrackClosed();
}
QThreadPool::globalInstance()->clear();
AudioLevelsTask::closeAll();
event->accept();
emit aboutToShutDown();
if (m_exitCode == EXIT_SUCCESS) {
QApplication::quit();
LOG_DEBUG() << "end";
::_Exit(0);
} else {
QApplication::exit(m_exitCode);
LOG_DEBUG() << "end";
}
return;
}
event->ignore();
}
void MainWindow::showEvent(QShowEvent *event)
{
// This is needed to prevent a crash on windows on startup when timeline
// is visible and dock title bars are hidden.
Q_UNUSED(event)
ui->actionShowTitleBars->setChecked(Settings.showTitleBars());
on_actionShowTitleBars_triggered(Settings.showTitleBars());
ui->actionShowToolbar->setChecked(Settings.showToolBar());
on_actionShowToolbar_triggered(Settings.showToolBar());
ui->actionShowTextUnderIcons->setChecked(Settings.textUnderIcons());
on_actionShowTextUnderIcons_toggled(Settings.textUnderIcons());
ui->actionShowSmallIcons->setChecked(Settings.smallIcons());
on_actionShowSmallIcons_toggled(Settings.smallIcons());
windowHandle()->installEventFilter(this);
#ifndef SHOTCUT_NOUPGRADE
if (!Settings.noUpgrade() && !qApp->property("noupgrade").toBool())
QTimer::singleShot(0, this, SLOT(showUpgradePrompt()));
#endif
#ifdef Q_OS_WIN
WindowsTaskbarButton::getInstance().setParentWindow(this);
#endif
onAutosaveTimeout();
QTimer::singleShot(100, this, [ = ]() {
Database::singleton(this);
#ifdef Q_OS_WIN
this->setProperty("windowOpacity", 1.0);
#endif
});
}
void MainWindow::hideEvent(QHideEvent *event)
{
Q_UNUSED(event)
#ifdef Q_OS_WIN
setProperty("windowOpacity", 0.0);
#endif
}
void MainWindow::on_actionOpenOther_triggered()
{
// these static are used to open dialog with previous configuration
OpenOtherDialog dialog(this);
if (MLT.producer())
dialog.load(MLT.producer());
if (dialog.exec() == QDialog::Accepted) {
closeProducer();
auto &profile = MLT.profile();
auto producer = dialog.newProducer(profile);
if (!profile.is_explicit()) {
profile.from_producer(*producer);
profile.set_width(Util::coerceMultiple(profile.width()));
profile.set_height(Util::coerceMultiple(profile.height()));
}
MLT.updatePreviewProfile();
setPreviewScale(Settings.playerPreviewScale());
open(producer);
}
}
void MainWindow::onProducerOpened(bool withReopen)
{
QWidget *w = loadProducerWidget(MLT.producer());
if (withReopen && w && !MLT.producer()->get(kMultitrackItemProperty)) {
if (-1 != w->metaObject()->indexOfSignal("producerReopened(bool)"))
connect(w, SIGNAL(producerReopened(bool)), m_player, SLOT(onProducerOpened(bool)));
} else if (MLT.isPlaylist()) {
m_playlistDock->model()->load();
if (playlist()) {
m_isPlaylistLoaded = true;
m_player->setIn(-1);
m_player->setOut(-1);
m_playlistDock->setVisible(true);
m_playlistDock->raise();
m_player->enableTab(Player::ProjectTabIndex);
m_player->switchToTab(Player::ProjectTabIndex);
}
} else if (MLT.isMultitrack()) {
m_timelineDock->blockSelection(true);
m_timelineDock->model()->load();
m_timelineDock->blockSelection(false);
if (isMultitrackValid()) {
m_player->setIn(-1);
m_player->setOut(-1);
m_timelineDock->setVisible(true);
m_timelineDock->raise();
m_player->enableTab(Player::ProjectTabIndex);
m_player->switchToTab(Player::ProjectTabIndex);
m_timelineDock->selectMultitrack();
QTimer::singleShot(0, this, [ = ]() {
m_timelineDock->setSelection();
});
}
}
if (MLT.isClip()) {
m_player->enableTab(Player::SourceTabIndex);
m_player->switchToTab(Player::SourceTabIndex);
Util::getHash(*MLT.producer());
ui->actionPaste->setEnabled(true);
}
ui->actionSave->setEnabled(true);
QMutexLocker locker(&m_autosaveMutex);
if (m_autosaveFile)
setCurrentFile(m_autosaveFile->managedFileName());
else if (!MLT.URL().isEmpty())
setCurrentFile(MLT.URL());
on_actionJack_triggered(ui->actionJack && ui->actionJack->isChecked());
}
void MainWindow::onProducerChanged()
{
MLT.refreshConsumer();
if (playlist() && MLT.producer() && MLT.producer()->is_valid()
&& MLT.producer()->get_int(kPlaylistIndexProperty))
m_playlistDock->setUpdateButtonEnabled(true);
sourceUpdated();
}
bool MainWindow::on_actionSave_triggered()
{
m_timelineDock->stopRecording();
if (m_currentFile.isEmpty()) {
return on_actionSave_As_triggered();
} else {
if (Util::warnIfNotWritable(m_currentFile, this, tr("Save XML")))
return false;
bool success = saveXML(m_currentFile);
QMutexLocker locker(&m_autosaveMutex);
m_autosaveFile.reset(new AutoSaveFile(m_currentFile));
setCurrentFile(m_currentFile);
setWindowModified(false);
if (success) {
showStatusMessage(tr("Saved %1").arg(m_currentFile));
} else {
showSaveError();
}
m_undoStack->setClean();
return true;
}
}
bool MainWindow::on_actionSave_As_triggered()
{
QString path = Settings.savePath();
if (!m_currentFile.isEmpty())
path = m_currentFile;
QString caption = tr("Save XML");
QString filename = QFileDialog::getSaveFileName(this, caption, path,
tr("MLT XML (*.mlt)"), nullptr, Util::getFileDialogOptions());
if (!filename.isEmpty()) {
QFileInfo fi(filename);
Settings.setSavePath(fi.path());
if (fi.suffix() != "mlt")
filename += ".mlt";
if (Util::warnIfNotWritable(filename, this, caption))
return false;
newProject(filename);
}
return !filename.isEmpty();
}
bool MainWindow::continueModified()
{
if (isWindowModified()) {
QMessageBox dialog(QMessageBox::Warning,
qApp->applicationName(),
tr("The project has been modified.\n"
"Do you want to save your changes?"),
QMessageBox::No |
QMessageBox::Cancel |
QMessageBox::Yes,
this);
dialog.setWindowModality(QmlApplication::dialogModality());
dialog.setDefaultButton(QMessageBox::Yes);
dialog.setEscapeButton(QMessageBox::Cancel);
int r = dialog.exec();
if (r == QMessageBox::Yes || r == QMessageBox::No) {
if (r == QMessageBox::Yes) {
return on_actionSave_triggered();
} else {
QMutexLocker locker(&m_autosaveMutex);
m_autosaveFile.reset();
}
} else if (r == QMessageBox::Cancel) {
return false;
}
}
return true;
}
bool MainWindow::continueJobsRunning()
{
if (JOBS.hasIncomplete()) {
QMessageBox dialog(QMessageBox::Warning,
qApp->applicationName(),
tr("There are incomplete jobs.\n"
"Do you want to still want to exit?"),
QMessageBox::No |
QMessageBox::Yes,
this);
dialog.setWindowModality(QmlApplication::dialogModality());
dialog.setDefaultButton(QMessageBox::Yes);
dialog.setEscapeButton(QMessageBox::No);
return (dialog.exec() == QMessageBox::Yes);
}
if (m_encodeDock->isExportInProgress()) {
QMessageBox dialog(QMessageBox::Warning,
qApp->applicationName(),
tr("An export is in progress.\n"
"Do you want to still want to exit?"),
QMessageBox::No |
QMessageBox::Yes,
this);
dialog.setWindowModality(QmlApplication::dialogModality());
dialog.setDefaultButton(QMessageBox::Yes);
dialog.setEscapeButton(QMessageBox::No);
return (dialog.exec() == QMessageBox::Yes);
}
return true;
}
QUndoStack *MainWindow::undoStack() const
{
return m_undoStack;
}
void MainWindow::onEncodeTriggered(bool checked)
{
if (checked) {
m_encodeDock->show();
m_encodeDock->raise();
}
}
void MainWindow::onCaptureStateChanged(bool started)
{
if (started && (MLT.resource().startsWith("x11grab:") ||
MLT.resource().startsWith("gdigrab:") ||
MLT.resource().startsWith("avfoundation"))
&& !MLT.producer()->get_int(kBackgroundCaptureProperty))
showMinimized();
}
void MainWindow::onJobsDockTriggered(bool checked)
{
if (checked) {
m_jobsDock->show();
m_jobsDock->raise();
}
}
void MainWindow::onRecentDockTriggered(bool checked)
{
if (checked) {
m_recentDock->show();
m_recentDock->raise();
}
}
void MainWindow::onPropertiesDockTriggered(bool checked)
{
if (checked) {
m_propertiesDock->show();
m_propertiesDock->raise();
}
}
void MainWindow::onPlaylistDockTriggered(bool checked)
{
if (checked) {
m_playlistDock->show();
m_playlistDock->raise();
}
}
void MainWindow::onTimelineDockTriggered(bool checked)
{
if (checked) {
m_timelineDock->show();
m_timelineDock->raise();
}
}
void MainWindow::onHistoryDockTriggered(bool checked)
{
if (checked) {
m_historyDock->show();
m_historyDock->raise();
}
}
void MainWindow::onFiltersDockTriggered(bool checked)
{
if (checked) {
m_filtersDock->show();
m_filtersDock->raise();
}
}
void MainWindow::onKeyframesDockTriggered(bool checked)
{
if (checked) {
m_keyframesDock->show();
m_keyframesDock->raise();
}
}
void MainWindow::onMarkersDockTriggered(bool checked)
{
if (checked) {
m_markersDock->show();
m_markersDock->raise();
}
}
void MainWindow::onNotesDockTriggered(bool checked)
{
if (checked) {
m_notesDock->show();
m_notesDock->raise();
}
}
void MainWindow::onPlaylistCreated()
{
if (!playlist() || playlist()->count() == 0) return;
m_player->enableTab(Player::ProjectTabIndex, true);
}
void MainWindow::onPlaylistLoaded()
{
updateMarkers();
m_player->enableTab(Player::ProjectTabIndex, true);
}
void MainWindow::onPlaylistCleared()
{
m_player->onTabBarClicked(Player::SourceTabIndex);
setWindowModified(true);
}
void MainWindow::onPlaylistClosed()
{
closeProducer();
setProfile(Settings.playerProfile());
resetVideoModeMenu();
setAudioChannels(Settings.playerAudioChannels());
setCurrentFile("");
setWindowModified(false);
resetSourceUpdated();
m_undoStack->clear();
MLT.resetURL();
QMutexLocker locker(&m_autosaveMutex);
m_autosaveFile.reset(new AutoSaveFile(untitledFileName()));
if (!isMultitrackValid())
m_player->enableTab(Player::ProjectTabIndex, false);
}
void MainWindow::onPlaylistModified()
{
setWindowModified(true);
if (MLT.producer() && playlist()
&& (void *) MLT.producer()->get_producer() == (void *) playlist()->get_playlist())
m_player->onDurationChanged();
updateMarkers();
m_player->enableTab(Player::ProjectTabIndex, true);
}
void MainWindow::onMultitrackCreated()
{
m_player->enableTab(Player::ProjectTabIndex, true);
}
void MainWindow::onMultitrackClosed()
{
setAudioChannels(Settings.playerAudioChannels());
closeProducer();
setProfile(Settings.playerProfile());
resetVideoModeMenu();
setCurrentFile("");
setWindowModified(false);
resetSourceUpdated();
m_undoStack->clear();
MLT.resetURL();
QMutexLocker locker(&m_autosaveMutex);
m_autosaveFile.reset(new AutoSaveFile(untitledFileName()));
if (!playlist() || playlist()->count() == 0)
m_player->enableTab(Player::ProjectTabIndex, false);
}
void MainWindow::onMultitrackModified()
{
setWindowModified(true);
// Reflect this playlist info onto the producer for keyframes dock.
if (!m_timelineDock->selection().isEmpty()) {
int trackIndex = m_timelineDock->selection().first().y();
int clipIndex = m_timelineDock->selection().first().x();
QScopedPointer info(m_timelineDock->getClipInfo(trackIndex, clipIndex));
if (info && info->producer && info->producer->is_valid()) {
int expected = info->frame_in;
QScopedPointer info2(m_timelineDock->getClipInfo(trackIndex, clipIndex - 1));
if (info2 && info2->producer && info2->producer->is_valid()
&& info2->producer->get(kShotcutTransitionProperty)) {
// Factor in a transition left of the clip.
expected -= info2->frame_count;
info->producer->set(kPlaylistStartProperty, info2->start);
} else {
info->producer->set(kPlaylistStartProperty, info->start);
}
if (expected != info->producer->get_int(kFilterInProperty)) {
int delta = expected - info->producer->get_int(kFilterInProperty);
info->producer->set(kFilterInProperty, expected);
emit m_filtersDock->producerInChanged(delta);
}
expected = info->frame_out;
info2.reset(m_timelineDock->getClipInfo(trackIndex, clipIndex + 1));
if (info2 && info2->producer && info2->producer->is_valid()
&& info2->producer->get(kShotcutTransitionProperty)) {
// Factor in a transition right of the clip.
expected += info2->frame_count;
}
if (expected != info->producer->get_int(kFilterOutProperty)) {
int delta = expected - info->producer->get_int(kFilterOutProperty);
info->producer->set(kFilterOutProperty, expected);
emit m_filtersDock->producerOutChanged(delta);
}
}
}
MLT.refreshConsumer();
}
void MainWindow::onMultitrackDurationChanged()
{
if (MLT.producer()
&& (void *) MLT.producer()->get_producer() == (void *) multitrack()->get_producer())
m_player->onDurationChanged();
}
void MainWindow::onNoteModified()
{
setWindowModified(true);
}
void MainWindow::onCutModified()
{
if (!playlist() && !multitrack()) {
setWindowModified(true);
}
if (playlist())
m_playlistDock->setUpdateButtonEnabled(true);
sourceUpdated();
}
void MainWindow::onProducerModified()
{
setWindowModified(true);
sourceUpdated();
}
void MainWindow::onFilterModelChanged()
{
MLT.refreshConsumer();
setWindowModified(true);
sourceUpdated();
if (playlist())
m_playlistDock->setUpdateButtonEnabled(true);
}
void MainWindow::updateMarkers()
{
if (playlist() && MLT.isPlaylist()) {
QList markers;
int n = playlist()->count();
for (int i = 0; i < n; i++)
markers.append(playlist()->clip_start(i));
m_player->setMarkers(markers);
}
}
void MainWindow::updateThumbnails()
{
if (Settings.playlistThumbnails() != "hidden")
m_playlistDock->model()->refreshThumbnails();
}
void MainWindow::on_actionUndo_triggered()
{
TimelineSelectionBlocker blocker(*m_timelineDock);
m_undoStack->undo();
}
void MainWindow::on_actionRedo_triggered()
{
TimelineSelectionBlocker blocker(*m_timelineDock);
m_undoStack->redo();
}
void MainWindow::on_actionFAQ_triggered()
{
QDesktopServices::openUrl(QUrl("https://www.shotcut.org/FAQ/"));
}
void MainWindow::on_actionForum_triggered()
{
QDesktopServices::openUrl(QUrl("https://forum.shotcut.org/"));
}
bool MainWindow::saveXML(const QString &filename, bool withRelativePaths)
{
bool result;
QString notes = m_notesDock->getText();
if (m_timelineDock->model()->rowCount() > 0) {
result = MLT.saveXML(filename, multitrack(), withRelativePaths, nullptr, false, notes);
} else if (m_playlistDock->model()->rowCount() > 0) {
int in = MLT.producer()->get_in();
int out = MLT.producer()->get_out();
MLT.producer()->set_in_and_out(0, MLT.producer()->get_length() - 1);
result = MLT.saveXML(filename, playlist(), withRelativePaths, nullptr, false, notes);
MLT.producer()->set_in_and_out(in, out);
} else if (MLT.producer()) {
result = MLT.saveXML(filename, (MLT.isMultitrack()
|| MLT.isPlaylist()) ? MLT.savedProducer() : 0, withRelativePaths, nullptr, false, notes);
} else {
// Save an empty playlist, which is accepted by both MLT and Shotcut.
Mlt::Playlist playlist(MLT.profile());
result = MLT.saveXML(filename, &playlist, withRelativePaths, nullptr, false, notes);
}
return result;
}
void MainWindow::changeTheme(const QString &theme)
{
LOG_DEBUG() << "begin";
if (theme == "dark") {
QApplication::setStyle("Fusion");
QPalette palette;
palette.setColor(QPalette::Window, QColor(50, 50, 50));
palette.setColor(QPalette::WindowText, QColor(220, 220, 220));
palette.setColor(QPalette::Base, QColor(30, 30, 30));
palette.setColor(QPalette::AlternateBase, QColor(40, 40, 40));
palette.setColor(QPalette::Highlight, QColor(23, 92, 118));
palette.setColor(QPalette::HighlightedText, Qt::white);
palette.setColor(QPalette::ToolTipBase, palette.color(QPalette::Highlight));
palette.setColor(QPalette::ToolTipText, palette.color(QPalette::WindowText));
palette.setColor(QPalette::Text, palette.color(QPalette::WindowText));
palette.setColor(QPalette::BrightText, Qt::red);
palette.setColor(QPalette::Button, palette.color(QPalette::Window));
palette.setColor(QPalette::ButtonText, palette.color(QPalette::WindowText));
palette.setColor(QPalette::Link, palette.color(QPalette::Highlight).lighter());
palette.setColor(QPalette::LinkVisited, palette.color(QPalette::Highlight));
palette.setColor(QPalette::Disabled, QPalette::Text, Qt::darkGray);
palette.setColor(QPalette::Disabled, QPalette::ButtonText, Qt::darkGray);
QApplication::setPalette(palette);
QIcon::setThemeName("dark");
QMetaObject::invokeMethod(&MAIN, "on_actionShowTextUnderIcons_toggled", Qt::QueuedConnection,
Q_ARG(bool, Settings.textUnderIcons()));
} else if (theme == "light") {
QStyle *style = QStyleFactory::create("Fusion");
QApplication::setStyle(style);
QApplication::setPalette(style->standardPalette());
QIcon::setThemeName("light");
QMetaObject::invokeMethod(&MAIN, "on_actionShowTextUnderIcons_toggled", Qt::QueuedConnection,
Q_ARG(bool, Settings.textUnderIcons()));
} else {
QApplication::setStyle(qApp->property("system-style").toString());
QIcon::setThemeName("oxygen");
}
emit QmlApplication::singleton().paletteChanged();
LOG_DEBUG() << "end";
}
Mlt::Playlist *MainWindow::playlist() const
{
return m_playlistDock->model()->playlist();
}
bool MainWindow::isPlaylistValid() const
{
return m_playlistDock->model()->playlist()
&& m_playlistDock->model()->rowCount() > 0;
}
Mlt::Producer *MainWindow::multitrack() const
{
return m_timelineDock->model()->tractor();
}
bool MainWindow::isMultitrackValid() const
{
return m_timelineDock->model()->tractor()
&& !m_timelineDock->model()->trackList().empty();
}
QWidget *MainWindow::loadProducerWidget(Mlt::Producer *producer)
{
QWidget *w = 0;
QScrollArea *scrollArea = (QScrollArea *) m_propertiesDock->widget();
if (!producer || !producer->is_valid()) {
if (scrollArea->widget())
scrollArea->widget()->deleteLater();
return w;
} else {
scrollArea->show();
}
QString service(producer->get("mlt_service"));
QString resource = QString::fromUtf8(producer->get("resource"));
QString shotcutProducer(producer->get(kShotcutProducerProperty));
if (resource.startsWith("video4linux2:")
|| QString::fromUtf8(producer->get("resource1")).startsWith("video4linux2:"))
w = new Video4LinuxWidget(this);
else if (resource.startsWith("pulse:"))
w = new PulseAudioWidget(this);
else if (resource.startsWith("jack:"))
w = new JackProducerWidget(this);
else if (resource.startsWith("alsa:"))
w = new AlsaWidget(this);
else if (resource.startsWith("dshow:")
|| QString::fromUtf8(producer->get("resource1")).startsWith("dshow:"))
w = new DirectShowVideoWidget(this);
else if (resource.startsWith("avfoundation:"))
w = new AvfoundationProducerWidget(this);
else if (resource.startsWith("x11grab:"))
w = new X11grabWidget(this);
else if (resource.startsWith("gdigrab:"))
w = new GDIgrabWidget(this);
else if (service.startsWith("avformat") || shotcutProducer == "avformat")
w = new AvformatProducerWidget(this);
else if (MLT.isImageProducer(producer)) {
w = new ImageProducerWidget(this);
connect(m_player, SIGNAL(outChanged(int)), w, SLOT(updateDuration()));
} else if (service == "decklink" || resource.contains("decklink"))
w = new DecklinkProducerWidget(this);
else if (service == "color")
w = new ColorProducerWidget(this);
else if (service == "glaxnimate")
w = new GlaxnimateProducerWidget(this);
else if (service == "noise")
w = new NoiseWidget(this);
else if (service == "frei0r.ising0r")
w = new IsingWidget(this);
else if (service == "frei0r.lissajous0r")
w = new LissajousWidget(this);
else if (service == "frei0r.plasma")
w = new PlasmaWidget(this);
else if (service == "frei0r.test_pat_B")
w = new ColorBarsWidget(this);
else if (service == "tone")
w = new ToneProducerWidget(this);
else if (service == "count")
w = new CountProducerWidget(this);
else if (service == "blipflash")
w = new BlipProducerWidget(this);
else if (producer->parent().get(kShotcutTransitionProperty)) {
w = new LumaMixTransition(producer->parent(), this);
scrollArea->setWidget(w);
if (-1 != w->metaObject()->indexOfSignal("modified()"))
connect(w, SIGNAL(modified()), SLOT(onProducerModified()));
return w;
} else if (mlt_service_playlist_type == producer->type()) {
int trackIndex = m_timelineDock->currentTrack();
bool isBottomVideo = m_timelineDock->model()->data(m_timelineDock->model()->index(trackIndex),
MultitrackModel::IsBottomVideoRole).toBool();
if (!isBottomVideo) {
w = new TrackPropertiesWidget(*producer, this);
scrollArea->setWidget(w);
return w;
}
} else if (mlt_service_tractor_type == producer->type()) {
w = new TimelinePropertiesWidget(*producer, this);
scrollArea->setWidget(w);
connect(w, SIGNAL(editProfile()), SLOT(on_actionAddCustomProfile_triggered()));
return w;
}
if (w) {
dynamic_cast(w)->setProducer(producer);
if (-1 != w->metaObject()->indexOfSignal("producerChanged(Mlt::Producer*)")) {
connect(w, SIGNAL(producerChanged(Mlt::Producer *)), SLOT(onProducerChanged()));
connect(w, SIGNAL(producerChanged(Mlt::Producer *)), m_filterController,
SLOT(setProducer(Mlt::Producer *)));
connect(w, SIGNAL(producerChanged(Mlt::Producer *)), m_playlistDock,
SLOT(onProducerChanged(Mlt::Producer *)));
if (producer->get(kMultitrackItemProperty))
connect(w, SIGNAL(producerChanged(Mlt::Producer *)), m_timelineDock,
SLOT(onProducerChanged(Mlt::Producer *)));
}
if (-1 != w->metaObject()->indexOfSignal("modified()")) {
connect(w, SIGNAL(modified()), SLOT(onProducerModified()));
connect(w, SIGNAL(modified()), m_playlistDock, SLOT(onProducerModified()));
connect(w, SIGNAL(modified()), m_timelineDock, SLOT(onProducerModified()));
connect(w, SIGNAL(modified()), m_keyframesDock, SLOT(onProducerModified()));
connect(w, SIGNAL(modified()), m_filterController, SLOT(onProducerChanged()));
}
if (-1 != w->metaObject()->indexOfSlot("updateDuration()")) {
connect(m_timelineDock, SIGNAL(durationChanged()), w, SLOT(updateDuration()));
}
if (-1 != w->metaObject()->indexOfSlot("rename()")) {
connect(this, SIGNAL(renameRequested()), w, SLOT(rename()));
}
if (-1 != w->metaObject()->indexOfSlot("offerConvert(QString)")) {
connect(m_filterController->attachedModel(), SIGNAL(requestConvert(QString, bool, bool)), w,
SLOT(offerConvert(QString, bool, bool)));
}
scrollArea->setWidget(w);
onProducerChanged();
} else if (scrollArea->widget()) {
scrollArea->widget()->deleteLater();
}
return w;
}
void MainWindow::on_actionEnter_Full_Screen_triggered()
{
#ifdef Q_OS_WIN
bool isFull = isMaximized();
#else
bool isFull = isFullScreen();
#endif
if (isFull) {
showNormal();
ui->actionEnter_Full_Screen->setText(tr("Enter Full Screen"));
} else {
#ifdef Q_OS_WIN
showMaximized();
#else
showFullScreen();
#endif
ui->actionEnter_Full_Screen->setText(tr("Exit Full Screen"));
}
}
void MainWindow::onGpuNotSupported()
{
Settings.setPlayerGPU(false);
if (ui->actionGPU) {
ui->actionGPU->setChecked(false);
ui->actionGPU->setDisabled(true);
}
LOG_WARNING() << "";
QMessageBox::critical(this, qApp->applicationName(),
tr("GPU effects are not supported"));
}
void MainWindow::stepLeftOneFrame()
{
m_player->seek(m_player->position() - 1);
}
void MainWindow::stepRightOneFrame()
{
m_player->seek(m_player->position() + 1);
}
void MainWindow::stepLeftOneSecond()
{
stepLeftBySeconds(-1);
}
void MainWindow::stepRightOneSecond()
{
stepLeftBySeconds(1);
}
void MainWindow::setInToCurrent(bool ripple)
{
if (m_player->tabIndex() == Player::ProjectTabIndex && isMultitrackValid()) {
m_timelineDock->trimClipAtPlayhead(TimelineDock::TrimInPoint, ripple);
} else if (MLT.isSeekableClip()) {
m_player->setIn(m_player->position());
int delta = m_player->position() - MLT.producer()->get_in();
emit m_player->inChanged(delta);
}
}
void MainWindow::setOutToCurrent(bool ripple)
{
if (m_player->tabIndex() == Player::ProjectTabIndex && isMultitrackValid()) {
m_timelineDock->trimClipAtPlayhead(TimelineDock::TrimOutPoint, ripple);
} else if (MLT.isSeekableClip()) {
m_player->setOut(m_player->position());
int delta = m_player->position() - MLT.producer()->get_out();
emit m_player->outChanged(delta);
}
}
void MainWindow::onShuttle(float x)
{
if (x == 0) {
m_player->pause();
} else if (x > 0) {
m_player->play(10.0 * x);
} else {
m_player->play(20.0 * x);
}
}
void MainWindow::showUpgradePrompt()
{
if (Settings.checkUpgradeAutomatic()) {
showStatusMessage("Checking for upgrade...");
m_network.get(QNetworkRequest(QUrl("https://check.shotcut.org/version.json")));
} else {
QAction *action = new QAction(tr("Click here to check for a new version of Shotcut."), 0);
connect(action, SIGNAL(triggered(bool)), SLOT(on_actionUpgrade_triggered()));
showStatusMessage(action, 15 /* seconds */);
}
}
void MainWindow::on_actionRealtime_triggered(bool checked)
{
Settings.setPlayerRealtime(checked);
if (Settings.playerGPU())
MLT.pause();
if (MLT.consumer()) {
MLT.restart();
}
}
void MainWindow::on_actionProgressive_triggered(bool checked)
{
MLT.videoWidget()->setProperty("progressive", checked);
if (Settings.playerGPU())
MLT.pause();
if (MLT.consumer()) {
MLT.profile().set_progressive(checked);
MLT.updatePreviewProfile();
MLT.restart();
}
Settings.setPlayerProgressive(checked);
}
void MainWindow::changeAudioChannels(bool checked, int channels)
{
if ( checked ) {
Settings.setPlayerAudioChannels(channels);
setAudioChannels(Settings.playerAudioChannels());
}
}
void MainWindow::on_actionChannels1_triggered(bool checked)
{
changeAudioChannels(checked, 1);
}
void MainWindow::on_actionChannels2_triggered(bool checked)
{
changeAudioChannels(checked, 2);
}
void MainWindow::on_actionChannels6_triggered(bool checked)
{
changeAudioChannels(checked, 6);
}
void MainWindow::changeDeinterlacer(bool checked, const char *method)
{
if (checked) {
MLT.videoWidget()->setProperty("deinterlace_method", method);
if (MLT.consumer()) {
MLT.consumer()->set("deinterlace_method", method);
MLT.refreshConsumer();
}
}
Settings.setPlayerDeinterlacer(method);
}
void MainWindow::on_actionOneField_triggered(bool checked)
{
changeDeinterlacer(checked, "onefield");
}
void MainWindow::on_actionLinearBlend_triggered(bool checked)
{
changeDeinterlacer(checked, "linearblend");
}
void MainWindow::on_actionYadifTemporal_triggered(bool checked)
{
changeDeinterlacer(checked, "yadif-nospatial");
}
void MainWindow::on_actionYadifSpatial_triggered(bool checked)
{
changeDeinterlacer(checked, "yadif");
}
void MainWindow::changeInterpolation(bool checked, const char *method)
{
if (checked) {
MLT.videoWidget()->setProperty("rescale", method);
if (MLT.consumer()) {
MLT.consumer()->set("rescale", method);
MLT.refreshConsumer();
}
}
Settings.setPlayerInterpolation(method);
}
void MainWindow::processMultipleFiles()
{
if (m_multipleFiles.length() <= 0)
return;
QStringList multipleFiles = m_multipleFiles;
m_multipleFiles.clear();
int count = multipleFiles.length();
if (count > 1) {
LongUiTask longTask(tr("Open Files"));
m_playlistDock->show();
m_playlistDock->raise();
for (int i = 0; i < count; i++) {
QString filename = multipleFiles.takeFirst();
LOG_DEBUG() << filename;
longTask.reportProgress(QFileInfo(filename).fileName(), i, count);
Mlt::Producer p(MLT.profile(), filename.toUtf8().constData());
if (p.is_valid()) {
if (QDir::toNativeSeparators(filename) == QDir::toNativeSeparators(MAIN.fileName())) {
MAIN.showStatusMessage(QObject::tr("You cannot add a project to itself!"));
continue;
}
Util::getHash(p);
Mlt::Producer *producer = MLT.setupNewProducer(&p);
ProxyManager::generateIfNotExists(*producer);
undoStack()->push(new Playlist::AppendCommand(*m_playlistDock->model(), MLT.XML(producer), false));
m_recentDock->add(filename.toUtf8().constData());
delete producer;
}
}
emit m_playlistDock->model()->modified();
}
if (m_isPlaylistLoaded && Settings.playerGPU()) {
updateThumbnails();
m_isPlaylistLoaded = false;
}
}
void MainWindow::onLanguageTriggered(QAction *action)
{
Settings.setLanguage(action->data().toString());
QMessageBox dialog(QMessageBox::Information,
qApp->applicationName(),
tr("You must restart Shotcut to switch to the new language.\n"
"Do you want to restart now?"),
QMessageBox::No | QMessageBox::Yes,
this);
dialog.setDefaultButton(QMessageBox::Yes);
dialog.setEscapeButton(QMessageBox::No);
dialog.setWindowModality(QmlApplication::dialogModality());
if (dialog.exec() == QMessageBox::Yes) {
m_exitCode = EXIT_RESTART;
QApplication::closeAllWindows();
}
}
void MainWindow::on_actionNearest_triggered(bool checked)
{
changeInterpolation(checked, "nearest");
}
void MainWindow::on_actionBilinear_triggered(bool checked)
{
changeInterpolation(checked, "bilinear");
}
void MainWindow::on_actionBicubic_triggered(bool checked)
{
changeInterpolation(checked, "bicubic");
}
void MainWindow::on_actionHyper_triggered(bool checked)
{
changeInterpolation(checked, "hyper");
}
void MainWindow::on_actionJack_triggered(bool checked)
{
Settings.setPlayerJACK(checked);
if (!MLT.enableJack(checked)) {
if (ui->actionJack)
ui->actionJack->setChecked(false);
Settings.setPlayerJACK(false);
QMessageBox::warning(this, qApp->applicationName(),
tr("Failed to connect to JACK.\nPlease verify that JACK is installed and running."));
}
}
void MainWindow::on_actionGPU_triggered(bool checked)
{
if (checked) {
QMessageBox dialog(QMessageBox::Warning,
qApp->applicationName(),
tr("GPU effects are experimental and may cause instability on some systems. "
"Some CPU effects are incompatible with GPU effects and will be disabled. "
"A project created with GPU effects can not be converted to a CPU only project later."
"\n\n"
"Do you want to enable GPU effects and restart Shotcut?"),
QMessageBox::No | QMessageBox::Yes,
this);
dialog.setDefaultButton(QMessageBox::Yes);
dialog.setEscapeButton(QMessageBox::No);
dialog.setWindowModality(QmlApplication::dialogModality());
if (dialog.exec() == QMessageBox::Yes) {
m_exitCode = EXIT_RESTART;
QApplication::closeAllWindows();
} else {
ui->actionGPU->setChecked(false);
}
} else {
QMessageBox dialog(QMessageBox::Information,
qApp->applicationName(),
tr("Shotcut must restart to disable GPU effects."
"\n\n"
"Disable GPU effects and restart?"),
QMessageBox::No | QMessageBox::Yes,
this);
dialog.setDefaultButton(QMessageBox::Yes);
dialog.setEscapeButton(QMessageBox::No);
dialog.setWindowModality(QmlApplication::dialogModality());
if (dialog.exec() == QMessageBox::Yes) {
m_exitCode = EXIT_RESTART;
QApplication::closeAllWindows();
} else {
ui->actionGPU->setChecked(true);
}
}
}
void MainWindow::onExternalTriggered(QAction *action)
{
LOG_DEBUG() << action->data().toString();
bool isExternal = !action->data().toString().isEmpty();
Settings.setPlayerExternal(action->data().toString());
MLT.stop();
bool ok = false;
int screen = action->data().toInt(&ok);
if (ok || action->data().toString().isEmpty()) {
m_player->moveVideoToScreen(ok ? screen : -2);
isExternal = false;
MLT.videoWidget()->setProperty("mlt_service", QVariant());
} else {
m_player->moveVideoToScreen(-2);
MLT.videoWidget()->setProperty("mlt_service", action->data());
}
QString profile = Settings.playerProfile();
// Automatic not permitted for SDI/HDMI
if (isExternal && profile.isEmpty()) {
profile = "atsc_720p_50";
Settings.setPlayerProfile(profile);
setProfile(profile);
MLT.restart();
foreach (QAction *a, m_profileGroup->actions()) {
if (a->data() == profile) {
a->setChecked(true);
break;
}
}
} else {
MLT.consumerChanged();
}
// Automatic not permitted for SDI/HDMI
m_profileGroup->actions().at(0)->setEnabled(!isExternal);
// Disable progressive option when SDI/HDMI
ui->actionProgressive->setEnabled(!isExternal);
bool isProgressive = isExternal
? MLT.profile().progressive()
: ui->actionProgressive->isChecked();
MLT.videoWidget()->setProperty("progressive", isProgressive);
if (MLT.consumer()) {
MLT.consumer()->set("progressive", isProgressive);
MLT.restart();
}
if (m_keyerMenu)
m_keyerMenu->setEnabled(action->data().toString().startsWith("decklink"));
// Preview scaling not permitted for SDI/HDMI
if (isExternal) {
setPreviewScale(0);
m_previewScaleGroup->setEnabled(false);
} else {
setPreviewScale(Settings.playerPreviewScale());
m_previewScaleGroup->setEnabled(true);
}
}
void MainWindow::onKeyerTriggered(QAction *action)
{
LOG_DEBUG() << action->data().toString();
MLT.videoWidget()->setProperty("keyer", action->data());
MLT.consumerChanged();
Settings.setPlayerKeyerMode(action->data().toInt());
}
void MainWindow::onProfileTriggered(QAction *action)
{
Settings.setPlayerProfile(action->data().toString());
if (MLT.producer() && MLT.producer()->is_valid()) {
// Save the XML to get correct in/out points before profile is changed.
QString xml = MLT.XML();
setProfile(action->data().toString());
MLT.restart(xml);
emit producerOpened(false);
} else {
setProfile(action->data().toString());
}
}
void MainWindow::onProfileChanged()
{
if (multitrack() && MLT.isMultitrack() &&
(m_timelineDock->selection().isEmpty() || m_timelineDock->currentTrack() == -1)) {
emit m_timelineDock->selected(multitrack());
}
}
void MainWindow::on_actionAddCustomProfile_triggered()
{
QString xml;
if (MLT.producer() && MLT.producer()->is_valid()) {
// Save the XML to get correct in/out points before profile is changed.
xml = MLT.XML();
}
CustomProfileDialog dialog(this);
dialog.setWindowModality(QmlApplication::dialogModality());
if (dialog.exec() == QDialog::Accepted) {
QString name = dialog.profileName();
if (!name.isEmpty()) {
addCustomProfile(name, customProfileMenu(), actionProfileRemove(), profileGroup());
} else if (m_profileGroup->checkedAction()) {
m_profileGroup->checkedAction()->setChecked(false);
}
// Use the new profile.
emit profileChanged();
if (!xml.isEmpty()) {
MLT.restart(xml);
emit producerOpened(false);
}
}
}
void MainWindow::restartAfterChangeTheme()
{
QMessageBox dialog(QMessageBox::Information,
qApp->applicationName(),
tr("You must restart %1 to switch to the new theme.\n"
"Do you want to restart now?").arg(qApp->applicationName()),
QMessageBox::No | QMessageBox::Yes,
this);
dialog.setDefaultButton(QMessageBox::Yes);
dialog.setEscapeButton(QMessageBox::No);
dialog.setWindowModality(QmlApplication::dialogModality());
if (dialog.exec() == QMessageBox::Yes) {
m_exitCode = EXIT_RESTART;
QApplication::closeAllWindows();
}
}
void MainWindow::on_actionSystemTheme_triggered()
{
Settings.setTheme("system");
restartAfterChangeTheme();
}
void MainWindow::on_actionFusionDark_triggered()
{
Settings.setTheme("dark");
restartAfterChangeTheme();
}
void MainWindow::on_actionFusionLight_triggered()
{
Settings.setTheme("light");
restartAfterChangeTheme();
}
void MainWindow::on_actionJobPriorityLow_triggered()
{
Settings.setJobPriority("low");
}
void MainWindow::on_actionJobPriorityNormal_triggered()
{
Settings.setJobPriority("normal");
}
void MainWindow::on_actionTutorials_triggered()
{
QDesktopServices::openUrl(QUrl("https://www.shotcut.org/tutorials/"));
}
void MainWindow::on_actionRestoreLayout_triggered()
{
auto mode = Settings.layoutMode();
if (mode != LayoutMode::Custom) {
// Clear the saved layout for this mode
Settings.setLayout(QString(kReservedLayoutPrefix).arg(mode), QByteArray(), QByteArray());
// Reset the layout mode so the current layout is saved as custom when trigger action
Settings.setLayoutMode();
}
switch (mode) {
case LayoutMode::Custom:
ui->actionLayoutEditing->setChecked(true);
Q_FALLTHROUGH();
case LayoutMode::Editing:
on_actionLayoutEditing_triggered();
break;
case LayoutMode::Logging:
on_actionLayoutLogging_triggered();
break;
case LayoutMode::Effects:
on_actionLayoutEffects_triggered();
break;
case LayoutMode::Color:
on_actionLayoutColor_triggered();
break;
case LayoutMode::Audio:
on_actionLayoutAudio_triggered();
break;
case LayoutMode::PlayerOnly:
on_actionLayoutPlayer_triggered();
break;
}
}
void MainWindow::on_actionShowTitleBars_triggered(bool checked)
{
QList docks = findChildren();
for (int i = 0; i < docks.count(); i++) {
QDockWidget *dock = docks.at(i);
if (checked) {
dock->setTitleBarWidget(0);
} else {
if (!dock->isFloating()) {
dock->setTitleBarWidget(new QWidget);
}
}
}
Settings.setShowTitleBars(checked);
}
void MainWindow::on_actionShowToolbar_triggered(bool checked)
{
ui->mainToolBar->setVisible(checked);
}
void MainWindow::onToolbarVisibilityChanged(bool visible)
{
ui->actionShowToolbar->setChecked(visible);
Settings.setShowToolBar(visible);
}
void MainWindow::on_menuExternal_aboutToShow()
{
foreach (QAction *action, m_externalGroup->actions()) {
bool ok = false;
int i = action->data().toInt(&ok);
if (ok) {
if (i == QApplication::desktop()->screenNumber(this)) {
if (action->isChecked()) {
m_externalGroup->actions().first()->setChecked(true);
Settings.setPlayerExternal(QString());
}
action->setDisabled(true);
} else {
action->setEnabled(true);
}
}
}
}
void MainWindow::on_actionUpgrade_triggered()
{
if (Settings.askUpgradeAutomatic()) {
QMessageBox dialog(QMessageBox::Question,
qApp->applicationName(),
tr("Do you want to automatically check for updates in the future?"),
QMessageBox::No |
QMessageBox::Yes,
this);
dialog.setWindowModality(QmlApplication::dialogModality());
dialog.setDefaultButton(QMessageBox::Yes);
dialog.setEscapeButton(QMessageBox::No);
dialog.setCheckBox(new QCheckBox(tr("Do not show this anymore.",
"Automatic upgrade check dialog")));
Settings.setCheckUpgradeAutomatic(dialog.exec() == QMessageBox::Yes);
if (dialog.checkBox()->isChecked())
Settings.setAskUpgradeAutomatic(false);
}
showStatusMessage("Checking for upgrade...");
m_network.get(QNetworkRequest(QUrl("https://check.shotcut.org/version.json")));
}
void MainWindow::on_actionOpenXML_triggered()
{
QString path = Settings.openPath();
#ifdef Q_OS_MAC
path.append("/*");
#endif
QStringList filenames = QFileDialog::getOpenFileNames(this, tr("Open File"), path,
tr("MLT XML (*.mlt);;All Files (*)"), nullptr, Util::getFileDialogOptions());
if (filenames.length() > 0) {
QString url = filenames.first();
MltXmlChecker checker;
if (checker.check(url) == QXmlStreamReader::NoError) {
if (!isCompatibleWithGpuMode(checker))
return;
isXmlRepaired(checker, url);
// Check if the locale usage differs.
// Get current locale.
QString localeName = QString(::setlocale(MLT_LC_CATEGORY, nullptr)).toUpper();
// Test if it is C or POSIX.
bool currentlyUsingLocale = (localeName != "" && localeName != "C" && localeName != "POSIX");
if (currentlyUsingLocale != checker.usesLocale()) {
// Show a warning dialog and cancel if requested.
QMessageBox dialog(QMessageBox::Question,
qApp->applicationName(),
tr("The decimal point of the MLT XML file\nyou want to open is incompatible.\n\n"
"Do you want to continue to open this MLT XML file?"),
QMessageBox::No |
QMessageBox::Yes,
this);
dialog.setWindowModality(QmlApplication::dialogModality());
dialog.setDefaultButton(QMessageBox::No);
dialog.setEscapeButton(QMessageBox::No);
if (dialog.exec() != QMessageBox::Yes)
return;
}
} else {
showStatusMessage(tr("Failed to open ").append(url));
showIncompatibleProjectMessage(checker.shotcutVersion());
return;
}
Settings.setOpenPath(QFileInfo(url).path());
activateWindow();
if (filenames.length() > 1)
m_multipleFiles = filenames;
if (!MLT.openXML(url)) {
open(MLT.producer());
m_recentDock->add(url);
LOG_INFO() << url;
} else {
showStatusMessage(tr("Failed to open ") + url);
emit openFailed(url);
}
}
}
void MainWindow::on_actionGammaSRGB_triggered(bool checked)
{
Q_UNUSED(checked)
Settings.setPlayerGamma("iec61966_2_1");
MLT.restart();
MLT.refreshConsumer();
}
void MainWindow::on_actionGammaRec709_triggered(bool checked)
{
Q_UNUSED(checked)
Settings.setPlayerGamma("bt709");
MLT.restart();
MLT.refreshConsumer();
}
void MainWindow::onFocusChanged(QWidget *, QWidget * ) const
{
LOG_DEBUG() << "Focuswidget changed";
LOG_DEBUG() << "Current focusWidget:" << QApplication::focusWidget();
LOG_DEBUG() << "Current focusObject:" << QApplication::focusObject();
LOG_DEBUG() << "Current focusWindow:" << QApplication::focusWindow();
}
void MainWindow::on_actionScrubAudio_triggered(bool checked)
{
Settings.setPlayerScrubAudio(checked);
}
#if !defined(Q_OS_MAC)
void MainWindow::onDrawingMethodTriggered(QAction *action)
{
Settings.setDrawMethod(action->data().toInt());
QMessageBox dialog(QMessageBox::Information,
qApp->applicationName(),
tr("You must restart Shotcut to change the display method.\n"
"Do you want to restart now?"),
QMessageBox::No | QMessageBox::Yes,
this);
dialog.setDefaultButton(QMessageBox::Yes);
dialog.setEscapeButton(QMessageBox::No);
dialog.setWindowModality(QmlApplication::dialogModality());
if (dialog.exec() == QMessageBox::Yes) {
m_exitCode = EXIT_RESTART;
QApplication::closeAllWindows();
}
}
#endif
void MainWindow::on_actionApplicationLog_triggered()
{
TextViewerDialog dialog(this);
QDir dir = Settings.appDataLocation();
QFile logFile(dir.filePath("shotcut-log.txt"));
logFile.open(QIODevice::ReadOnly | QIODevice::Text);
dialog.setText(logFile.readAll());
logFile.close();
dialog.setWindowTitle(tr("Application Log"));
dialog.exec();
}
void MainWindow::on_actionClose_triggered()
{
m_timelineDock->stopRecording();
if (continueModified()) {
LOG_DEBUG() << "";
QMutexLocker locker(&m_autosaveMutex);
m_autosaveFile.reset();
locker.unlock();
setCurrentFile("");
MLT.resetURL();
MLT.setProjectFolder(QString());
ui->actionSave->setEnabled(false);
MLT.stop();
if (multitrack())
m_timelineDock->model()->close();
if (playlist())
m_playlistDock->model()->close();
else
onMultitrackClosed();
m_notesDock->setText("");
m_player->enableTab(Player::SourceTabIndex, false);
MLT.purgeMemoryPool();
MLT.resetLocale();
}
}
void MainWindow::onPlayerTabIndexChanged(int index)
{
if (Player::SourceTabIndex == index)
m_timelineDock->saveAndClearSelection();
else
m_timelineDock->restoreSelection();
}
void MainWindow::onUpgradeCheckFinished(QNetworkReply *reply)
{
if (!reply->error()) {
QByteArray response = reply->readAll();
LOG_DEBUG() << "response: " << response;
QJsonDocument json = QJsonDocument::fromJson(response);
QString current = qApp->applicationVersion();
if (!json.isNull() && json.object().value("version_string").type() == QJsonValue::String) {
QString latest = json.object().value("version_string").toString();
if (current != "adhoc"
&& QVersionNumber::fromString(current) < QVersionNumber::fromString(latest)) {
QAction *action = new QAction(tr("Shotcut version %1 is available! Click here to get it.").arg(
latest), 0);
connect(action, SIGNAL(triggered(bool)), SLOT(onUpgradeTriggered()));
if (!json.object().value("url").isUndefined())
m_upgradeUrl = json.object().value("url").toString();
showStatusMessage(action, 15 /* seconds */);
} else {
showStatusMessage(tr("You are running the latest version of Shotcut."));
}
reply->deleteLater();
return;
} else {
LOG_WARNING() << "failed to parse version.json";
}
} else {
LOG_WARNING() << reply->errorString();
}
QAction *action = new QAction(
tr("Failed to read version.json when checking. Click here to go to the Web site."), 0);
connect(action, SIGNAL(triggered(bool)), SLOT(onUpgradeTriggered()));
showStatusMessage(action);
reply->deleteLater();
}
void MainWindow::onUpgradeTriggered()
{
QDesktopServices::openUrl(QUrl(m_upgradeUrl));
}
void MainWindow::onTimelineSelectionChanged()
{
bool enable = (m_timelineDock->selection().size() > 0);
ui->actionCut->setEnabled(enable);
ui->actionCopy->setEnabled(enable);
}
void MainWindow::on_actionCut_triggered()
{
m_timelineDock->show();
m_timelineDock->raise();
m_timelineDock->removeSelection(true);
}
void MainWindow::on_actionCopy_triggered()
{
m_timelineDock->show();
m_timelineDock->raise();
if (!m_timelineDock->selection().isEmpty())
m_timelineDock->copy(m_timelineDock->selection().first().y(),
m_timelineDock->selection().first().x());
}
void MainWindow::on_actionPaste_triggered()
{
m_timelineDock->show();
m_timelineDock->raise();
m_timelineDock->insert(-1);
}
void MainWindow::onClipCopied()
{
m_player->enableTab(Player::SourceTabIndex);
}
void MainWindow::on_actionExportEDL_triggered()
{
// Dialog to get export file name.
QString path = Settings.savePath();
QString caption = tr("Export EDL");
QString saveFileName = QFileDialog::getSaveFileName(this, caption, path,
tr("EDL (*.edl);;All Files (*)"), nullptr, Util::getFileDialogOptions());
if (!saveFileName.isEmpty()) {
QFileInfo fi(saveFileName);
if (fi.suffix() != "edl")
saveFileName += ".edl";
if (Util::warnIfNotWritable(saveFileName, this, caption))
return;
// Locate the JavaScript file in the filesystem.
QDir qmlDir = QmlUtilities::qmlDir();
qmlDir.cd("export-edl");
QString jsFileName = qmlDir.absoluteFilePath("export-edl.js");
QFile scriptFile(jsFileName);
if (scriptFile.open(QIODevice::ReadOnly)) {
// Read JavaScript into a string.
QTextStream stream(&scriptFile);
stream.setCodec("UTF-8");
stream.setAutoDetectUnicode(true);
QString contents = stream.readAll();
scriptFile.close();
// Evaluate JavaScript.
QJSEngine jsEngine;
QJSValue result = jsEngine.evaluate(contents, jsFileName);
if (!result.isError()) {
// Call the JavaScript main function.
QJSValue options = jsEngine.newObject();
options.setProperty("useBaseNameForReelName", true);
options.setProperty("useBaseNameForClipComment", true);
options.setProperty("channelsAV", "AA/V");
QJSValueList args;
args << MLT.XML(0, true, true) << options;
result = result.call(args);
if (!result.isError()) {
// Save the result with the export file name.
QFile f(saveFileName);
f.open(QIODevice::WriteOnly | QIODevice::Text);
f.write(result.toString().toLatin1());
f.close();
}
}
if (result.isError()) {
LOG_ERROR() << "Uncaught exception at line"
<< result.property("lineNumber").toInt()
<< ":" << result.toString();
showStatusMessage(tr("A JavaScript error occurred during export."));
}
} else {
showStatusMessage(tr("Failed to open export-edl.js"));
}
}
}
void MainWindow::on_actionExportFrame_triggered()
{
if (!MLT.producer() || !MLT.producer()->is_valid()) return;
filterController()->setCurrentFilter(QmlFilter::DeselectCurrentFilter);
Mlt::GLWidget *glw = qobject_cast(MLT.videoWidget());
connect(glw, SIGNAL(imageReady()), SLOT(onGLWidgetImageReady()));
MLT.setPreviewScale(0);
glw->requestImage();
MLT.refreshConsumer();
}
void MainWindow::onGLWidgetImageReady()
{
Mlt::GLWidget *glw = qobject_cast(MLT.videoWidget());
QImage image = glw->image();
disconnect(glw, SIGNAL(imageReady()), this, nullptr);
if (Settings.playerGPU() || Settings.playerPreviewScale()) {
MLT.setPreviewScale(Settings.playerPreviewScale());
}
if (!image.isNull() &&
(glw->imageIsProxy() || (MLT.isMultitrack() && Settings.proxyEnabled()))
) {
QMessageBox dialog(QMessageBox::Question,
tr("Export frame from proxy?"),
tr("This frame may be from a lower resolution proxy instead of the original source.\n\n"
"Do you still want to continue?"),
QMessageBox::No | QMessageBox::Yes,
this);
dialog.setDefaultButton(QMessageBox::Yes);
dialog.setEscapeButton(QMessageBox::No);
dialog.setWindowModality(QmlApplication::dialogModality());
if (dialog.exec() != QMessageBox::Yes) {
return;
}
}
if (!image.isNull()) {
SaveImageDialog dialog(this, tr("Export Frame"), image);
dialog.exec();
if ( !dialog.saveFile().isEmpty() ) {
m_recentDock->add(dialog.saveFile());
}
} else {
showStatusMessage(tr("Unable to export frame."));
}
}
void MainWindow::on_actionAppDataSet_triggered()
{
QMessageBox dialog(QMessageBox::Information,
qApp->applicationName(),
tr("You must restart Shotcut to change the data directory.\n"
"Do you want to continue?"),
QMessageBox::No | QMessageBox::Yes,
this);
dialog.setDefaultButton(QMessageBox::Yes);
dialog.setEscapeButton(QMessageBox::No);
dialog.setWindowModality(QmlApplication::dialogModality());
if (dialog.exec() != QMessageBox::Yes) return;
QString dirName = QFileDialog::getExistingDirectory(this, tr("Data Directory"),
Settings.appDataLocation(),
Util::getFileDialogOptions());
if (!dirName.isEmpty()) {
// Move the data files.
QDirIterator it(Settings.appDataLocation());
while (it.hasNext()) {
if (!it.filePath().isEmpty() && it.fileName() != "." && it.fileName() != "..") {
if (!QFile::exists(dirName + "/" + it.fileName())) {
if (it.fileInfo().isDir()) {
if (!QFile::rename(it.filePath(), dirName + "/" + it.fileName()))
LOG_WARNING() << "Failed to move" << it.filePath() << "to" << dirName + "/" + it.fileName();
} else {
if (!QFile::copy(it.filePath(), dirName + "/" + it.fileName()))
LOG_WARNING() << "Failed to copy" << it.filePath() << "to" << dirName + "/" + it.fileName();
}
}
}
it.next();
}
writeSettings();
Settings.setAppDataLocally(dirName);
m_exitCode = EXIT_RESTART;
QApplication::closeAllWindows();
}
}
void MainWindow::on_actionAppDataShow_triggered()
{
Util::showInFolder(Settings.appDataLocation());
}
void MainWindow::on_actionNew_triggered()
{
on_actionClose_triggered();
}
void MainWindow::on_actionKeyboardShortcuts_triggered()
{
QDesktopServices::openUrl(QUrl("https://www.shotcut.org/howtos/keyboard-shortcuts/"));
}
void MainWindow::on_actionLayoutLogging_triggered()
{
Settings.setLayout(QString(kReservedLayoutPrefix).arg(Settings.layoutMode()), QByteArray(),
saveState());
Settings.setLayoutMode(LayoutMode::Logging);
auto state = Settings.layoutState(QString(kReservedLayoutPrefix).arg(LayoutMode::Logging));
if (state.isEmpty()) {
restoreState(kLayoutLoggingDefault);
// setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
// setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
// setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
// setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
// resizeDocks({m_playlistDock, m_propertiesDock},
// {qFloor(width() * 0.25), qFloor(width() * 0.25)}, Qt::Horizontal);
} else {
// LOG_DEBUG() << state.toBase64();
restoreState(state);
}
Settings.setWindowState(saveState());
}
void MainWindow::on_actionLayoutEditing_triggered()
{
Settings.setLayout(QString(kReservedLayoutPrefix).arg(Settings.layoutMode()), QByteArray(),
saveState());
Settings.setLayoutMode(LayoutMode::Editing);
auto state = Settings.layoutState(QString(kReservedLayoutPrefix).arg(LayoutMode::Editing));
if (state.isEmpty()) {
restoreState(kLayoutEditingDefault);
// resetDockCorners();
} else {
// LOG_DEBUG() << state.toBase64();
restoreState(state);
}
Settings.setWindowState(saveState());
}
void MainWindow::on_actionLayoutEffects_triggered()
{
Settings.setLayout(QString(kReservedLayoutPrefix).arg(Settings.layoutMode()), QByteArray(),
saveState());
Settings.setLayoutMode(LayoutMode::Effects);
auto state = Settings.layoutState(QString(kReservedLayoutPrefix).arg(LayoutMode::Effects));
if (state.isEmpty()) {
restoreState(kLayoutEffectsDefault);
// resetDockCorners();
} else {
// LOG_DEBUG() << state.toBase64();
restoreState(state);
}
Settings.setWindowState(saveState());
}
void MainWindow::on_actionLayoutColor_triggered()
{
Settings.setLayout(QString(kReservedLayoutPrefix).arg(Settings.layoutMode()), QByteArray(),
saveState());
Settings.setLayoutMode(LayoutMode::Color);
auto state = Settings.layoutState(QString(kReservedLayoutPrefix).arg(LayoutMode::Color));
if (state.isEmpty()) {
restoreState(kLayoutColorDefault);
// setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
// setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
// setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea);
// setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
} else {
// LOG_DEBUG() << state.toBase64();
restoreState(state);
}
Settings.setWindowState(saveState());
}
void MainWindow::on_actionLayoutAudio_triggered()
{
Settings.setLayout(QString(kReservedLayoutPrefix).arg(Settings.layoutMode()), QByteArray(),
saveState());
Settings.setLayoutMode(LayoutMode::Audio);
auto state = Settings.layoutState(QString(kReservedLayoutPrefix).arg(LayoutMode::Audio));
if (state.isEmpty()) {
restoreState(kLayoutAudioDefault);
// setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
// setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
// setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea);
// setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
} else {
// LOG_DEBUG() << state.toBase64();
restoreState(state);
}
Settings.setWindowState(saveState());
}
void MainWindow::on_actionLayoutPlayer_triggered()
{
Settings.setLayout(QString(kReservedLayoutPrefix).arg(Settings.layoutMode()), QByteArray(),
saveState());
Settings.setLayoutMode(LayoutMode::PlayerOnly);
auto state = Settings.layoutState(QString(kReservedLayoutPrefix).arg(LayoutMode::PlayerOnly));
if (state.isEmpty()) {
restoreState(kLayoutPlayerDefault);
// resetDockCorners();
} else {
// LOG_DEBUG() << state.toBase64();
restoreState(state);
}
Settings.setWindowState(saveState());
}
void MainWindow::on_actionLayoutPlaylist_triggered()
{
if (Settings.layoutMode() != LayoutMode::Custom) {
Settings.setLayout(QString(kReservedLayoutPrefix).arg(Settings.layoutMode()), QByteArray(),
saveState());
Settings.setLayoutMode(LayoutMode::Custom);
}
clearCurrentLayout();
restoreState(Settings.windowStateDefault());
m_recentDock->show();
m_recentDock->raise();
m_playlistDock->show();
m_playlistDock->raise();
Settings.setWindowState(saveState());
}
void MainWindow::on_actionLayoutClip_triggered()
{
if (Settings.layoutMode() != LayoutMode::Custom) {
Settings.setLayout(QString(kReservedLayoutPrefix).arg(Settings.layoutMode()), QByteArray(),
saveState());
Settings.setLayoutMode(LayoutMode::Custom);
}
clearCurrentLayout();
restoreState(Settings.windowStateDefault());
m_recentDock->show();
m_recentDock->raise();
m_filtersDock->show();
m_filtersDock->raise();
Settings.setWindowState(saveState());
}
void MainWindow::on_actionLayoutAdd_triggered()
{
QInputDialog dialog(this);
dialog.setInputMode(QInputDialog::TextInput);
dialog.setWindowTitle(tr("Add Custom Layout"));
dialog.setLabelText(tr("Name"));
dialog.setWindowModality(QmlApplication::dialogModality());
auto result = dialog.exec();
auto name = dialog.textValue();
if (result == QDialog::Accepted && !name.isEmpty()) {
if (Settings.setLayout(name, saveGeometry(), saveState())) {
Settings.setLayoutMode();
clearCurrentLayout();
Settings.sync();
if (Settings.layouts().size() == 1) {
ui->menuLayout->addAction(ui->actionLayoutRemove);
ui->menuLayout->addSeparator();
}
ui->menuLayout->addAction(addLayout(m_layoutGroup, name));
}
}
}
void MainWindow::onLayoutTriggered(QAction *action)
{
if (Settings.layoutMode() != LayoutMode::Custom) {
Settings.setLayout(QString(kReservedLayoutPrefix).arg(Settings.layoutMode()), QByteArray(),
saveState());
Settings.setLayoutMode(LayoutMode::Custom);
}
clearCurrentLayout();
restoreState(Settings.layoutState(action->text()));
Settings.setWindowState(saveState());
}
void MainWindow::on_actionProfileRemove_triggered()
{
QDir dir(Settings.appDataLocation());
if (dir.cd("profiles")) {
// Setup the dialog.
QStringList profiles = dir.entryList(QDir::Files | QDir::NoDotAndDotDot | QDir::Readable);
ListSelectionDialog dialog(profiles, this);
dialog.setWindowModality(QmlApplication::dialogModality());
dialog.setWindowTitle(tr("Remove Video Mode"));
// Show the dialog.
if (dialog.exec() == QDialog::Accepted) {
removeCustomProfiles(dialog.selection(), dir, customProfileMenu(), actionProfileRemove());
}
}
}
void MainWindow::on_actionLayoutRemove_triggered()
{
// Setup the dialog.
ListSelectionDialog dialog(Settings.layouts(), this);
dialog.setWindowModality(QmlApplication::dialogModality());
dialog.setWindowTitle(tr("Remove Layout"));
// Show the dialog.
if (dialog.exec() == QDialog::Accepted) {
foreach (const QString &layout, dialog.selection()) {
// Update the configuration.
if (Settings.removeLayout(layout))
Settings.sync();
// Locate the menu item.
foreach (QAction *action, ui->menuLayout->actions()) {
if (action->text() == layout) {
// Remove the menu item.
delete action;
break;
}
}
}
// If no more custom layouts.
if (Settings.layouts().size() == 0) {
// Remove the Remove action and separator.
ui->menuLayout->removeAction(ui->actionLayoutRemove);
bool isSecondSeparator = false;
foreach (QAction *action, ui->menuLayout->actions()) {
if (action->isSeparator()) {
if (isSecondSeparator) {
delete action;
break;
} else {
isSecondSeparator = true;
}
}
}
}
}
}
void MainWindow::on_actionOpenOther2_triggered()
{
ui->actionOpenOther2->menu()->popup(mapToGlobal(ui->mainToolBar->geometry().bottomLeft()) + QPoint(
64, 0));
}
void MainWindow::onOpenOtherTriggered(QWidget *widget)
{
QDialog dialog(this);
dialog.resize(426, 288);
QVBoxLayout vlayout(&dialog);
vlayout.addWidget(widget);
QDialogButtonBox buttonBox(&dialog);
buttonBox.setOrientation(Qt::Horizontal);
buttonBox.setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
vlayout.addWidget(&buttonBox);
connect(&buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
connect(&buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject()));
QString name = widget->objectName();
if (name == "NoiseWidget" || dialog.exec() == QDialog::Accepted) {
closeProducer();
auto &profile = MLT.profile();
auto producer = dynamic_cast(widget)->newProducer(profile);
if (!profile.is_explicit()) {
profile.from_producer(*producer);
profile.set_width(Util::coerceMultiple(profile.width()));
profile.set_height(Util::coerceMultiple(profile.height()));
}
MLT.updatePreviewProfile();
setPreviewScale(Settings.playerPreviewScale());
open(producer);
if (name == "TextProducerWidget") {
m_filtersDock->show();
m_filtersDock->raise();
} else {
m_propertiesDock->show();
m_propertiesDock->raise();
}
}
delete widget;
}
void MainWindow::onOpenOtherTriggered()
{
if (sender()->objectName() == "color")
onOpenOtherTriggered(new ColorProducerWidget(this));
else if (sender()->objectName() == "text")
onOpenOtherTriggered(new TextProducerWidget(this));
else if (sender()->objectName() == "glaxnimate")
onOpenOtherTriggered(new GlaxnimateProducerWidget(this));
else if (sender()->objectName() == "noise")
onOpenOtherTriggered(new NoiseWidget(this));
else if (sender()->objectName() == "ising0r")
onOpenOtherTriggered(new IsingWidget(this));
else if (sender()->objectName() == "lissajous0r")
onOpenOtherTriggered(new LissajousWidget(this));
else if (sender()->objectName() == "plasma")
onOpenOtherTriggered(new PlasmaWidget(this));
else if (sender()->objectName() == "test_pat_B")
onOpenOtherTriggered(new ColorBarsWidget(this));
else if (sender()->objectName() == "tone")
onOpenOtherTriggered(new ToneProducerWidget(this));
else if (sender()->objectName() == "count")
onOpenOtherTriggered(new CountProducerWidget(this));
else if (sender()->objectName() == "blipflash")
onOpenOtherTriggered(new BlipProducerWidget(this));
else if (sender()->objectName() == "v4l2")
onOpenOtherTriggered(new Video4LinuxWidget(this));
else if (sender()->objectName() == "pulse")
onOpenOtherTriggered(new PulseAudioWidget(this));
else if (sender()->objectName() == "jack")
onOpenOtherTriggered(new JackProducerWidget(this));
else if (sender()->objectName() == "alsa")
onOpenOtherTriggered(new AlsaWidget(this));
#if defined(Q_OS_MAC)
else if (sender()->objectName() == "device")
onOpenOtherTriggered(new AvfoundationProducerWidget(this));
#elif defined(Q_OS_WIN)
else if (sender()->objectName() == "device")
onOpenOtherTriggered(new DirectShowVideoWidget(this));
#endif
else if (sender()->objectName() == "decklink")
onOpenOtherTriggered(new DecklinkProducerWidget(this));
}
void MainWindow::on_actionClearRecentOnExit_toggled(bool arg1)
{
Settings.setClearRecent(arg1);
if (arg1)
Settings.setRecent(QStringList());
}
void MainWindow::onSceneGraphInitialized()
{
if (Settings.playerGPU() && Settings.playerWarnGPU()) {
QMessageBox dialog(QMessageBox::Warning,
qApp->applicationName(),
tr("GPU effects are EXPERIMENTAL, UNSTABLE and UNSUPPORTED! Unsupported means do not report bugs about it.\n\n"
"Do you want to disable GPU effects and restart Shotcut?"),
QMessageBox::No | QMessageBox::Yes,
this);
dialog.setDefaultButton(QMessageBox::Yes);
dialog.setEscapeButton(QMessageBox::No);
dialog.setWindowModality(QmlApplication::dialogModality());
if (dialog.exec() == QMessageBox::Yes) {
ui->actionGPU->setChecked(false);
m_exitCode = EXIT_RESTART;
QApplication::closeAllWindows();
} else {
ui->actionGPU->setVisible(true);
}
} else if (Settings.playerGPU()) {
ui->actionGPU->setVisible(true);
}
}
void MainWindow::on_actionShowTextUnderIcons_toggled(bool b)
{
ui->mainToolBar->setToolButtonStyle(b ? Qt::ToolButtonTextUnderIcon : Qt::ToolButtonIconOnly);
Settings.setTextUnderIcons(b);
updateLayoutSwitcher();
}
void MainWindow::on_actionShowSmallIcons_toggled(bool b)
{
ui->mainToolBar->setIconSize(b ? QSize(16, 16) : QSize());
Settings.setSmallIcons(b);
updateLayoutSwitcher();
}
void MainWindow::onPlaylistInChanged(int in)
{
m_player->blockSignals(true);
m_player->setIn(in);
m_player->blockSignals(false);
}
void MainWindow::onPlaylistOutChanged(int out)
{
m_player->blockSignals(true);
m_player->setOut(out);
m_player->blockSignals(false);
}
void MainWindow::on_actionPreviewNone_triggered(bool checked)
{
if (checked) {
Settings.setPlayerPreviewScale(0);
setPreviewScale(0);
m_player->showIdleStatus();
}
}
void MainWindow::on_actionPreview360_triggered(bool checked)
{
if (checked) {
Settings.setPlayerPreviewScale(360);
setPreviewScale(360);
m_player->showIdleStatus();
}
}
void MainWindow::on_actionPreview540_triggered(bool checked)
{
if (checked) {
Settings.setPlayerPreviewScale(540);
setPreviewScale(540);
m_player->showIdleStatus();
}
}
void MainWindow::on_actionPreview720_triggered(bool checked)
{
if (checked) {
Settings.setPlayerPreviewScale(720);
setPreviewScale(720);
m_player->showIdleStatus();
}
}
QUuid MainWindow::timelineClipUuid(int trackIndex, int clipIndex)
{
QScopedPointer info(m_timelineDock->getClipInfo(trackIndex, clipIndex));
if (info && info->cut && info->cut->is_valid())
return MLT.ensureHasUuid(*info->cut);
return QUuid();
}
void MainWindow::replaceInTimeline(const QUuid &uuid, Mlt::Producer &producer)
{
int trackIndex = -1;
int clipIndex = -1;
// lookup the current track and clip index by UUID
QScopedPointer info(MAIN.timelineClipInfoByUuid(uuid, trackIndex, clipIndex));
if (trackIndex >= 0 && clipIndex >= 0) {
Util::getHash(producer);
Util::applyCustomProperties(producer, *info->producer, producer.get_in(), producer.get_out());
m_timelineDock->replace(trackIndex, clipIndex, MLT.XML(&producer));
}
}
Mlt::ClipInfo *MainWindow::timelineClipInfoByUuid(const QUuid &uuid, int &trackIndex,
int &clipIndex)
{
return m_timelineDock->model()->findClipByUuid(uuid, trackIndex, clipIndex);
}
void MainWindow::replaceAllByHash(const QString &hash, Mlt::Producer &producer, bool isProxy)
{
Util::getHash(producer);
if (!isProxy)
m_recentDock->add(producer.get("resource"));
if (MLT.isClip() && Util::getHash(*MLT.producer()) == hash) {
Util::applyCustomProperties(producer, *MLT.producer(), MLT.producer()->get_in(),
MLT.producer()->get_out());
MLT.copyFilters(*MLT.producer(), producer);
MLT.close();
m_player->setPauseAfterOpen(true);
open(new Mlt::Producer(MLT.profile(), "xml-string", MLT.XML(&producer).toUtf8().constData()));
} else if (MLT.savedProducer() && Util::getHash(*MLT.savedProducer()) == hash) {
Util::applyCustomProperties(producer, *MLT.savedProducer(), MLT.savedProducer()->get_in(),
MLT.savedProducer()->get_out());
MLT.copyFilters(*MLT.savedProducer(), producer);
MLT.setSavedProducer(&producer);
}
if (playlist()) {
if (isProxy) {
m_playlistDock->replaceClipsWithHash(hash, producer);
} else {
// Append to playlist
producer.set(kPlaylistIndexProperty, playlist()->count());
MAIN.undoStack()->push(
new Playlist::AppendCommand(*m_playlistDock->model(), MLT.XML(&producer)));
}
}
if (isMultitrackValid()) {
m_timelineDock->replaceClipsWithHash(hash, producer);
}
}
void MainWindow::on_actionTopics_triggered()
{
QDesktopServices::openUrl(QUrl("https://www.shotcut.org/howtos/"));
}
void MainWindow::on_actionSync_triggered()
{
auto dialog = new SystemSyncDialog(this);
dialog->show();
dialog->raise();
dialog->activateWindow();
}
void MainWindow::on_actionUseProxy_triggered(bool checked)
{
if (MLT.producer()) {
QDir dir(m_currentFile.isEmpty() ? QDir::tempPath() : QFileInfo(m_currentFile).dir());
QScopedPointer tmp(new QTemporaryFile(dir.filePath("shotcut-XXXXXX.mlt")));
tmp->open();
tmp->close();
QString fileName = tmp->fileName();
tmp->remove();
tmp.reset();
LOG_DEBUG() << fileName;
if (saveXML(fileName)) {
MltXmlChecker checker;
Settings.setProxyEnabled(checked);
checker.check(fileName);
if (!isXmlRepaired(checker, fileName)) {
QFile::remove(fileName);
return;
}
if (checker.isUpdated()) {
QFile::remove(fileName);
fileName = checker.tempFile().fileName();
}
// Open the temporary file
int result = 0;
{
LongUiTask longTask(checked ? tr("Turn Proxy On") : tr("Turn Proxy Off"));
QFuture future = QtConcurrent::run([ = ]() {
return MLT.open(QDir::fromNativeSeparators(fileName), QDir::fromNativeSeparators(m_currentFile));
});
result = longTask.wait(tr("Converting"), future);
}
if (!result) {
auto position = m_player->position();
m_undoStack->clear();
m_player->stop();
m_player->setPauseAfterOpen(true);
open(MLT.producer());
MLT.seek(m_player->position());
m_player->seek(position);
if (checked && (isPlaylistValid() || isMultitrackValid())) {
// Prompt user if they want to create missing proxies
QMessageBox dialog(QMessageBox::Question, qApp->applicationName(),
tr("Do you want to create missing proxies for every file in this project?\n\n"
"You must reopen your project after all proxy jobs are finished."),
QMessageBox::No | QMessageBox::Yes, this);
dialog.setWindowModality(QmlApplication::dialogModality());
dialog.setDefaultButton(QMessageBox::Yes);
dialog.setEscapeButton(QMessageBox::No);
if (dialog.exec() == QMessageBox::Yes) {
Mlt::Producer producer(playlist());
if (producer.is_valid()) {
ProxyManager::generateIfNotExistsAll(producer);
}
producer = multitrack();
if (producer.is_valid()) {
ProxyManager::generateIfNotExistsAll(producer);
}
}
}
} else if (fileName != untitledFileName()) {
showStatusMessage(tr("Failed to open ") + fileName);
emit openFailed(fileName);
}
} else {
ui->actionUseProxy->setChecked(!checked);
showSaveError();
}
QFile::remove(fileName);
} else {
Settings.setProxyEnabled(checked);
}
m_player->showIdleStatus();
}
void MainWindow::on_actionProxyStorageSet_triggered()
{
// Present folder dialog just like App Data Directory
QString dirName = QFileDialog::getExistingDirectory(this, tr("Proxy Folder"),
Settings.proxyFolder(),
Util::getFileDialogOptions());
if (!dirName.isEmpty() && dirName != Settings.proxyFolder()) {
auto oldFolder = Settings.proxyFolder();
Settings.setProxyFolder(dirName);
Settings.sync();
// Get a count for the progress dialog
auto oldDir = QDir(oldFolder);
auto dirList = oldDir.entryList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
auto count = dirList.size();
if (count > 0) {
// Prompt user if they want to create missing proxies
QMessageBox dialog(QMessageBox::Question, qApp->applicationName(),
tr("Do you want to move all files from the old folder to the new folder?"),
QMessageBox::No | QMessageBox::Yes, this);
dialog.setWindowModality(QmlApplication::dialogModality());
dialog.setDefaultButton(QMessageBox::Yes);
dialog.setEscapeButton(QMessageBox::No);
if (dialog.exec() == QMessageBox::Yes) {
// Move the existing files
LongUiTask longTask(tr("Moving Files"));
int i = 0;
for (const auto &fileName : dirList) {
if (!fileName.isEmpty() && !QFile::exists(dirName + "/" + fileName)) {
LOG_DEBUG() << "moving" << oldDir.filePath(fileName) << "to" << dirName + "/" + fileName;
longTask.reportProgress(fileName, i++, count);
if (!QFile::rename(oldDir.filePath(fileName), dirName + "/" + fileName))
LOG_WARNING() << "Failed to move" << oldDir.filePath(fileName);
}
}
}
}
}
}
void MainWindow::on_actionProxyStorageShow_triggered()
{
Util::showInFolder(ProxyManager::dir().path());
}
void MainWindow::on_actionProxyUseProjectFolder_triggered(bool checked)
{
Settings.setProxyUseProjectFolder(checked);
}
void MainWindow::on_actionProxyUseHardware_triggered(bool checked)
{
if (checked && Settings.encodeHardware().isEmpty()) {
if (!m_encodeDock->detectHardwareEncoders())
ui->actionProxyUseHardware->setChecked(false);
}
Settings.setProxyUseHardware(ui->actionProxyUseHardware->isChecked());
}
void MainWindow::on_actionProxyConfigureHardware_triggered()
{
m_encodeDock->on_hwencodeButton_clicked();
if (Settings.encodeHardware().isEmpty()) {
ui->actionProxyUseHardware->setChecked(false);
Settings.setProxyUseHardware(false);
}
}
void MainWindow::updateLayoutSwitcher()
{
if (Settings.textUnderIcons() && !Settings.smallIcons()) {
auto layoutSwitcher = findChild(kLayoutSwitcherName);
if (layoutSwitcher) {
layoutSwitcher->show();
for (const auto &child : layoutSwitcher->findChildren()) {
child->show();
}
} else {
layoutSwitcher = new QWidget;
layoutSwitcher->setObjectName(kLayoutSwitcherName);
auto layoutGrid = new QGridLayout(layoutSwitcher);
layoutGrid->setContentsMargins(0, 0, 0, 0);
ui->mainToolBar->insertWidget(ui->dummyAction, layoutSwitcher);
auto button = new QToolButton;
button->setAutoRaise(true);
button->setDefaultAction(ui->actionLayoutLogging);
layoutGrid->addWidget(button, 0, 0, Qt::AlignCenter);
button = new QToolButton;
button->setAutoRaise(true);
button->setDefaultAction(ui->actionLayoutEditing);
layoutGrid->addWidget(button, 0, 1, Qt::AlignCenter);
button = new QToolButton;
button->setAutoRaise(true);
button->setDefaultAction(ui->actionLayoutEffects);
layoutGrid->addWidget(button, 0, 2, Qt::AlignCenter);
button = new QToolButton;
button->setAutoRaise(true);
button->setDefaultAction(ui->actionLayoutColor);
layoutGrid->addWidget(button, 1, 0, Qt::AlignCenter);
button = new QToolButton;
button->setAutoRaise(true);
button->setDefaultAction(ui->actionLayoutAudio);
layoutGrid->addWidget(button, 1, 1, Qt::AlignCenter);
button = new QToolButton;
button->setAutoRaise(true);
button->setDefaultAction(ui->actionLayoutPlayer);
layoutGrid->addWidget(button, 1, 2, Qt::AlignCenter);
}
ui->mainToolBar->removeAction(ui->actionLayoutLogging);
ui->mainToolBar->removeAction(ui->actionLayoutEditing);
ui->mainToolBar->removeAction(ui->actionLayoutEffects);
ui->mainToolBar->removeAction(ui->actionLayoutColor);
ui->mainToolBar->removeAction(ui->actionLayoutAudio);
ui->mainToolBar->removeAction(ui->actionLayoutPlayer);
} else {
auto layoutSwitcher = findChild(kLayoutSwitcherName);
if (layoutSwitcher) {
layoutSwitcher->hide();
for (const auto &child : layoutSwitcher->findChildren()) {
child->hide();
}
ui->mainToolBar->insertAction(ui->dummyAction, ui->actionLayoutLogging);
ui->mainToolBar->insertAction(ui->dummyAction, ui->actionLayoutEditing);
ui->mainToolBar->insertAction(ui->dummyAction, ui->actionLayoutEffects);
ui->mainToolBar->insertAction(ui->dummyAction, ui->actionLayoutColor);
ui->mainToolBar->insertAction(ui->dummyAction, ui->actionLayoutAudio);
ui->mainToolBar->insertAction(ui->dummyAction, ui->actionLayoutPlayer);
}
}
}
void MainWindow::clearCurrentLayout()
{
auto currentLayout = ui->actionLayoutLogging->actionGroup()->checkedAction();
if (currentLayout) {
currentLayout->setChecked(false);
}
}
void MainWindow::onClipboardChanged()
{
auto s = QGuiApplication::clipboard()->text();
if (MLT.isMltXml(s) && !s.contains(kShotcutFiltersClipboard)) {
m_clipboardUpdatedAt = QDateTime::currentDateTime();
LOG_DEBUG() << m_clipboardUpdatedAt;
}
}
void MainWindow::sourceUpdated()
{
if (MLT.isClip()) {
m_sourceUpdatedAt = QDateTime::currentDateTime();
}
}
void MainWindow::resetSourceUpdated()
{
m_sourceUpdatedAt.setSecsSinceEpoch(0);
}
void MainWindow::on_actionExportChapters_triggered()
{
// Dialog to get export file name.
QString path = Settings.savePath();
QString caption = tr("Export Chapters");
QString saveFileName = QFileDialog::getSaveFileName(this, caption, path,
tr("Text (*.txt);;All Files (*)"), nullptr, Util::getFileDialogOptions());
if (!saveFileName.isEmpty()) {
QFileInfo fi(saveFileName);
if (fi.suffix() != "txt")
saveFileName += ".txt";
if (Util::warnIfNotWritable(saveFileName, this, caption))
return;
// Locate the JavaScript file in the filesystem.
QDir qmlDir = QmlUtilities::qmlDir();
qmlDir.cd("export-chapters");
QString jsFileName = qmlDir.absoluteFilePath("export-chapters.js");
QFile scriptFile(jsFileName);
if (scriptFile.open(QIODevice::ReadOnly)) {
// Read JavaScript into a string.
QTextStream stream(&scriptFile);
stream.setCodec("UTF-8");
stream.setAutoDetectUnicode(true);
QString contents = stream.readAll();
scriptFile.close();
// Evaluate JavaScript.
QJSEngine jsEngine;
QJSValue result = jsEngine.evaluate(contents, jsFileName);
if (!result.isError()) {
// Call the JavaScript main function.
QJSValueList args;
args << MLT.XML(0, true, true);
result = result.call(args);
if (!result.isError()) {
// Save the result with the export file name.
QFile f(saveFileName);
f.open(QIODevice::WriteOnly | QIODevice::Text);
f.write(result.toString().toLatin1());
f.close();
}
}
if (result.isError()) {
LOG_ERROR() << "Uncaught exception at line"
<< result.property("lineNumber").toInt()
<< ":" << result.toString();
showStatusMessage(tr("A JavaScript error occurred during export."));
}
} else {
showStatusMessage(tr("Failed to open export-chapters.js"));
}
}
}
void MainWindow::on_actionAudioVideoDevice_triggered()
{
QDialog dialog(this);
dialog.setWindowModality(QmlApplication::dialogModality());
auto layout = new QVBoxLayout(&dialog);
QWidget *widget = nullptr;
#if defined(Q_OS_LINUX)
widget = new Video4LinuxWidget;
dialog.resize(500, 400);
dialog.setSizeGripEnabled(true);
#elif defined(Q_OS_MAC)
widget = new AvfoundationProducerWidget;
dialog.resize(1, 1);
#elif defined(Q_OS_WIN)
widget = new DirectShowVideoWidget;
dialog.resize(1, 1);
#endif
layout->addWidget(widget);
auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
layout->addWidget(buttonBox);
if (dialog.exec() == QDialog::Accepted) {
Mlt::Profile profile;
profile.set_explicit(1);
delete dynamic_cast(widget)->newProducer(profile);
}
}