/* * Copyright (c) 2011-2026 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 "Logger.h" #include "actions.h" #include "autosavefile.h" #include "database.h" #include "dialogs/filedownloaddialog.h" #include "dialogs/listselectiondialog.h" #include "dialogs/longuitask.h" #include "dialogs/resourcedialog.h" #include "dialogs/textviewerdialog.h" #include "docks/jobsdock.h" #include "jobqueue.h" #include "openotherdialog.h" #include "settings.h" #include "util.h" // Widget includes disabled - video-specific modules // #include "widgets/..." #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SHOTCUT_THEME 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"); static QRegularExpression kBackupFileRegex("^(.+) " "([0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[" "0-3]|[01][0-9])-([0-5][0-9])-([0-5][0-9]).mlt$"); MainWindow::MainWindow() : QMainWindow(0) , ui(new Ui::MainWindow) , m_isKKeyPressed(false) , m_keyerGroup(0) , m_previewScaleGroup(0) , m_keyerMenu(0) , m_multipleFilesLoading(false) , m_isPlaylistLoaded(false) , m_exitCode(EXIT_SUCCESS) , m_upgradeUrl("https://www.shotcut.org/download/") , m_keyframesDock(0) { #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) 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(); 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); setDockNestingEnabled(true); const auto highlight = palette().highlight().color(); setStyleSheet(QString("QMainWindow::separator {" " width: 10px;" "}" "QMainWindow::separator:hover {" " background-color: rgba(%1, %2, %3, 51);" "}") .arg(highlight.red()) .arg(highlight.green()) .arg(highlight.blue())); ui->statusBar->hide(); connectUISignals(); // Accept drag-n-drop of files. this->setAcceptDrops(true); setupAndConnectUndoStack(); // Add the player widget. // setupAndConnectPlayerWidget(); // DISABLED: video player removed setupSettingsMenu(); setupOpenOtherMenu(); // readPlayerSettings(); // DISABLED: video-specific // configureVideoWidget(); // DISABLED: video-specific // Restore custom colors from settings Settings.restoreCustomColors(); // centerLayoutInRemainingToolbarSpace(); // DISABLED: video-specific #ifndef SHOTCUT_NOUPGRADE if (Settings.noUpgrade() || qApp->property("noupgrade").toBool()) #endif delete ui->actionUpgrade; // setupAndConnectDocks(); // DISABLED: mostly video // setupMenuFile(); // DISABLED // setupMenuView(); // DISABLED // connectVideoWidgetSignals(); // DISABLED readWindowSettings(); setupActions(); setupLayoutSwitcher(); setFocus(); setCurrentFile(""); 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())); QThreadPool::globalInstance()->setThreadPriority(QThread::LowPriority); QImageReader::setAllocationLimit(1024); ProxyManager::removePending(); for (auto &child : findChildren()) { if (child->whatsThis().isEmpty() && !child->toolTip().isEmpty()) child->setWhatsThis(child->toolTip()); } LOG_DEBUG() << "end"; } void MainWindow::connectFocusSignals() { if (!qEnvironmentVariableIsEmpty("OBSERVE_FOCUS")) { 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 (!qEnvironmentVariableIsEmpty("EVENT_DEBUG")) 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(ui->actionWhatsThis, SIGNAL(triggered()), this, SLOT(on_actionWhatsThis_triggered())); connect(this, &MainWindow::producerOpened, this, &MainWindow::onProducerOpened); connect(ui->mainToolBar, SIGNAL(visibilityChanged(bool)), SLOT(onToolbarVisibilityChanged(bool))); ui->actionSave->setEnabled(false); connect(this, &MainWindow::audioChannelsChanged, this, &MainWindow::updateWindowTitle); connect(this, &MainWindow::producerOpened, this, &MainWindow::updateWindowTitle); connect(this, &MainWindow::profileChanged, this, &MainWindow::updateWindowTitle); } 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->addAction(undoAction); ui->menuEdit->addAction(redoAction); ui->menuEdit->addSeparator(); 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); qApp->installEventFilter(this); // Install global event filter for WhatsThis events 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(started()), SLOT(processSingleFile())); 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))); ui->menuPlayer->addAction(Actions["playerPlayPauseAction"]); ui->menuPlayer->addAction(Actions["playerLoopAction"]); QMenu *loopRangeMenu = new QMenu(tr("Set Loop Range"), this); loopRangeMenu->addAction(Actions["playerLoopRangeAllAction"]); loopRangeMenu->addAction(Actions["playerLoopRangeMarkerAction"]); loopRangeMenu->addAction(Actions["playerLoopRangeSelectionAction"]); loopRangeMenu->addAction(Actions["playerLoopRangeAroundAction"]); ui->menuPlayer->addMenu(loopRangeMenu); ui->menuPlayer->addAction(Actions["playerFastForwardAction"]); ui->menuPlayer->addAction(Actions["playerRewindAction"]); ui->menuPlayer->addAction(Actions["playerSkipNextAction"]); ui->menuPlayer->addAction(Actions["playerSkipPreviousAction"]); ui->menuPlayer->addAction(Actions["playerSeekStartAction"]); ui->menuPlayer->addAction(Actions["playerSeekEndAction"]); ui->menuPlayer->addAction(Actions["playerNextFrameAction"]); ui->menuPlayer->addAction(Actions["playerPreviousFrameAction"]); ui->menuPlayer->addAction(Actions["playerForwardOneSecondAction"]); ui->menuPlayer->addAction(Actions["playerBackwardOneSecondAction"]); ui->menuPlayer->addAction(Actions["playerForwardTwoSecondsAction"]); ui->menuPlayer->addAction(Actions["playerBackwardTwoAction"]); ui->menuPlayer->addAction(Actions["playerForwardFiveSecondsAction"]); ui->menuPlayer->addAction(Actions["playerBackwardFiveSecondsAction"]); ui->menuPlayer->addAction(Actions["playerForwardTenSecondsAction"]); ui->menuPlayer->addAction(Actions["playerBackwardTenSecondsAction"]); ui->menuPlayer->addAction(Actions["playerForwardJumpAction"]); ui->menuPlayer->addAction(Actions["playerBackwardJumpAction"]); ui->menuPlayer->addAction(Actions["playerSetJumpAction"]); ui->menuPlayer->addAction(Actions["playerSetInAction"]); ui->menuPlayer->addAction(Actions["playerSetOutAction"]); ui->menuPlayer->addAction(Actions["playerSetPositionAction"]); ui->menuPlayer->addAction(Actions["playerToggleVui"]); ui->menuPlayer->addAction(Actions["playerSwitchSourceProgramAction"]); } 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); filterController()->metadataModel()->setFilter(MetadataModel::VideoFilter); filterController()->metadataModel()->setSearch("#color"); break; case LayoutMode::Audio: ui->actionLayoutAudio->setChecked(true); filterController()->metadataModel()->setFilter(MetadataModel::AudioFilter); 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->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); QMenu *viewModeMenu = ui->menuPlaylist->addMenu(tr("View Mode")); viewModeMenu->addAction(Actions["playlistViewDetailsAction"]); viewModeMenu->addAction(Actions["playlistViewTilesAction"]); viewModeMenu->addAction(Actions["playlistViewIconsAction"]); QMenu *subMenu = ui->menuPlaylist->addMenu(tr("Thumbnails")); subMenu->addAction(Actions["playlistThumbnailsHiddenAction"]); subMenu->addAction(Actions["playlistThumbnailsLeftAndRightAction"]); subMenu->addAction(Actions["playlistThumbnailsTopAndBottomAction"]); subMenu->addAction(Actions["playlistThumbnailsInOnlySmallAction"]); subMenu->addAction(Actions["playlistThumbnailsInOnlyLargeAction"]); ui->menuPlaylist->addAction(Actions["playlistPlayAfterOpenAction"]); m_filesDock = new FilesDock(this); m_filesDock->hide(); m_filesDock->toggleViewAction()->setShortcuts( {QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_4), QKeySequence(Qt::Key_F10)}); ui->menuView->addAction(m_filesDock->toggleViewAction()); // connect(m_filesDock, SIGNAL(itemActivated(QString)), this, SLOT(open(QString))); connect(m_filesDock->toggleViewAction(), SIGNAL(triggered(bool)), this, SLOT(onFilesDockTriggered(bool))); connect(ui->actionFiles, SIGNAL(triggered()), this, SLOT(onFilesDockTriggered())); 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(), &QAbstractItemModel::rowsInserted, m_playlistDock, &PlaylistDock::refreshTimelineSmartBins); connect(m_timelineDock->model(), &QAbstractItemModel::rowsRemoved, m_playlistDock, &PlaylistDock::refreshTimelineSmartBins); 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->markersModel(), SIGNAL(modified()), SLOT(onMultitrackModified())); connect(m_timelineDock, SIGNAL(selected(Mlt::Producer *)), SLOT(loadProducerWidget(Mlt::Producer *))); 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, bool)), SLOT(onAddAllToTimeline(Mlt::Playlist *, bool, bool))); connect(m_player, SIGNAL(previousSought()), m_timelineDock, SLOT(seekPreviousEdit())); connect(m_player, SIGNAL(nextSought()), m_timelineDock, SLOT(seekNextEdit())); connect(m_player, SIGNAL(loopChanged(int, int)), m_timelineDock, SLOT(onLoopChanged(int, int))); connect(m_timelineDock, SIGNAL(isRecordingChanged(bool)), m_player, SLOT(onMuteButtonToggled(bool))); connect(m_player, SIGNAL(trimIn()), m_timelineDock, SLOT(trimClipIn())); connect(m_player, SIGNAL(trimOut()), m_timelineDock, SLOT(trimClipOut())); ui->menuEdit->addAction(Actions["timelineCutAction"]); ui->menuEdit->addAction(Actions["timelineCopyAction"]); ui->menuEdit->addAction(Actions["timelinePasteAction"]); ui->menuTimeline->addAction(Actions["timelineSnapAction"]); ui->menuTimeline->addAction(Actions["timelineSnapAction"]); ui->menuTimeline->addAction(Actions["timelineScrubDragAction"]); ui->menuTimeline->addAction(Actions["timelineRippleAction"]); ui->menuTimeline->addAction(Actions["timelineRippleAllTracksAction"]); ui->menuTimeline->addAction(Actions["timelineRippleMarkersAction"]); ui->menuTimeline->addAction(Actions["timelineToggleRippleAndAllTracksAction"]); ui->menuTimeline->addAction(Actions["timelineToggleRippleAllTracksAndMarkersAction"]); ui->menuTimeline->addSeparator(); ui->menuTimeline->addAction(Actions["timelineAdjustGainAction"]); ui->menuTimeline->addAction(Actions["timelineAutoAddTracksAction"]); ui->menuTimeline->addAction(Actions["timelineRectangleSelectAction"]); ui->menuTimeline->addAction(Actions["timelineShowWaveformsAction"]); ui->menuTimeline->addAction(Actions["timelineShowThumbnailsAction"]); auto submenu = ui->menuTimeline->addMenu(tr("Scrolling")); auto *group = new QActionGroup(this); submenu->addAction(Actions["timelineScrollingCenterPlayhead"]); group->addAction(Actions["timelineScrollingCenterPlayhead"]); submenu->addAction(Actions["timelineScrollingNo"]); group->addAction(Actions["timelineScrollingNo"]); submenu->addAction(Actions["timelineScrollingPage"]); group->addAction(Actions["timelineScrollingPage"]); submenu->addAction(Actions["timelineScrollingSmooth"]); group->addAction(Actions["timelineScrollingSmooth"]); submenu->addAction(Actions["timelineScrollZoomAction"]); m_filterController = new FilterController(this); m_filtersDock = new FiltersDock(m_filterController->metadataModel(), m_filterController->attachedModel(), m_filterController->motionTrackerModel(), m_timelineDock->subtitlesModel(), 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(m_filterController, &FilterController::undoOrRedo, m_filtersDock, &FiltersDock::load); 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(changed()), m_timelineDock, SLOT(onFilterModelChanged())); connect(m_filtersDock, SIGNAL(changed()), m_timelineDock, SLOT(onFilterModelChanged())); 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(gainChanged(double)), m_filterController, SLOT(onGainChanged())); 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(this, SIGNAL(serviceInChanged(int, Mlt::Service *)), m_filtersDock, SLOT(onServiceInChanged(int, Mlt::Service *))); 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 *))); connect(m_playlistDock->model(), &PlaylistModel::removing, m_filterController->motionTrackerModel(), &MotionTrackerModel::removeFromService); connect(m_timelineDock->model(), &MultitrackModel::removing, m_filterController->motionTrackerModel(), &MotionTrackerModel::removeFromService); connect(this, SIGNAL(audioChannelsChanged()), m_filterController, SLOT(setProducer())); connect(this, SIGNAL(processingModeChanged()), m_filterController, SLOT(setProducer())); connect(m_timelineDock, &TimelineDock::trimStarted, m_filterController, &FilterController::pauseUndoTracking); connect(m_timelineDock, &TimelineDock::trimEnded, m_filterController, &FilterController::resumeUndoTracking); 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))); connect(m_filterController, &FilterController::undoOrRedo, m_keyframesDock, &KeyframesDock::reload); m_historyDock = new QDockWidget(tr("History"), this); m_historyDock->hide(); m_historyDock->setObjectName("historyDock"); 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()), m_encodeDock, SLOT(on_encodeButton_clicked())); 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())); connect(m_timelineDock->model(), &MultitrackModel::filteredChanged, m_encodeDock, &EncodeDock::onProfileChanged); connect(m_filterController, &FilterController::filterChanged, this, [&](Mlt::Service *filter) { if (filter && filter->is_valid() && !::qstrcmp("reframe", filter->get(kShotcutFilterProperty))) { m_encodeDock->onReframeChanged(); } }); connect(m_filterController->attachedModel(), &AttachedFiltersModel::addedOrRemoved, this, [&](Mlt::Producer *producer) { m_encodeDock->onReframeChanged(); }); 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())); m_subtitlesDock = new SubtitlesDock(this); m_subtitlesDock->hide(); m_subtitlesDock->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_9)); m_subtitlesDock->setModel(m_timelineDock->subtitlesModel(), m_timelineDock->subtitlesSelectionModel()); ui->menuView->addAction(m_subtitlesDock->toggleViewAction()); connect(m_subtitlesDock->toggleViewAction(), SIGNAL(triggered(bool)), this, SLOT(onSubtitlesDockTriggered(bool))); connect(ui->actionSubtitles, SIGNAL(triggered()), this, SLOT(onSubtitlesDockTriggered())); connect(m_subtitlesDock, SIGNAL(seekRequested(int)), SLOT(seekTimeline(int))); connect(m_timelineDock, SIGNAL(positionChanged(int)), m_subtitlesDock, SLOT(onPositionChanged(int))); connect(m_subtitlesDock, SIGNAL(addAllTimeline(Mlt::Playlist *, bool, bool)), SLOT(onTimelineDockTriggered())); connect(m_subtitlesDock, SIGNAL(addAllTimeline(Mlt::Playlist *, bool, bool)), SLOT(onAddAllToTimeline(Mlt::Playlist *, bool, bool))); connect(m_subtitlesDock, &SubtitlesDock::createOrEditFilterOnOutput, this, &MainWindow::onCreateOrEditFilterOnOutput); connect(m_encodeDock, &EncodeDock::createOrEditFilterOnOutput, this, &MainWindow::onCreateOrEditFilterOnOutput); connect(m_timelineDock->subtitlesModel(), SIGNAL(modified()), this, SLOT(onSubtitleModified())); 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); addDockWidget(Qt::LeftDockWidgetArea, m_subtitlesDock); addDockWidget(Qt::RightDockWidgetArea, m_filesDock); 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); tabifyDockWidget(m_notesDock, m_subtitlesDock); splitDockWidget(m_recentDock, findChild("AudioWaveformDock"), Qt::Vertical); splitDockWidget(audioMeterDock, m_recentDock, Qt::Horizontal); tabifyDockWidget(m_recentDock, m_filesDock); tabifyDockWidget(m_filesDock, m_historyDock); tabifyDockWidget(m_historyDock, m_jobsDock); tabifyDockWidget(m_keyframesDock, m_timelineDock); m_recentDock->raise(); resetDockCorners(); } void MainWindow::setupMenuFile() { #ifdef Q_OS_MAC static auto sep = " "; #else static auto sep = "\t"; #endif connect(ui->menuOtherVersions, &QMenu::aboutToShow, this, [&] { ui->menuOtherVersions->clear(); QDir dir(QFileInfo(m_currentFile).absolutePath()); auto name = Util::baseName(m_currentFile, true); auto match = kBackupFileRegex.match(name); QStringList filters; if (match.hasMatch()) filters << match.captured(1) + "*.mlt"; else filters << QFileInfo(m_currentFile).baseName().split(" - ").first() + "*.mlt"; for (auto &fileInfo : dir.entryInfoList(filters, QDir::Files, QDir::Time)) { auto filename = fileInfo.fileName(); if (filename != name) { auto text = filename; if (!kBackupFileRegex.match(filename).hasMatch()) text += QString::fromLatin1("%1(%2)").arg(sep, fileInfo.lastModified().toString( Qt::ISODate)); ui->menuOtherVersions->addAction(text)->setData(dir.filePath(filename)); } } QCoreApplication::processEvents(); }); connect(ui->menuOtherVersions, &QMenu::triggered, this, [&](QAction *action) { open(action->data().toString()); }); } void MainWindow::setupMenuView() { ui->menuView->addSeparator(); ui->menuView->addAction(ui->actionResources); ui->menuView->addAction(ui->actionApplicationLog); } void MainWindow::connectVideoWidgetSignals() { auto videoWidget = static_cast(&MLT); connect(videoWidget, &Mlt::VideoWidget::dragStarted, m_playlistDock, &PlaylistDock::onPlayerDragStarted); connect(videoWidget, &Mlt::VideoWidget::seekTo, m_player, &Player::seek); connect(videoWidget, &Mlt::VideoWidget::gpuNotSupported, this, &MainWindow::onGpuNotSupported); connect(videoWidget->quickWindow(), &QQuickWindow::sceneGraphInitialized, videoWidget, &Mlt::VideoWidget::initialize, Qt::DirectConnection); connect(videoWidget->quickWindow(), &QQuickWindow::beforeRendering, videoWidget, &Mlt::VideoWidget::beforeRendering, Qt::DirectConnection); connect(videoWidget->quickWindow(), &QQuickWindow::beforeRenderPassRecording, videoWidget, &Mlt::VideoWidget::renderVideo, Qt::DirectConnection); connect(videoWidget->quickWindow(), &QQuickWindow::sceneGraphInitialized, this, &MainWindow::onSceneGraphInitialized, Qt::QueuedConnection); connect(videoWidget->quickWindow(), &QQuickWindow::sceneGraphInitialized, m_timelineDock, &TimelineDock::initLoad); connect(videoWidget, &Mlt::VideoWidget::frameDisplayed, m_scopeController, &ScopeController::newFrame); connect(m_filterController, &FilterController::currentFilterChanged, videoWidget, &Mlt::VideoWidget::setCurrentFilter); connect(m_player, &Player::toggleVuiRequested, videoWidget, &Mlt::VideoWidget::toggleVuiDisplay); } 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() { // DISABLED: MLT timeline/player // Switch to Project player. // if (m_player->tabIndex() != Player::ProjectTabIndex) { // m_timelineDock->saveAndClearSelection(); // m_player->onTabBarClicked(Player::ProjectTabIndex); // } } /* DISABLED: MLT playlist timeline function void MainWindow::onAddAllToTimeline(Mlt::Playlist *playlist, bool skipProxy, bool emptyTrack) { // DISABLED: MLT timeline/player // 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, emptyTrack); } */ MainWindow &MainWindow::singleton() { static MainWindow *instance = new MainWindow; return *instance; } MainWindow::~MainWindow() { delete ui; // Mlt::Controller::destroy(); // DISABLED: MLT } void MainWindow::setupSettingsMenu() { LOG_DEBUG() << "begin"; // Mlt::Filter filter(MLT.profile(), "color_transform"); // DISABLED: MLT // if (!filter.is_valid()) { // DISABLED: MLT // #if LIBMLT_VERSION_INT < ((7 << 16) + (34 << 8)) // ui->actionNative10bitCpu->setVisible(false); // #endif // ui->actionLinear10bitCpu->setVisible(false); // } // DISABLED: MLT QActionGroup *group = new QActionGroup(this); ui->actionNative8bitCpu->setData(ShotcutSettings::Native8Cpu); if (ui->actionNative10bitCpu->isVisible()) ui->actionNative10bitCpu->setData(ShotcutSettings::Native10Cpu); if (ui->actionLinear10bitCpu->isVisible()) ui->actionLinear10bitCpu->setData(ShotcutSettings::Linear10Cpu); ui->actionLinear10bitGpuCpu->setData(ShotcutSettings::Linear10GpuCpu); if (ui->actionNative8bitCpu->isVisible()) group->addAction(ui->actionNative8bitCpu); if (ui->actionNative10bitCpu->isVisible()) group->addAction(ui->actionNative10bitCpu); if (ui->actionLinear10bitCpu->isVisible()) group->addAction(ui->actionLinear10bitCpu); if (ui->actionLinear10bitGpuCpu->isVisible()) group->addAction(ui->actionLinear10bitGpuCpu); for (auto a : group->actions()) { const auto mode = (ShotcutSettings::ProcessingMode) a->data().toInt(); if (Settings.processingMode() == mode) { a->setChecked(true); setProcessingMode(mode); break; } } connect(group, &QActionGroup::triggered, this, [&](QAction *action) { const auto oldMode = Settings.processingMode(); const auto newMode = (ShotcutSettings::ProcessingMode) action->data().toInt(); if (oldMode == newMode) return; LOG_INFO() << "Processing Mode" << oldMode << "->" << newMode; if (newMode == ShotcutSettings::Linear10GpuCpu) { QMessageBox dialog(QMessageBox::Warning, qApp->applicationName(), tr("GPU processing is experimental and does not work on all computers. " "Plan to do some testing after turning this on.\n\n" "Do you want to enable GPU processing and restart Shotcut?"), QMessageBox::No | QMessageBox::Yes, this); dialog.setDefaultButton(QMessageBox::Yes); dialog.setEscapeButton(QMessageBox::No); dialog.setWindowModality(QmlApplication::dialogModality()); dialog.adjustSize(); if (dialog.exec() == QMessageBox::Yes) { Settings.setProcessingMode(newMode); m_exitCode = EXIT_RESTART; QApplication::closeAllWindows(); } for (auto a : action->actionGroup()->actions()) { if (oldMode == a->data().toInt()) { a->setChecked(true); break; } } } else if (oldMode == ShotcutSettings::Linear10GpuCpu) { QMessageBox dialog(QMessageBox::Information, qApp->applicationName(), tr("Shotcut must restart to disable GPU processing.\n" "Disable GPU processing and restart?"), QMessageBox::No | QMessageBox::Yes, this); dialog.setDefaultButton(QMessageBox::Yes); dialog.setEscapeButton(QMessageBox::No); dialog.setWindowModality(QmlApplication::dialogModality()); dialog.adjustSize(); if (dialog.exec() == QMessageBox::Yes) { Settings.setProcessingMode(newMode); m_exitCode = EXIT_RESTART; QApplication::closeAllWindows(); } ui->actionLinear10bitGpuCpu->setChecked(true); } else { setProcessingMode((ShotcutSettings::ProcessingMode) action->data().toInt()); } }); group = new QActionGroup(this); group->addAction(ui->actionChannels1); group->addAction(ui->actionChannels2); group->addAction(ui->actionChannels4); group->addAction(ui->actionChannels6); group = new QActionGroup(this); group->addAction(ui->actionOneField); group->addAction(ui->actionLinearBlend); group->addAction(ui->actionYadifTemporal); group->addAction(ui->actionYadifSpatial); group->addAction(ui->actionBwdif); #if defined(Q_OS_WIN) || defined(Q_OS_LINUX) auto submenu = new QMenu(tr("Audio API")); ui->menuPlayerSettings->insertMenu(ui->actionSync, submenu); group = new QActionGroup(this); #if defined(Q_OS_WIN) group->addAction(submenu->addAction("DirectSound"))->setData("directsound"); group->addAction(submenu->addAction("WASAPI"))->setData("wasapi"); group->addAction(submenu->addAction("Waveform Audio"))->setData("winmm"); #else group->addAction(submenu->addAction("ALSA"))->setData("alsa"); group->addAction(submenu->addAction("ESound"))->setData("esd"); group->addAction(submenu->addAction("OSS"))->setData("dsp"); group->addAction(submenu->addAction("PipeWire"))->setData("pipewire"); group->addAction(submenu->addAction("PulseAudio"))->setData("pulseaudio"); #endif for (auto a : group->actions()) { a->setCheckable(true); const auto data = a->data().toString(); if (data == Settings.playerAudioDriver()) a->setChecked(true); if ((data == "directsound" && Settings.playerAudioChannels() > 2) || (data == "winmm" && Settings.playerAudioChannels() <= 2) || (data == "pulseaudio")) { a->setText(a->text() + QString::fromLatin1(" (%1)").arg(tr("default"))); } } connect(group, &QActionGroup::triggered, this, [&](QAction *action) { Settings.setPlayerAudioDriver(action->data().toString()); QMessageBox dialog(QMessageBox::Information, qApp->applicationName(), tr("You must restart Shotcut to change the audio API.\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) { ::qunsetenv("SDL_AUDIODRIVER"); m_exitCode = EXIT_RESTART; QApplication::closeAllWindows(); } }); #endif group = new QActionGroup(this); ui->actionBackupManually->setData(0); group->addAction(ui->actionBackupManually); ui->actionBackupHourly->setData(60); group->addAction(ui->actionBackupHourly); ui->actionBackupDaily->setData(24 * 60); group->addAction(ui->actionBackupDaily); ui->actionBackupWeekly->setData(7 * 24 * 60); group->addAction(ui->actionBackupWeekly); for (auto a : group->actions()) { if (Settings.backupPeriod() == a->data().toInt()) a->setChecked(true); } connect(group, &QActionGroup::triggered, this, [&](QAction *action) { Settings.setBackupPeriod(action->data().toInt()); }); 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); m_previewScaleGroup->addAction(ui->actionPreview1080); group = new QActionGroup(this); group->addAction(ui->actionTimeFrames); ui->actionTimeFrames->setData(mlt_time_frames); group->addAction(ui->actionTimeClock); ui->actionTimeClock->setData(mlt_time_clock); group->addAction(ui->actionTimeDF); ui->actionTimeDF->setData(mlt_time_smpte_df); group->addAction(ui->actionTimeNDF); ui->actionTimeNDF->setData(mlt_time_smpte_ndf); switch (Settings.timeFormat()) { case mlt_time_frames: ui->actionTimeFrames->setChecked(true); break; case mlt_time_clock: ui->actionTimeClock->setChecked(true); break; case mlt_time_smpte_df: ui->actionTimeDF->setChecked(true); break; default: ui->actionTimeNDF->setChecked(true); break; } connect(group, &QActionGroup::triggered, this, [&](QAction *action) { Settings.setTimeFormat(action->data().toInt()); }); group = new QActionGroup(this); group->addAction(ui->actionNearest); group->addAction(ui->actionBilinear); group->addAction(ui->actionBicubic); group->addAction(ui->actionHyper); 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); #ifdef USE_SCREENS_FOR_EXTERNAL_MONITORING 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); } #endif 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(QStringLiteral("device.%1").arg(i).toLatin1().constData())); if (!device.isEmpty()) { QAction *action = new QAction(device, this); action->setCheckable(true); action->setData(QStringLiteral("decklink:%1").arg(i)); m_externalGroup->addAction(action); if (!m_decklinkGammaGroup) { m_decklinkGammaGroup = new QActionGroup(this); action = new QAction(tr("SDR"), m_decklinkGammaGroup); action->setData(QVariant(0)); action->setCheckable(true); action = new QAction(tr("HLG HDR"), m_decklinkGammaGroup); action->setData(QVariant(1)); action->setCheckable(true); } 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_decklinkGammaGroup) { m_decklinkGammaMenu = ui->menuExternal->addMenu(tr("DeckLink Gamma")); m_decklinkGammaMenu->addActions(m_decklinkGammaGroup->actions()); m_decklinkGammaMenu->setDisabled(true); connect(m_decklinkGammaGroup, &QActionGroup::triggered, this, &MainWindow::onDecklinkGammaTriggered); } if (m_keyerGroup) { m_keyerMenu = ui->menuExternal->addMenu(tr("DeckLink Keyer")); m_keyerMenu->addActions(m_keyerGroup->actions()); m_keyerMenu->setDisabled(true); connect(m_keyerGroup, &QActionGroup::triggered, this, &MainWindow::onKeyerTriggered); } 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::Basque), m_languagesGroup); a->setCheckable(true); a->setData("eu"); 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(" (Simplified)"), m_languagesGroup); a->setCheckable(true); a->setData("zh-Hans"); 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("French (Canada)", m_languagesGroup); a->setCheckable(true); a->setData("fr_CA"); 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::Hebrew), m_languagesGroup); a->setCheckable(true); a->setData("he_IL"); a = new QAction(QLocale::languageToString(QLocale::Hungarian), m_languagesGroup); a->setCheckable(true); a->setData("hu"); a = new QAction(QLocale::languageToString(QLocale::Irish), m_languagesGroup); a->setCheckable(true); a->setData("ga"); 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::Lithuanian), m_languagesGroup); a->setCheckable(true); a->setData("lt"); 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 #if defined(SHOTCUT_THEME) group = new QActionGroup(this); group->addAction(ui->actionSystemTheme); group->addAction(ui->actionSystemFusion); 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 if (Settings.theme() == "system-fusion") ui->actionSystemFusion->setChecked(true); else ui->actionSystemTheme->setChecked(true); #else delete ui->menuTheme; #endif #if defined(Q_OS_WIN) // 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 = nullptr; #else std::unique_ptr filters(MLT.repository()->filters()); if (filters && !filters->get_data("jack")) { delete ui->actionJack; ui->actionJack = nullptr; } #endif #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) // Setup the display method actions. if (!Settings.playerGPU()) { group = new QActionGroup(this); delete ui->actionDrawingAutomatic; delete ui->actionDrawingDirectX; 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; case Qt::AA_UseSoftwareOpenGL: ui->actionDrawingSoftware->setChecked(true); break; default: ui->actionDrawingOpenGL->setChecked(true); break; } } else { // GPU mode only works with OpenGL. delete ui->menuDrawingMethod; ui->menuDrawingMethod = 0; } #else 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()); // Initialize the preview scaling submenu #if LIBMLT_VERSION_INT > ((7 << 16) + (34 << 8)) ui->actionPreviewHardwareDecoder->setChecked(Settings.playerPreviewHardwareDecoder()); #else ui->actionPreviewHardwareDecoder->setVisible(false); #endif 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); ui->menuNew->addSeparator(); // populate the generators if (mltProducers->get_data("color")) { ui->menuNew->addAction(tr("Color"), this, SLOT(onOpenOtherTriggered())) ->setObjectName("color"); otherMenu->addAction(ui->menuNew->actions().constLast()); if (mltProducers->get_data("qtext") && mltFilters->get_data("dynamictext")) { ui->menuNew->addAction(tr("Text"), this, SLOT(onOpenOtherTriggered())) ->setObjectName("text"); otherMenu->addAction(ui->menuNew->actions().constLast()); } } if (mltProducers->get_data("glaxnimate")) { ui->menuNew->addAction(tr("Drawing/Animation"), this, SLOT(onOpenOtherTriggered())) ->setObjectName("glaxnimate"); otherMenu->addAction(ui->menuNew->actions().constLast()); } #ifdef EXTERNAL_LAUNCHERS ui->menuNew->addAction(tr("Image/Video from HTML"), this, SLOT(onHtmlGeneratorTriggered())) ->setObjectName("html"); #endif otherMenu->addAction(ui->menuNew->actions().constLast()); if (mltProducers->get_data("noise")) { ui->menuNew->addAction(tr("Noise"), this, SLOT(onOpenOtherTriggered())) ->setObjectName("noise"); otherMenu->addAction(ui->menuNew->actions().constLast()); } if (mltProducers->get_data("frei0r.test_pat_B")) { ui->menuNew->addAction(tr("Color Bars"), this, SLOT(onOpenOtherTriggered())) ->setObjectName("test_pat_B"); otherMenu->addAction(ui->menuNew->actions().constLast()); } if (mltProducers->get_data("tone")) { ui->menuNew->addAction(tr("Audio Tone"), this, SLOT(onOpenOtherTriggered())) ->setObjectName("tone"); otherMenu->addAction(ui->menuNew->actions().constLast()); } if (mltProducers->get_data("count")) { ui->menuNew->addAction(tr("Count"), this, SLOT(onOpenOtherTriggered())) ->setObjectName("count"); otherMenu->addAction(ui->menuNew->actions().constLast()); } if (mltProducers->get_data("blipflash")) { ui->menuNew->addAction(tr("Blip Flash"), this, SLOT(onOpenOtherTriggered())) ->setObjectName("blipflash"); otherMenu->addAction(ui->menuNew->actions().constLast()); } #if defined(EXTERNAL_LAUNCHERS) ui->actionScreenSnapshot->setVisible(true); ui->menuNew->addAction(tr("Screen Snapshot"), this, SLOT(on_actionScreenSnapshot_triggered())) ->setObjectName("screenSnapshot"); otherMenu->addAction(ui->menuNew->actions().constLast()); if (QSysInfo::productType() != QStringLiteral("windows") || QSysInfo::productVersion() != QStringLiteral("10")) { ui->actionScreenRecording->setVisible(true); ui->menuNew ->addAction(tr("Screen Recording"), this, SLOT(on_actionScreenRecording_triggered())) ->setObjectName("screenRecording"); otherMenu->addAction(ui->menuNew->actions().constLast()); } #endif } 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, bool play) { 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 < QGuiApplication::screens().count() // && QGuiApplication::screens().at(screen) != this->screen()) { // m_player->moveVideoToScreen(screen); // } // no else here because open() will delete the producer if open fails if (!MLT.setProducer(producer)) { if (!play) m_player->setPauseAfterOpen(true); emit producerOpened(); if (MLT.isProjectProducer(producer)) { m_filterController->motionTrackerModel()->load(); emit profileChanged(); } else if (!MLT.profile().is_explicit()) { emit profileChanged(); } } m_player->setFocus(); emit m_playlistDock->enableUpdate(false); // Needed on Windows. Upon first file open, window is deactivated, perhaps OpenGL-related. activateWindow(); } /* DISABLED: MLT XML checker bool MainWindow::isCompatibleWithGpuMode(MltXmlChecker &checker, QString &fileName) { bool result = true; if (checker.needsGPU() && !Settings.playerGPU()) { LOG_INFO() << "file uses GPU but GPU not enabled"; QMessageBox dialog( QMessageBox::Question, qApp->applicationName(), tr("The file you opened uses GPU processing, which is not enabled.\n" "Do you want Shotcut to convert it for CPU? Conversion is an approximation.\n\n" "If you choose Yes, Shotcut will create a copy of your project\n" "with \"- Converted for CPU\" in the file name and open it."), QMessageBox::No | QMessageBox::Yes, this); dialog.setWindowModality(QmlApplication::dialogModality()); dialog.setDefaultButton(QMessageBox::Yes); dialog.setEscapeButton(QMessageBox::No); if (dialog.exec() == QMessageBox::Yes) result = saveConvertedXmlFile(checker, fileName); else result = false; } else if (checker.needsCPU() && Settings.playerGPU()) { LOG_INFO() << "file uses GPU incompatible filters but GPU processing is enabled"; QMessageBox dialog(QMessageBox::Question, qApp->applicationName(), tr("The file you opened uses CPU processing, which is not enabled.\n" "Do you want Shotcut to convert it for GPU?\n\n" "If you choose Yes, Shotcut will create a copy of your project\n" "with \"- Converted for GPU\" 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 = saveConvertedXmlFile(checker, fileName); else result = false; } return result; } */ /* bool MainWindow::saveConvertedXmlFile_disabled(MltXmlChecker &checker, QString &fileName) { QFileInfo fi(fileName); const auto convertedStr = Settings.playerGPU() ? tr("Converted for GPU") : tr("Converted for CPU"); auto filename = QStringLiteral("%1/%2 - %3.%4") .arg(fi.path(), fi.completeBaseName(), convertedStr, fi.suffix()); auto caption = tr("Save Converted XML"); filename = QFileDialog::getSaveFileName(this, caption, filename, tr("MLT XML (*.mlt)"), nullptr, Util::getFileDialogOptions()); if (!filename.isEmpty()) { QFile converted(filename); if (converted.open(QIODevice::WriteOnly) && checker.tempFile().exists() && checker.tempFile().open()) { LOG_INFO() << "converted MLT XML file name" << converted.fileName(); QByteArray xml = checker.tempFile().readAll(); checker.tempFile().close(); if (Settings.proxyEnabled()) { auto s = QString::fromUtf8(xml); if (ProxyManager::filterXML(s, QDir::fromNativeSeparators(fi.absolutePath()))) { xml = s.toUtf8(); } } qint64 n = converted.write(xml); while (n > 0 && n < xml.size()) { qint64 x = converted.write(xml.right(xml.size() - n)); if (x > 0) n += x; else n = x; } converted.close(); if (n == xml.size()) { fileName = converted.fileName(); return true; } } QMessageBox::warning(this, qApp->applicationName(), tr("Converting the project failed.")); LOG_WARNING() << "converting failed"; } return false; } */ /* bool MainWindow::saveRepairedXmlFile_disabled(MltXmlChecker &checker, QString &fileName) { QFileInfo fi(fileName); auto filename = QStringLiteral("%1/%2 - %3.%4") .arg(fi.path(), fi.completeBaseName(), tr("Repaired"), 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); if (repaired.open(QIODevice::WriteOnly) && checker.tempFile().exists() && checker.tempFile().open()) { LOG_INFO() << "repaired MLT XML file name" << repaired.fileName(); QByteArray xml = checker.tempFile().readAll(); checker.tempFile().close(); if (Settings.proxyEnabled()) { auto s = QString::fromUtf8(xml); if (ProxyManager::filterXML(s, QDir::fromNativeSeparators(fi.absolutePath()))) { xml = s.toUtf8(); } } 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) { Q_UNUSED(checker) Q_UNUSED(fileName) return true; // DISABLED: MLT XML checker } /* bool MainWindow::isXmlRepaired_disabled(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::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) { showFullScreen(); ui->actionEnterFullScreen->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) { // DISABLED: MLT video profile LOG_DEBUG() << profile_name; // MLT.setProfile(profile_name); // emit profileChanged(); } /* DISABLED: MLT player references 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(); } */ /* DISABLED: MLT audio/video settings 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 == 4) ui->actionChannels4->setChecked(true); else if (channels == 6) ui->actionChannels6->setChecked(true); emit audioChannelsChanged(); } void MainWindow::setProcessingMode(ShotcutSettings::ProcessingMode mode) { LOG_DEBUG() << mode; if (mode != Settings.processingMode()) { Settings.setProcessingMode(mode); } switch (mode) { case ShotcutSettings::Native8Cpu: case ShotcutSettings::Linear8Cpu: ui->actionNative8bitCpu->setChecked(true); break; case ShotcutSettings::Native10Cpu: ui->actionNative10bitCpu->setChecked(true); break; case ShotcutSettings::Linear10Cpu: ui->actionLinear10bitCpu->setChecked(true); break; case ShotcutSettings::Linear10GpuCpu: ui->actionLinear10bitGpuCpu->setChecked(true); break; } MLT.videoWidget()->setProperty("processing_mode", mode); MLT.setProcessingMode(mode); emit processingModeChanged(); } */ 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; case 1080: ui->actionPreview1080->setChecked(true); break; default: ui->actionPreviewNone->setChecked(true); break; } const auto changed = scale != Settings.playerPreviewScale(); MLT.setPreviewScale(scale); if (!m_externalGroup->checkedAction()->data().toString().isEmpty()) { // DeckLink external monitor MLT.consumerChanged(); } else { // System monitor MLT.refreshConsumer(); } if (changed) { MLT.configureHardwareDecoder(true); MLT.reload(QString()); emit producerOpened(false); } } 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()) { // Automatic backup backupPeriodically(); // Auto-save to recovery file auto result = QtConcurrent::run(autosaveTask, this); } static QMessageBox *dialog = nullptr; if (!dialog) { dialog = new QMessageBox(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()); connect(dialog, &QDialog::finished, this, [&](int result) { switch (result) { case QMessageBox::Save: on_actionBackupSave_triggered(); m_exitCode = EXIT_RESTART; QApplication::closeAllWindows(); break; case QMessageBox::Retry: onAutosaveTimeout(); break; default: JOBS.resumeCurrent(); break; } }); } if (Settings.warnLowMemory()) { if (Util::isMemoryLow()) { MLT.pause(); JOBS.pauseCurrent(); dialog->show(); } else if (dialog->isVisible()) { dialog->hide(); QCoreApplication::processEvents(); JOBS.resumeCurrent(); } } } /* DISABLED: MLT open function - too large and video-specific bool MainWindow::open_disabled(QString url, const Mlt::Properties *properties, bool play, bool skipConvert) { // returns false when MLT is unable to open the file, possibly because it has percent sign in the path LOG_DEBUG() << url; bool modified = false; bool converted = false; MltXmlChecker checker; QFileInfo info(url); if (info.isRelative()) { QDir pwd(QDir::currentPath()); url = pwd.filePath(url); info.setFile(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: converted = true; // isCompatibleWithGpuMode(checker, url); // DISABLED if (!converted) { showStatusMessage(tr("Failed to open ").append(url)); return true; } break; case QXmlStreamReader::CustomError: // showIncompatibleProjectMessage(checker.shotcutVersion()); // DISABLED: MLT return true; default: showStatusMessage(tr("Failed to open ").append(url)); return true; } // only check for a modified project when loading a project, not a simple producer if (!continueModified()) return true; QCoreApplication::processEvents(); // close existing project // if (playlist()) { // DISABLED: MLT // m_playlistDock->model()->close(); // } // if (multitrack()) { // DISABLED: MLT // m_timelineDock->model()->close(); // } // MLT.purgeMemoryPool(); // DISABLED // if (!isXmlRepaired(checker, url)) // DISABLED // return true; modified = checkAutoSave(url); if (modified) { if (checker.check(url) == QXmlStreamReader::NoError) { converted = true; // isCompatibleWithGpuMode(checker, url); // DISABLED if (!converted) return true; } else { showStatusMessage(tr("Failed to open ").append(url)); showIncompatibleProjectMessage(checker.shotcutVersion()); return true; } if (!isXmlRepaired(checker, url)) return true; } // 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 true; 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); } QString urlToOpen = checker.isUpdated() ? checker.tempFile().fileName() : url; if (!MLT.open(QDir::fromNativeSeparators(urlToOpen), QDir::fromNativeSeparators(url), skipConvert) && 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()); Mlt::Filter filter(MLT.profile(), "color_transform"); if (filter.is_valid()) setProcessingMode(MLT.processingMode()); if (url.endsWith(".mlt") || url.endsWith(".xml")) { if (MLT.producer()->get_int(kShotcutProjectFolder)) { MLT.setProjectFolder(info.absolutePath()); ProxyManager::removePending(); } else { MLT.setProjectFolder(QString()); } 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; } if (converted) saveXML(url); } else if (url != untitledFileName()) { showStatusMessage(tr("Failed to open ") + url); emit openFailed(url); return false; } return true; } // This one is invoked from the command line. 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()); } } */ bool MainWindow::open(QString url, const Mlt::Properties *properties, bool play, bool skipConvert) { Q_UNUSED(url) Q_UNUSED(properties) Q_UNUSED(play) Q_UNUSED(skipConvert) return false; // DISABLED: MLT } // This one is invoked from above (command line) or drag-n-drop. void MainWindow::openMultiple(const QList &urls) { if (urls.size() > 1) { m_multipleFiles = Util::sortedFileList(Util::expandDirectories(urls)); // open(m_multipleFiles.first(), nullptr, true, true); // DISABLED: MLT } else if (urls.size() > 0) { QUrl url = urls.first(); // if (!open(Util::removeFileScheme(url))) // DISABLED: MLT // open(Util::removeFileScheme(url, false)); } } // DISABLED: openVideo - video-specific /* 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(), nullptr, true, filenames.length() > 1); } 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); if (producer && producer->is_valid() && !MLT.isClosedClip(producer)) MLT.seek(producer->get_in()); } */ void MainWindow::hideProducer() { // DISABLED: MLT producer hiding // 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() { // DISABLED: MLT producer closing // QCoreApplication::processEvents(); // hideProducer(); // m_filterController->motionTrackerModel()->load(); // MLT.close(); // MLT.setSavedProducer(nullptr); } void MainWindow::showStatusMessage(QAction *action, int timeoutSeconds) { // DISABLED: MLT player status // 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); delete action; } void MainWindow::showStatusMessage(const QString &message, int timeoutSeconds, QPalette::ColorRole role) { // DISABLED: MLT player status 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) { // DISABLED: MLT playlist seeking // 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); Q_UNUSED(start) } void MainWindow::seekTimeline(int position, bool seekPlayer) { // DISABLED: MLT timeline seeking // 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); // } // if (seekPlayer) // m_player->seek(position); // else // m_player->pause(); Q_UNUSED(position) Q_UNUSED(seekPlayer) } void MainWindow::seekKeyframes(int position) { // DISABLED: MLT keyframe seeking // m_player->seek(position); Q_UNUSED(position) } void MainWindow::readPlayerSettings() { LOG_DEBUG() << "begin"; ui->actionPauseAfterSeek->setChecked(Settings.playerPauseAfterSeek()); ui->actionRealtime->setChecked(Settings.playerRealtime()); ui->actionProgressive->setChecked(Settings.playerProgressive()); ui->actionScrubAudio->setChecked(Settings.playerScrubAudio()); if (ui->actionJack) ui->actionJack->setChecked(Settings.playerJACK()); QString external = Settings.playerExternal(); bool ok = false; external.toInt(&ok); auto isExternalPeripheral = !external.isEmpty() && !ok; setAudioChannels(Settings.playerAudioChannels()); if (isExternalPeripheral) { ui->actionPreview360->setEnabled(false); ui->actionPreview540->setEnabled(false); } else { ui->actionPreview360->setEnabled(true); ui->actionPreview540->setEnabled(true); } setPreviewScale(Settings.playerPreviewScale()); #if LIBMLT_VERSION_INT > ((7 << 16) + (34 << 8)) MLT.configureHardwareDecoder(Settings.playerPreviewHardwareDecoder()); #endif 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 if (deinterlacer == "yadif") ui->actionYadifSpatial->setChecked(true); else ui->actionBwdif->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()) { #ifndef USE_SCREENS_FOR_EXTERNAL_MONITORING if (isExternalPeripheral) #endif if (a->data() == external) { a->setChecked(true); if (a->data().toString().startsWith("decklink")) { if (m_decklinkGammaMenu) m_decklinkGammaMenu->setEnabled(true); if (m_keyerMenu) m_keyerMenu->setEnabled(true); } break; } } if (m_decklinkGammaGroup) { auto gamma = Settings.playerDecklinkGamma(); for (auto a : m_decklinkGammaGroup->actions()) { if (a->data() == gamma) { a->setChecked(true); break; } } } if (m_keyerGroup) { auto keyer = Settings.playerKeyerMode(); for (auto 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; } } #if defined(Q_OS_WIN) || defined(Q_OS_LINUX) if (!::qEnvironmentVariableIsSet("SDL_AUDIODRIVER")) { ::qputenv("SDL_AUDIODRIVER", Settings.playerAudioDriver().toLocal8Bit().constData()); } #endif LOG_DEBUG() << "end"; } void MainWindow::readWindowSettings() { LOG_DEBUG() << "begin"; Settings.setWindowGeometryDefault(saveGeometry()); Settings.setWindowStateDefault(saveState()); Settings.sync(); if (!Settings.windowGeometry().isEmpty()) { restoreState(Settings.windowState()); restoreGeometry(Settings.windowGeometry()); } else { restoreState(kLayoutEditingDefault); } LOG_DEBUG() << "end"; } void MainWindow::setupActions() { QAction *action; // Setup these actions as separators ui->actionProject->setSeparator(true); ui->actionUser_Interface->setSeparator(true); // Setup full screen action action = ui->actionEnterFullScreen; QList fullScreenShortcuts; #ifdef Q_OS_MAC fullScreenShortcuts << QKeySequence(Qt::CTRL | Qt::META | Qt::Key_F); fullScreenShortcuts << QKeySequence(Qt::Key_F11); action->setShortcuts(fullScreenShortcuts); action = new QAction(tr("Preferences"), this); action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Comma)); connect(action, &QAction::triggered, this, &MainWindow::showSettingsMenu); ui->menuEdit->addAction(action); #else fullScreenShortcuts << QKeySequence(Qt::Key_F11); fullScreenShortcuts << QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_F); action->setShortcuts(fullScreenShortcuts); #endif action = new QAction(tr("Rename Clip"), this); action->setShortcut(QKeySequence(Qt::Key_F2)); connect(action, &QAction::triggered, this, [&]() { onPropertiesDockTriggered(true); emit renameRequested(); }); addAction(action); Actions.add("propertiesRenameClipAction", action, tr("Properties")); action = new QAction(tr("Find"), this); action->setShortcut(QKeySequence(Qt::Key_F3)); connect(action, &QAction::triggered, this, [&]() { onRecentDockTriggered(true); m_recentDock->find(); }); addAction(action); Actions.add("recentFindAction", action, tr("Recent")); action = new QAction(tr("Reload"), this); action->setShortcut(QKeySequence(Qt::Key_F5)); connect(action, &QAction::triggered, this, [&]() { m_timelineDock->model()->reload(); m_keyframesDock->model().reload(); m_filtersDock->load(); }); addAction(action); Actions.add("timelineReload", action, tr("Timeline")); action = new QAction(tr("Rerun Filter Analysis"), this); connect(action, &QAction::triggered, this, [&](bool checked) { FindAnalysisFilterParser parser; parser.skipAnalyzed(false); // Look for two pass filters if (m_timelineDock->model()->tractor() && m_timelineDock->model()->tractor()->is_valid()) { parser.start(*m_timelineDock->model()->tractor()); } if (m_playlistDock->model()->playlist() && m_playlistDock->model()->playlist()->is_valid()) { parser.start(*m_playlistDock->model()->playlist()); } if (parser.filters().size() > 0) { QMessageBox dialog(QMessageBox::Question, windowTitle(), tr("This will start %n analysis job(s). Continue?", nullptr, parser.filters().size()), QMessageBox::No | QMessageBox::Yes, this); dialog.setDefaultButton(QMessageBox::Yes); dialog.setEscapeButton(QMessageBox::No); dialog.setWindowModality(QmlApplication::dialogModality()); if (QMessageBox::Yes == dialog.exec()) { // If dialog accepted enqueue jobs. foreach (Mlt::Filter filter, parser.filters()) { QScopedPointer meta(new QmlMetadata); QmlFilter qmlFilter(filter, meta.data()); bool isAudio = !::qstrcmp("loudness", filter.get("mlt_service")); qmlFilter.analyze(isAudio, false); } } } else { emit showStatusMessage(tr("No filters to analyze.")); } }); Actions.add("analyzeFilters", action); ui->menuFile->insertAction(ui->actionShowProjectFolder, action); Actions.loadFromMenu(ui->menuFile); Actions.loadFromMenu(ui->menuEdit); Actions.loadFromMenu(ui->menuView); Actions.loadFromMenu(ui->menuPlayer); Actions.loadFromMenu(ui->menuSettings); Actions.loadFromMenu(ui->menuHelp); auto shortcuts{ui->actionKeyboardShortcuts->shortcuts()}; shortcuts << QKeySequence(Qt::Key_Slash); ui->actionKeyboardShortcuts->setShortcuts(shortcuts); // Shortcuts for actions that are only in context menus do not work unless // they are added to something visible. addAction(Actions["timelineMergeWithNextAction"]); addAction(Actions["timelineDetachAudioAction"]); addAction(Actions["timelineAlignToReferenceAction"]); addAction(Actions["timelineUpdateThumbnailsAction"]); addAction(Actions["timelineRebuildAudioWaveformAction"]); addAction(Actions["keyframesTypePrevHoldAction"]); addAction(Actions["keyframesTypePrevLinearAction"]); addAction(Actions["keyframesTypePrevSmoothNaturalAction"]); addAction(Actions["keyframesTypePrevEaseOutSinuAction"]); addAction(Actions["keyframesTypePrevEaseOutQuadAction"]); addAction(Actions["keyframesTypePrevEaseOutCubeAction"]); addAction(Actions["keyframesTypePrevEaseOutQuartAction"]); addAction(Actions["keyframesTypePrevEaseOutQuintAction"]); addAction(Actions["keyframesTypePrevEaseOutExpoAction"]); addAction(Actions["keyframesTypePrevEaseOutCircAction"]); addAction(Actions["keyframesTypePrevEaseOutBackAction"]); addAction(Actions["keyframesTypePrevEaseOutElasAction"]); addAction(Actions["keyframesTypePrevEaseOutBounAction"]); addAction(Actions["keyframesTypeHoldAction"]); addAction(Actions["keyframesTypeLinearAction"]); addAction(Actions["keyframesTypeSmoothNaturalAction"]); addAction(Actions["keyframesTypeEaseInSinuAction"]); addAction(Actions["keyframesTypeEaseInQuadAction"]); addAction(Actions["keyframesTypeEaseInCubeAction"]); addAction(Actions["keyframesTypeEaseInQuartAction"]); addAction(Actions["keyframesTypeEaseInQuintAction"]); addAction(Actions["keyframesTypeEaseInExpoAction"]); addAction(Actions["keyframesTypeEaseInCircAction"]); addAction(Actions["keyframesTypeEaseInBackAction"]); addAction(Actions["keyframesTypeEaseInElasAction"]); addAction(Actions["keyframesTypeEaseInBounAction"]); addAction(Actions["keyframesTypeEaseInOutSinuAction"]); addAction(Actions["keyframesTypeEaseInOutQuadAction"]); addAction(Actions["keyframesTypeEaseInOutCubeAction"]); addAction(Actions["keyframesTypeEaseInOutQuartAction"]); addAction(Actions["keyframesTypeEaseInOutQuintAction"]); addAction(Actions["keyframesTypeEaseInOutExpoAction"]); addAction(Actions["keyframesTypeEaseInOutCircAction"]); addAction(Actions["keyframesTypeEaseInOutBackAction"]); addAction(Actions["keyframesTypeEaseInOutElasAction"]); addAction(Actions["keyframesTypeEaseInOutBounAction"]); addAction(Actions["keyframesRemoveAction"]); addAction(Actions["filesSearch"]); addAction(Actions["playlistSearch"]); Actions.initializeShortcuts(); } void MainWindow::writeSettings() { #ifndef Q_OS_MAC if (isFullScreen()) showNormal(); #endif 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 { // DeckLink external monitor must strictly follow video mode 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 if (ui->actionChannels4->isChecked()) setAudioChannels(4); else setAudioChannels(6); if (ui->actionOneField->isChecked()) MLT.videoWidget()->setProperty("deinterlacer", "onefield"); else if (ui->actionLinearBlend->isChecked()) MLT.videoWidget()->setProperty("deinterlacer", "linearblend"); else if (ui->actionYadifTemporal->isChecked()) MLT.videoWidget()->setProperty("deinterlacer", "yadif-nospatial"); else if (ui->actionYadifSpatial->isChecked()) MLT.videoWidget()->setProperty("deinterlacer", "yadif"); else MLT.videoWidget()->setProperty("deinterlacer", "bwdif"); 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_decklinkGammaGroup && m_decklinkGammaGroup->isEnabled()) MLT.videoWidget()->setProperty("decklinkGamma", m_decklinkGammaGroup->checkedAction()->data()); if (m_keyerGroup) MLT.videoWidget()->setProperty("keyer", m_keyerGroup->checkedAction()->data()); LOG_DEBUG() << "end"; } void MainWindow::setCurrentFile(const QString &filename) { if (filename == untitledFileName()) m_currentFile.clear(); else m_currentFile = filename; updateWindowTitle(); ui->actionShowProjectFolder->setDisabled(m_currentFile.isEmpty()); } void MainWindow::updateWindowTitle() { QString shownName = tr("Untitled"); if (!m_currentFile.isEmpty()) { shownName = QFileInfo(m_currentFile).filePath(); shownName = fontMetrics().elidedText(shownName, Qt::ElideLeft, width() / 4); } QString profileText = tr("%1x%2 %3fps %4ch") .arg(QString::number(MLT.profile().width(), 'f', 0), QString::number(MLT.profile().height(), 'f', 0), QString::number(MLT.profile().fps(), 'f', 2), QString::number(Settings.playerAudioChannels(), 'f', 0)); if (!MLT.profile().is_explicit()) profileText = tr("Automatic"); #ifdef Q_OS_MAC setWindowTitle( QStringLiteral("%1 - %2 - %3").arg(shownName).arg(profileText).arg(qApp->applicationName())); #else setWindowTitle(QStringLiteral("%1[*] - %2 - %3") .arg(shownName) .arg(profileText) .arg(qApp->applicationName())); #endif } void MainWindow::on_actionAbout_Shotcut_triggered() { const auto copyright = QStringLiteral( "Copyright © 2011-2026 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()), QStringLiteral( "

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:

    " "
  • Qt application and UI framework
  • " "
  • MLT multimedia authoring " "framework
  • " "
  • FFmpeg multimedia format and codec " "libraries
  • " "
  • x264 H.264 " "encoder
  • " "
  • dav1d AV1 " "decoder
  • " "
  • SVT-AV1 AV1 encoder
  • " "
  • Opus audio codec
  • " "
  • Frei0r video plugins
  • " "
  • LADSPA audio plugins
  • " "
  • Glaxnimate vector animation " "program
  • " "

" "

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()) return; bool handled = true; switch (event->key()) { case Qt::Key_J: if (m_isKKeyPressed) m_player->previousFrame(); else m_player->rewind(false); break; case Qt::Key_K: m_player->pause(); m_isKKeyPressed = true; break; case Qt::Key_L: if (event->modifiers() == Qt::NoModifier) { if (m_isKKeyPressed) m_player->nextFrame(); else m_player->fastForward(false); } 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(); handled = false; 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() { ui->actionAppDataSet->setVisible(false); } 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::WhatsThis) { QHelpEvent *whatsThisEvent = static_cast(event); // Search for whatsThis text in the target widget and its parents QWidget *widget = static_cast(target); QString text = widget ? widget->whatsThis() : QString(); while (widget && text.isEmpty()) { widget = widget->parentWidget(); if (widget) text = widget->whatsThis(); } if (text.startsWith("http")) { Util::openUrl(text); QWhatsThis::leaveWhatsThisMode(); return true; } return false; } 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()) { Actions["playerFocus"]->trigger(); return true; } } #if 0 QQuickWidget *focusedQuickWidget = qobject_cast(qApp->focusWidget()); if (focusedQuickWidget && focusedQuickWidget->quickWindow()->activeFocusItem()) { event->accept(); #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) qApp->sendEvent(focusedQuickWidget->quickWindow()->activeFocusItem(), event); #else focusedQuickWidget->quickWindow() ->sendEvent(focusedQuickWidget->quickWindow()->activeFocusItem(), event); #endif QWidget *w = focusedQuickWidget->parentWidget(); if (!event->isAccepted()) qApp->sendEvent(w, event); return true; } #endif } return QMainWindow::eventFilter(target, event); } void MainWindow::dragEnterEvent(QDragEnterEvent *event) { // Simulate the player firing a dragStarted event to make the playlist close // its help text view. This lets one drop a clip directly into the playlist // from a fresh start. auto *videoWidget = (Mlt::VideoWidget *) &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()) { // Use QTimer to workaround stupid drag from Windows Explorer bug const auto &urls = mimeData->urls(); QTimer::singleShot(0, this, [=]() { openMultiple(urls); }); event->acceptProposedAction(); } else if (mimeData->hasFormat(Mlt::XmlMimeType) && MLT.XML() != mimeData->data(Mlt::XmlMimeType)) { m_playlistDock->onOpenActionTriggered(); event->acceptProposedAction(); } } void MainWindow::closeEvent(QCloseEvent *event) { m_timelineDock->stopRecording(); if (continueJobsRunning() && continueModified()) { LOG_DEBUG() << "begin"; JOBS.cleanup(); if (m_exitCode != EXIT_RESET) { 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 #if defined(Q_OS_WIN) && (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) WindowsTaskbarButton::getInstance().setParentWindow(this); #endif onAutosaveTimeout(); QTimer::singleShot(400, this, [=]() { Database::singleton(this); this->setProperty("windowOpacity", 1.0); }); } void MainWindow::hideEvent(QHideEvent *event) { Q_UNUSED(event) setProperty("windowOpacity", 0.0); } void MainWindow::on_actionOpenOther_triggered() { auto dialog = new OpenOtherDialog(this); if (MLT.producer()) { // open dialog with previous configuration dialog->load(MLT.producer()); } auto result = dialog->exec(); m_producerWidget.reset(dialog->currentWidget()); onOpenOtherFinished(result); } void MainWindow::onProducerOpened(bool withReopen) { // DISABLED: MLT producer opening // 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->model()->load(); // 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(); // m_timelineDock->setSelection(); // } // } // if (MLT.isClip()) { // m_filterController->setProducer(MLT.producer()); // m_player->enableTab(Player::SourceTabIndex); // m_player->switchToTab(MLT.isClosedClip() ? Player::ProjectTabIndex : Player::SourceTabIndex); // Util::getHash(*MLT.producer()); // } 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()); Q_UNUSED(withReopen) } void MainWindow::onProducerChanged() { MLT.refreshConsumer(); if (playlist() && MLT.producer() && MLT.producer()->is_valid() && MLT.producer()->get_int(kPlaylistIndexProperty)) { emit m_playlistDock->enableUpdate(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; backupPeriodically(); 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(); } void MainWindow::on_actionBackupSave_triggered() { m_timelineDock->stopRecording(); if (m_currentFile.isEmpty()) { on_actionSave_As_triggered(); } else { backup(); if (isWindowModified()) on_actionSave_triggered(); } } void MainWindow::on_actionPauseAfterSeek_triggered(bool checked) { Settings.setPlayerPauseAfterSeek(checked); } /* DISABLED: MLT crop/marker/selection functions void MainWindow::cropSource(const QRectF &rect) { // DISABLED: MLT video cropping // filterController()->removeCurrent(); // auto model = filterController()->attachedModel(); // Mlt::Service service; // for (int i = 0; i < model->rowCount(); i++) { // service = model->getService(i); // if (!qstrcmp("crop", service.get("mlt_service"))) // break; // } // if (!service.is_valid()) { // auto meta = filterController()->metadata("crop"); // service = model->getService(model->add(meta)); // service.set("use_profile", 1); // } // service.set("left", rect.x()); // service.set("right", MLT.profile().width() - rect.x() - rect.width()); // service.set("top", rect.y()); // service.set("bottom", MLT.profile().height() - rect.y() - rect.height()); // auto newWidth = Util::coerceMultiple(rect.width()); // auto newHeight = Util::coerceMultiple(rect.height()); // QMessageBox dialog(QMessageBox::Question, // qApp->applicationName(), // tr("Do you also want to change the Video Mode to %1 x %2?") // .arg(newWidth) // .arg(newHeight), // QMessageBox::No | QMessageBox::Yes, // this); // dialog.setWindowModality(QmlApplication::dialogModality()); // dialog.setDefaultButton(QMessageBox::Yes); // dialog.setEscapeButton(QMessageBox::No); // if (QMessageBox::Yes == dialog.exec()) { // auto leftRatio = rect.x() / MLT.profile().width(); // auto rightRatio = 1.0 - (rect.x() + newWidth) / MLT.profile().width(); // auto topRatio = rect.y() / MLT.profile().height(); // auto bottomRatio = 1.0 - (rect.y() + newHeight) / MLT.profile().height(); // // service.set("left", qRound(leftRatio * newWidth)); // service.set("right", qRound(rightRatio * newWidth)); // service.set("top", qRound(topRatio * newHeight)); // service.set("bottom", qRound(bottomRatio * newHeight)); // // MLT.profile().set_width(newWidth); // MLT.profile().set_height(newHeight); // MLT.profile().set_display_aspect(newWidth * MLT.profile().sar(), newHeight); // MLT.updatePreviewProfile(); // MLT.setPreviewScale(Settings.playerPreviewScale()); // auto xml = MLT.XML(); // emit profileChanged(); // MLT.reload(xml); // } // emit producerOpened(false); } void MainWindow::getMarkerRange(int position, int *start, int *end) { // DISABLED: MLT timeline markers // if (!MLT.isMultitrack()) { // showStatusMessage(tr("Timeline is not loaded")); // } else { // MarkersModel *model = m_timelineDock->markersModel(); // int markerIndex = model->rangeMarkerIndexForPosition(position); // if (markerIndex >= 0) { // Markers::Marker marker = model->getMarker(markerIndex); // *start = marker.start; // *end = marker.end; // return; // } else { // showStatusMessage(tr("Range marker not found under the timeline cursor")); // } // } *start = -1; *end = -1; } void MainWindow::getSelectionRange(int *start, int *end) { if (MLT.isMultitrack()) { m_timelineDock->getSelectionRange(start, end); } else if (MLT.isPlaylist()) { m_playlistDock->getSelectionRange(start, end); } else if (MLT.isSeekableClip()) { *start = MLT.producer()->get_in(); *end = MLT.producer()->get_out(); } else { *start = -1; *end = -1; } } */ /* DISABLED: MLT binPlaylist Mlt::Playlist *MainWindow::binPlaylist() { return m_playlistDock->binPlaylist(); } */ void MainWindow::showInFiles(const QString &filePath) { onFilesDockTriggered(); m_filesDock->changeDirectory(filePath); } /* DISABLED: MLT hardware decoder void MainWindow::turnOffHardwareDecoder() { ui->actionPreviewHardwareDecoder->setChecked(false); Settings.setPlayerPreviewHardwareDecoder(false); MLT.configureHardwareDecoder(false); } */ 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 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 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("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::onSubtitlesDockTriggered(bool checked) { if (checked) { m_subtitlesDock->show(); m_subtitlesDock->raise(); } } void MainWindow::onFilesDockTriggered(bool checked) { if (checked) { m_filesDock->show(); m_filesDock->raise(); } } /* DISABLED: MLT player/playlist functions void MainWindow::onPlaylistCreated() { // DISABLED: MLT playlist updateWindowTitle(); // if (!playlist() || playlist()->count() == 0) // return; // m_player->enableTab(Player::ProjectTabIndex, true); } void MainWindow::onPlaylistLoaded() { // DISABLED: MLT playlist loading // updateMarkers(); // m_player->enableTab(Player::ProjectTabIndex, true); } void MainWindow::onPlaylistCleared() { // DISABLED: MLT playlist clearing // m_player->onTabBarClicked(Player::SourceTabIndex); setWindowModified(true); } */ void MainWindow::onPlaylistClosed() { // setProfile(Settings.playerProfile()); // DISABLED: video // resetVideoModeMenu(); // DISABLED: video // setAudioChannels(Settings.playerAudioChannels()); // DISABLED: video setCurrentFile(""); setWindowModified(false); // resetSourceUpdated(); // DISABLED: video m_undoStack->clear(); // MLT.resetURL(); // DISABLED: MLT QMutexLocker locker(&m_autosaveMutex); m_autosaveFile.reset(new AutoSaveFile(untitledFileName())); if (!isMultitrackValid()) m_player->enableTab(Player::ProjectTabIndex, false); } void MainWindow::onPlaylistModified() { // DISABLED: MLT playlist modification 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() { // DISABLED: MLT multitrack creation // m_player->enableTab(Player::ProjectTabIndex, true); // QString trackTransitionService = m_timelineDock->model()->trackTransitionService(); // m_filterController->setTrackTransitionService(trackTransitionService); } void MainWindow::onMultitrackClosed() { // DISABLED: MLT multitrack closing // setAudioChannels(Settings.playerAudioChannels()); // 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() { // DISABLED: MLT multitrack modification with timeline dock 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(); // auto info = m_timelineDock->model()->getClipInfo(trackIndex, clipIndex); // if (info && info->producer && info->producer->is_valid()) { // int expected = info->frame_in; // auto info2 = m_timelineDock->model()->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 = m_timelineDock->model()->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() { // DISABLED: MLT multitrack duration change // if (MLT.producer() // && (void *) MLT.producer()->get_producer() == (void *) multitrack()->get_producer()) // m_player->onDurationChanged(); } void MainWindow::onNoteModified() { setWindowModified(true); } void MainWindow::onSubtitleModified() { setWindowModified(true); } void MainWindow::onCutModified() { if (!playlist() && !multitrack()) { setWindowModified(true); } if (playlist()) { emit m_playlistDock->enableUpdate(true); } sourceUpdated(); } void MainWindow::onProducerModified() { setWindowModified(true); sourceUpdated(); MLT.refreshConsumer(); } void MainWindow::onFilterModelChanged() { MLT.refreshConsumer(); setWindowModified(true); sourceUpdated(); if (playlist()) { emit m_playlistDock->enableUpdate(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() { m_undoStack->undo(); } void MainWindow::on_actionRedo_triggered() { m_undoStack->redo(); } void MainWindow::on_actionFAQ_triggered() { Util::openUrl(QUrl("https://www.shotcut.org/FAQ/")); } void MainWindow::on_actionForum_triggered() { Util::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 && MLT.producer() && MLT.producer()->is_valid()) { 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() && MLT.producer()->is_valid()) { 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; } static const auto kThemeDark = QStringLiteral("dark"); static const auto kThemeLight = QStringLiteral("light"); static const auto kThemeSystem = QStringLiteral("system"); static const auto kThemeSystemFusion = QStringLiteral("system-fusion"); static const auto kStyleFusion = QStringLiteral("Fusion"); static const auto kIconsOxygen = QStringLiteral("oxygen"); static const auto kIconsDarkOxygen = QStringLiteral("oxygen-dark"); void MainWindow::changeTheme(const QString &theme) { LOG_DEBUG() << "begin"; LOG_DEBUG() << "Available styles:" << QStyleFactory::keys(); auto mytheme = theme; #if !defined(SHOTCUT_THEME) // Workaround Quick Controls not using our custom palette - temporarily? std::unique_ptr style{QStyleFactory::create("fusion")}; auto brightness = style->standardPalette().color(QPalette::Text).lightnessF(); LOG_DEBUG() << brightness; mytheme = brightness < 0.5f ? kThemeLight : kThemeDark; QApplication::setStyle(kStyleFusion); QIcon::setThemeName(mytheme); #if defined(Q_OS_MAC) if (mytheme == kThemeDark) { auto palette = QGuiApplication::palette(); palette.setColor(QPalette::AlternateBase, palette.color(QPalette::Base).lighter()); QGuiApplication::setPalette(palette); } #elif defined(Q_OS_WIN) QGuiApplication::setPalette(style->standardPalette()); #endif #else if (mytheme == kThemeDark) { QApplication::setStyle(kStyleFusion); 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); palette.setColor(QPalette::Disabled, QPalette::Light, Qt::transparent); QApplication::setPalette(palette); QIcon::setThemeName(kThemeDark); ::qputenv("QT_QUICK_CONTROLS_CONF", ":/resources/qtquickcontrols2-dark.conf"); } else if (mytheme == "light") { QApplication::setStyle(kStyleFusion); QPalette palette; palette.setColor(QPalette::Window, "#efefef"); palette.setColor(QPalette::WindowText, "#000000"); palette.setColor(QPalette::Base, "#ffffff"); palette.setColor(QPalette::AlternateBase, "#f7f7f7"); palette.setColor(QPalette::Highlight, "#308cc6"); palette.setColor(QPalette::HighlightedText, "#ffffff"); palette.setColor(QPalette::ToolTipBase, "#308cc6"); palette.setColor(QPalette::ToolTipText, "#000000"); palette.setColor(QPalette::Text, "#000000"); palette.setColor(QPalette::BrightText, "#ffffff"); palette.setColor(QPalette::Button, "#efefef"); palette.setColor(QPalette::ButtonText, "#000000"); palette.setColor(QPalette::Link, "#308cc6"); palette.setColor(QPalette::LinkVisited, "#308cc6"); palette.setColor(QPalette::Disabled, QPalette::Text, Qt::darkGray); palette.setColor(QPalette::Disabled, QPalette::ButtonText, Qt::darkGray); palette.setColor(QPalette::Disabled, QPalette::Light, Qt::transparent); QApplication::setPalette(palette); QIcon::setThemeName(kThemeLight); ::qputenv("QT_QUICK_CONTROLS_CONF", ":/resources/qtquickcontrols2-light.conf"); } else { // Use a macro since this can change on some OS after setStyle(Fusion) #define isDark (QGuiApplication::palette().color(QPalette::Text).lightnessF() > 0.5f) #if defined(Q_OS_WIN) if (!::qEnvironmentVariableIsSet("QT_STYLE_OVERRIDE")) { // The modern Windows style adopted in Qt 6.7 changes spinboxes to have // larger arrow buttons side-by-side thus making the numeric area // smaller and incompatible with every other combination of style and OS. // Windows, windows11, & windowsvista styles all break the width of drop down menus! QApplication::setStyle(kStyleFusion); if (isDark) { QPalette palette; palette.setColor(QPalette::AlternateBase, palette.color(QPalette::Window)); palette.setColor(QPalette::Button, palette.color(QPalette::Window).lighter()); QApplication::setPalette(palette); } } #elif defined(Q_OS_MAC) if (!::qEnvironmentVariableIsSet("QT_STYLE_OVERRIDE")) { // The macOS style is hideous in dark mode! QApplication::setStyle(isDark ? kStyleFusion : QStringLiteral("macOS")); } if (isDark) { QPalette palette; palette.setColor(QPalette::AlternateBase, palette.color(QPalette::Window)); QApplication::setPalette(palette); } #else QApplication::setStyle(qApp->property("system-style").toString()); #endif if (isDark) QIcon::setThemeName(mytheme == kThemeSystemFusion ? kThemeDark : kIconsDarkOxygen); else QIcon::setThemeName(mytheme == kThemeSystemFusion ? kThemeLight : kIconsOxygen); if (!isDark) ::qputenv("QT_QUICK_CONTROLS_CONF", ":/resources/qtquickcontrols2-light.conf"); else ::qputenv("QT_QUICK_CONTROLS_CONF", ":/resources/qtquickcontrols2-dark.conf"); } #endif // auto pal = QGuiApplication::palette(); // LOG_INFO() << "altBase" << pal.alternateBase().color().name(); // LOG_INFO() << "base" << pal.base().color().name(); // LOG_INFO() << "window" << pal.window().color().name(); // LOG_INFO() << "windowText" << pal.windowText().color().name(); // LOG_INFO() << "toolTipBase" << pal.toolTipBase().color().name(); // LOG_INFO() << "toolTipText" << pal.toolTipText().color().name(); // LOG_INFO() << "text" << pal.text().color().name(); // LOG_INFO() << "button" << pal.button().color().name(); // LOG_INFO() << "buttonText" << pal.buttonText().color().name(); // LOG_INFO() << "placeholderText" << pal.placeholderText().color().name(); // LOG_INFO() << "brightText" << pal.brightText().color().name(); // LOG_INFO() << "highlight" << pal.highlight().color().name(); // LOG_INFO() << "highlightedText" << pal.highlightedText().color().name(); // LOG_INFO() << "link" << pal.link().color().name(); // LOG_INFO() << "linkVisited" << pal.linkVisited().color().name(); LOG_DEBUG() << "end"; } // DISABLED: MLT accessors /* 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(); } */ /* DISABLED: MLT producer widget loading 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("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 (QString::fromLatin1(producer->get(kPrivateProducerProperty)) == "htmlGenerator") w = new HtmlGeneratorWidget(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 (service == "xml-clip") w = new MltClipProducerWidget(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())); } if (-1 != w->metaObject()->indexOfSlot("onPlaying()")) { connect(MLT.videoWidget(), SIGNAL(playing()), w, SLOT(onPlaying())); } 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) { AbstractProducerWidget *pw = dynamic_cast(w); if (pw) { pw->setProducer(producer); } else { LOG_ERROR() << "Widget is not a producer widget."; return w; } 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()->indexOfSignal("showInFiles(QString)")) { connect(w, SIGNAL(showInFiles(QString)), this, SLOT(onFilesDockTriggered())); connect(w, SIGNAL(showInFiles(QString)), m_filesDock, SLOT(changeDirectory(QString))); } 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)), Qt::QueuedConnection); } scrollArea->setWidget(w); onProducerChanged(); } else if (scrollArea->widget()) { scrollArea->widget()->deleteLater(); } return w; } */ void MainWindow::on_actionEnterFullScreen_triggered() { bool isFull = isFullScreen(); if (isFull) { showNormal(); ui->actionEnterFullScreen->setText(tr("Enter Full Screen")); } else { showFullScreen(); ui->actionEnterFullScreen->setText(tr("Exit Full Screen")); } } void MainWindow::onGpuNotSupported() { // DISABLED: MLT GPU processing // if (Settings.processingMode() == ShotcutSettings::Linear10GpuCpu) { // Settings.setProcessingMode(ShotcutSettings::Native8Cpu); // } // ui->actionLinear10bitGpuCpu->setChecked(false); // ui->actionLinear10bitGpuCpu->setDisabled(true); LOG_WARNING() << "GPU not supported (disabled for mail client)"; // QMessageBox::critical(this, qApp->applicationName(), tr("GPU processing is not supported")); } void MainWindow::onShuttle(float x) { // DISABLED: MLT player shuttle // 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.consumerChanged(); } } 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.consumerChanged(); } 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_actionChannels4_triggered(bool checked) { changeAudioChannels(checked, 4); } void MainWindow::on_actionChannels6_triggered(bool checked) { changeAudioChannels(checked, 6); } void MainWindow::changeDeinterlacer(bool checked, const char *method) { if (checked) { MLT.videoWidget()->setProperty("deinterlacer", method); if (MLT.consumer()) { MLT.consumer()->set("deinterlacer", 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::on_actionBwdif_triggered(bool checked) { changeDeinterlacer(checked, "bwdif"); } 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) { m_multipleFilesLoading = true; LongUiTask longTask(tr("Open Files")); m_playlistDock->show(); m_playlistDock->raise(); ResourceDialog dialog(this); 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); producer->set(kShotcutSkipConvertProperty, true); undoStack()->push( new Playlist::AppendCommand(*m_playlistDock->model(), MLT.XML(producer), false)); m_recentDock->add(filename.toUtf8().constData()); dialog.add(producer); delete producer; } } emit m_playlistDock->model()->modified(); if (Settings.showConvertClipDialog() && dialog.hasTroubleClips()) { dialog.selectTroubleClips(); dialog.setWindowTitle(tr("Opened Files")); dialog.exec(); } m_multipleFilesLoading = false; } if (m_isPlaylistLoaded && Settings.playerGPU()) { updateThumbnails(); m_isPlaylistLoaded = false; } } void MainWindow::processSingleFile() { if (!m_multipleFilesLoading && Settings.showConvertClipDialog() && !MLT.producer()->get_int(kShotcutSkipConvertProperty)) { QString convertAdvice = Util::getConversionAdvice(MLT.producer()); if (!convertAdvice.isEmpty()) { MLT.producer()->set(kShotcutSkipConvertProperty, true); LongUiTask::cancel(); MLT.pause(); Util::offerSingleFileConversion(convertAdvice, MLT.producer(), this); } } } 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::onExternalTriggered(QAction *action) { // DISABLED: MLT external monitor output LOG_DEBUG() << action->data().toString(); // bool isExternal = !action->data().toString().isEmpty(); // QString profile = Settings.playerProfile(); // if (Settings.playerGPU() && MLT.producer() && Settings.playerExternal() != action->data()) { // if (confirmRestartExternalMonitor()) { // Settings.setPlayerExternal(action->data().toString()); // if (isExternal && profile.isEmpty()) { // profile = "atsc_720p_50"; // Settings.setPlayerProfile(profile); // } // m_exitCode = EXIT_RESTART; // QApplication::closeAllWindows(); // } else { // for (auto a : m_externalGroup->actions()) { // if (a->data() == Settings.playerExternal()) { // a->setChecked(true); // if (a->data().toString().startsWith("decklink")) { // if (m_decklinkGammaMenu) // m_decklinkGammaMenu->setEnabled(true); // if (m_keyerMenu) // m_keyerMenu->setEnabled(true); // } // break; // } // } // } // return; // } // 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()); // } // // Automatic not permitted for SDI/HDMI // if (isExternal && profile.isEmpty()) { // auto xml = MLT.XML(); // profile = "atsc_720p_50"; // Settings.setPlayerProfile(profile); // setProfile(profile); // MLT.reload(xml); // 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.consumerChanged(); // } // if (action->data().toString().startsWith("decklink")) { // if (m_decklinkGammaMenu) // m_decklinkGammaMenu->setEnabled(true); // if (m_keyerMenu) // m_keyerMenu->setEnabled(true); // } // // Preview scaling not permitted for SDI/HDMI // if (isExternal) { // ui->actionPreview360->setEnabled(false); // ui->actionPreview540->setEnabled(false); // } else { // ui->actionPreview360->setEnabled(true); // ui->actionPreview540->setEnabled(true); // } // setPreviewScale(Settings.playerPreviewScale()); Q_UNUSED(action) } void MainWindow::onDecklinkGammaTriggered(QAction *action) { LOG_DEBUG() << action->data().toString(); MLT.videoWidget()->setProperty("decklinkGamma", action->data()); if (Settings.playerGPU() && MLT.producer()) { if (confirmRestartExternalMonitor()) { Settings.setPlayerDecklinkGamma(action->data().toInt()); m_exitCode = EXIT_RESTART; QApplication::closeAllWindows(); } else { auto gamma = Settings.playerDecklinkGamma(); for (auto a : m_decklinkGammaGroup->actions()) { if (a->data() == gamma) { a->setChecked(true); break; } } } return; } MLT.consumerChanged(); Settings.setPlayerDecklinkGamma(action->data().toInt()); } void MainWindow::onKeyerTriggered(QAction *action) { LOG_DEBUG() << action->data().toString(); MLT.videoWidget()->setProperty("keyer", action->data()); if (Settings.playerGPU() && MLT.producer()) { if (confirmRestartExternalMonitor()) { Settings.setPlayerKeyerMode(action->data().toInt()); m_exitCode = EXIT_RESTART; QApplication::closeAllWindows(); } else { auto keyer = Settings.playerKeyerMode(); for (auto a : m_keyerGroup->actions()) { if (a->data() == keyer) { a->setChecked(true); break; } } } return; } MLT.consumerChanged(); Settings.setPlayerKeyerMode(action->data().toInt()); } void MainWindow::onProfileTriggered(QAction *action) { if (MLT.producer() && MLT.producer()->is_valid()) { if (!confirmProfileChange()) return; Settings.setPlayerProfile(action->data().toString()); // Figure out the top-level project producer auto producer = MLT.producer(); if (m_timelineDock->model()->rowCount() > 0) { producer = multitrack(); } else if (m_playlistDock->model()->rowCount() > 0) { producer = playlist(); } else if (MLT.isMultitrack() || MLT.isPlaylist()) { producer = MLT.savedProducer(); } MLT.fixLengthProperties(*producer); // Save the XML to get correct in/out points before profile is changed. QString xml = MLT.XML(producer); setProfile(action->data().toString()); MLT.reload(xml); emit producerOpened(false); } else { Settings.setPlayerProfile(action->data().toString()); setProfile(action->data().toString()); } } void MainWindow::onProfileChanged() { if (multitrack() && MLT.isMultitrack() && (m_timelineDock->selection().isEmpty() || m_timelineDock->currentTrack() == -1)) { emit m_timelineDock->selected(multitrack()); } updateWindowTitle(); } void MainWindow::on_actionAddCustomProfile_triggered() { QString xml; if (MLT.producer() && MLT.producer()->is_valid()) { if (!confirmProfileChange()) return; // 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.reload(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::backup() { QFileInfo info(m_currentFile); auto dateTime = info.lastModified().toString(Qt::ISODate); dateTime.replace(':', '-'); auto filename = QStringLiteral("%1/%2 %3.mlt") .arg(info.canonicalPath(), info.completeBaseName(), dateTime); if (!QFile::exists(filename) && !Util::warnIfNotWritable(filename, this, tr("Save XML")) && QFile::copy(m_currentFile, filename)) { showStatusMessage(tr("Saved backup %1").arg(filename)); } } void MainWindow::backupPeriodically() { auto dateTime = QFileInfo(m_currentFile).lastModified(); if (Settings.backupPeriod() > 0 && !kBackupFileRegex.match(m_currentFile).hasMatch() && dateTime.secsTo(QDateTime::currentDateTime()) / 60 > Settings.backupPeriod()) { backup(); } } bool MainWindow::confirmProfileChange() { if (MLT.isClip() || !Settings.askChangeVideoMode()) return true; QMessageBox dialog(QMessageBox::Warning, QCoreApplication::applicationName(), tr("

Please review your entire project after making this change.

" "

Shotcut does not automatically adjust things that are sensitive to " "size and position if you change resolution or aspect ratio.The timing of edits and keyframes may be slightly different if you " "change frame rate.

" "

It is a good idea to use File > Backup and Save before or " "after this operation.

" "

Do you want to change the Video Mode now?

"), QMessageBox::No | QMessageBox::Yes, &MAIN); dialog.setWindowModality(QmlApplication::dialogModality()); dialog.setDefaultButton(QMessageBox::Yes); dialog.setEscapeButton(QMessageBox::No); dialog.setCheckBox( new QCheckBox(tr("Do not show this anymore.", "Change video mode warning dialog"))); auto result = QMessageBox::Yes == dialog.exec(); if (dialog.checkBox()->isChecked()) Settings.setAskChangeVideoMode(false); return result; } bool MainWindow::confirmRestartExternalMonitor() { QMessageBox dialog(QMessageBox::Information, qApp->applicationName(), tr("Shotcut must restart to change external monitoring.\n" "Do you want to restart now?"), QMessageBox::No | QMessageBox::Yes, this); dialog.setDefaultButton(QMessageBox::Yes); dialog.setEscapeButton(QMessageBox::No); dialog.setWindowModality(QmlApplication::dialogModality()); return dialog.exec() == QMessageBox::Yes; } void MainWindow::resetFilterMenuIfNeeded() { // Reset to Favorites if currently set to Color or Audio if (filterController()->metadataModel()->search() == "#color" || filterController()->metadataModel()->filter() == MetadataModel::AudioFilter) { filterController()->metadataModel()->setFilter(MetadataModel::FavoritesFilter); filterController()->metadataModel()->setSearch(""); } } void MainWindow::on_actionSystemTheme_triggered() { Settings.setTheme("system"); restartAfterChangeTheme(); } void MainWindow::on_actionSystemFusion_triggered() { Settings.setTheme("system-fusion"); 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() { Util::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() { #ifdef USE_SCREENS_FOR_EXTERNAL_MONITORING foreach (QAction *action, m_externalGroup->actions()) { bool ok = false; int i = action->data().toInt(&ok); if (ok && i < QGuiApplication::screens().count()) { if (QGuiApplication::screens().at(i) == screen()) { if (action->isChecked()) { m_externalGroup->actions().first()->setChecked(true); Settings.setPlayerExternal(QString()); } action->setDisabled(true); } else { action->setEnabled(true); } } } #endif } 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, url)) return; isXmlRepaired(checker, url); } 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_actionShowProjectFolder_triggered() { Util::showInFolder(m_currentFile); } 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_actionResources_triggered() { ResourceDialog dialog(this); dialog.search(multitrack()); dialog.search(playlist()); dialog.exec(); } 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")); const auto previousLogName = dir.filePath("shotcut-log.bak"); if (QFile::exists(previousLogName)) { auto button = dialog.buttonBox()->addButton(tr("Previous"), QDialogButtonBox::ActionRole); connect(button, &QAbstractButton::clicked, this, [&]() { QFile logFile(previousLogName); logFile.open(QIODevice::ReadOnly | QIODevice::Text); dialog.setText(logFile.readAll()); logFile.close(); button->setEnabled(false); }); } 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 (MLT.consumer() && MLT.consumer()->is_valid()) MLT.consumer()->purge(); QCoreApplication::processEvents(); if (multitrack()) m_timelineDock->model()->close(); if (playlist()) m_playlistDock->model()->close(); else onMultitrackClosed(); closeProducer(); m_notesDock->setText(""); m_player->enableTab(Player::SourceTabIndex, false); MLT.purgeMemoryPool(); } } 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(); if (reply->error() == QNetworkReply::UnknownNetworkError) { m_network.get(QNetworkRequest(QUrl("http://check.shotcut.org/version.json"))); } } 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() { Util::openUrl(QUrl(m_upgradeUrl)); } void MainWindow::onClipCopied() { // DISABLED: MLT player tab enabling // 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.setEncoding(QStringConverter::Utf8); 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().toUtf8()); 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); auto *videoWidget = qobject_cast(MLT.videoWidget()); connect(videoWidget, &Mlt::VideoWidget::imageReady, this, &MainWindow::onVideoWidgetImageReady); MLT.setPreviewScale(0); videoWidget->requestImage(); MLT.refreshConsumer(); } void MainWindow::onVideoWidgetImageReady() { auto *videoWidget = qobject_cast(MLT.videoWidget()); QImage image = videoWidget->image(); disconnect(videoWidget, SIGNAL(imageReady()), this, nullptr); if (Settings.playerGPU() || Settings.playerPreviewScale()) { MLT.setPreviewScale(Settings.playerPreviewScale()); } if (!image.isNull() && (videoWidget->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() { // Make the transitions sub-folder if it does not exist QmlApplication::wipes(); Util::showInFolder(Settings.appDataLocation()); } void MainWindow::on_actionNew_triggered() { on_actionClose_triggered(); } void MainWindow::on_actionScreenSnapshot_triggered() { auto fileName = QmlApplication::getNextProjectFile("screenshot-.png"); if (fileName.isEmpty()) { fileName = Settings.savePath() + "/%1.png"; fileName = QFileDialog::getSaveFileName(this, tr("Screen Snapshot"), fileName.arg(tr("screenshot")), tr("PNG Files (*.png)")); if (fileName.isEmpty()) return; // Ensure the filename ends with .png if (!fileName.endsWith(".png", Qt::CaseInsensitive)) { fileName += ".png"; } } const bool maximized = windowState() & Qt::WindowMaximized; const auto mode = ScreenCapture::isWayland() ? ScreenCapture::Fullscreen : ScreenCapture::Interactive; m_screenCapture = new ScreenCapture(fileName, mode, this); connect(m_screenCapture, &ScreenCapture::minimizeShotcut, this, [this]() { showMinimized(); }); connect(m_screenCapture, &ScreenCapture::finished, this, [=](bool success) { if (success) // Automatically open the captured file QTimer::singleShot(500, this, [this, fileName]() { open(fileName); }); if (maximized) showMaximized(); else showNormal(); }); m_screenCapture->startSnapshot(); } void MainWindow::on_actionScreenRecording_triggered() { QString filenameExtension; auto mode = ScreenCapture::Interactive; #ifdef Q_OS_WIN Util::openUrl({"ms-screenclip:?type=recording", QUrl::TolerantMode}); return; #elif defined(Q_OS_MAC) filenameExtension = ".mov"; #else bool isGNOMEorKDEonWayland = false; // GNOME and KDE have built-in screen recording compatible with Wayland if (ScreenCapture::isWayland()) { const auto desktop = qEnvironmentVariable("XDG_CURRENT_DESKTOP").toLower(); isGNOMEorKDEonWayland = desktop.contains("gnome") || desktop.contains("kde") || desktop.contains("plasma"); if (isGNOMEorKDEonWayland) { filenameExtension = ".webm"; mode = ScreenCapture::Fullscreen; } } else { filenameExtension = ".mp4"; } #endif QString fileName; if (!filenameExtension.isEmpty()) { fileName = QmlApplication::getNextProjectFile("screen-" + filenameExtension); if (fileName.isEmpty()) { fileName = Settings.savePath() + "/%1" + filenameExtension; fileName = QFileDialog::getSaveFileName(this, tr("Screen Recording"), fileName.arg(tr("screen")), QString("%1 %2 (*%3)") .arg(filenameExtension.toUpper(), tr("Files"), filenameExtension)); if (fileName.isEmpty()) return; // Ensure the filename ends with extension if (!fileName.endsWith(filenameExtension, Qt::CaseInsensitive)) { fileName += filenameExtension; } } } #ifdef Q_OS_MAC ScreenCaptureJob *job = new ScreenCaptureJob(tr("Screen Recording"), fileName, QRect(), true); JOBS.add(job); return; #elif defined(Q_OS_UNIX) && !defined(Q_OS_MAC) // On Linux with Wayland and not GNOME or KDE if (ScreenCapture::isWayland() && !isGNOMEorKDEonWayland) { // Let user pick a program to use on Wayland and not GNOME or KDE. // OBS Studio (default) supports Wayland. QString exePath = Settings.screenRecorderPath(); // Check if saved path is still valid if (!exePath.isEmpty() && !QFile::exists(exePath)) { exePath.clear(); } // If not found, prompt user if (exePath.isEmpty()) { exePath = Util::getExecutable(this); if (exePath.isEmpty()) { return; // User cancelled } Settings.setScreenRecorderPath(exePath); } // Launch the screen recorder if (Util::startDetached(exePath, QStringList())) { showStatusMessage(tr("Screen recorder launched")); } else { showStatusMessage(tr("Failed to launch screen recorder")); } return; } #endif m_screenCapture = new ScreenCapture(fileName, mode, this); connect(m_screenCapture, &ScreenCapture::minimizeShotcut, this, [this]() { showMinimized(); }); connect(m_screenCapture, &ScreenCapture::beginRecording, this, [=](const QRect &rect, bool recordAudio) { ScreenCaptureJob *job = new ScreenCaptureJob(tr("Screen Recording"), fileName, rect, recordAudio); JOBS.add(job); }); m_screenCapture->startRecording(); } void MainWindow::on_actionKeyboardShortcuts_triggered() { auto name = QString::fromLatin1("actionsDialog"); auto dialog = QObject::findChild(name, Qt::FindDirectChildrenOnly); if (!dialog) { dialog = new ActionsDialog(this); dialog->setObjectName(name); } dialog->show(); dialog->activateWindow(); dialog->raise(); } 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()); resetFilterMenuIfNeeded(); } 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()); resetFilterMenuIfNeeded(); } 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()); resetFilterMenuIfNeeded(); } 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()); // Set filter menu to Video filters for Color layout filterController()->metadataModel()->setFilter(MetadataModel::VideoFilter); filterController()->metadataModel()->setSearch("#color"); } 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()); // Set filter menu to Audio filters for Audio layout filterController()->metadataModel()->setFilter(MetadataModel::AudioFilter); filterController()->metadataModel()->setSearch(""); } void MainWindow::on_actionLayoutPlayer_triggered() { if (isMultitrackValid()) { // Clearing selection causes Filters to clear, which prevents showing a filter's VUI m_timelineDock->setSelection(); } 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()); resetFilterMenuIfNeeded(); } 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()); resetFilterMenuIfNeeded(); } 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()); resetFilterMenuIfNeeded(); } 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() { const auto widget = ui->mainToolBar->widgetForAction(ui->actionOpenOther2); ui->actionOpenOther2->menu()->popup(widget->mapToGlobal(QPoint(0, widget->height()))); } void MainWindow::onOpenOtherTriggered(QWidget *widget) { m_producerWidget.reset(widget); auto dialog = new QDialog(this); dialog->resize(426, 288); dialog->setWindowModality(QmlApplication::dialogModality()); auto vlayout = new QVBoxLayout(dialog); vlayout->addWidget(widget); auto buttonBox = new QDialogButtonBox(dialog); buttonBox->setOrientation(Qt::Horizontal); buttonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Open); if (!AbstractProducerWidget::isDevice(widget)) { auto button = buttonBox->addButton(tr("Add To Timeline"), QDialogButtonBox::ApplyRole); connect(button, &QPushButton::clicked, this, [=]() { dialog->done(-1); }); } vlayout->addWidget(buttonBox); connect(buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject); connect(dialog, &QDialog::finished, this, &MainWindow::onOpenOtherFinished); dialog->show(); } void MainWindow::onOpenOtherFinished(int result) { // Handle screen capture custom result codes if (result == 42) { // Screen Snapshot on_actionScreenSnapshot_triggered(); return; } else if (result == 43) { // Screen Recording on_actionScreenRecording_triggered(); return; } if (QDialog::Rejected == result || !m_producerWidget) return; if (AbstractProducerWidget::isDevice(m_producerWidget.get()) && !Settings.playerGPU()) closeProducer(); const auto name = m_producerWidget->objectName(); auto dialog = dynamic_cast(m_producerWidget.get()); if (QLatin1String("GlaxnimateProducerWidget") == name) { auto glax = qobject_cast(m_producerWidget.get()); glax->setLaunchOnNew(false); } auto &profile = MLT.profile(); auto producer = dialog->newProducer(profile); if (!(producer && producer->is_valid())) { delete producer; m_producerWidget.reset(); return; } 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()); if (QDialog::Accepted == result) { // open in the source player open(producer); // Mlt::Controller owns the producer now } else { m_player->switchToTab(Player::ProjectTabIndex); auto trackType = (QLatin1String("ToneProducerWidget") == name) ? AudioTrackType : VideoTrackType; auto trackIndex = m_timelineDock->addTrackIfNeeded(trackType); m_timelineDock->overwrite(trackIndex, -1, MLT.XML(producer), false); delete producer; } if (QLatin1String("TextProducerWidget") == name) { m_filtersDock->show(); m_filtersDock->raise(); } else { m_propertiesDock->show(); m_propertiesDock->raise(); } m_producerWidget.reset(); } 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)); } void MainWindow::onHtmlGeneratorTriggered() { if (!Util::isChromiumAvailable()) { QMessageBox dialog(QMessageBox::Warning, QApplication::applicationName(), this->tr( "

This feature requires Google Chrome or a Chromium-based browser.

" "

If you already installed one it could not be " "found at the expected location: %1

Click OK to " "continue and locate the program on your system.

") .arg(Settings.chromiumPath()), QMessageBox::Cancel | QMessageBox::Ok, this); dialog.setWindowModality(QmlApplication::dialogModality()); dialog.setDefaultButton(QMessageBox::Ok); dialog.setEscapeButton(QMessageBox::Cancel); if (QMessageBox::Cancel == dialog.exec()) return; const auto executable = Util::getExecutable(this); if (executable.isEmpty()) return; Settings.setChromiumPath(executable); } // Create a color producer with special property and open it auto producer = new Mlt::Producer(MLT.profile(), "color", "#00000000"); producer->set(kPrivateProducerProperty, "htmlGenerator"); open(producer, false); m_propertiesDock->show(); m_propertiesDock->raise(); m_producerWidget.reset(); } void MainWindow::on_actionClearRecentOnExit_toggled(bool arg1) { Settings.setClearRecent(arg1); if (arg1) { Settings.setRecent(QStringList()); Settings.setProjects(QStringList()); } } void MainWindow::onSceneGraphInitialized() { if (Settings.playerGPU() && Settings.playerWarnGPU()) { QMessageBox dialog(QMessageBox::Warning, qApp->applicationName(), tr("GPU processing is EXPERIMENTAL, UNSTABLE and UNSUPPORTED! Unsupported " "means do not report bugs about it.\n\n" "Do you want to disable GPU processing 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) { Settings.setProcessingMode(ShotcutSettings::Native8Cpu); m_exitCode = EXIT_RESTART; QApplication::closeAllWindows(); } } auto videoWidget = (Mlt::VideoWidget *) &(MLT); videoWidget->setBlankScene(); } void MainWindow::on_actionShowTextUnderIcons_toggled(bool b) { ui->mainToolBar->setToolButtonStyle(b ? Qt::ToolButtonTextUnderIcon : Qt::ToolButtonIconOnly); Settings.setTextUnderIcons(b); updateLayoutSwitcher(); if (b && this->width() < 1800) { ui->mainToolBar->removeAction(ui->actionOpenOther2); ui->mainToolBar->removeAction(ui->actionFiles); ui->mainToolBar->removeAction(ui->actionMarkers); ui->mainToolBar->removeAction(ui->actionNotes); } } void MainWindow::on_actionShowSmallIcons_toggled(bool b) { ui->mainToolBar->setIconSize(b ? QSize(16, 16) : QSize()); Settings.setSmallIcons(b); updateLayoutSwitcher(); } void MainWindow::onPlaylistInChanged(int in) { // DISABLED: MLT player in point setting // m_player->blockSignals(true); // m_player->setIn(in); // m_player->blockSignals(false); Q_UNUSED(in) } void MainWindow::onPlaylistOutChanged(int out) { // DISABLED: MLT player out point setting // m_player->blockSignals(true); // m_player->setOut(out); // m_player->blockSignals(false); Q_UNUSED(out) } void MainWindow::on_actionPreviewNone_triggered(bool checked) { if (checked) { Settings.setPlayerPreviewScale(0); setPreviewScale(0); m_player->showIdleStatus(); } } void MainWindow::on_actionPreview360_triggered(bool checked) { // DISABLED: MLT player preview scaling // if (checked) { // Settings.setPlayerPreviewScale(360); // setPreviewScale(360); // m_player->showIdleStatus(); // } Q_UNUSED(checked) } void MainWindow::on_actionPreview540_triggered(bool checked) { // DISABLED: MLT player preview scaling // if (checked) { // Settings.setPlayerPreviewScale(540); // setPreviewScale(540); // m_player->showIdleStatus(); // } Q_UNUSED(checked) } void MainWindow::on_actionPreview720_triggered(bool checked) { if (checked) { Settings.setPlayerPreviewScale(720); setPreviewScale(720); m_player->showIdleStatus(); } } void MainWindow::on_actionPreview1080_triggered(bool checked) { if (checked) { Settings.setPlayerPreviewScale(1080); setPreviewScale(1080); m_player->showIdleStatus(); } } void MainWindow::on_actionPreviewHardwareDecoder_triggered(bool checked) { Settings.setPlayerPreviewHardwareDecoder(checked); MLT.configureHardwareDecoder(checked); MLT.reload(QString()); emit producerOpened(false); } QUuid MainWindow::timelineClipUuid(int trackIndex, int clipIndex) { auto info = m_timelineDock->model()->getClipInfo(trackIndex, clipIndex); if (info && info->producer && info->producer->is_valid()) return MLT.ensureHasUuid(*info->producer); 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 auto info = m_timelineDock->model()->findClipByUuid(uuid, trackIndex, clipIndex); if (info && 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)); } } 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); } } int MainWindow::mltIndexForTrack(int trackIndex) const { return m_timelineDock->model()->mltIndexForTrack(trackIndex); } int MainWindow::bottomVideoTrackIndex() const { return m_timelineDock->model()->bottomVideoTrackIndex(); } void MainWindow::on_actionTopics_triggered() { Util::openUrl(QUrl("https://www.shotcut.org/howtos/")); } void MainWindow::on_actionWhatsThis_triggered() { QWhatsThis::enterWhatsThisMode(); } void MainWindow::on_actionSync_triggered() { auto dialog = new SystemSyncDialog(this); dialog->show(); dialog->raise(); dialog->activateWindow(); } void MainWindow::on_actionUseProxy_triggered(bool checked) { // DISABLED: MLT proxy handling // 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?"), // 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(); Q_UNUSED(checked) } 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() { // Options dialog auto uniqueColors = m_timelineDock->markersModel()->allColors(); if (uniqueColors.isEmpty()) { return; } std::sort(uniqueColors.begin(), uniqueColors.end(), [=](const QColor &a, const QColor &b) { if (a.hue() == b.hue()) { if (a.saturation() == b.saturation()) { return a.value() <= b.value(); } return a.saturation() <= b.saturation(); } return a.hue() <= b.hue(); }); QStringList colors; for (auto &color : uniqueColors) { colors << color.name(); } const auto rangesOption = tr("Include ranges (Duration > 1 frame)?"); QStringList initialOptions; for (auto &m : m_timelineDock->markersModel()->getMarkers()) { if (m.end != m.start) { initialOptions << rangesOption; break; } } ListSelectionDialog dialog(initialOptions, this); dialog.setWindowModality(QmlApplication::dialogModality()); dialog.setWindowTitle(tr("Choose Markers")); if (Settings.exportRangeMarkers()) { dialog.setSelection({rangesOption}); } dialog.setColors(colors); if (dialog.exec() != QDialog::Accepted) { return; } auto selection = dialog.selection(); Settings.setExportRangeMarkers(selection.contains(rangesOption)); // 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.setEncoding(QStringConverter::Utf8); 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(); if (selection.contains(rangesOption)) { options.setProperty("includeRanges", true); selection.removeOne(rangesOption); } QJSValue array = jsEngine.newArray(selection.size()); for (int i = 0; i < selection.size(); ++i) array.setProperty(i, selection[i].toUpper()); options.setProperty("colors", array); 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().toUtf8()); 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); } } void MainWindow::on_actionReset_triggered() { QMessageBox dialog(QMessageBox::Question, qApp->applicationName(), tr("This will reset all settings, and Shotcut must restart afterwards.\n" "Do you want to reset and restart now?"), QMessageBox::No | QMessageBox::Yes, this); dialog.setDefaultButton(QMessageBox::Yes); dialog.setEscapeButton(QMessageBox::No); dialog.setWindowModality(QmlApplication::dialogModality()); if (dialog.exec() == QMessageBox::Yes) { Settings.reset(); m_exitCode = EXIT_RESET; QApplication::closeAllWindows(); } } void MainWindow::onCreateOrEditFilterOnOutput(Mlt::Filter *filter, const QStringList &key_properties) { m_timelineDock->selectMultitrack(); onFiltersDockTriggered(); m_timelineDock->emitSelectedFromSelection(); m_filterController->addOrEditFilter(filter, key_properties); } void MainWindow::showSettingsMenu() const { QPoint point(140 * devicePixelRatioF(), ui->menuBar->height()); #if !defined(Q_OS_MAC) point = ui->menuBar->mapToGlobal(point); #endif ui->menuSettings->popup(point, ui->menuSettings->defaultAction()); }