diff --git a/src/assets/view/widgets/buttonparamwidget.cpp b/src/assets/view/widgets/buttonparamwidget.cpp
index 7871e067b..e871a788e 100644
--- a/src/assets/view/widgets/buttonparamwidget.cpp
+++ b/src/assets/view/widgets/buttonparamwidget.cpp
@@ -1,173 +1,173 @@
/***************************************************************************
* Copyright (C) 2019 by Jean-Baptiste Mardelle *
* This file is part of Kdenlive. See www.kdenlive.org. *
* *
* 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 2 of the License, or *
* (at your option) version 3 or any later version accepted by the *
* membership of KDE e.V. (or its successor approved by the membership *
* of KDE e.V.), which shall act as a proxy defined in Section 14 of *
* version 3 of the license. *
* *
* 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 "buttonparamwidget.hpp"
#include "assets/model/assetparametermodel.hpp"
#include "jobs/filterclipjob.h"
#include "jobs/jobmanager.h"
#include "assets/model/assetcommand.hpp"
#include "core.h"
#include
#include
#include
#include
ButtonParamWidget::ButtonParamWidget(std::shared_ptr model, QModelIndex index, QWidget *parent)
: AbstractParamWidget(std::move(model), index, parent)
, m_label(nullptr)
{
// setup the comment
m_buttonName = m_model->data(m_index, Qt::DisplayRole).toString();
m_alternatebuttonName = m_model->data(m_index, AssetParameterModel::AlternateNameRole).toString();
//QString name = m_model->data(m_index, AssetParameterModel::NameRole).toString();
QString comment = m_model->data(m_index, AssetParameterModel::CommentRole).toString();
setToolTip(comment);
//setEnabled(m_model->getOwnerId().first != ObjectType::TimelineTrack);
auto *layout = new QVBoxLayout(this);
QVariantList filterData = m_model->data(m_index, AssetParameterModel::FilterJobParamsRole).toList();
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
QStringList filterAddedParams = m_model->data(m_index, AssetParameterModel::FilterParamsRole).toString().split(QLatin1Char(' '), QString::SkipEmptyParts);
#else
QStringList filterAddedParams = m_model->data(m_index, AssetParameterModel::FilterParamsRole).toString().split(QLatin1Char(' '), Qt::SkipEmptyParts);
#endif
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
QStringList consumerParams = m_model->data(m_index, AssetParameterModel::FilterConsumerParamsRole).toString().split(QLatin1Char(' '), QString::SkipEmptyParts);
#else
QStringList consumerParams = m_model->data(m_index, AssetParameterModel::FilterConsumerParamsRole).toString().split(QLatin1Char(' '), Qt::SkipEmptyParts);
#endif
QString conditionalInfo;
- for (const QVariant jobElement : filterData) {
+ for (const QVariant &jobElement : filterData) {
QStringList d = jobElement.toStringList();
if (d.size() == 2) {
if (d.at(0) == QLatin1String("conditionalinfo")) {
conditionalInfo = d.at(1);
} else if (d.at(0) == QLatin1String("key")) {
m_keyParam = d.at(1);
}
}
}
QVector> filterParams = m_model->getAllParameters();
m_displayConditional = true;
for (const auto ¶m : filterParams) {
if (param.first == m_keyParam) {
if (!param.second.toString().isEmpty()) {
m_displayConditional = false;
}
break;
}
}
if (!conditionalInfo.isEmpty()) {
m_label = new KMessageWidget(conditionalInfo, this);
m_label->setWordWrap(true);
layout->addWidget(m_label);
m_label->setVisible(m_displayConditional);
}
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
m_button = new QPushButton(m_displayConditional ? m_buttonName : m_alternatebuttonName, this);
layout->addWidget(m_button);
setMinimumHeight(m_button->sizeHint().height() + (m_label != nullptr ? m_label->sizeHint().height() : 0));
// emit the signal of the base class when appropriate
connect(this->m_button, &QPushButton::clicked, [&, filterData, filterAddedParams, consumerParams]() {
// Trigger job
if (!m_displayConditional) {
QVector> values;
values << QPair(m_keyParam,QVariant());
auto *command = new AssetUpdateCommand(m_model, values);
pCore->pushUndo(command);
return;
}
QVector> filterLastParams = m_model->getAllParameters();
ObjectId owner = m_model->getOwnerId();
const QString assetId = m_model->getAssetId();
QString binId;
int cid = -1;
int in = -1;
int out = -1;
if (owner.first == ObjectType::BinClip) {
binId = QString::number(owner.second);
} else if (owner.first == ObjectType::TimelineClip) {
cid = owner.second;
binId = pCore->getTimelineClipBinId(cid);
in = pCore->getItemIn(owner);
out = in + pCore->getItemDuration(owner);
} else if (owner.first == ObjectType::TimelineTrack || owner.first == ObjectType::Master) {
in = 0;
out = pCore->getItemDuration(owner);
}
std::unordered_map fParams;
std::unordered_map fData;
- for (const QVariant jobElement : filterData) {
+ for (const QVariant &jobElement : filterData) {
QStringList d = jobElement.toStringList();
if (d.size() == 2)
fData.insert({d.at(0), d.at(1)});
}
for (const auto ¶m : filterLastParams) {
if (param.first != m_keyParam) {
fParams.insert({param.first, param.second});
}
}
for (const QString &fparam : filterAddedParams) {
if (fparam.contains(QLatin1Char('='))) {
fParams.insert({fparam.section(QLatin1Char('='), 0, 0), fparam.section(QLatin1Char('='), 1)});
}
}
pCore->jobManager()->startJob({binId}, -1, QString(), owner, m_model, assetId, in, out, assetId, fParams, fData, consumerParams);
if (m_label) {
m_label->setVisible(false);
}
m_button->setEnabled(false);
});
}
void ButtonParamWidget::slotShowComment(bool show)
{
Q_UNUSED(show);
//if (!m_labelComment->text().isEmpty()) {
// m_widgetComment->setVisible(show);
//}
}
void ButtonParamWidget::slotRefresh()
{
QVector> filterParams = m_model->getAllParameters();
m_displayConditional = true;
for (const auto ¶m : filterParams) {
if (param.first == m_keyParam && !param.second.isNull()) {
m_displayConditional = false;
break;
}
}
if (m_label) {
m_label->setVisible(m_displayConditional);
}
m_button->setText(m_displayConditional ? m_buttonName : m_alternatebuttonName);
m_button->setEnabled(true);
updateGeometry();
}
bool ButtonParamWidget::getValue()
{
return true;
}
diff --git a/src/audiomixer/mixermanager.cpp b/src/audiomixer/mixermanager.cpp
index 5669c321e..a43789e60 100644
--- a/src/audiomixer/mixermanager.cpp
+++ b/src/audiomixer/mixermanager.cpp
@@ -1,239 +1,231 @@
/***************************************************************************
* Copyright (C) 2019 by Jean-Baptiste Mardelle *
* This file is part of Kdenlive. See www.kdenlive.org. *
* *
* 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 2 of the License, or *
* (at your option) version 3 or any later version accepted by the *
* membership of KDE e.V. (or its successor approved by the membership *
* of KDE e.V.), which shall act as a proxy defined in Section 14 of *
* version 3 of the license. *
* *
* 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 "mixermanager.hpp"
#include "mixerwidget.hpp"
#include "timeline2/model/timelineitemmodel.hpp"
#include "kdenlivesettings.h"
#include "mlt++/MltService.h"
#include "mlt++/MltTractor.h"
#include
#include
#include
#include
#include
#include
#include
const double log_factor = 1.0 / log10(1.0 / 127);
-static inline double levelToDB(double level)
-{
- if (level <= 0) {
- return -100;
- }
- return 100 * (1.0 - log10(level) * log_factor);
-}
-
MixerManager::MixerManager(QWidget *parent)
: QWidget(parent)
, m_masterMixer(nullptr)
, m_visibleMixerManager(false)
, m_expandedWidth(-1)
, m_recommandedWidth(300)
{
m_masterBox = new QHBoxLayout;
setContentsMargins(0, 0, 0, 0);
m_channelsBox = new QScrollArea(this);
m_channelsBox->setContentsMargins(0, 0, 0, 0);
m_box = new QHBoxLayout;
m_box->setSpacing(0);
auto *channelsBoxContainer = new QWidget(this);
m_channelsBox->setWidget(channelsBoxContainer);
m_channelsBox->setWidgetResizable(true);
m_channelsBox->setFrameShape(QFrame::NoFrame);
m_box->addWidget(m_channelsBox);
m_channelsLayout = new QHBoxLayout;
m_channelsLayout->setContentsMargins(0, 0, 0, 0);
m_masterBox->setContentsMargins(0, 0, 0, 0);
m_channelsLayout->setSpacing(4);
channelsBoxContainer->setLayout(m_channelsLayout);
m_channelsLayout->addStretch(10);
m_box->addLayout(m_masterBox);
setLayout(m_box);
}
void MixerManager::registerTrack(int tid, std::shared_ptr service, const QString &trackTag)
{
if (m_mixers.count(tid) > 0) {
// Track already registered
return;
}
std::shared_ptr mixer(new MixerWidget(tid, service, trackTag, this));
connect(mixer.get(), &MixerWidget::muteTrack, [&](int id, bool mute) {
m_model->setTrackProperty(id, "hide", mute ? QStringLiteral("1") : QStringLiteral("3"));
});
if (m_visibleMixerManager) {
mixer->connectMixer(!KdenliveSettings::mixerCollapse());
}
connect(this, &MixerManager::updateLevels, mixer.get(), &MixerWidget::updateAudioLevel);
connect(this, &MixerManager::clearMixers, mixer.get(), &MixerWidget::clear);
connect(mixer.get(), &MixerWidget::toggleSolo, [&](int trid, bool solo) {
if (!solo) {
// unmute
for (int id : m_soloMuted) {
if (m_mixers.count(id) > 0) {
m_model->setTrackProperty(id, "hide", QStringLiteral("1"));
}
}
m_soloMuted.clear();
} else {
if (!m_soloMuted.isEmpty()) {
// Another track was solo, discard first
for (int id : m_soloMuted) {
if (m_mixers.count(id) > 0) {
m_model->setTrackProperty(id, "hide", QStringLiteral("1"));
}
}
m_soloMuted.clear();
}
for (auto item : m_mixers) {
if (item.first != trid && !item.second->isMute()) {
m_model->setTrackProperty(item.first, "hide", QStringLiteral("3"));
m_soloMuted << item.first;
item.second->unSolo();
}
}
}
});
m_mixers[tid] = mixer;
QFrame *line = new QFrame(this);
line->setFrameShape(QFrame::VLine);
line->setFrameShadow(QFrame::Sunken);
m_channelsLayout->insertWidget(0, line);
m_channelsLayout->insertWidget(0, mixer.get());
m_recommandedWidth = (mixer->minimumWidth() + 12 + line->minimumWidth()) * (qMin(2, int(m_mixers.size())));
m_channelsBox->setMinimumWidth(m_recommandedWidth);
}
void MixerManager::deregisterTrack(int tid)
{
Q_ASSERT(m_mixers.count(tid) > 0);
m_mixers.erase(tid);
}
void MixerManager::cleanup()
{
while (QLayoutItem* item = m_channelsLayout->takeAt(0)) {
if (QWidget* widget = item->widget()) {
widget->deleteLater();
}
delete item;
}
m_channelsLayout->addStretch(10);
m_mixers.clear();
if (m_masterMixer) {
m_masterMixer->reset();
}
}
void MixerManager::setModel(std::shared_ptr model)
{
// Insert master mixer
m_model = model;
connect(m_model.get(), &TimelineItemModel::dataChanged, [&](const QModelIndex &topLeft, const QModelIndex &, const QVector &roles) {
if (roles.contains(TimelineModel::IsDisabledRole)) {
int id = (int) topLeft.internalId();
if (m_mixers.count(id) > 0) {
m_mixers[id]->setMute(m_model->data(topLeft, TimelineModel::IsDisabledRole).toBool());
} else {
qDebug()<<"=== MODEL DATA CHANGED: MUTE DONE TRACK NOT FOUND!!!";
}
}
});
Mlt::Tractor *service = model->tractor();
if (m_masterMixer != nullptr) {
// delete previous master mixer
m_masterBox->removeWidget(m_masterMixer.get());
}
m_masterMixer.reset(new MixerWidget(-1, service, i18n("Master"), this));
connect(m_masterMixer.get(), &MixerWidget::muteTrack, [&](int /*id*/, bool mute) {
m_model->tractor()->set("hide", mute ? 3 : 1);
});
if (m_visibleMixerManager) {
m_masterMixer->connectMixer(true);
}
connect(this, &MixerManager::updateLevels, m_masterMixer.get(), &MixerWidget::updateAudioLevel);
connect(this, &MixerManager::clearMixers, m_masterMixer.get(), &MixerWidget::clear);
m_masterBox->addWidget(m_masterMixer.get());
if (KdenliveSettings::mixerCollapse()) {
collapseMixers();
}
}
void MixerManager::recordStateChanged(int tid, bool recording)
{
if (m_mixers.count(tid) > 0) {
m_mixers[tid]->setRecordState(recording);
}
}
void MixerManager::connectMixer(bool doConnect)
{
m_visibleMixerManager = doConnect;
for (auto item : m_mixers) {
item.second->connectMixer(m_visibleMixerManager && !KdenliveSettings::mixerCollapse());
}
if (m_masterMixer != nullptr) {
m_masterMixer->connectMixer(m_visibleMixerManager);
}
}
void MixerManager::collapseMixers()
{
connectMixer(m_visibleMixerManager);
if (KdenliveSettings::mixerCollapse()) {
m_expandedWidth = width();
m_channelsBox->setFixedWidth(0);
//m_line->setMaximumWidth(0);
setFixedWidth(m_masterMixer->width() + 2 * m_box->contentsMargins().left());
} else {
//m_line->setMaximumWidth(QWIDGETSIZE_MAX);
m_channelsBox->setMaximumWidth(QWIDGETSIZE_MAX);
m_channelsBox->setMinimumWidth(m_recommandedWidth);
setFixedWidth(m_expandedWidth);
QMetaObject::invokeMethod(this, "resetSizePolicy", Qt::QueuedConnection);
}
}
void MixerManager::resetSizePolicy()
{
setMaximumWidth(QWIDGETSIZE_MAX);
setMinimumWidth(0);
}
QSize MixerManager::sizeHint() const
{
return QSize(m_recommandedWidth, 0);
}
void MixerManager::pauseMonitoring(bool pause)
{
for (auto item : m_mixers) {
item.second->pauseMonitoring(pause);
}
if (m_masterMixer != nullptr) {
m_masterMixer->pauseMonitoring(pause);
}
}
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index e32d1d6a6..bd2f61819 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1,4074 +1,4074 @@
/***************************************************************************
* Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
* *
* 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 2 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, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
***************************************************************************/
#include "mainwindow.h"
#include "assets/assetpanel.hpp"
#include "bin/clipcreator.hpp"
#include "bin/generators/generators.h"
#include "bin/projectclip.h"
#include "bin/projectfolder.h"
#include "bin/projectitemmodel.h"
#include "core.h"
#include "dialogs/clipcreationdialog.h"
#include "dialogs/kdenlivesettingsdialog.h"
#include "dialogs/renderwidget.h"
#include "dialogs/wizard.h"
#include "doc/docundostack.hpp"
#include "doc/kdenlivedoc.h"
#include "effects/effectlist/view/effectlistwidget.hpp"
#include "effectslist/effectbasket.h"
#include "hidetitlebars.h"
#include "jobs/jobmanager.h"
#include "jobs/scenesplitjob.hpp"
#include "jobs/speedjob.hpp"
#include "jobs/stabilizejob.hpp"
#include "jobs/transcodeclipjob.h"
#include "kdenlivesettings.h"
#include "layoutmanagement.h"
#include "library/librarywidget.h"
#include "audiomixer/mixermanager.hpp"
#include "mainwindowadaptor.h"
#include "mltconnection.h"
#include "mltcontroller/clipcontroller.h"
#include "monitor/monitor.h"
#include "monitor/monitormanager.h"
#include "monitor/scopes/audiographspectrum.h"
#include "profiles/profilemodel.hpp"
#include "project/cliptranscode.h"
#include "project/dialogs/archivewidget.h"
#include "project/dialogs/projectsettings.h"
#include "project/projectcommands.h"
#include "project/projectmanager.h"
#include "scopes/scopemanager.h"
#include "timeline2/view/timelinecontroller.h"
#include "timeline2/view/timelinetabs.hpp"
#include "timeline2/view/timelinewidget.h"
#include "titler/titlewidget.h"
#include "transitions/transitionlist/view/transitionlistwidget.hpp"
#include "transitions/transitionsrepository.hpp"
#include "utils/resourcewidget.h"
#include "utils/thememanager.h"
#include "utils/otioconvertions.h"
#include "profiles/profilerepository.hpp"
#include "widgets/progressbutton.h"
#include
#include "project/dialogs/temporarydata.h"
#ifdef USE_JOGSHUTTLE
#include "jogshuttle/jogmanager.h"
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kdenlive_debug.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static const char version[] = KDENLIVE_VERSION;
namespace Mlt {
class Producer;
}
QMap MainWindow::m_lumacache;
QMap MainWindow::m_lumaFiles;
/*static bool sortByNames(const QPair &a, const QPair &b)
{
return a.first < b.first;
}*/
// determine the default KDE style as defined BY THE USER
// (as opposed to whatever style KDE considers default)
static QString defaultStyle(const char *fallback = nullptr)
{
KSharedConfigPtr kdeGlobals = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::NoGlobals);
KConfigGroup cg(kdeGlobals, "KDE");
return cg.readEntry("widgetStyle", fallback);
}
MainWindow::MainWindow(QWidget *parent)
: KXmlGuiWindow(parent)
{
}
void MainWindow::init()
{
QString desktopStyle = QApplication::style()->objectName();
// Load themes
auto themeManager = new ThemeManager(actionCollection());
actionCollection()->addAction(QStringLiteral("themes_menu"), themeManager);
connect(themeManager, &ThemeManager::themeChanged, this, &MainWindow::slotThemeChanged);
if (!KdenliveSettings::widgetstyle().isEmpty() && QString::compare(desktopStyle, KdenliveSettings::widgetstyle(), Qt::CaseInsensitive) != 0) {
// User wants a custom widget style, init
doChangeStyle();
}
// Widget themes for non KDE users
KActionMenu *stylesAction = new KActionMenu(i18n("Style"), this);
auto *stylesGroup = new QActionGroup(stylesAction);
// GTK theme does not work well with Kdenlive, and does not support color theming, so avoid it
QStringList availableStyles = QStyleFactory::keys();
if (KdenliveSettings::widgetstyle().isEmpty()) {
// First run
QStringList incompatibleStyles = {QStringLiteral("GTK+"), QStringLiteral("windowsvista"), QStringLiteral("Windows")};
if (incompatibleStyles.contains(desktopStyle, Qt::CaseInsensitive)) {
if (availableStyles.contains(QStringLiteral("breeze"), Qt::CaseInsensitive)) {
// Auto switch to Breeze theme
KdenliveSettings::setWidgetstyle(QStringLiteral("Breeze"));
QApplication::setStyle(QStyleFactory::create(QStringLiteral("Breeze")));
} else if (availableStyles.contains(QStringLiteral("fusion"), Qt::CaseInsensitive)) {
KdenliveSettings::setWidgetstyle(QStringLiteral("Fusion"));
QApplication::setStyle(QStyleFactory::create(QStringLiteral("Fusion")));
}
} else {
KdenliveSettings::setWidgetstyle(QStringLiteral("Default"));
}
}
// Add default style action
QAction *defaultStyle = new QAction(i18n("Default"), stylesGroup);
defaultStyle->setData(QStringLiteral("Default"));
defaultStyle->setCheckable(true);
stylesAction->addAction(defaultStyle);
if (KdenliveSettings::widgetstyle() == QLatin1String("Default") || KdenliveSettings::widgetstyle().isEmpty()) {
defaultStyle->setChecked(true);
}
for (const QString &style : availableStyles) {
auto *a = new QAction(style, stylesGroup);
a->setCheckable(true);
a->setData(style);
if (KdenliveSettings::widgetstyle() == style) {
a->setChecked(true);
}
stylesAction->addAction(a);
}
connect(stylesGroup, &QActionGroup::triggered, this, &MainWindow::slotChangeStyle);
// QIcon::setThemeSearchPaths(QStringList() <setCurrentProfile(defaultProfile.isEmpty() ? ProjectManager::getDefaultProjectFormat() : defaultProfile);
m_commandStack = new QUndoGroup();
// If using a custom profile, make sure the file exists or fallback to default
QString currentProfilePath = pCore->getCurrentProfile()->path();
if (currentProfilePath.startsWith(QLatin1Char('/')) && !QFile::exists(currentProfilePath)) {
KMessageBox::sorry(this, i18n("Cannot find your default profile, switching to ATSC 1080p 25"));
pCore->setCurrentProfile(QStringLiteral("atsc_1080p_25"));
KdenliveSettings::setDefault_profile(QStringLiteral("atsc_1080p_25"));
}
m_gpuAllowed = EffectsRepository::get()->hasInternalEffect(QStringLiteral("glsl.manager"));
m_shortcutRemoveFocus = new QShortcut(QKeySequence(QStringLiteral("Esc")), this);
connect(m_shortcutRemoveFocus, &QShortcut::activated, this, &MainWindow::slotRemoveFocus);
/// Add Widgets
setDockOptions(dockOptions() | QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks);
setDockOptions(dockOptions() | QMainWindow::GroupedDragging);
setTabPosition(Qt::AllDockWidgetAreas, (QTabWidget::TabPosition)KdenliveSettings::tabposition());
m_timelineToolBar = toolBar(QStringLiteral("timelineToolBar"));
m_timelineToolBarContainer = new TimelineContainer(this);
auto *ctnLay = new QVBoxLayout;
ctnLay->setSpacing(0);
ctnLay->setContentsMargins(0, 0, 0, 0);
m_timelineToolBarContainer->setLayout(ctnLay);
QFrame *topFrame = new QFrame(this);
topFrame->setFrameShape(QFrame::HLine);
topFrame->setFixedHeight(1);
topFrame->setLineWidth(1);
connect(this, &MainWindow::focusTimeline, [topFrame](bool focus, bool highlight) {
if (focus) {
KColorScheme scheme(QApplication::palette().currentColorGroup(), KColorScheme::Tooltip);
if (highlight) {
QColor col = scheme.decoration(KColorScheme::HoverColor).color();
topFrame->setStyleSheet(QString("QFrame {border: 1px solid rgba(%1,%2,%3,70)}").arg(col.red()).arg(col.green()).arg(col.blue()));
} else {
QColor col = scheme.decoration(KColorScheme::FocusColor).color();
topFrame->setStyleSheet(QString("QFrame {border: 1px solid rgba(%1,%2,%3,100)}").arg(col.red()).arg(col.green()).arg(col.blue()));
}
} else {
topFrame->setStyleSheet(QString());
}
});
ctnLay->addWidget(topFrame);
ctnLay->addWidget(m_timelineToolBar);
KSharedConfigPtr config = KSharedConfig::openConfig();
KConfigGroup mainConfig(config, QStringLiteral("MainWindow"));
KConfigGroup tbGroup(&mainConfig, QStringLiteral("Toolbar timelineToolBar"));
m_timelineToolBar->applySettings(tbGroup);
QFrame *fr = new QFrame(this);
fr->setFrameShape(QFrame::HLine);
fr->setMaximumHeight(1);
fr->setLineWidth(1);
ctnLay->addWidget(fr);
setupActions();
QDockWidget *libraryDock = addDock(i18n("Library"), QStringLiteral("library"), pCore->library());
m_clipMonitor = new Monitor(Kdenlive::ClipMonitor, pCore->monitorManager(), this);
pCore->bin()->setMonitor(m_clipMonitor);
connect(m_clipMonitor, &Monitor::addMarker, this, &MainWindow::slotAddMarkerGuideQuickly);
connect(m_clipMonitor, &Monitor::deleteMarker, this, &MainWindow::slotDeleteClipMarker);
connect(m_clipMonitor, &Monitor::seekToPreviousSnap, this, &MainWindow::slotSnapRewind);
connect(m_clipMonitor, &Monitor::seekToNextSnap, this, &MainWindow::slotSnapForward);
connect(pCore->bin(), &Bin::findInTimeline, this, &MainWindow::slotClipInTimeline, Qt::DirectConnection);
connect(pCore->bin(), &Bin::setupTargets, this, [&] (bool hasVideo, QMap audioStreams) {
getCurrentTimeline()->controller()->setTargetTracks(hasVideo, audioStreams);
}
);
// TODO deprecated, replace with Bin methods if necessary
/*connect(m_projectList, SIGNAL(loadingIsOver()), this, SLOT(slotElapsedTime()));
connect(m_projectList, SIGNAL(updateRenderStatus()), this, SLOT(slotCheckRenderStatus()));
connect(m_projectList, SIGNAL(updateProfile(QString)), this, SLOT(slotUpdateProjectProfile(QString)));
connect(m_projectList, SIGNAL(refreshClip(QString,bool)), pCore->monitorManager(), SLOT(slotRefreshCurrentMonitor(QString)));
connect(m_clipMonitor, SIGNAL(zoneUpdated(QPoint)), m_projectList, SLOT(slotUpdateClipCut(QPoint)));*/
connect(m_clipMonitor, &Monitor::passKeyPress, this, &MainWindow::triggerKey);
m_projectMonitor = new Monitor(Kdenlive::ProjectMonitor, pCore->monitorManager(), this);
connect(m_projectMonitor, &Monitor::passKeyPress, this, &MainWindow::triggerKey);
connect(m_projectMonitor, &Monitor::addMarker, this, &MainWindow::slotAddMarkerGuideQuickly);
connect(m_projectMonitor, &Monitor::deleteMarker, this, &MainWindow::slotDeleteGuide);
connect(m_projectMonitor, &Monitor::seekToPreviousSnap, this, &MainWindow::slotSnapRewind);
connect(m_projectMonitor, &Monitor::seekToNextSnap, this, &MainWindow::slotSnapForward);
connect(m_loopClip, &QAction::triggered, [&]() {
QPoint inOut = getMainTimeline()->controller()->selectionInOut();
m_projectMonitor->slotLoopClip(inOut);
});
pCore->monitorManager()->initMonitors(m_clipMonitor, m_projectMonitor);
connect(m_clipMonitor, &Monitor::addMasterEffect, pCore->bin(), &Bin::slotAddEffect);
m_timelineTabs = new TimelineTabs(this);
ctnLay->addWidget(m_timelineTabs);
setCentralWidget(m_timelineToolBarContainer);
// Screen grab widget
QWidget *grabWidget = new QWidget(this);
QVBoxLayout *grabLayout = new QVBoxLayout;
grabWidget->setLayout(grabLayout);
QToolBar *recToolbar = new QToolBar(grabWidget);
grabLayout->addWidget(recToolbar);
grabLayout->addStretch(10);
// Check number of monitors for FFmpeg screen capture
int screens = QApplication::screens().count();
if (screens > 1) {
QComboBox *screenCombo = new QComboBox(recToolbar);
for (int ix = 0; ix < screens; ix++) {
screenCombo->addItem(i18n("Monitor %1", ix));
}
connect(screenCombo, static_cast(&QComboBox::currentIndexChanged), m_clipMonitor, &Monitor::slotSetScreen);
recToolbar->addWidget(screenCombo);
// Update screen grab monitor choice in case we changed from fullscreen
screenCombo->setEnabled(KdenliveSettings::grab_capture_type() == 0);
}
QAction *recAction = m_clipMonitor->recAction();
addAction(QStringLiteral("screengrab_record"), recAction);
recToolbar->addAction(recAction);
QAction *recConfig = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure Recording"), this);
recToolbar->addAction(recConfig);
connect(recConfig, &QAction::triggered, [&]() {
pCore->showConfigDialog(4, 0);
});
QDockWidget *screenGrabDock = addDock(i18n("Screen Grab"), QStringLiteral("screengrab"), grabWidget);
// Audio spectrum scope
m_audioSpectrum = new AudioGraphSpectrum(pCore->monitorManager());
QDockWidget *spectrumDock = addDock(i18n("Audio Spectrum"), QStringLiteral("audiospectrum"), m_audioSpectrum);
connect(spectrumDock, &QDockWidget::visibilityChanged, [&](bool visible) {
m_audioSpectrum->dockVisible(visible);
});
// Close library and audiospectrum on first run
screenGrabDock->close();
libraryDock->close();
spectrumDock->close();
m_projectBinDock = addDock(i18n("Project Bin"), QStringLiteral("project_bin"), pCore->bin());
m_assetPanel = new AssetPanel(this);
m_effectStackDock = addDock(i18n("Effect/Composition Stack"), QStringLiteral("effect_stack"), m_assetPanel);
connect(m_assetPanel, &AssetPanel::doSplitEffect, m_projectMonitor, &Monitor::slotSwitchCompare);
connect(m_assetPanel, &AssetPanel::doSplitBinEffect, m_clipMonitor, &Monitor::slotSwitchCompare);
connect(m_assetPanel, &AssetPanel::switchCurrentComposition, [&](int cid, const QString &compositionId) {
getMainTimeline()->controller()->getModel()->switchComposition(cid, compositionId);
});
connect(m_timelineTabs, &TimelineTabs::showTransitionModel, m_assetPanel, &AssetPanel::showTransition);
connect(m_timelineTabs, &TimelineTabs::showTransitionModel, [&] () {
m_effectStackDock->raise();
});
connect(m_timelineTabs, &TimelineTabs::showItemEffectStack, m_assetPanel, &AssetPanel::showEffectStack);
connect(m_timelineTabs, &TimelineTabs::showItemEffectStack, [&] () {
m_effectStackDock->raise();
});
connect(m_timelineTabs, &TimelineTabs::updateZoom, this, &MainWindow::updateZoomSlider);
connect(pCore->bin(), &Bin::requestShowEffectStack, m_assetPanel, &AssetPanel::showEffectStack);
connect(pCore->bin(), &Bin::requestShowEffectStack, [&] () {
// Don't raise effect stack on clip bin in case it is docked with bin or clip monitor
// m_effectStackDock->raise();
});
connect(this, &MainWindow::clearAssetPanel, m_assetPanel, &AssetPanel::clearAssetPanel);
connect(this, &MainWindow::assetPanelWarning, m_assetPanel, &AssetPanel::assetPanelWarning);
connect(m_assetPanel, &AssetPanel::seekToPos, [this](int pos) {
ObjectId oId = m_assetPanel->effectStackOwner();
switch (oId.first) {
case ObjectType::TimelineTrack:
case ObjectType::TimelineClip:
case ObjectType::TimelineComposition:
case ObjectType::Master:
m_projectMonitor->requestSeek(pos);
break;
case ObjectType::BinClip:
m_clipMonitor->requestSeek(pos);
break;
default:
qDebug() << "ERROR unhandled object type";
break;
}
});
m_effectList2 = new EffectListWidget(this);
connect(m_effectList2, &EffectListWidget::activateAsset, pCore->projectManager(), &ProjectManager::activateAsset);
connect(m_assetPanel, &AssetPanel::reloadEffect, m_effectList2, &EffectListWidget::reloadCustomEffect);
m_effectListDock = addDock(i18n("Effects"), QStringLiteral("effect_list"), m_effectList2);
m_transitionList2 = new TransitionListWidget(this);
m_transitionListDock = addDock(i18n("Compositions"), QStringLiteral("transition_list"), m_transitionList2);
// Add monitors here to keep them at the right of the window
m_clipMonitorDock = addDock(i18n("Clip Monitor"), QStringLiteral("clip_monitor"), m_clipMonitor);
m_projectMonitorDock = addDock(i18n("Project Monitor"), QStringLiteral("project_monitor"), m_projectMonitor);
m_undoView = new QUndoView();
m_undoView->setCleanIcon(QIcon::fromTheme(QStringLiteral("edit-clear")));
m_undoView->setEmptyLabel(i18n("Clean"));
m_undoView->setGroup(m_commandStack);
m_undoViewDock = addDock(i18n("Undo History"), QStringLiteral("undo_history"), m_undoView);
// Color and icon theme stuff
connect(m_commandStack, &QUndoGroup::cleanChanged, m_saveAction, &QAction::setDisabled);
addAction(QStringLiteral("styles_menu"), stylesAction);
QAction *iconAction = new QAction(i18n("Force Breeze Icon Theme"), this);
iconAction->setCheckable(true);
iconAction->setChecked(KdenliveSettings::force_breeze());
addAction(QStringLiteral("force_icon_theme"), iconAction);
connect(iconAction, &QAction::triggered, this, &MainWindow::forceIconSet);
QDockWidget *mixerDock = addDock(i18n("Audio Mixer"), QStringLiteral("mixer"), pCore->mixer());
QAction *showMixer = new QAction(QIcon::fromTheme(QStringLiteral("view-media-equalizer")), i18n("Audio Mixer"), this);
showMixer->setCheckable(true);
addAction(QStringLiteral("audiomixer_button"), showMixer);
connect(mixerDock, &QDockWidget::visibilityChanged, [&, showMixer](bool visible) {
pCore->mixer()->connectMixer(visible);
showMixer->setChecked(visible);
});
connect(showMixer, &QAction::triggered, [&, mixerDock]() {
if (mixerDock->isVisible() && !mixerDock->visibleRegion().isEmpty()) {
mixerDock->close();
} else {
mixerDock->show();
mixerDock->raise();
}
});
// Close non-general docks for the initial layout
// only show important ones
m_undoViewDock->close();
mixerDock->close();
/// Tabify Widgets
tabifyDockWidget(m_clipMonitorDock, m_projectMonitorDock);
tabifyDockWidget(m_transitionListDock, m_effectListDock);
tabifyDockWidget(m_effectStackDock, pCore->bin()->clipPropertiesDock());
bool firstRun = readOptions();
// Build effects menu
m_effectsMenu = new QMenu(i18n("Add Effect"), this);
m_effectActions = new KActionCategory(i18n("Effects"), actionCollection());
m_effectList2->reloadEffectMenu(m_effectsMenu, m_effectActions);
m_transitionsMenu = new QMenu(i18n("Add Transition"), this);
m_transitionActions = new KActionCategory(i18n("Transitions"), actionCollection());
auto *scmanager = new ScopeManager(this);
new LayoutManagement(this);
new HideTitleBars(this);
m_extraFactory = new KXMLGUIClient(this);
buildDynamicActions();
// Create Effect Basket (dropdown list of favorites)
m_effectBasket = new EffectBasket(this);
connect(m_effectBasket, &EffectBasket::activateAsset, pCore->projectManager(), &ProjectManager::activateAsset);
connect(m_effectList2, &EffectListWidget::reloadFavorites, m_effectBasket, &EffectBasket::slotReloadBasket);
auto *widgetlist = new QWidgetAction(this);
widgetlist->setDefaultWidget(m_effectBasket);
// widgetlist->setText(i18n("Favorite Effects"));
widgetlist->setToolTip(i18n("Favorite Effects"));
widgetlist->setIcon(QIcon::fromTheme(QStringLiteral("favorite")));
auto *menu = new QMenu(this);
menu->addAction(widgetlist);
auto *basketButton = new QToolButton(this);
basketButton->setMenu(menu);
basketButton->setToolButtonStyle(toolBar()->toolButtonStyle());
basketButton->setDefaultAction(widgetlist);
basketButton->setPopupMode(QToolButton::InstantPopup);
// basketButton->setText(i18n("Favorite Effects"));
basketButton->setToolTip(i18n("Favorite Effects"));
basketButton->setIcon(QIcon::fromTheme(QStringLiteral("favorite")));
auto *toolButtonAction = new QWidgetAction(this);
toolButtonAction->setText(i18n("Favorite Effects"));
toolButtonAction->setIcon(QIcon::fromTheme(QStringLiteral("favorite")));
toolButtonAction->setDefaultWidget(basketButton);
addAction(QStringLiteral("favorite_effects"), toolButtonAction);
connect(toolButtonAction, &QAction::triggered, basketButton, &QToolButton::showMenu);
connect(m_effectBasket, &EffectBasket::activateAsset, menu, &QMenu::close);
// Render button
ProgressButton *timelineRender = new ProgressButton(i18n("Render"), 100, this);
auto *tlrMenu = new QMenu(this);
timelineRender->setMenu(tlrMenu);
connect(this, &MainWindow::setRenderProgress, timelineRender, &ProgressButton::setProgress);
auto *renderButtonAction = new QWidgetAction(this);
renderButtonAction->setText(i18n("Render Button"));
renderButtonAction->setIcon(QIcon::fromTheme(QStringLiteral("media-record")));
renderButtonAction->setDefaultWidget(timelineRender);
addAction(QStringLiteral("project_render_button"), renderButtonAction);
// Timeline preview button
ProgressButton *timelinePreview = new ProgressButton(i18n("Rendering preview"), 1000, this);
auto *tlMenu = new QMenu(this);
timelinePreview->setMenu(tlMenu);
connect(this, &MainWindow::setPreviewProgress, timelinePreview, &ProgressButton::setProgress);
auto *previewButtonAction = new QWidgetAction(this);
previewButtonAction->setText(i18n("Timeline Preview"));
previewButtonAction->setIcon(QIcon::fromTheme(QStringLiteral("preview-render-on")));
previewButtonAction->setDefaultWidget(timelinePreview);
addAction(QStringLiteral("timeline_preview_button"), previewButtonAction);
setupGUI(KXmlGuiWindow::ToolBar | KXmlGuiWindow::StatusBar | KXmlGuiWindow::Save | KXmlGuiWindow::Create);
if (firstRun) {
if (QScreen *current = QApplication::primaryScreen()) {
if (current->availableSize().height() < 1000) {
resize(current->availableSize());
} else {
resize(current->availableSize() / 1.5);
}
}
}
updateActionsToolTip();
m_timelineToolBar->setToolButtonStyle(Qt::ToolButtonFollowStyle);
m_timelineToolBar->setProperty("otherToolbar", true);
timelinePreview->setToolButtonStyle(m_timelineToolBar->toolButtonStyle());
connect(m_timelineToolBar, &QToolBar::toolButtonStyleChanged, timelinePreview, &ProgressButton::setToolButtonStyle);
timelineRender->setToolButtonStyle(toolBar()->toolButtonStyle());
/*ScriptingPart* sp = new ScriptingPart(this, QStringList());
guiFactory()->addClient(sp);*/
loadGenerators();
loadDockActions();
loadClipActions();
// Timeline clip menu
QMenu *timelineClipMenu = new QMenu(this);
timelineClipMenu->addAction(actionCollection()->action(QStringLiteral("edit_copy")));
timelineClipMenu->addAction(actionCollection()->action(QStringLiteral("paste_effects")));
timelineClipMenu->addAction(actionCollection()->action(QStringLiteral("group_clip")));
timelineClipMenu->addAction(actionCollection()->action(QStringLiteral("ungroup_clip")));
timelineClipMenu->addAction(actionCollection()->action(QStringLiteral("edit_item_duration")));
timelineClipMenu->addAction(actionCollection()->action(QStringLiteral("clip_split")));
timelineClipMenu->addAction(actionCollection()->action(QStringLiteral("clip_switch")));
timelineClipMenu->addAction(actionCollection()->action(QStringLiteral("delete_timeline_clip")));
timelineClipMenu->addAction(actionCollection()->action(QStringLiteral("extract_clip")));
timelineClipMenu->addAction(actionCollection()->action(QStringLiteral("save_to_bin")));
QMenu *markerMenu = static_cast(factory()->container(QStringLiteral("marker_menu"), this));
timelineClipMenu->addMenu(markerMenu);
timelineClipMenu->addAction(actionCollection()->action(QStringLiteral("set_audio_align_ref")));
timelineClipMenu->addAction(actionCollection()->action(QStringLiteral("align_audio")));
timelineClipMenu->addAction(actionCollection()->action(QStringLiteral("edit_item_speed")));
timelineClipMenu->addAction(actionCollection()->action(QStringLiteral("clip_in_project_tree")));
timelineClipMenu->addAction(actionCollection()->action(QStringLiteral("cut_timeline_clip")));
// Timeline composition menu
QMenu *compositionMenu = new QMenu(this);
compositionMenu->addAction(actionCollection()->action(QStringLiteral("edit_item_duration")));
compositionMenu->addAction(actionCollection()->action(QStringLiteral("edit_copy")));
compositionMenu->addAction(actionCollection()->action(QStringLiteral("delete_timeline_clip")));
// Timeline main menu
QMenu *timelineMenu = new QMenu(this);
timelineMenu->addAction(actionCollection()->action(QStringLiteral("edit_paste")));
timelineMenu->addAction(actionCollection()->action(QStringLiteral("insert_space")));
timelineMenu->addAction(actionCollection()->action(QStringLiteral("delete_space")));
timelineMenu->addAction(actionCollection()->action(QStringLiteral("delete_space_all_tracks")));
timelineMenu->addAction(actionCollection()->action(QStringLiteral("add_guide")));
timelineMenu->addAction(actionCollection()->action(QStringLiteral("edit_guide")));
QMenu *guideMenu = new QMenu(i18n("Go to Guide..."), this);
timelineMenu->addMenu(guideMenu);
// Timeline ruler menu
QMenu *timelineRulerMenu = new QMenu(this);
timelineRulerMenu->addAction(actionCollection()->action(QStringLiteral("add_guide")));
timelineRulerMenu->addAction(actionCollection()->action(QStringLiteral("edit_guide")));
timelineRulerMenu->addMenu(guideMenu);
timelineRulerMenu->addAction(actionCollection()->action(QStringLiteral("add_project_note")));
// Timeline headers menu
QMenu *timelineHeadersMenu = new QMenu(this);
timelineHeadersMenu->addAction(actionCollection()->action(QStringLiteral("insert_track")));
timelineHeadersMenu->addAction(actionCollection()->action(QStringLiteral("delete_track")));
timelineHeadersMenu->addAction(actionCollection()->action(QStringLiteral("show_track_record")));
QAction *separate_channels = new QAction(QIcon(), i18n("Separate Channels"), this);
separate_channels->setCheckable(true);
separate_channels->setChecked(KdenliveSettings::displayallchannels());
separate_channels->setData("separate_channels");
connect(separate_channels, &QAction::triggered, this, &MainWindow::slotSeparateAudioChannel);
timelineHeadersMenu->addAction(separate_channels);
QMenu *thumbsMenu = new QMenu(i18n("Thumbnails"), this);
QActionGroup *thumbGroup = new QActionGroup(this);
QAction *inFrame = new QAction(i18n("In Frame"), thumbGroup);
inFrame->setData(QStringLiteral("2"));
inFrame->setCheckable(true);
thumbsMenu->addAction(inFrame);
QAction *inOutFrame = new QAction(i18n("In/Out Frames"), thumbGroup);
inOutFrame->setData(QStringLiteral("0"));
inOutFrame->setCheckable(true);
thumbsMenu->addAction(inOutFrame);
QAction *allFrame = new QAction(i18n("All Frames"), thumbGroup);
allFrame->setData(QStringLiteral("1"));
allFrame->setCheckable(true);
thumbsMenu->addAction(allFrame);
QAction *noFrame = new QAction(i18n("No Thumbnails"), thumbGroup);
noFrame->setData(QStringLiteral("3"));
noFrame->setCheckable(true);
thumbsMenu->addAction(noFrame);
QMenu *openGLMenu = static_cast(factory()->container(QStringLiteral("qt_opengl"), this));
#if defined(Q_OS_WIN)
connect(openGLMenu, &QMenu::triggered, [&](QAction *ac) {
KdenliveSettings::setOpengl_backend(ac->data().toInt());
if (KMessageBox::questionYesNo(this, i18n("Kdenlive needs to be restarted to change this setting. Do you want to proceed?")) != KMessageBox::Yes) {
return;
}
slotRestart(false);
});
#else
if (openGLMenu) {
openGLMenu->menuAction()->setVisible(false);;
}
#endif
// Connect monitor overlay info menu.
QMenu *monitorOverlay = static_cast(factory()->container(QStringLiteral("monitor_config_overlay"), this));
connect(monitorOverlay, &QMenu::triggered, this, &MainWindow::slotSwitchMonitorOverlay);
m_projectMonitor->setupMenu(static_cast(factory()->container(QStringLiteral("monitor_go"), this)), monitorOverlay, m_playZone, m_loopZone, nullptr,
m_loopClip);
m_clipMonitor->setupMenu(static_cast(factory()->container(QStringLiteral("monitor_go"), this)), monitorOverlay, m_playZone, m_loopZone,
static_cast(factory()->container(QStringLiteral("marker_menu"), this)));
QMenu *clipInTimeline = static_cast(factory()->container(QStringLiteral("clip_in_timeline"), this));
clipInTimeline->setIcon(QIcon::fromTheme(QStringLiteral("go-jump")));
pCore->bin()->setupGeneratorMenu();
connect(pCore->monitorManager(), &MonitorManager::updateOverlayInfos, this, &MainWindow::slotUpdateMonitorOverlays);
// Setup and fill effects and transitions menus.
QMenu *m = static_cast(factory()->container(QStringLiteral("video_effects_menu"), this));
connect(m, &QMenu::triggered, this, &MainWindow::slotAddEffect);
connect(m_effectsMenu, &QMenu::triggered, this, &MainWindow::slotAddEffect);
connect(m_transitionsMenu, &QMenu::triggered, this, &MainWindow::slotAddTransition);
m_timelineContextMenu = new QMenu(this);
m_timelineContextMenu->addAction(actionCollection()->action(QStringLiteral("insert_space")));
m_timelineContextMenu->addAction(actionCollection()->action(QStringLiteral("delete_space")));
m_timelineContextMenu->addAction(actionCollection()->action(QStringLiteral("delete_space_all_tracks")));
m_timelineContextMenu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Paste)));
// QMenu *markersMenu = static_cast(factory()->container(QStringLiteral("marker_menu"), this));
/*m_timelineClipActions->addMenu(markersMenu);
m_timelineClipActions->addSeparator();
m_timelineClipActions->addMenu(m_transitionsMenu);
m_timelineClipActions->addMenu(m_effectsMenu);*/
slotConnectMonitors();
m_timelineToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
// TODO: let user select timeline toolbar toolbutton style
// connect(toolBar(), &QToolBar::iconSizeChanged, m_timelineToolBar, &QToolBar::setToolButtonStyle);
m_timelineToolBar->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_timelineToolBar, &QWidget::customContextMenuRequested, this, &MainWindow::showTimelineToolbarMenu);
QAction *prevRender = actionCollection()->action(QStringLiteral("prerender_timeline_zone"));
QAction *stopPrevRender = actionCollection()->action(QStringLiteral("stop_prerender_timeline"));
tlMenu->addAction(stopPrevRender);
tlMenu->addAction(actionCollection()->action(QStringLiteral("set_render_timeline_zone")));
tlMenu->addAction(actionCollection()->action(QStringLiteral("unset_render_timeline_zone")));
tlMenu->addAction(actionCollection()->action(QStringLiteral("clear_render_timeline_zone")));
// Automatic timeline preview action
QAction *autoRender = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Automatic Preview"), this);
autoRender->setCheckable(true);
autoRender->setChecked(KdenliveSettings::autopreview());
connect(autoRender, &QAction::triggered, this, &MainWindow::slotToggleAutoPreview);
tlMenu->addAction(autoRender);
tlMenu->addSeparator();
tlMenu->addAction(actionCollection()->action(QStringLiteral("disable_preview")));
tlMenu->addAction(actionCollection()->action(QStringLiteral("manage_cache")));
timelinePreview->defineDefaultAction(prevRender, stopPrevRender);
timelinePreview->setAutoRaise(true);
QAction *showRender = actionCollection()->action(QStringLiteral("project_render"));
tlrMenu->addAction(showRender);
tlrMenu->addAction(actionCollection()->action(QStringLiteral("stop_project_render")));
timelineRender->defineDefaultAction(showRender, showRender);
timelineRender->setAutoRaise(true);
// Populate encoding profiles
KConfig conf(QStringLiteral("encodingprofiles.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation);
/*KConfig conf(QStringLiteral("encodingprofiles.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation);
if (KdenliveSettings::proxyparams().isEmpty() || KdenliveSettings::proxyextension().isEmpty()) {
KConfigGroup group(&conf, "proxy");
QMap values = group.entryMap();
QMapIterator i(values);
if (i.hasNext()) {
i.next();
QString proxystring = i.value();
KdenliveSettings::setProxyparams(proxystring.section(QLatin1Char(';'), 0, 0));
KdenliveSettings::setProxyextension(proxystring.section(QLatin1Char(';'), 1, 1));
}
}*/
if (KdenliveSettings::v4l_parameters().isEmpty() || KdenliveSettings::v4l_extension().isEmpty()) {
KConfigGroup group(&conf, "video4linux");
QMap values = group.entryMap();
QMapIterator i(values);
if (i.hasNext()) {
i.next();
QString v4lstring = i.value();
KdenliveSettings::setV4l_parameters(v4lstring.section(QLatin1Char(';'), 0, 0));
KdenliveSettings::setV4l_extension(v4lstring.section(QLatin1Char(';'), 1, 1));
}
}
if (KdenliveSettings::grab_parameters().isEmpty() || KdenliveSettings::grab_extension().isEmpty()) {
KConfigGroup group(&conf, "screengrab");
QMap values = group.entryMap();
QMapIterator i(values);
if (i.hasNext()) {
i.next();
QString grabstring = i.value();
KdenliveSettings::setGrab_parameters(grabstring.section(QLatin1Char(';'), 0, 0));
KdenliveSettings::setGrab_extension(grabstring.section(QLatin1Char(';'), 1, 1));
}
}
if (KdenliveSettings::decklink_parameters().isEmpty() || KdenliveSettings::decklink_extension().isEmpty()) {
KConfigGroup group(&conf, "decklink");
QMap values = group.entryMap();
QMapIterator i(values);
if (i.hasNext()) {
i.next();
QString decklinkstring = i.value();
KdenliveSettings::setDecklink_parameters(decklinkstring.section(QLatin1Char(';'), 0, 0));
KdenliveSettings::setDecklink_extension(decklinkstring.section(QLatin1Char(';'), 1, 1));
}
}
if (!QDir(KdenliveSettings::currenttmpfolder()).isReadable())
KdenliveSettings::setCurrenttmpfolder(QStandardPaths::writableLocation(QStandardPaths::TempLocation));
QTimer::singleShot(0, this, &MainWindow::GUISetupDone);
#ifdef USE_JOGSHUTTLE
new JogManager(this);
#endif
getMainTimeline()->setTimelineMenu(timelineClipMenu, compositionMenu, timelineMenu, guideMenu, timelineRulerMenu, actionCollection()->action(QStringLiteral("edit_guide")), timelineHeadersMenu, thumbsMenu);
scmanager->slotCheckActiveScopes();
// m_messageLabel->setMessage(QStringLiteral("This is a beta version. Always backup your data"), MltError);
}
void MainWindow::slotThemeChanged(const QString &name)
{
KSharedConfigPtr config = KSharedConfig::openConfig(name);
QPalette plt = KColorScheme::createApplicationPalette(config);
// qApp->setPalette(plt);
// Required for qml palette change
QGuiApplication::setPalette(plt);
QColor background = plt.window().color();
bool useDarkIcons = background.value() < 100;
if (m_assetPanel) {
m_assetPanel->updatePalette();
}
if (m_effectList2) {
// Trigger a repaint to have icons adapted
m_effectList2->reset();
}
if (m_transitionList2) {
// Trigger a repaint to have icons adapted
m_transitionList2->reset();
}
if (m_clipMonitor) {
m_clipMonitor->setPalette(plt);
}
if (m_projectMonitor) {
m_projectMonitor->setPalette(plt);
}
if (m_timelineTabs) {
m_timelineTabs->setPalette(plt);
getMainTimeline()->controller()->resetView();
}
if (m_audioSpectrum) {
m_audioSpectrum->refreshPixmap();
}
KSharedConfigPtr kconfig = KSharedConfig::openConfig();
KConfigGroup initialGroup(kconfig, "version");
if (initialGroup.exists() && KdenliveSettings::force_breeze() && useDarkIcons != KdenliveSettings::use_dark_breeze()) {
// We need to reload icon theme
QIcon::setThemeName(useDarkIcons ? QStringLiteral("breeze-dark") : QStringLiteral("breeze"));
KdenliveSettings::setUse_dark_breeze(useDarkIcons);
}
}
void MainWindow::updateActionsToolTip()
{
// Add shortcut to action tooltips
QList collections = KActionCollection::allCollections();
for (int i = 0; i < collections.count(); ++i) {
KActionCollection *coll = collections.at(i);
for (QAction *tempAction : coll->actions()) {
// find the shortcut pattern and delete (note the preceding space in the RegEx)
QString strippedTooltip = tempAction->toolTip().remove(QRegExp(QStringLiteral("\\s\\(.*\\)")));
// append shortcut if it exists for action
if (tempAction->shortcut() == QKeySequence()) {
tempAction->setToolTip(strippedTooltip);
} else {
tempAction->setToolTip(strippedTooltip + QStringLiteral(" (") + tempAction->shortcut().toString() + QLatin1Char(')'));
}
connect(tempAction, &QAction::changed, this, &MainWindow::updateAction);
}
}
}
void MainWindow::updateAction()
{
auto *action = qobject_cast(sender());
QString toolTip = KLocalizedString::removeAcceleratorMarker(action->toolTip());
QString strippedTooltip = toolTip.remove(QRegExp(QStringLiteral("\\s\\(.*\\)")));
action->setToolTip(i18nc("@info:tooltip Tooltip of toolbar button", "%1 (%2)", strippedTooltip, action->shortcut().toString()));
}
MainWindow::~MainWindow()
{
pCore->prepareShutdown();
delete m_timelineTabs;
delete m_audioSpectrum;
if (m_projectMonitor) {
m_projectMonitor->stop();
}
if (m_clipMonitor) {
m_clipMonitor->stop();
}
ClipController::mediaUnavailable.reset();
delete m_projectMonitor;
delete m_clipMonitor;
delete m_shortcutRemoveFocus;
delete m_effectList2;
delete m_transitionList2;
qDeleteAll(m_transitions);
// Mlt::Factory::close();
}
// virtual
bool MainWindow::queryClose()
{
if (m_renderWidget) {
int waitingJobs = m_renderWidget->waitingJobsCount();
if (waitingJobs > 0) {
switch (
KMessageBox::warningYesNoCancel(this,
i18np("You have 1 rendering job waiting in the queue.\nWhat do you want to do with this job?",
"You have %1 rendering jobs waiting in the queue.\nWhat do you want to do with these jobs?", waitingJobs),
QString(), KGuiItem(i18n("Start them now")), KGuiItem(i18n("Delete them")))) {
case KMessageBox::Yes:
// create script with waiting jobs and start it
if (!m_renderWidget->startWaitingRenderJobs()) {
return false;
}
break;
case KMessageBox::No:
// Don't do anything, jobs will be deleted
break;
default:
return false;
}
}
}
saveOptions();
// WARNING: According to KMainWindow::queryClose documentation we are not supposed to close the document here?
return pCore->projectManager()->closeCurrentDocument(true, true);
}
void MainWindow::loadGenerators()
{
QMenu *addMenu = static_cast(factory()->container(QStringLiteral("generators"), this));
Generators::getGenerators(KdenliveSettings::producerslist(), addMenu);
connect(addMenu, &QMenu::triggered, this, &MainWindow::buildGenerator);
}
void MainWindow::buildGenerator(QAction *action)
{
Generators gen(action->data().toString(), this);
if (gen.exec() == QDialog::Accepted) {
pCore->bin()->slotAddClipToProject(gen.getSavedClip());
}
}
void MainWindow::saveProperties(KConfigGroup &config)
{
// save properties here
KXmlGuiWindow::saveProperties(config);
// TODO: fix session management
if (qApp->isSavingSession() && pCore->projectManager()) {
if (pCore->currentDoc() && !pCore->currentDoc()->url().isEmpty()) {
config.writeEntry("kdenlive_lastUrl", pCore->currentDoc()->url().toLocalFile());
}
}
}
void MainWindow::readProperties(const KConfigGroup &config)
{
// read properties here
KXmlGuiWindow::readProperties(config);
// TODO: fix session management
/*if (qApp->isSessionRestored()) {
pCore->projectManager()->openFile(QUrl::fromLocalFile(config.readEntry("kdenlive_lastUrl", QString())));
}*/
}
void MainWindow::saveNewToolbarConfig()
{
KXmlGuiWindow::saveNewToolbarConfig();
// TODO for some reason all dynamically inserted actions are removed by the save toolbar
// So we currently re-add them manually....
loadDockActions();
loadClipActions();
pCore->bin()->rebuildMenu();
QMenu *monitorOverlay = static_cast(factory()->container(QStringLiteral("monitor_config_overlay"), this));
if (monitorOverlay) {
m_projectMonitor->setupMenu(static_cast(factory()->container(QStringLiteral("monitor_go"), this)), monitorOverlay, m_playZone, m_loopZone,
nullptr, m_loopClip);
m_clipMonitor->setupMenu(static_cast(factory()->container(QStringLiteral("monitor_go"), this)), monitorOverlay, m_playZone, m_loopZone,
static_cast(factory()->container(QStringLiteral("marker_menu"), this)));
}
}
void MainWindow::slotReloadEffects(const QStringList &paths)
{
for (const QString &p : paths) {
EffectsRepository::get()->reloadCustom(p);
}
m_effectList2->reloadEffectMenu(m_effectsMenu, m_effectActions);
}
void MainWindow::configureNotifications()
{
KNotifyConfigWidget::configure(this);
}
void MainWindow::slotFullScreen()
{
KToggleFullScreenAction::setFullScreen(this, actionCollection()->action(QStringLiteral("fullscreen"))->isChecked());
}
void MainWindow::slotConnectMonitors()
{
// connect(m_projectList, SIGNAL(deleteProjectClips(QStringList,QMap)), this,
// SLOT(slotDeleteProjectClips(QStringList,QMap)));
connect(m_clipMonitor, &Monitor::refreshClipThumbnail, pCore->bin(), &Bin::slotRefreshClipThumbnail);
connect(m_projectMonitor, &Monitor::requestFrameForAnalysis, this, &MainWindow::slotMonitorRequestRenderFrame);
connect(m_projectMonitor, &Monitor::createSplitOverlay, this, &MainWindow::createSplitOverlay, Qt::DirectConnection);
connect(m_projectMonitor, &Monitor::removeSplitOverlay, this, &MainWindow::removeSplitOverlay, Qt::DirectConnection);
}
void MainWindow::createSplitOverlay(std::shared_ptr filter)
{
if (m_assetPanel->effectStackOwner().first == ObjectType::TimelineClip) {
getMainTimeline()->controller()->createSplitOverlay(m_assetPanel->effectStackOwner().second, filter);
m_projectMonitor->activateSplit();
} else {
pCore->displayMessage(i18n("Select a clip to compare effect"), InformationMessage);
}
}
void MainWindow::removeSplitOverlay()
{
getMainTimeline()->controller()->removeSplitOverlay();
}
void MainWindow::addAction(const QString &name, QAction *action, const QKeySequence &shortcut, KActionCategory *category)
{
m_actionNames.append(name);
if (category) {
category->addAction(name, action);
} else {
actionCollection()->addAction(name, action);
}
actionCollection()->setDefaultShortcut(action, shortcut);
}
QAction *MainWindow::addAction(const QString &name, const QString &text, const QObject *receiver, const char *member, const QIcon &icon,
const QKeySequence &shortcut, KActionCategory *category)
{
auto *action = new QAction(text, this);
if (!icon.isNull()) {
action->setIcon(icon);
}
addAction(name, action, shortcut, category);
connect(action, SIGNAL(triggered(bool)), receiver, member);
return action;
}
void MainWindow::setupActions()
{
// create edit mode buttons
m_normalEditTool = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-normal-edit")), i18n("Normal mode"), this);
m_normalEditTool->setCheckable(true);
m_normalEditTool->setChecked(true);
m_overwriteEditTool = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-overwrite-edit")), i18n("Overwrite mode"), this);
m_overwriteEditTool->setCheckable(true);
m_overwriteEditTool->setChecked(false);
m_insertEditTool = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-insert-edit")), i18n("Insert mode"), this);
m_insertEditTool->setCheckable(true);
m_insertEditTool->setChecked(false);
KSelectAction *sceneMode = new KSelectAction(i18n("Timeline Edit Mode"), this);
sceneMode->addAction(m_normalEditTool);
sceneMode->addAction(m_overwriteEditTool);
sceneMode->addAction(m_insertEditTool);
sceneMode->setCurrentItem(0);
connect(sceneMode, static_cast(&KSelectAction::triggered), this, &MainWindow::slotChangeEdit);
addAction(QStringLiteral("timeline_mode"), sceneMode);
m_useTimelineZone = new KDualAction(i18n("Do not Use Timeline Zone for Insert"), i18n("Use Timeline Zone for Insert"), this);
m_useTimelineZone->setActiveIcon(QIcon::fromTheme(QStringLiteral("timeline-use-zone-on")));
m_useTimelineZone->setInactiveIcon(QIcon::fromTheme(QStringLiteral("timeline-use-zone-off")));
m_useTimelineZone->setAutoToggle(true);
connect(m_useTimelineZone, &KDualAction::activeChangedByUser, this, &MainWindow::slotSwitchTimelineZone);
addAction(QStringLiteral("use_timeline_zone_in_edit"), m_useTimelineZone);
m_compositeAction = new KSelectAction(QIcon::fromTheme(QStringLiteral("composite-track-off")), i18n("Track compositing"), this);
m_compositeAction->setToolTip(i18n("Track compositing"));
QAction *noComposite = new QAction(QIcon::fromTheme(QStringLiteral("composite-track-off")), i18n("None"), this);
noComposite->setCheckable(true);
noComposite->setData(0);
m_compositeAction->addAction(noComposite);
QString compose = TransitionsRepository::get()->getCompositingTransition();
if (compose == QStringLiteral("movit.overlay")) {
// Movit, do not show "preview" option since movit is faster
QAction *hqComposite = new QAction(QIcon::fromTheme(QStringLiteral("composite-track-on")), i18n("High Quality"), this);
hqComposite->setCheckable(true);
hqComposite->setData(2);
m_compositeAction->addAction(hqComposite);
m_compositeAction->setCurrentAction(hqComposite);
} else {
QAction *previewComposite = new QAction(QIcon::fromTheme(QStringLiteral("composite-track-preview")), i18n("Preview"), this);
previewComposite->setCheckable(true);
previewComposite->setData(1);
m_compositeAction->addAction(previewComposite);
if (compose != QStringLiteral("composite")) {
QAction *hqComposite = new QAction(QIcon::fromTheme(QStringLiteral("composite-track-on")), i18n("High Quality"), this);
hqComposite->setData(2);
hqComposite->setCheckable(true);
m_compositeAction->addAction(hqComposite);
m_compositeAction->setCurrentAction(hqComposite);
} else {
m_compositeAction->setCurrentAction(previewComposite);
}
}
connect(m_compositeAction, static_cast(&KSelectAction::triggered), this, &MainWindow::slotUpdateCompositing);
addAction(QStringLiteral("timeline_compositing"), m_compositeAction);
QAction *splitView = new QAction(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")), i18n("Split Audio Tracks"), this);
addAction(QStringLiteral("timeline_view_split"), splitView);
splitView->setData(QVariant::fromValue(1));
splitView->setCheckable(true);
splitView->setChecked(KdenliveSettings::audiotracksbelow() == 1);
QAction *splitView2 = new QAction(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")), i18n("Split Audio Tracks (reverse)"), this);
addAction(QStringLiteral("timeline_view_split_reverse"), splitView2);
splitView2->setData(QVariant::fromValue(2));
splitView2->setCheckable(true);
splitView2->setChecked(KdenliveSettings::audiotracksbelow() == 2);
QAction *mixedView = new QAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Mixed Audio tracks"), this);
addAction(QStringLiteral("timeline_mixed_view"), mixedView);
mixedView->setData(QVariant::fromValue(0));
mixedView->setCheckable(true);
mixedView->setChecked(KdenliveSettings::audiotracksbelow() == 0);
auto *clipTypeGroup = new QActionGroup(this);
clipTypeGroup->addAction(mixedView);
clipTypeGroup->addAction(splitView);
clipTypeGroup->addAction(splitView2);
connect(clipTypeGroup, &QActionGroup::triggered, this, &MainWindow::slotUpdateTimelineView);
auto tlsettings = new QMenu(this);
tlsettings->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
tlsettings->addAction(m_compositeAction);
tlsettings->addAction(mixedView);
tlsettings->addAction(splitView);
tlsettings->addAction(splitView2);
QToolButton *timelineSett = new QToolButton(this);
timelineSett->setPopupMode(QToolButton::InstantPopup);
timelineSett->setMenu(tlsettings);
timelineSett->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
auto *tlButtonAction = new QWidgetAction(this);
tlButtonAction->setDefaultWidget(timelineSett);
tlButtonAction->setText(i18n("Track menu"));
addAction(QStringLiteral("timeline_settings"), tlButtonAction);
m_timeFormatButton = new KSelectAction(QStringLiteral("00:00:00:00 / 00:00:00:00"), this);
m_timeFormatButton->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
m_timeFormatButton->addAction(i18n("hh:mm:ss:ff"));
m_timeFormatButton->addAction(i18n("Frames"));
if (KdenliveSettings::frametimecode()) {
m_timeFormatButton->setCurrentItem(1);
} else {
m_timeFormatButton->setCurrentItem(0);
}
connect(m_timeFormatButton, static_cast(&KSelectAction::triggered), this, &MainWindow::slotUpdateTimecodeFormat);
m_timeFormatButton->setToolBarMode(KSelectAction::MenuMode);
m_timeFormatButton->setToolButtonPopupMode(QToolButton::InstantPopup);
addAction(QStringLiteral("timeline_timecode"), m_timeFormatButton);
// create tools buttons
m_buttonSelectTool = new QAction(QIcon::fromTheme(QStringLiteral("cursor-arrow")), i18n("Selection tool"), this);
// toolbar->addAction(m_buttonSelectTool);
m_buttonSelectTool->setCheckable(true);
m_buttonSelectTool->setChecked(true);
m_buttonRazorTool = new QAction(QIcon::fromTheme(QStringLiteral("edit-cut")), i18n("Razor tool"), this);
// toolbar->addAction(m_buttonRazorTool);
m_buttonRazorTool->setCheckable(true);
m_buttonRazorTool->setChecked(false);
m_buttonSpacerTool = new QAction(QIcon::fromTheme(QStringLiteral("distribute-horizontal-x")), i18n("Spacer tool"), this);
// toolbar->addAction(m_buttonSpacerTool);
m_buttonSpacerTool->setCheckable(true);
m_buttonSpacerTool->setChecked(false);
auto *toolGroup = new QActionGroup(this);
toolGroup->addAction(m_buttonSelectTool);
toolGroup->addAction(m_buttonRazorTool);
toolGroup->addAction(m_buttonSpacerTool);
toolGroup->setExclusive(true);
QAction *collapseItem = new QAction(QIcon::fromTheme(QStringLiteral("collapse-all")), i18n("Collapse/Expand Item"), this);
addAction(QStringLiteral("collapse_expand"), collapseItem, Qt::Key_Less);
connect(collapseItem, &QAction::triggered, this, &MainWindow::slotCollapse);
// toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
/*QWidget * actionWidget;
int max = toolbar->iconSizeDefault() + 2;
actionWidget = toolbar->widgetForAction(m_normalEditTool);
actionWidget->setMaximumWidth(max);
actionWidget->setMaximumHeight(max - 4);
actionWidget = toolbar->widgetForAction(m_insertEditTool);
actionWidget->setMaximumWidth(max);
actionWidget->setMaximumHeight(max - 4);
actionWidget = toolbar->widgetForAction(m_overwriteEditTool);
actionWidget->setMaximumWidth(max);
actionWidget->setMaximumHeight(max - 4);
actionWidget = toolbar->widgetForAction(m_buttonSelectTool);
actionWidget->setMaximumWidth(max);
actionWidget->setMaximumHeight(max - 4);
actionWidget = toolbar->widgetForAction(m_buttonRazorTool);
actionWidget->setMaximumWidth(max);
actionWidget->setMaximumHeight(max - 4);
actionWidget = toolbar->widgetForAction(m_buttonSpacerTool);
actionWidget->setMaximumWidth(max);
actionWidget->setMaximumHeight(max - 4);*/
connect(toolGroup, &QActionGroup::triggered, this, &MainWindow::slotChangeTool);
m_buttonVideoThumbs = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-show-videothumb")), i18n("Show video thumbnails"), this);
m_buttonVideoThumbs->setCheckable(true);
m_buttonVideoThumbs->setChecked(KdenliveSettings::videothumbnails());
connect(m_buttonVideoThumbs, &QAction::triggered, this, &MainWindow::slotSwitchVideoThumbs);
m_buttonAudioThumbs = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-show-audiothumb")), i18n("Show audio thumbnails"), this);
m_buttonAudioThumbs->setCheckable(true);
m_buttonAudioThumbs->setChecked(KdenliveSettings::audiothumbnails());
connect(m_buttonAudioThumbs, &QAction::triggered, this, &MainWindow::slotSwitchAudioThumbs);
m_buttonShowMarkers = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-show-markers")), i18n("Show markers comments"), this);
m_buttonShowMarkers->setCheckable(true);
m_buttonShowMarkers->setChecked(KdenliveSettings::showmarkers());
connect(m_buttonShowMarkers, &QAction::triggered, this, &MainWindow::slotSwitchMarkersComments);
m_buttonSnap = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-snap")), i18n("Snap"), this);
m_buttonSnap->setCheckable(true);
m_buttonSnap->setChecked(KdenliveSettings::snaptopoints());
connect(m_buttonSnap, &QAction::triggered, this, &MainWindow::slotSwitchSnap);
m_buttonAutomaticTransition = new QAction(QIcon::fromTheme(QStringLiteral("auto-transition")), i18n("Automatic transitions"), this);
m_buttonAutomaticTransition->setCheckable(true);
m_buttonAutomaticTransition->setChecked(KdenliveSettings::automatictransitions());
connect(m_buttonAutomaticTransition, &QAction::triggered, this, &MainWindow::slotSwitchAutomaticTransition);
m_buttonFitZoom = new QAction(QIcon::fromTheme(QStringLiteral("zoom-fit-best")), i18n("Fit zoom to project"), this);
m_buttonFitZoom->setCheckable(false);
m_zoomSlider = new QSlider(Qt::Horizontal, this);
m_zoomSlider->setRange(0, 20);
m_zoomSlider->setPageStep(1);
m_zoomSlider->setInvertedAppearance(true);
m_zoomSlider->setInvertedControls(true);
m_zoomSlider->setMaximumWidth(150);
m_zoomSlider->setMinimumWidth(100);
m_zoomIn = KStandardAction::zoomIn(this, SLOT(slotZoomIn()), actionCollection());
m_zoomOut = KStandardAction::zoomOut(this, SLOT(slotZoomOut()), actionCollection());
connect(m_zoomSlider, SIGNAL(valueChanged(int)), this, SLOT(slotSetZoom(int)));
connect(m_zoomSlider, &QAbstractSlider::sliderMoved, this, &MainWindow::slotShowZoomSliderToolTip);
connect(m_buttonFitZoom, &QAction::triggered, this, &MainWindow::slotFitZoom);
KToolBar *toolbar = new KToolBar(QStringLiteral("statusToolBar"), this, Qt::BottomToolBarArea);
toolbar->setMovable(false);
toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
if (KdenliveSettings::gpu_accel()) {
QLabel *warnLabel = new QLabel(i18n("Experimental GPU processing enabled - not for production"), this);
warnLabel->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
warnLabel->setAlignment(Qt::AlignHCenter);
warnLabel->setStyleSheet(QStringLiteral("QLabel { background-color :red; color:black;padding-left:2px;padding-right:2px}"));
toolbar->addWidget(warnLabel);
}
m_trimLabel = new QLabel(QString(), this);
m_trimLabel->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
m_trimLabel->setAlignment(Qt::AlignHCenter);
//m_trimLabel->setStyleSheet(QStringLiteral("QLabel { background-color :red; }"));
toolbar->addWidget(m_trimLabel);
toolbar->addAction(m_buttonAutomaticTransition);
toolbar->addAction(m_buttonVideoThumbs);
toolbar->addAction(m_buttonAudioThumbs);
toolbar->addAction(m_buttonShowMarkers);
toolbar->addAction(m_buttonSnap);
toolbar->addSeparator();
toolbar->addAction(m_buttonFitZoom);
toolbar->addAction(m_zoomOut);
toolbar->addWidget(m_zoomSlider);
toolbar->addAction(m_zoomIn);
int small = style()->pixelMetric(QStyle::PM_SmallIconSize);
statusBar()->setMaximumHeight(2 * small);
m_messageLabel = new StatusBarMessageLabel(this);
m_messageLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding);
connect(this, &MainWindow::displayMessage, m_messageLabel, &StatusBarMessageLabel::setMessage);
connect(this, &MainWindow::displayProgressMessage, m_messageLabel, &StatusBarMessageLabel::setProgressMessage);
statusBar()->addWidget(m_messageLabel, 0);
QWidget *spacer = new QWidget(this);
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
statusBar()->addWidget(spacer, 1);
statusBar()->addPermanentWidget(toolbar);
toolbar->setIconSize(QSize(small, small));
toolbar->layout()->setContentsMargins(0, 0, 0, 0);
statusBar()->setContentsMargins(0, 0, 0, 0);
addAction(QStringLiteral("normal_mode"), m_normalEditTool);
addAction(QStringLiteral("overwrite_mode"), m_overwriteEditTool);
addAction(QStringLiteral("insert_mode"), m_insertEditTool);
addAction(QStringLiteral("select_tool"), m_buttonSelectTool, Qt::Key_S);
addAction(QStringLiteral("razor_tool"), m_buttonRazorTool, Qt::Key_X);
addAction(QStringLiteral("spacer_tool"), m_buttonSpacerTool, Qt::Key_M);
addAction(QStringLiteral("automatic_transition"), m_buttonAutomaticTransition);
addAction(QStringLiteral("show_video_thumbs"), m_buttonVideoThumbs);
addAction(QStringLiteral("show_audio_thumbs"), m_buttonAudioThumbs);
addAction(QStringLiteral("show_markers"), m_buttonShowMarkers);
addAction(QStringLiteral("snap"), m_buttonSnap);
addAction(QStringLiteral("zoom_fit"), m_buttonFitZoom);
#if defined(Q_OS_WIN)
int glBackend = KdenliveSettings::opengl_backend();
QAction *openGLAuto = new QAction(i18n("Auto"), this);
openGLAuto->setData(0);
openGLAuto->setCheckable(true);
openGLAuto->setChecked(glBackend == 0);
QAction *openGLDesktop = new QAction(i18n("OpenGL"), this);
openGLDesktop->setData(Qt::AA_UseDesktopOpenGL);
openGLDesktop->setCheckable(true);
openGLDesktop->setChecked(glBackend == Qt::AA_UseDesktopOpenGL);
QAction *openGLES = new QAction(i18n("DirectX (ANGLE)"), this);
openGLES->setData(Qt::AA_UseOpenGLES);
openGLES->setCheckable(true);
openGLES->setChecked(glBackend == Qt::AA_UseOpenGLES);
QAction *openGLSoftware = new QAction(i18n("Software OpenGL"), this);
openGLSoftware->setData(Qt::AA_UseSoftwareOpenGL);
openGLSoftware->setCheckable(true);
openGLSoftware->setChecked(glBackend == Qt::AA_UseSoftwareOpenGL);
addAction(QStringLiteral("opengl_auto"), openGLAuto);
addAction(QStringLiteral("opengl_desktop"), openGLDesktop);
addAction(QStringLiteral("opengl_es"), openGLES);
addAction(QStringLiteral("opengl_software"), openGLSoftware);
#endif
addAction(QStringLiteral("run_wizard"), i18n("Run Config Wizard"), this, SLOT(slotRunWizard()), QIcon::fromTheme(QStringLiteral("tools-wizard")));
addAction(QStringLiteral("project_settings"), i18n("Project Settings"), this, SLOT(slotEditProjectSettings()),
QIcon::fromTheme(QStringLiteral("configure")));
addAction(QStringLiteral("project_render"), i18n("Render"), this, SLOT(slotRenderProject()), QIcon::fromTheme(QStringLiteral("media-record")),
Qt::CTRL + Qt::Key_Return);
addAction(QStringLiteral("stop_project_render"), i18n("Stop Render"), this, SLOT(slotStopRenderProject()),
QIcon::fromTheme(QStringLiteral("media-record")));
addAction(QStringLiteral("project_clean"), i18n("Clean Project"), this, SLOT(slotCleanProject()), QIcon::fromTheme(QStringLiteral("edit-clear")));
QAction *resetAction = new QAction(QIcon::fromTheme(QStringLiteral("reload")), i18n("Reset configuration"), this);
addAction(QStringLiteral("reset_config"), resetAction);
connect(resetAction, &QAction::triggered, [&]() {
slotRestart(true);
});
addAction("project_adjust_profile", i18n("Adjust Profile to Current Clip"), pCore->bin(), SLOT(adjustProjectProfileToItem()));
m_playZone = addAction(QStringLiteral("monitor_play_zone"), i18n("Play Zone"), pCore->monitorManager(), SLOT(slotPlayZone()),
QIcon::fromTheme(QStringLiteral("media-playback-start")), Qt::CTRL + Qt::Key_Space);
m_loopZone = addAction(QStringLiteral("monitor_loop_zone"), i18n("Loop Zone"), pCore->monitorManager(), SLOT(slotLoopZone()),
QIcon::fromTheme(QStringLiteral("media-playback-start")), Qt::ALT + Qt::Key_Space);
m_loopClip = new QAction(QIcon::fromTheme(QStringLiteral("media-playback-start")), i18n("Loop selected clip"), this);
addAction(QStringLiteral("monitor_loop_clip"), m_loopClip);
m_loopClip->setEnabled(false);
addAction(QStringLiteral("dvd_wizard"), i18n("DVD Wizard"), this, SLOT(slotDvdWizard()), QIcon::fromTheme(QStringLiteral("media-optical")));
addAction(QStringLiteral("transcode_clip"), i18n("Transcode Clips"), this, SLOT(slotTranscodeClip()), QIcon::fromTheme(QStringLiteral("edit-copy")));
QAction *exportAction = new QAction(QIcon::fromTheme(QStringLiteral("document-export")), i18n("E&xport project"), this);
connect(exportAction, &QAction::triggered, &m_otioConvertions, &OtioConvertions::slotExportProject);
addAction(QStringLiteral("export_project"), exportAction);
QAction *importAction = new QAction(QIcon::fromTheme(QStringLiteral("document-import")), i18n("&Import project"), this);
connect(importAction, &QAction::triggered, &m_otioConvertions, &OtioConvertions::slotImportProject);
addAction(QStringLiteral("import_project"), importAction);
addAction(QStringLiteral("archive_project"), i18n("Archive Project"), this, SLOT(slotArchiveProject()),
QIcon::fromTheme(QStringLiteral("document-save-all")));
addAction(QStringLiteral("switch_monitor"), i18n("Switch monitor"), this, SLOT(slotSwitchMonitors()), QIcon(), Qt::Key_T);
addAction(QStringLiteral("expand_timeline_clip"), i18n("Expand Clip"), this, SLOT(slotExpandClip()),
QIcon::fromTheme(QStringLiteral("document-open")));
QAction *overlayInfo = new QAction(QIcon::fromTheme(QStringLiteral("help-hint")), i18n("Monitor Info Overlay"), this);
addAction(QStringLiteral("monitor_overlay"), overlayInfo);
overlayInfo->setCheckable(true);
overlayInfo->setData(0x01);
QAction *overlayTCInfo = new QAction(QIcon::fromTheme(QStringLiteral("help-hint")), i18n("Monitor Overlay Timecode"), this);
addAction(QStringLiteral("monitor_overlay_tc"), overlayTCInfo);
overlayTCInfo->setCheckable(true);
overlayTCInfo->setData(0x02);
QAction *overlayFpsInfo = new QAction(QIcon::fromTheme(QStringLiteral("help-hint")), i18n("Monitor Overlay Playback Fps"), this);
addAction(QStringLiteral("monitor_overlay_fps"), overlayFpsInfo);
overlayFpsInfo->setCheckable(true);
overlayFpsInfo->setData(0x20);
QAction *overlayMarkerInfo = new QAction(QIcon::fromTheme(QStringLiteral("help-hint")), i18n("Monitor Overlay Markers"), this);
addAction(QStringLiteral("monitor_overlay_markers"), overlayMarkerInfo);
overlayMarkerInfo->setCheckable(true);
overlayMarkerInfo->setData(0x04);
QAction *overlayAudioInfo = new QAction(QIcon::fromTheme(QStringLiteral("help-hint")), i18n("Monitor Overlay Audio Waveform"), this);
addAction(QStringLiteral("monitor_overlay_audiothumb"), overlayAudioInfo);
overlayAudioInfo->setCheckable(true);
overlayAudioInfo->setData(0x10);
connect(overlayInfo, &QAction::toggled, [&, overlayTCInfo, overlayFpsInfo, overlayMarkerInfo, overlayAudioInfo](bool toggled) {
overlayTCInfo->setEnabled(toggled);
overlayFpsInfo->setEnabled(toggled);
overlayMarkerInfo->setEnabled(toggled);
overlayAudioInfo->setEnabled(toggled);
});
-#if LIBMLT_VERSION_INT >= MLT_VERSION_PREVIEW_SCALE
+#if LIBMLT_VERSION_INT >= QT_VERSION_CHECK(6,20,0)
// Monitor resolution scaling
m_scaleGroup = new QActionGroup(this);
m_scaleGroup->setExclusive(true);
m_scaleGroup->setEnabled(!KdenliveSettings::external_display());
QAction *scale_no = new QAction(i18n("Full Resolution (1:1)"), m_scaleGroup);
addAction(QStringLiteral("scale_no_preview"), scale_no);
scale_no->setCheckable(true);
scale_no->setData(1);
QAction *scale_2 = new QAction(i18n("720p"), m_scaleGroup);
addAction(QStringLiteral("scale_2_preview"), scale_2);
scale_2->setCheckable(true);
scale_2->setData(2);
QAction *scale_4 = new QAction(i18n("540p"), m_scaleGroup);
addAction(QStringLiteral("scale_4_preview"), scale_4);
scale_4->setCheckable(true);
scale_4->setData(4);
QAction *scale_8 = new QAction(i18n("360p"), m_scaleGroup);
addAction(QStringLiteral("scale_8_preview"), scale_8);
scale_8->setCheckable(true);
scale_8->setData(8);
QAction *scale_16 = new QAction(i18n("270p"), m_scaleGroup);
addAction(QStringLiteral("scale_16_preview"), scale_16);
scale_16->setCheckable(true);
scale_16->setData(16);
connect(pCore->monitorManager(), &MonitorManager::scalingChanged, [scale_2, scale_4, scale_8, scale_16, scale_no]() {
switch (KdenliveSettings::previewScaling()) {
case 2:
scale_2->setChecked(true);
break;
case 4:
scale_4->setChecked(true);
break;
case 8:
scale_8->setChecked(true);
break;
case 16:
scale_16->setChecked(true);
break;
default:
scale_no->setChecked(true);
break;
}
});
pCore->monitorManager()->scalingChanged();
connect(m_scaleGroup, &QActionGroup::triggered, [] (QAction *ac) {
int scaling = ac->data().toInt();
KdenliveSettings::setPreviewScaling(scaling);
// Clear timeline selection so that any qml monitor scene is reset
pCore->monitorManager()->updatePreviewScaling();
});
#endif
QAction *dropFrames = new QAction(QIcon(), i18n("Real Time (drop frames)"), this);
dropFrames->setCheckable(true);
dropFrames->setChecked(KdenliveSettings::monitor_dropframes());
addAction(QStringLiteral("mlt_realtime"), dropFrames);
connect(dropFrames, &QAction::toggled, this, &MainWindow::slotSwitchDropFrames);
KSelectAction *monitorGamma = new KSelectAction(i18n("Monitor Gamma"), this);
monitorGamma->addAction(i18n("sRGB (computer)"));
monitorGamma->addAction(i18n("Rec. 709 (TV)"));
addAction(QStringLiteral("mlt_gamma"), monitorGamma);
monitorGamma->setCurrentItem(KdenliveSettings::monitor_gamma());
connect(monitorGamma, static_cast(&KSelectAction::triggered), this, &MainWindow::slotSetMonitorGamma);
addAction(QStringLiteral("switch_trim"), i18n("Trim Mode"), this, SLOT(slotSwitchTrimMode()), QIcon::fromTheme(QStringLiteral("cursor-arrow")));
// disable shortcut until fully working, Qt::CTRL + Qt::Key_T);
addAction(QStringLiteral("insert_project_tree"), i18n("Insert Zone in Project Bin"), this, SLOT(slotInsertZoneToTree()),
QIcon::fromTheme(QStringLiteral("kdenlive-add-clip")), Qt::CTRL + Qt::Key_I);
addAction(QStringLiteral("monitor_seek_snap_backward"), i18n("Go to Previous Snap Point"), this, SLOT(slotSnapRewind()),
QIcon::fromTheme(QStringLiteral("media-seek-backward")), Qt::ALT + Qt::Key_Left);
addAction(QStringLiteral("monitor_seek_guide_backward"), i18n("Go to Previous Guide"), this, SLOT(slotGuideRewind()),
QIcon::fromTheme(QStringLiteral("media-seek-backward")), Qt::CTRL + Qt::Key_Left);
addAction(QStringLiteral("seek_clip_start"), i18n("Go to Clip Start"), this, SLOT(slotClipStart()), QIcon::fromTheme(QStringLiteral("media-seek-backward")),
Qt::Key_Home);
addAction(QStringLiteral("seek_clip_end"), i18n("Go to Clip End"), this, SLOT(slotClipEnd()), QIcon::fromTheme(QStringLiteral("media-seek-forward")),
Qt::Key_End);
addAction(QStringLiteral("monitor_seek_snap_forward"), i18n("Go to Next Snap Point"), this, SLOT(slotSnapForward()),
QIcon::fromTheme(QStringLiteral("media-seek-forward")), Qt::ALT + Qt::Key_Right);
addAction(QStringLiteral("monitor_seek_guide_forward"), i18n("Go to Next Guide"), this, SLOT(slotGuideForward()),
QIcon::fromTheme(QStringLiteral("media-seek-forward")), Qt::CTRL + Qt::Key_Right);
addAction(QStringLiteral("align_playhead"), i18n("Align Playhead to Mouse Position"), this, SLOT(slotAlignPlayheadToMousePos()), QIcon(), Qt::Key_P);
addAction(QStringLiteral("grab_item"), i18n("Grab Current Item"), this, SLOT(slotGrabItem()), QIcon::fromTheme(QStringLiteral("transform-move")),
Qt::SHIFT + Qt::Key_G);
QAction *stickTransition = new QAction(i18n("Automatic Transition"), this);
stickTransition->setData(QStringLiteral("auto"));
stickTransition->setCheckable(true);
stickTransition->setEnabled(false);
addAction(QStringLiteral("auto_transition"), stickTransition);
connect(stickTransition, &QAction::triggered, this, &MainWindow::slotAutoTransition);
addAction(QStringLiteral("overwrite_to_in_point"), i18n("Overwrite Clip Zone in Timeline"), this, SLOT(slotInsertClipOverwrite()),
QIcon::fromTheme(QStringLiteral("timeline-overwrite")), Qt::Key_B);
addAction(QStringLiteral("insert_to_in_point"), i18n("Insert Clip Zone in Timeline"), this, SLOT(slotInsertClipInsert()),
QIcon::fromTheme(QStringLiteral("timeline-insert")), Qt::Key_V);
addAction(QStringLiteral("remove_extract"), i18n("Extract Timeline Zone"), this, SLOT(slotExtractZone()),
QIcon::fromTheme(QStringLiteral("timeline-extract")), Qt::SHIFT + Qt::Key_X);
addAction(QStringLiteral("remove_lift"), i18n("Lift Timeline Zone"), this, SLOT(slotLiftZone()), QIcon::fromTheme(QStringLiteral("timeline-lift")),
Qt::Key_Z);
addAction(QStringLiteral("set_render_timeline_zone"), i18n("Add Preview Zone"), this, SLOT(slotDefinePreviewRender()),
QIcon::fromTheme(QStringLiteral("preview-add-zone")));
addAction(QStringLiteral("unset_render_timeline_zone"), i18n("Remove Preview Zone"), this, SLOT(slotRemovePreviewRender()),
QIcon::fromTheme(QStringLiteral("preview-remove-zone")));
addAction(QStringLiteral("clear_render_timeline_zone"), i18n("Remove All Preview Zones"), this, SLOT(slotClearPreviewRender()),
QIcon::fromTheme(QStringLiteral("preview-remove-all")));
addAction(QStringLiteral("prerender_timeline_zone"), i18n("Start Preview Render"), this, SLOT(slotPreviewRender()),
QIcon::fromTheme(QStringLiteral("preview-render-on")), QKeySequence(Qt::SHIFT + Qt::Key_Return));
addAction(QStringLiteral("stop_prerender_timeline"), i18n("Stop Preview Render"), this, SLOT(slotStopPreviewRender()),
QIcon::fromTheme(QStringLiteral("preview-render-off")));
addAction(QStringLiteral("select_timeline_clip"), i18n("Select Clip"), this, SLOT(slotSelectTimelineClip()),
QIcon::fromTheme(QStringLiteral("edit-select")), Qt::Key_Plus);
addAction(QStringLiteral("deselect_timeline_clip"), i18n("Deselect Clip"), this, SLOT(slotDeselectTimelineClip()),
QIcon::fromTheme(QStringLiteral("edit-select")), Qt::Key_Minus);
addAction(QStringLiteral("select_add_timeline_clip"), i18n("Add Clip To Selection"), this, SLOT(slotSelectAddTimelineClip()),
QIcon::fromTheme(QStringLiteral("edit-select")), Qt::ALT + Qt::Key_Plus);
addAction(QStringLiteral("select_timeline_transition"), i18n("Select Transition"), this, SLOT(slotSelectTimelineTransition()),
QIcon::fromTheme(QStringLiteral("edit-select")), Qt::SHIFT + Qt::Key_Plus);
addAction(QStringLiteral("deselect_timeline_transition"), i18n("Deselect Transition"), this, SLOT(slotDeselectTimelineTransition()),
QIcon::fromTheme(QStringLiteral("edit-select")), Qt::SHIFT + Qt::Key_Minus);
addAction(QStringLiteral("select_add_timeline_transition"), i18n("Add Transition To Selection"), this, SLOT(slotSelectAddTimelineTransition()),
QIcon::fromTheme(QStringLiteral("edit-select")), Qt::ALT + Qt::SHIFT + Qt::Key_Plus);
addAction(QStringLiteral("delete_all_clip_markers"), i18n("Delete All Markers"), this, SLOT(slotDeleteAllClipMarkers()),
QIcon::fromTheme(QStringLiteral("edit-delete")));
addAction(QStringLiteral("add_marker_guide_quickly"), i18n("Add Marker/Guide quickly"), this, SLOT(slotAddMarkerGuideQuickly()),
QIcon::fromTheme(QStringLiteral("bookmark-new")), Qt::Key_Asterisk);
// Clip actions. We set some category info on the action data to enable/disable it contextually in timelinecontroller
KActionCategory *clipActionCategory = new KActionCategory(i18n("Current Selection"), actionCollection());
QAction *addMarker = addAction(QStringLiteral("add_clip_marker"), i18n("Add Marker"), this, SLOT(slotAddClipMarker()), QIcon::fromTheme(QStringLiteral("bookmark-new")), QKeySequence(), clipActionCategory);
addMarker->setData('P');
QAction *delMarker = addAction(QStringLiteral("delete_clip_marker"), i18n("Delete Marker"), this, SLOT(slotDeleteClipMarker()), QIcon::fromTheme(QStringLiteral("edit-delete")), QKeySequence(), clipActionCategory);
delMarker->setData('P');
QAction *editClipMarker = addAction(QStringLiteral("edit_clip_marker"), i18n("Edit Marker"), this, SLOT(slotEditClipMarker()),
QIcon::fromTheme(QStringLiteral("document-properties")), QKeySequence(), clipActionCategory);
editClipMarker->setObjectName(QStringLiteral("edit_marker"));
editClipMarker->setData('P');
QAction *splitAudio = addAction(QStringLiteral("clip_split"), i18n("Split Audio"), this, SLOT(slotSplitAV()),
QIcon::fromTheme(QStringLiteral("document-new")), QKeySequence(), clipActionCategory);
// "S" will be handled specifically to change the action name depending on current selection
splitAudio->setData('S');
splitAudio->setEnabled(false);
QAction *extractClip = addAction(QStringLiteral("extract_clip"), i18n("Extract Clip"), this, SLOT(slotExtractClip()), QIcon::fromTheme(QStringLiteral("timeline-extract")), QKeySequence(), clipActionCategory);
extractClip->setData('C');
extractClip->setEnabled(false);
QAction *extractToBin = addAction(QStringLiteral("save_to_bin"), i18n("Save Timeline Zone to Bin"), this, SLOT(slotSaveZoneToBin()), QIcon(), QKeySequence(), clipActionCategory);
extractToBin->setData('C');
extractToBin->setEnabled(false);
QAction *switchEnable = addAction(QStringLiteral("clip_switch"), i18n("Disable Clip"), this, SLOT(slotSwitchClip()),
QIcon(), QKeySequence(), clipActionCategory);
// "W" will be handled specifically to change the action name depending on current selection
switchEnable->setData('W');
switchEnable->setEnabled(false);
QAction *setAudioAlignReference = addAction(QStringLiteral("set_audio_align_ref"), i18n("Set Audio Reference"), this, SLOT(slotSetAudioAlignReference()),
QIcon(), QKeySequence(), clipActionCategory);
// "A" as data means this action should only be available for clips with audio
setAudioAlignReference->setData('A');
setAudioAlignReference->setEnabled(false);
QAction *alignAudio =
addAction(QStringLiteral("align_audio"), i18n("Align Audio to Reference"), this, SLOT(slotAlignAudio()), QIcon(), QKeySequence(), clipActionCategory);
// "A" as data means this action should only be available for clips with audio
//alignAudio->setData('A');
alignAudio->setEnabled(false);
QAction *act = addAction(QStringLiteral("edit_item_duration"), i18n("Edit Duration"), this, SLOT(slotEditItemDuration()),
QIcon::fromTheme(QStringLiteral("measure")), QKeySequence(), clipActionCategory);
act->setEnabled(false);
act = addAction(QStringLiteral("edit_item_speed"), i18n("Change Speed"), this, SLOT(slotEditItemSpeed()),
QIcon::fromTheme(QStringLiteral("speedometer")), QKeySequence(), clipActionCategory);
act->setEnabled(false);
act = addAction(QStringLiteral("clip_in_project_tree"), i18n("Clip in Project Bin"), this, SLOT(slotClipInProjectTree()),
QIcon::fromTheme(QStringLiteral("find-location")), QKeySequence(), clipActionCategory);
act->setEnabled(false);
// "C" as data means this action should only be available for clips - not for compositions
act->setData('C');
act = addAction(QStringLiteral("cut_timeline_clip"), i18n("Cut Clip"), this, SLOT(slotCutTimelineClip()), QIcon::fromTheme(QStringLiteral("edit-cut")),
Qt::SHIFT + Qt::Key_R);
act = addAction(QStringLiteral("cut_timeline_all_clips"), i18n("Cut All Clips"), this, SLOT(slotCutTimelineAllClips()), QIcon::fromTheme(QStringLiteral("edit-cut")),
Qt::CTRL + Qt::SHIFT + Qt::Key_R);
act = addAction(QStringLiteral("delete_timeline_clip"), i18n("Delete Selected Item"), this, SLOT(slotDeleteItem()),
QIcon::fromTheme(QStringLiteral("edit-delete")), Qt::Key_Delete);
QAction *resizeStart = new QAction(QIcon(), i18n("Resize Item Start"), this);
addAction(QStringLiteral("resize_timeline_clip_start"), resizeStart, Qt::Key_1);
connect(resizeStart, &QAction::triggered, this, &MainWindow::slotResizeItemStart);
QAction *resizeEnd = new QAction(QIcon(), i18n("Resize Item End"), this);
addAction(QStringLiteral("resize_timeline_clip_end"), resizeEnd, Qt::Key_2);
connect(resizeEnd, &QAction::triggered, this, &MainWindow::slotResizeItemEnd);
QAction *pasteEffects = addAction(QStringLiteral("paste_effects"), i18n("Paste Effects"), this, SLOT(slotPasteEffects()),
QIcon::fromTheme(QStringLiteral("edit-paste")), QKeySequence(), clipActionCategory);
pasteEffects->setEnabled(false);
// "C" as data means this action should only be available for clips - not for compositions
pasteEffects->setData('C');
QAction *groupClip = addAction(QStringLiteral("group_clip"), i18n("Group Clips"), this, SLOT(slotGroupClips()),
QIcon::fromTheme(QStringLiteral("object-group")), Qt::CTRL + Qt::Key_G, clipActionCategory);
// "G" as data means this action should only be available for multiple items selection
groupClip->setData('G');
groupClip->setEnabled(false);
QAction *ungroupClip = addAction(QStringLiteral("ungroup_clip"), i18n("Ungroup Clips"), this, SLOT(slotUnGroupClips()),
QIcon::fromTheme(QStringLiteral("object-ungroup")), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_G), clipActionCategory);
// "U" as data means this action should only be available if selection is a group
ungroupClip->setData('U');
ungroupClip->setEnabled(false);
act = clipActionCategory->addAction(KStandardAction::Copy, this, SLOT(slotCopy()));
act->setEnabled(false);
KStandardAction::paste(this, SLOT(slotPaste()), actionCollection());
/*act = KStandardAction::copy(this, SLOT(slotCopy()), actionCollection());
clipActionCategory->addAction(KStandardAction::name(KStandardAction::Copy), act);
act->setEnabled(false);
act = KStandardAction::paste(this, SLOT(slotPaste()), actionCollection());
clipActionCategory->addAction(KStandardAction::name(KStandardAction::Paste), act);
act->setEnabled(false);*/
kdenliveCategoryMap.insert(QStringLiteral("timelineselection"), clipActionCategory);
addAction(QStringLiteral("insert_space"), i18n("Insert Space"), this, SLOT(slotInsertSpace()));
addAction(QStringLiteral("delete_space"), i18n("Remove Space"), this, SLOT(slotRemoveSpace()));
addAction(QStringLiteral("delete_space_all_tracks"), i18n("Remove Space In All Tracks"), this, SLOT(slotRemoveAllSpace()));
KActionCategory *timelineActions = new KActionCategory(i18n("Tracks"), actionCollection());
QAction *insertTrack = new QAction(QIcon(), i18n("Insert Track"), this);
connect(insertTrack, &QAction::triggered, this, &MainWindow::slotInsertTrack);
timelineActions->addAction(QStringLiteral("insert_track"), insertTrack);
QAction *masterEffectStack = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-composite")), i18n("Master effects"), this);
connect(masterEffectStack, &QAction::triggered, [&]() {
pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor);
getCurrentTimeline()->controller()->showMasterEffects();
});
timelineActions->addAction(QStringLiteral("master_effects"), masterEffectStack);
QAction *switchTrackTarget = new QAction(QIcon(), i18n("Switch Track Target Audio Stream"), this);
connect(switchTrackTarget, &QAction::triggered, this, &MainWindow::slotSwitchTrackAudioStream);
timelineActions->addAction(QStringLiteral("switch_target_stream"), switchTrackTarget);
actionCollection()->setDefaultShortcut(switchTrackTarget, Qt::Key_Apostrophe);
QAction *deleteTrack = new QAction(QIcon(), i18n("Delete Track"), this);
connect(deleteTrack, &QAction::triggered, this, &MainWindow::slotDeleteTrack);
timelineActions->addAction(QStringLiteral("delete_track"), deleteTrack);
deleteTrack->setData("delete_track");
QAction *showAudio = new QAction(QIcon(), i18n("Show Record Controls"), this);
connect(showAudio, &QAction::triggered, this, &MainWindow::slotShowTrackRec);
timelineActions->addAction(QStringLiteral("show_track_record"), showAudio);
showAudio->setCheckable(true);
showAudio->setData("show_track_record");
QAction *selectTrack = new QAction(QIcon(), i18n("Select All in Current Track"), this);
connect(selectTrack, &QAction::triggered, this, &MainWindow::slotSelectTrack);
timelineActions->addAction(QStringLiteral("select_track"), selectTrack);
QAction *selectAll = KStandardAction::selectAll(this, SLOT(slotSelectAllTracks()), this);
selectAll->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-select-all")));
selectAll->setShortcutContext(Qt::WidgetWithChildrenShortcut);
timelineActions->addAction(QStringLiteral("select_all_tracks"), selectAll);
QAction *unselectAll = KStandardAction::deselect(this, SLOT(slotUnselectAllTracks()), this);
unselectAll->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-unselect-all")));
unselectAll->setShortcutContext(Qt::WidgetWithChildrenShortcut);
timelineActions->addAction(QStringLiteral("unselect_all_tracks"), unselectAll);
kdenliveCategoryMap.insert(QStringLiteral("timeline"), timelineActions);
// Cached data management
addAction(QStringLiteral("manage_cache"), i18n("Manage Cached Data"), this, SLOT(slotManageCache()),
QIcon::fromTheme(QStringLiteral("network-server-database")));
QAction *disablePreview = new QAction(i18n("Disable Timeline Preview"), this);
disablePreview->setCheckable(true);
addAction(QStringLiteral("disable_preview"), disablePreview);
addAction(QStringLiteral("add_guide"), i18n("Add/Remove Guide"), this, SLOT(slotAddGuide()), QIcon::fromTheme(QStringLiteral("list-add")), Qt::Key_G);
addAction(QStringLiteral("delete_guide"), i18n("Delete Guide"), this, SLOT(slotDeleteGuide()), QIcon::fromTheme(QStringLiteral("edit-delete")));
addAction(QStringLiteral("edit_guide"), i18n("Edit Guide"), this, SLOT(slotEditGuide()), QIcon::fromTheme(QStringLiteral("document-properties")));
addAction(QStringLiteral("delete_all_guides"), i18n("Delete All Guides"), this, SLOT(slotDeleteAllGuides()),
QIcon::fromTheme(QStringLiteral("edit-delete")));
m_saveAction = KStandardAction::save(pCore->projectManager(), SLOT(saveFile()), actionCollection());
m_saveAction->setIcon(QIcon::fromTheme(QStringLiteral("document-save")));
QAction *sentToLibrary = addAction(QStringLiteral("send_library"), i18n("Add Timeline Selection to Library"), pCore->library(), SLOT(slotAddToLibrary()),
QIcon::fromTheme(QStringLiteral("bookmark-new")));
sentToLibrary->setEnabled(false);
pCore->library()->setupActions(QList() << sentToLibrary);
KStandardAction::showMenubar(this, SLOT(showMenuBar(bool)), actionCollection());
act = KStandardAction::quit(this, SLOT(close()), actionCollection());
// act->setIcon(QIcon::fromTheme(QStringLiteral("application-exit")));
KStandardAction::keyBindings(this, SLOT(slotEditKeys()), actionCollection());
KStandardAction::preferences(this, SLOT(slotPreferences()), actionCollection());
KStandardAction::configureNotifications(this, SLOT(configureNotifications()), actionCollection());
KStandardAction::fullScreen(this, SLOT(slotFullScreen()), this, actionCollection());
QAction *undo = KStandardAction::undo(m_commandStack, SLOT(undo()), actionCollection());
undo->setEnabled(false);
connect(m_commandStack, &QUndoGroup::canUndoChanged, undo, &QAction::setEnabled);
connect(this, &MainWindow::enableUndo, [this, undo] (bool enable) {
if (enable) {
enable = m_commandStack->activeStack()->canUndo();
}
undo->setEnabled(enable);
});
QAction *redo = KStandardAction::redo(m_commandStack, SLOT(redo()), actionCollection());
redo->setEnabled(false);
connect(m_commandStack, &QUndoGroup::canRedoChanged, redo, &QAction::setEnabled);
connect(this, &MainWindow::enableUndo, [this, redo] (bool enable) {
if (enable) {
enable = m_commandStack->activeStack()->canRedo();
}
redo->setEnabled(enable);
});
QAction *disableEffects = addAction(QStringLiteral("disable_timeline_effects"), i18n("Disable Timeline Effects"), pCore->projectManager(),
SLOT(slotDisableTimelineEffects(bool)), QIcon::fromTheme(QStringLiteral("favorite")));
disableEffects->setData("disable_timeline_effects");
disableEffects->setCheckable(true);
disableEffects->setChecked(false);
addAction(QStringLiteral("switch_track_lock"), i18n("Toggle Track Lock"), pCore->projectManager(), SLOT(slotSwitchTrackLock()), QIcon(),
Qt::SHIFT + Qt::Key_L);
addAction(QStringLiteral("switch_all_track_lock"), i18n("Toggle All Track Lock"), pCore->projectManager(), SLOT(slotSwitchAllTrackLock()), QIcon(),
Qt::CTRL + Qt::SHIFT + Qt::Key_L);
addAction(QStringLiteral("switch_track_target"), i18n("Toggle Track Target"), pCore->projectManager(), SLOT(slotSwitchTrackTarget()), QIcon(),
Qt::SHIFT + Qt::Key_T);
addAction(QStringLiteral("switch_active_target"), i18n("Toggle Track Active"), pCore->projectManager(), SLOT(slotSwitchTrackActive()), QIcon(),
Qt::Key_A);
addAction(QStringLiteral("switch_all_targets"), i18n("Toggle All Tracks Active"), pCore->projectManager(), SLOT(slotSwitchAllTrackActive()), QIcon(),
Qt::SHIFT + Qt::Key_A);
addAction(QStringLiteral("activate_all_targets"), i18n("Switch All Tracks Active"), pCore->projectManager(), SLOT(slotMakeAllTrackActive()), QIcon(),
Qt::SHIFT + Qt::ALT + Qt::Key_A);
addAction(QStringLiteral("add_project_note"), i18n("Add Project Note"), pCore->projectManager(), SLOT(slotAddProjectNote()),
QIcon::fromTheme(QStringLiteral("bookmark-new")));
pCore->bin()->setupMenu();
// Setup effects and transitions actions.
KActionCategory *transitionActions = new KActionCategory(i18n("Transitions"), actionCollection());
// m_transitions = new QAction*[transitions.count()];
auto allTransitions = TransitionsRepository::get()->getNames();
for (const auto &transition : allTransitions) {
auto *transAction = new QAction(transition.first, this);
transAction->setData(transition.second);
transAction->setIconVisibleInMenu(false);
transitionActions->addAction("transition_" + transition.second, transAction);
}
// monitor actions
addAction(QStringLiteral("extract_frame"), i18n("Extract frame..."), pCore->monitorManager(), SLOT(slotExtractCurrentFrame()),
QIcon::fromTheme(QStringLiteral("insert-image")));
addAction(QStringLiteral("extract_frame_to_project"), i18n("Extract frame to project..."), pCore->monitorManager(),
SLOT(slotExtractCurrentFrameToProject()), QIcon::fromTheme(QStringLiteral("insert-image")));
}
void MainWindow::saveOptions()
{
KdenliveSettings::self()->save();
}
bool MainWindow::readOptions()
{
KSharedConfigPtr config = KSharedConfig::openConfig();
pCore->projectManager()->recentFilesAction()->loadEntries(KConfigGroup(config, "Recent Files"));
if (KdenliveSettings::defaultprojectfolder().isEmpty()) {
QDir dir(QStandardPaths::writableLocation(QStandardPaths::MoviesLocation));
dir.mkpath(QStringLiteral("."));
KdenliveSettings::setDefaultprojectfolder(dir.absolutePath());
}
QFont ft = QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont);
// Default unit for timeline.qml objects size
int baseUnit = qMax(28, (int) (QFontInfo(ft).pixelSize() * 1.8 + 0.5));
if (KdenliveSettings::trackheight() == 0) {
int trackHeight = qMax(50, (int) (2.2 * baseUnit + 6));
KdenliveSettings::setTrackheight(trackHeight);
}
bool firstRun = false;
KConfigGroup initialGroup(config, "version");
if (!initialGroup.exists() || KdenliveSettings::sdlAudioBackend().isEmpty()) {
// First run, check if user is on a KDE Desktop
firstRun = true;
//Define default video location for first run
KRecentDirs::add(QStringLiteral(":KdenliveClipFolder"), QStandardPaths::writableLocation(QStandardPaths::MoviesLocation));
// this is our first run, show Wizard
QPointer w = new Wizard(true, false);
if (w->exec() == QDialog::Accepted && w->isOk()) {
w->adjustSettings();
delete w;
} else {
delete w;
::exit(1);
}
} else if (!KdenliveSettings::ffmpegpath().isEmpty() && !QFile::exists(KdenliveSettings::ffmpegpath())) {
// Invalid entry for FFmpeg, check system
QPointer w = new Wizard(true, config->name().contains(QLatin1String("appimage")));
if (w->exec() == QDialog::Accepted && w->isOk()) {
w->adjustSettings();
}
delete w;
}
initialGroup.writeEntry("version", version);
return firstRun;
}
void MainWindow::slotRunWizard()
{
QPointer w = new Wizard(false, false, this);
if (w->exec() == QDialog::Accepted && w->isOk()) {
w->adjustSettings();
}
delete w;
}
void MainWindow::slotRefreshProfiles()
{
KdenliveSettingsDialog *d = static_cast(KConfigDialog::exists(QStringLiteral("settings")));
if (d) {
d->checkProfile();
}
}
void MainWindow::slotEditProjectSettings()
{
KdenliveDoc *project = pCore->currentDoc();
QPair p = getMainTimeline()->getTracksCount();
int channels = qMin(project->getDocumentProperty(QStringLiteral("audioChannels"), QStringLiteral("2")).toInt(), 2);
ProjectSettings *w = new ProjectSettings(project, project->metadata(), getMainTimeline()->controller()->extractCompositionLumas(), p.first, p.second, channels,
project->projectTempFolder(), true, !project->isModified(), this);
connect(w, &ProjectSettings::disableProxies, this, &MainWindow::slotDisableProxies);
// connect(w, SIGNAL(disablePreview()), pCore->projectManager()->currentTimeline(), SLOT(invalidateRange()));
connect(w, &ProjectSettings::refreshProfiles, this, &MainWindow::slotRefreshProfiles);
if (w->exec() == QDialog::Accepted) {
QString profile = w->selectedProfile();
// project->setProjectFolder(w->selectedFolder());
bool modified = false;
if (m_renderWidget) {
m_renderWidget->updateDocumentPath();
}
if (KdenliveSettings::videothumbnails() != w->enableVideoThumbs()) {
slotSwitchVideoThumbs();
}
if (KdenliveSettings::audiothumbnails() != w->enableAudioThumbs()) {
slotSwitchAudioThumbs();
}
if (project->getDocumentProperty(QStringLiteral("previewparameters")) != w->proxyParams() ||
project->getDocumentProperty(QStringLiteral("previewextension")) != w->proxyExtension()) {
modified = true;
project->setDocumentProperty(QStringLiteral("previewparameters"), w->previewParams());
project->setDocumentProperty(QStringLiteral("previewextension"), w->previewExtension());
slotClearPreviewRender(false);
}
if (project->getDocumentProperty(QStringLiteral("proxyparams")) != w->proxyParams() ||
project->getDocumentProperty(QStringLiteral("proxyextension")) != w->proxyExtension()) {
modified = true;
project->setDocumentProperty(QStringLiteral("proxyparams"), w->proxyParams());
project->setDocumentProperty(QStringLiteral("proxyextension"), w->proxyExtension());
if (pCore->projectItemModel()->clipsCount() > 0 &&
KMessageBox::questionYesNo(this, i18n("You have changed the proxy parameters. Do you want to recreate all proxy clips for this project?")) ==
KMessageBox::Yes) {
pCore->bin()->rebuildProxies();
}
}
if (project->getDocumentProperty(QStringLiteral("externalproxyparams")) != w->externalProxyParams()) {
modified = true;
project->setDocumentProperty(QStringLiteral("externalproxyparams"), w->externalProxyParams());
if (pCore->projectItemModel()->clipsCount() > 0 &&
KMessageBox::questionYesNo(this, i18n("You have changed the proxy parameters. Do you want to recreate all proxy clips for this project?")) ==
KMessageBox::Yes) {
pCore->bin()->rebuildProxies();
}
}
if (project->getDocumentProperty(QStringLiteral("generateproxy")) != QString::number((int)w->generateProxy())) {
modified = true;
project->setDocumentProperty(QStringLiteral("generateproxy"), QString::number((int)w->generateProxy()));
}
if (project->getDocumentProperty(QStringLiteral("proxyminsize")) != QString::number(w->proxyMinSize())) {
modified = true;
project->setDocumentProperty(QStringLiteral("proxyminsize"), QString::number(w->proxyMinSize()));
}
if (project->getDocumentProperty(QStringLiteral("generateimageproxy")) != QString::number((int)w->generateImageProxy())) {
modified = true;
project->setDocumentProperty(QStringLiteral("generateimageproxy"), QString::number((int)w->generateImageProxy()));
}
if (project->getDocumentProperty(QStringLiteral("proxyimageminsize")) != QString::number(w->proxyImageMinSize())) {
modified = true;
project->setDocumentProperty(QStringLiteral("proxyimageminsize"), QString::number(w->proxyImageMinSize()));
}
if (project->getDocumentProperty(QStringLiteral("proxyimagesize")) != QString::number(w->proxyImageSize())) {
modified = true;
project->setDocumentProperty(QStringLiteral("proxyimagesize"), QString::number(w->proxyImageSize()));
}
if (QString::number((int)w->useProxy()) != project->getDocumentProperty(QStringLiteral("enableproxy"))) {
project->setDocumentProperty(QStringLiteral("enableproxy"), QString::number((int)w->useProxy()));
modified = true;
slotUpdateProxySettings();
}
if (QString::number((int)w->useExternalProxy()) != project->getDocumentProperty(QStringLiteral("enableexternalproxy"))) {
project->setDocumentProperty(QStringLiteral("enableexternalproxy"), QString::number((int)w->useExternalProxy()));
modified = true;
}
if (w->metadata() != project->metadata()) {
project->setMetadata(w->metadata());
}
QString newProjectFolder = w->storageFolder();
if (newProjectFolder.isEmpty()) {
newProjectFolder = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
}
if (newProjectFolder != project->projectTempFolder()) {
KMessageBox::ButtonCode answer;
// Project folder changed:
if (project->isModified()) {
answer = KMessageBox::warningContinueCancel(this, i18n("The current project has not been saved. This will first save the project, then move "
"all temporary files from %1 to %2, and the project file will be reloaded",
project->projectTempFolder(), newProjectFolder));
if (answer == KMessageBox::Continue) {
pCore->projectManager()->saveFile();
}
} else {
answer = KMessageBox::warningContinueCancel(
this, i18n("This will move all temporary files from %1 to %2, the project file will then be reloaded",
project->projectTempFolder(), newProjectFolder));
}
if (answer == KMessageBox::Continue) {
// Proceed with move
QString documentId = QDir::cleanPath(project->getDocumentProperty(QStringLiteral("documentid")));
bool ok;
documentId.toLongLong(&ok, 10);
if (!ok || documentId.isEmpty()) {
KMessageBox::sorry(this, i18n("Cannot perform operation, invalid document id: %1", documentId));
} else {
QDir newDir(newProjectFolder);
QDir oldDir(project->projectTempFolder());
if (newDir.exists(documentId)) {
KMessageBox::sorry(this, i18n("Cannot perform operation, target directory already exists: %1", newDir.absoluteFilePath(documentId)));
} else {
// Proceed with the move
pCore->projectManager()->moveProjectData(oldDir.absoluteFilePath(documentId), newDir.absolutePath());
}
}
}
}
if (pCore->getCurrentProfile()->path() != profile || project->profileChanged(profile)) {
if (!qFuzzyCompare(pCore->getCurrentProfile()->fps() - ProfileRepository::get()->getProfile(profile)->fps(), 0.)) {
// Fps was changed, we save the project to an xml file with updated profile and reload project
// Check if blank project
if (project->url().fileName().isEmpty() && !project->isModified()) {
// Trying to switch project profile from an empty project
pCore->setCurrentProfile(profile);
pCore->projectManager()->newFile(profile, false);
return;
}
pCore->projectManager()->saveWithUpdatedProfile(profile);
} else {
bool darChanged = !qFuzzyCompare(pCore->getCurrentProfile()->dar(), ProfileRepository::get()->getProfile(profile)->dar());
pCore->setCurrentProfile(profile);
pCore->projectManager()->slotResetProfiles(darChanged);
slotUpdateDocumentState(true);
}
} else if (modified) {
project->setModified();
}
}
delete w;
}
void MainWindow::slotDisableProxies()
{
pCore->currentDoc()->setDocumentProperty(QStringLiteral("enableproxy"), QString::number((int)false));
pCore->currentDoc()->setModified();
slotUpdateProxySettings();
}
void MainWindow::slotStopRenderProject()
{
if (m_renderWidget) {
m_renderWidget->slotAbortCurrentJob();
}
}
void MainWindow::slotRenderProject()
{
KdenliveDoc *project = pCore->currentDoc();
if (!m_renderWidget) {
QString projectfolder = project ? project->projectDataFolder() + QDir::separator() : KdenliveSettings::defaultprojectfolder();
if (project) {
m_renderWidget = new RenderWidget(project->useProxy(), this);
connect(m_renderWidget, &RenderWidget::shutdown, this, &MainWindow::slotShutdown);
connect(m_renderWidget, &RenderWidget::selectedRenderProfile, this, &MainWindow::slotSetDocumentRenderProfile);
connect(m_renderWidget, &RenderWidget::abortProcess, this, &MainWindow::abortRenderJob);
connect(m_renderWidget, &RenderWidget::openDvdWizard, this, &MainWindow::slotDvdWizard);
connect(this, &MainWindow::updateRenderWidgetProfile, m_renderWidget, &RenderWidget::adjustViewToProfile);
m_renderWidget->setGuides(project->getGuideModel());
m_renderWidget->updateDocumentPath();
m_renderWidget->setRenderProfile(project->getRenderProperties());
}
if (m_compositeAction->currentAction()) {
m_renderWidget->errorMessage(RenderWidget::CompositeError, m_compositeAction->currentAction()->data().toInt() == 1
? i18n("Rendering using low quality track compositing")
: QString());
}
}
slotCheckRenderStatus();
m_renderWidget->show();
// m_renderWidget->showNormal();
// What are the following lines supposed to do?
// m_renderWidget->enableAudio(false);
// m_renderWidget->export_audio;
}
void MainWindow::slotCheckRenderStatus()
{
// Make sure there are no missing clips
// TODO
/*if (m_renderWidget)
m_renderWidget->missingClips(pCore->bin()->hasMissingClips());*/
}
void MainWindow::setRenderingProgress(const QString &url, int progress)
{
emit setRenderProgress(progress);
if (m_renderWidget) {
m_renderWidget->setRenderJob(url, progress);
}
}
void MainWindow::setRenderingFinished(const QString &url, int status, const QString &error)
{
emit setRenderProgress(100);
if (m_renderWidget) {
m_renderWidget->setRenderStatus(url, status, error);
}
}
void MainWindow::addProjectClip(const QString &url)
{
if (pCore->currentDoc()) {
QStringList ids = pCore->projectItemModel()->getClipByUrl(QFileInfo(url));
if (!ids.isEmpty()) {
// Clip is already in project bin, abort
return;
}
ClipCreator::createClipFromFile(url, pCore->projectItemModel()->getRootFolder()->clipId(), pCore->projectItemModel());
}
}
void MainWindow::addTimelineClip(const QString &url)
{
if (pCore->currentDoc()) {
QStringList ids = pCore->projectItemModel()->getClipByUrl(QFileInfo(url));
if (!ids.isEmpty()) {
pCore->selectBinClip(ids.constFirst());
slotInsertClipInsert();
}
}
}
void MainWindow::scriptRender(const QString &url)
{
slotRenderProject();
m_renderWidget->slotPrepareExport(true, url);
}
void MainWindow::exitApp()
{
QApplication::exit(0);
}
void MainWindow::slotCleanProject()
{
if (KMessageBox::warningContinueCancel(this, i18n("This will remove all unused clips from your project."), i18n("Clean up project")) ==
KMessageBox::Cancel) {
return;
}
pCore->bin()->cleanup();
}
void MainWindow::slotUpdateMousePosition(int pos)
{
if (pCore->currentDoc()) {
switch (m_timeFormatButton->currentItem()) {
case 0:
m_timeFormatButton->setText(pCore->currentDoc()->timecode().getTimecodeFromFrames(pos) + QStringLiteral(" / ") +
pCore->currentDoc()->timecode().getTimecodeFromFrames(getMainTimeline()->controller()->duration()));
break;
default:
m_timeFormatButton->setText(
QStringLiteral("%1 / %2").arg(pos, 6, 10, QLatin1Char('0')).arg(getMainTimeline()->controller()->duration(), 6, 10, QLatin1Char('0')));
}
}
}
void MainWindow::slotUpdateProjectDuration(int pos)
{
Q_UNUSED(pos)
if (pCore->currentDoc()) {
slotUpdateMousePosition(getMainTimeline()->controller()->getMousePos());
}
}
void MainWindow::slotUpdateDocumentState(bool modified)
{
setWindowTitle(pCore->currentDoc()->description());
setWindowModified(modified);
m_saveAction->setEnabled(modified);
}
void MainWindow::connectDocument()
{
KdenliveDoc *project = pCore->currentDoc();
connect(project, &KdenliveDoc::startAutoSave, pCore->projectManager(), &ProjectManager::slotStartAutoSave);
connect(project, &KdenliveDoc::reloadEffects, this, &MainWindow::slotReloadEffects);
KdenliveSettings::setProject_fps(pCore->getCurrentFps());
m_projectMonitor->slotLoadClipZone(project->zone());
connect(m_projectMonitor, &Monitor::multitrackView, getMainTimeline()->controller(), &TimelineController::slotMultitrackView, Qt::UniqueConnection);
connect(m_projectMonitor, &Monitor::activateTrack, getMainTimeline()->controller(), &TimelineController::activateTrackAndSelect, Qt::UniqueConnection);
connect(getMainTimeline()->controller(), &TimelineController::timelineClipSelected, [&] (bool selected) {
m_loopClip->setEnabled(selected);
pCore->library()->enableAddSelection(selected);
});
connect(pCore->library(), &LibraryWidget::saveTimelineSelection, getMainTimeline()->controller(), &TimelineController::saveTimelineSelection,
Qt::UniqueConnection);
connect(pCore->monitorManager(), &MonitorManager::frameDisplayed, [&](const SharedFrame &frame) {
pCore->mixer()->updateLevels(frame.get_position());
//QMetaObject::invokeMethod(this, "setAudioValues", Qt::QueuedConnection, Q_ARG(const QVector &, levels));
});
connect(pCore->mixer(), &MixerManager::purgeCache, m_projectMonitor, &Monitor::purgeCache);
// TODO REFAC: reconnect to new timeline
/*
Timeline *trackView = pCore->projectManager()->currentTimeline();
connect(trackView, &Timeline::configTrack, this, &MainWindow::slotConfigTrack);
connect(trackView, &Timeline::updateTracksInfo, this, &MainWindow::slotUpdateTrackInfo);
connect(trackView, &Timeline::mousePosition, this, &MainWindow::slotUpdateMousePosition);
connect(pCore->producerQueue(), &ProducerQueue::infoProcessingFinished, trackView->projectView(), &CustomTrackView::slotInfoProcessingFinished,
Qt::DirectConnection);
connect(trackView->projectView(), &CustomTrackView::importKeyframes, this, &MainWindow::slotProcessImportKeyframes);
connect(trackView->projectView(), &CustomTrackView::updateTrimMode, this, &MainWindow::setTrimMode);
connect(m_projectMonitor, SIGNAL(renderPosition(int)), trackView, SLOT(moveCursorPos(int)));
connect(m_projectMonitor, SIGNAL(zoneUpdated(QPoint)), trackView, SLOT(slotSetZone(QPoint)));
connect(trackView->projectView(), &CustomTrackView::guidesUpdated, this, &MainWindow::slotGuidesUpdated);
connect(trackView->projectView(), &CustomTrackView::loadMonitorScene, m_projectMonitor, &Monitor::slotShowEffectScene);
connect(trackView->projectView(), &CustomTrackView::setQmlProperty, m_projectMonitor, &Monitor::setQmlProperty);
connect(m_projectMonitor, SIGNAL(acceptRipple(bool)), trackView->projectView(), SLOT(slotAcceptRipple(bool)));
connect(m_projectMonitor, SIGNAL(switchTrimMode(int)), trackView->projectView(), SLOT(switchTrimMode(int)));
connect(project, &KdenliveDoc::saveTimelinePreview, trackView, &Timeline::slotSaveTimelinePreview);
connect(trackView, SIGNAL(showTrackEffects(int, TrackInfo)), this, SLOT(slotTrackSelected(int, TrackInfo)));
connect(trackView->projectView(), &CustomTrackView::clipItemSelected, this, &MainWindow::slotTimelineClipSelected, Qt::DirectConnection);
connect(trackView->projectView(), &CustomTrackView::setActiveKeyframe, m_effectStack, &EffectStackView2::setActiveKeyframe);
connect(trackView->projectView(), SIGNAL(transitionItemSelected(Transition *, int, QPoint, bool)), m_effectStack, SLOT(slotTransitionItemSelected(Transition
*, int, QPoint, bool)), Qt::DirectConnection);
connect(trackView->projectView(), SIGNAL(transitionItemSelected(Transition *, int, QPoint, bool)), this, SLOT(slotActivateTransitionView(Transition *)));
connect(trackView->projectView(), &CustomTrackView::zoomIn, this, &MainWindow::slotZoomIn);
connect(trackView->projectView(), &CustomTrackView::zoomOut, this, &MainWindow::slotZoomOut);
connect(trackView, SIGNAL(setZoom(int)), this, SLOT(slotSetZoom(int)));
connect(trackView, SIGNAL(displayMessage(QString, MessageType)), m_messageLabel, SLOT(setMessage(QString, MessageType)));
connect(trackView->projectView(), SIGNAL(displayMessage(QString, MessageType)), m_messageLabel, SLOT(setMessage(QString, MessageType)));
connect(pCore->bin(), &Bin::clipNameChanged, trackView->projectView(), &CustomTrackView::clipNameChanged);
connect(trackView->projectView(), SIGNAL(showClipFrame(QString, int)), pCore->bin(), SLOT(selectClipById(QString, int)));
connect(trackView->projectView(), SIGNAL(playMonitor()), m_projectMonitor, SLOT(slotPlay()));
connect(trackView->projectView(), &CustomTrackView::pauseMonitor, m_projectMonitor, &Monitor::pause, Qt::DirectConnection);
connect(m_projectMonitor, &Monitor::addEffect, trackView->projectView(), &CustomTrackView::slotAddEffectToCurrentItem);
connect(trackView->projectView(), SIGNAL(transitionItemSelected(Transition *, int, QPoint, bool)), m_projectMonitor, SLOT(slotSetSelectedClip(Transition
*)));
connect(pCore->bin(), SIGNAL(gotFilterJobResults(QString, int, int, stringMap, stringMap)), trackView->projectView(), SLOT(slotGotFilterJobResults(QString,
int, int, stringMap, stringMap)));
//TODO
//connect(m_projectList, SIGNAL(addMarkers(QString,QList)), trackView->projectView(), SLOT(slotAddClipMarker(QString,QList)));
// Effect stack signals
connect(m_effectStack, &EffectStackView2::updateEffect, trackView->projectView(), &CustomTrackView::slotUpdateClipEffect);
connect(m_effectStack, &EffectStackView2::updateClipRegion, trackView->projectView(), &CustomTrackView::slotUpdateClipRegion);
connect(m_effectStack, SIGNAL(removeEffect(ClipItem *, int, QDomElement)), trackView->projectView(), SLOT(slotDeleteEffect(ClipItem *, int, QDomElement)));
connect(m_effectStack, SIGNAL(removeEffectGroup(ClipItem *, int, QDomDocument)), trackView->projectView(), SLOT(slotDeleteEffectGroup(ClipItem *, int,
QDomDocument)));
connect(m_effectStack, SIGNAL(addEffect(ClipItem *, QDomElement, int)), trackView->projectView(), SLOT(slotAddEffect(ClipItem *, QDomElement, int)));
connect(m_effectStack, SIGNAL(changeEffectState(ClipItem *, int, QList, bool)), trackView->projectView(), SLOT(slotChangeEffectState(ClipItem *, int,
QList, bool)));
connect(m_effectStack, SIGNAL(changeEffectPosition(ClipItem *, int, QList, int)), trackView->projectView(), SLOT(slotChangeEffectPosition(ClipItem *,
int, QList, int)));
connect(m_effectStack, &EffectStackView2::refreshEffectStack, trackView->projectView(), &CustomTrackView::slotRefreshEffects);
connect(m_effectStack, &EffectStackView2::seekTimeline, trackView->projectView(), &CustomTrackView::seekCursorPos);
connect(m_effectStack, SIGNAL(importClipKeyframes(GraphicsRectItem, ItemInfo, QDomElement, QMap)), trackView->projectView(),
SLOT(slotImportClipKeyframes(GraphicsRectItem, ItemInfo, QDomElement, QMap)));
// Transition config signals
connect(m_effectStack->transitionConfig(), SIGNAL(transitionUpdated(Transition *, QDomElement)), trackView->projectView(),
SLOT(slotTransitionUpdated(Transition *, QDomElement)));
connect(m_effectStack->transitionConfig(), &TransitionSettings::seekTimeline, trackView->projectView(), &CustomTrackView::seekCursorPos);
connect(trackView->projectView(), SIGNAL(activateDocumentMonitor()), m_projectMonitor, SLOT(slotActivateMonitor()), Qt::DirectConnection);
connect(project, &KdenliveDoc::updateFps, this,
[this](double changed) {
if (changed == 0.0) {
slotUpdateProfile(false);
} else {
slotUpdateProfile(true);
}
}, Qt::DirectConnection);
connect(trackView, &Timeline::zoneMoved, this, &MainWindow::slotZoneMoved);
trackView->projectView()->setContextMenu(m_timelineContextMenu, m_timelineClipActions, m_timelineContextTransitionMenu, m_clipTypeGroup,
static_cast(factory()->container(QStringLiteral("marker_menu"), this)));
*/
getMainTimeline()->controller()->clipActions = kdenliveCategoryMap.value(QStringLiteral("timelineselection"))->actions();
connect(m_projectMonitor, SIGNAL(zoneUpdated(QPoint)), project, SLOT(setModified()));
connect(m_clipMonitor, SIGNAL(zoneUpdated(QPoint)), project, SLOT(setModified()));
connect(project, &KdenliveDoc::docModified, this, &MainWindow::slotUpdateDocumentState);
if (m_renderWidget) {
slotCheckRenderStatus();
m_renderWidget->setGuides(pCore->currentDoc()->getGuideModel());
m_renderWidget->updateDocumentPath();
m_renderWidget->setRenderProfile(project->getRenderProperties());
}
m_zoomSlider->setValue(project->zoom().x());
m_commandStack->setActiveStack(project->commandStack().get());
setWindowTitle(project->description());
setWindowModified(project->isModified());
m_saveAction->setEnabled(project->isModified());
m_normalEditTool->setChecked(true);
connect(m_projectMonitor, &Monitor::durationChanged, this, &MainWindow::slotUpdateProjectDuration);
connect(m_effectList2, &EffectListWidget::reloadFavorites, getMainTimeline(), &TimelineWidget::updateEffectFavorites);
connect(m_transitionList2, &TransitionListWidget::reloadFavorites, getMainTimeline(), &TimelineWidget::updateTransitionFavorites);
connect(pCore->bin(), &Bin::processDragEnd, getMainTimeline(), &TimelineWidget::endDrag);
// TODO REFAC: fix
// trackView->updateProfile(1.0);
// Init document zone
// m_projectMonitor->slotZoneMoved(trackView->inPoint(), trackView->outPoint());
// Update the mouse position display so it will display in DF/NDF format by default based on the project setting.
// slotUpdateMousePosition(0);
// Update guides info in render widget
// slotGuidesUpdated();
// set tool to select tool
setTrimMode(QString());
m_buttonSelectTool->setChecked(true);
connect(m_projectMonitorDock, &QDockWidget::visibilityChanged, m_projectMonitor, &Monitor::slotRefreshMonitor, Qt::UniqueConnection);
connect(m_clipMonitorDock, &QDockWidget::visibilityChanged, m_clipMonitor, &Monitor::slotRefreshMonitor, Qt::UniqueConnection);
getMainTimeline()->focusTimeline();
}
void MainWindow::slotGuidesUpdated()
{
if (m_renderWidget) {
m_renderWidget->setGuides(pCore->currentDoc()->getGuideModel());
}
}
void MainWindow::slotEditKeys()
{
KShortcutsDialog dialog(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this);
// Find the combobox inside KShortcutsDialog for choosing keyboard scheme
QComboBox *schemesList = nullptr;
foreach (QLabel *label, dialog.findChildren()) {
if (label->text() == i18n("Current scheme:")) {
schemesList = qobject_cast(label->buddy());
break;
}
}
// If scheme choosing combobox was found, find the "More Actions" button in the same
// dialog that provides a dropdown menu with additional actions, and add
// "Download New Keyboard Schemes..." button into that menu
if (schemesList) {
foreach (QPushButton *button, dialog.findChildren()) {
if (button->text() == i18n("More Actions")) {
QMenu *moreActionsMenu = button->menu();
moreActionsMenu->addAction(i18n("Download New Keyboard Schemes..."), this, [this, schemesList] { slotGetNewKeyboardStuff(schemesList); });
break;
}
}
} else {
qWarning() << "Could not get list of schemes. Downloading new schemes is not available.";
}
dialog.addCollection(actionCollection(), i18nc("general keyboard shortcuts", "General"));
dialog.configure();
}
void MainWindow::slotPreferences(int page, int option)
{
/*
* An instance of your dialog could be already created and could be
* cached, in which case you want to display the cached dialog
* instead of creating another one
*/
if (KConfigDialog::showDialog(QStringLiteral("settings"))) {
KdenliveSettingsDialog *d = static_cast(KConfigDialog::exists(QStringLiteral("settings")));
if (page != -1) {
d->showPage(page, option);
}
return;
}
// KConfigDialog didn't find an instance of this dialog, so lets
// create it :
// Get the mappable actions in localized form
QMap actions;
KActionCollection *collection = actionCollection();
QRegExp ampEx("&{1,1}");
for (const QString &action_name : m_actionNames) {
QString action_text = collection->action(action_name)->text();
action_text.remove(ampEx);
actions[action_text] = action_name;
}
auto *dialog = new KdenliveSettingsDialog(actions, m_gpuAllowed, this);
connect(dialog, &KConfigDialog::settingsChanged, this, &MainWindow::updateConfiguration);
connect(dialog, &KConfigDialog::settingsChanged, this, &MainWindow::configurationChanged);
connect(dialog, &KdenliveSettingsDialog::doResetConsumer, [this] (bool fullReset) {
m_scaleGroup->setEnabled(!KdenliveSettings::external_display());
pCore->projectManager()->slotResetConsumers(fullReset);
});
connect(dialog, &KdenliveSettingsDialog::checkTabPosition, this, &MainWindow::slotCheckTabPosition);
connect(dialog, &KdenliveSettingsDialog::restartKdenlive, this, &MainWindow::slotRestart);
connect(dialog, &KdenliveSettingsDialog::updateLibraryFolder, pCore.get(), &Core::updateLibraryPath);
connect(dialog, &KdenliveSettingsDialog::audioThumbFormatChanged, m_timelineTabs, &TimelineTabs::audioThumbFormatChanged);
connect(dialog, &KdenliveSettingsDialog::resetView, this, &MainWindow::resetTimelineTracks);
connect(dialog, &KdenliveSettingsDialog::updateMonitorBg, [&]() {
pCore->monitorManager()->updateBgColor();
});
dialog->show();
if (page != -1) {
dialog->showPage(page, option);
}
}
void MainWindow::slotCheckTabPosition()
{
int pos = tabPosition(Qt::LeftDockWidgetArea);
if (KdenliveSettings::tabposition() != pos) {
setTabPosition(Qt::AllDockWidgetAreas, (QTabWidget::TabPosition)KdenliveSettings::tabposition());
}
}
void MainWindow::slotRestart(bool clean)
{
if (clean) {
if (KMessageBox::questionYesNo(this, i18n("This will delete Kdenlive's configuration file and restart the application. Do you want to proceed?")) != KMessageBox::Yes) {
return;
}
}
m_exitCode = clean ? EXIT_CLEAN_RESTART : EXIT_RESTART;
QApplication::closeAllWindows();
}
void MainWindow::closeEvent(QCloseEvent *event)
{
KXmlGuiWindow::closeEvent(event);
if (event->isAccepted()) {
QApplication::exit(m_exitCode);
return;
}
}
void MainWindow::updateConfiguration()
{
// TODO: we should apply settings to all projects, not only the current one
m_buttonAudioThumbs->setChecked(KdenliveSettings::audiothumbnails());
m_buttonVideoThumbs->setChecked(KdenliveSettings::videothumbnails());
m_buttonShowMarkers->setChecked(KdenliveSettings::showmarkers());
slotSwitchAutomaticTransition();
// Update list of transcoding profiles
buildDynamicActions();
loadClipActions();
}
void MainWindow::slotSwitchVideoThumbs()
{
KdenliveSettings::setVideothumbnails(!KdenliveSettings::videothumbnails());
m_timelineTabs->showThumbnailsChanged();
m_buttonVideoThumbs->setChecked(KdenliveSettings::videothumbnails());
}
void MainWindow::slotSwitchAudioThumbs()
{
KdenliveSettings::setAudiothumbnails(!KdenliveSettings::audiothumbnails());
pCore->bin()->checkAudioThumbs();
m_timelineTabs->showAudioThumbnailsChanged();
m_buttonAudioThumbs->setChecked(KdenliveSettings::audiothumbnails());
}
void MainWindow::slotSwitchMarkersComments()
{
KdenliveSettings::setShowmarkers(!KdenliveSettings::showmarkers());
getMainTimeline()->controller()->showMarkersChanged();
m_buttonShowMarkers->setChecked(KdenliveSettings::showmarkers());
}
void MainWindow::slotSwitchSnap()
{
KdenliveSettings::setSnaptopoints(!KdenliveSettings::snaptopoints());
m_buttonSnap->setChecked(KdenliveSettings::snaptopoints());
getMainTimeline()->controller()->snapChanged();
}
void MainWindow::slotSwitchAutomaticTransition()
{
KdenliveSettings::setAutomatictransitions(!KdenliveSettings::automatictransitions());
m_buttonAutomaticTransition->setChecked(KdenliveSettings::automatictransitions());
}
void MainWindow::slotDeleteItem()
{
if ((QApplication::focusWidget() != nullptr) && (QApplication::focusWidget()->parentWidget() != nullptr) &&
QApplication::focusWidget()->parentWidget() == pCore->bin()) {
pCore->bin()->slotDeleteClip();
} else {
QWidget *widget = QApplication::focusWidget();
while ((widget != nullptr) && widget != this) {
if (widget == m_effectStackDock) {
m_assetPanel->deleteCurrentEffect();
return;
}
if (widget == pCore->bin()->clipPropertiesDock()) {
pCore->bin()->deleteMarkers();
return;
}
widget = widget->parentWidget();
}
// effect stack has no focus
getMainTimeline()->controller()->deleteSelectedClips();
}
}
void MainWindow::slotAddClipMarker()
{
std::shared_ptr clip(nullptr);
GenTime pos;
if (m_projectMonitor->isActive()) {
getMainTimeline()->controller()->addMarker();
return;
} else {
clip = m_clipMonitor->currentController();
pos = GenTime(m_clipMonitor->position(), pCore->getCurrentFps());
}
if (!clip) {
m_messageLabel->setMessage(i18n("Cannot find clip to add marker"), ErrorMessage);
return;
}
QString id = clip->AbstractProjectItem::clipId();
clip->getMarkerModel()->editMarkerGui(pos, this, true, clip.get());
}
void MainWindow::slotDeleteClipMarker(bool allowGuideDeletion)
{
std::shared_ptr clip(nullptr);
GenTime pos;
if (m_projectMonitor->isActive()) {
getMainTimeline()->controller()->deleteMarker();
return;
} else {
clip = m_clipMonitor->currentController();
pos = GenTime(m_clipMonitor->position(), pCore->getCurrentFps());
}
if (!clip) {
m_messageLabel->setMessage(i18n("Cannot find clip to remove marker"), ErrorMessage);
return;
}
QString id = clip->AbstractProjectItem::clipId();
bool markerFound = false;
CommentedTime marker = clip->getMarkerModel()->getMarker(pos, &markerFound);
if (!markerFound) {
if (allowGuideDeletion && m_projectMonitor->isActive()) {
slotDeleteGuide();
} else {
m_messageLabel->setMessage(i18n("No marker found at cursor time"), ErrorMessage);
}
return;
}
clip->getMarkerModel()->removeMarker(pos);
}
void MainWindow::slotDeleteAllClipMarkers()
{
std::shared_ptr clip(nullptr);
if (m_projectMonitor->isActive()) {
getMainTimeline()->controller()->deleteAllMarkers();
return;
} else {
clip = m_clipMonitor->currentController();
}
if (!clip) {
m_messageLabel->setMessage(i18n("Cannot find clip to remove marker"), ErrorMessage);
return;
}
bool ok = clip->getMarkerModel()->removeAllMarkers();
if (!ok) {
m_messageLabel->setMessage(i18n("An error occurred while deleting markers"), ErrorMessage);
return;
}
}
void MainWindow::slotEditClipMarker()
{
std::shared_ptr clip(nullptr);
GenTime pos;
if (m_projectMonitor->isActive()) {
getMainTimeline()->controller()->editMarker();
return;
} else {
clip = m_clipMonitor->currentController();
pos = GenTime(m_clipMonitor->position(), pCore->getCurrentFps());
}
if (!clip) {
m_messageLabel->setMessage(i18n("Cannot find clip to edit marker"), ErrorMessage);
return;
}
QString id = clip->AbstractProjectItem::clipId();
bool markerFound = false;
CommentedTime oldMarker = clip->getMarkerModel()->getMarker(pos, &markerFound);
if (!markerFound) {
m_messageLabel->setMessage(i18n("No marker found at cursor time"), ErrorMessage);
return;
}
clip->getMarkerModel()->editMarkerGui(pos, this, false, clip.get());
}
void MainWindow::slotAddMarkerGuideQuickly()
{
if (!getMainTimeline() || !pCore->currentDoc()) {
return;
}
if (m_clipMonitor->isActive()) {
pCore->bin()->addClipMarker(m_clipMonitor->activeClipId(), {m_clipMonitor->position()});
} else {
int selectedClip = getMainTimeline()->controller()->getMainSelectedItem();
if (selectedClip == -1) {
// Add timeline guide
getMainTimeline()->controller()->switchGuide();
} else {
// Add marker to main clip
getMainTimeline()->controller()->addQuickMarker(selectedClip);
}
}
}
void MainWindow::slotAddGuide()
{
getMainTimeline()->controller()->switchGuide();
}
void MainWindow::slotInsertSpace()
{
getMainTimeline()->controller()->insertSpace();
}
void MainWindow::slotRemoveSpace()
{
getMainTimeline()->controller()->removeSpace(-1, -1, false);
}
void MainWindow::slotRemoveAllSpace()
{
getMainTimeline()->controller()->removeSpace(-1, -1, true);
}
void MainWindow::slotSeparateAudioChannel()
{
KdenliveSettings::setDisplayallchannels(!KdenliveSettings::displayallchannels());
getCurrentTimeline()->controller()->audioThumbFormatChanged();
}
void MainWindow::slotInsertTrack()
{
pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor);
getCurrentTimeline()->controller()->addTrack(-1);
}
void MainWindow::slotDeleteTrack()
{
pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor);
getCurrentTimeline()->controller()->deleteTrack(-1);
}
void MainWindow::slotSwitchTrackAudioStream()
{
getCurrentTimeline()->showTargetMenu();
}
void MainWindow::slotShowTrackRec()
{
getCurrentTimeline()->controller()->switchTrackRecord();
}
void MainWindow::slotSelectTrack()
{
getCurrentTimeline()->controller()->selectCurrentTrack();
}
void MainWindow::slotSelectAllTracks()
{
if (QApplication::focusWidget() != nullptr) {
if (QApplication::focusWidget()->parentWidget() != nullptr && QApplication::focusWidget()->parentWidget() == pCore->bin()) {
pCore->bin()->selectAll();
return;
}
if (QApplication::focusWidget()->objectName() == QLatin1String("markers_list")) {
pCore->bin()->selectMarkers();
return;
}
}
getCurrentTimeline()->controller()->selectAll();
}
void MainWindow::slotUnselectAllTracks()
{
getCurrentTimeline()->model()->requestClearSelection();
}
void MainWindow::slotEditGuide()
{
getCurrentTimeline()->controller()->editGuide();
}
void MainWindow::slotDeleteGuide()
{
getCurrentTimeline()->controller()->switchGuide(-1, true);
}
void MainWindow::slotDeleteAllGuides()
{
pCore->currentDoc()->getGuideModel()->removeAllMarkers();
}
void MainWindow::slotCutTimelineClip()
{
getMainTimeline()->controller()->cutClipUnderCursor();
}
void MainWindow::slotCutTimelineAllClips()
{
getMainTimeline()->controller()->cutAllClipsUnderCursor();
}
void MainWindow::slotInsertClipOverwrite()
{
const QString &binId = m_clipMonitor->activeClipId();
if (binId.isEmpty()) {
// No clip in monitor
return;
}
getMainTimeline()->controller()->insertZone(binId, m_clipMonitor->getZoneInfo(), true);
}
void MainWindow::slotInsertClipInsert()
{
const QString &binId = m_clipMonitor->activeClipId();
if (binId.isEmpty()) {
// No clip in monitor
pCore->displayMessage(i18n("No clip selected in project bin"), InformationMessage);
return;
}
getMainTimeline()->controller()->insertZone(binId, m_clipMonitor->getZoneInfo(), false);
}
void MainWindow::slotExtractZone()
{
getMainTimeline()->controller()->extractZone(m_clipMonitor->getZoneInfo());
}
void MainWindow::slotExtractClip()
{
getMainTimeline()->controller()->extract();
}
void MainWindow::slotSaveZoneToBin()
{
getMainTimeline()->controller()->saveZone();
}
void MainWindow::slotLiftZone()
{
getMainTimeline()->controller()->extractZone(m_clipMonitor->getZoneInfo(), true);
}
void MainWindow::slotPreviewRender()
{
if (pCore->currentDoc()) {
getCurrentTimeline()->controller()->startPreviewRender();
}
}
void MainWindow::slotStopPreviewRender()
{
if (pCore->currentDoc()) {
getCurrentTimeline()->controller()->stopPreviewRender();
}
}
void MainWindow::slotDefinePreviewRender()
{
if (pCore->currentDoc()) {
getCurrentTimeline()->controller()->addPreviewRange(true);
}
}
void MainWindow::slotRemovePreviewRender()
{
if (pCore->currentDoc()) {
getCurrentTimeline()->controller()->addPreviewRange(false);
}
}
void MainWindow::slotClearPreviewRender(bool resetZones)
{
if (pCore->currentDoc()) {
getCurrentTimeline()->controller()->clearPreviewRange(resetZones);
}
}
void MainWindow::slotSelectTimelineClip()
{
getCurrentTimeline()->controller()->selectCurrentItem(ObjectType::TimelineClip, true);
}
void MainWindow::slotSelectTimelineTransition()
{
getCurrentTimeline()->controller()->selectCurrentItem(ObjectType::TimelineComposition, true);
}
void MainWindow::slotDeselectTimelineClip()
{
getCurrentTimeline()->controller()->selectCurrentItem(ObjectType::TimelineClip, false);
}
void MainWindow::slotDeselectTimelineTransition()
{
getCurrentTimeline()->controller()->selectCurrentItem(ObjectType::TimelineComposition, false);
}
void MainWindow::slotSelectAddTimelineClip()
{
getCurrentTimeline()->controller()->selectCurrentItem(ObjectType::TimelineClip, true, true);
}
void MainWindow::slotSelectAddTimelineTransition()
{
getCurrentTimeline()->controller()->selectCurrentItem(ObjectType::TimelineComposition, true, true);
}
void MainWindow::slotGroupClips()
{
getCurrentTimeline()->controller()->groupSelection();
}
void MainWindow::slotUnGroupClips()
{
getCurrentTimeline()->controller()->unGroupSelection();
}
void MainWindow::slotEditItemDuration()
{
getCurrentTimeline()->controller()->editItemDuration();
}
void MainWindow::slotAddProjectClip(const QUrl &url, const QString &folderInfo)
{
pCore->bin()->droppedUrls(QList() << url, folderInfo);
}
void MainWindow::slotAddProjectClipList(const QList &urls)
{
pCore->bin()->droppedUrls(urls);
}
void MainWindow::slotAddTransition(QAction *result)
{
if (!result) {
return;
}
// TODO refac
/*
QStringList info = result->data().toStringList();
if (info.isEmpty() || info.count() < 2) {
return;
}
QDomElement transition = transitions.getEffectByTag(info.at(0), info.at(1));
if (pCore->projectManager()->currentTimeline() && !transition.isNull()) {
pCore->projectManager()->currentTimeline()->projectView()->slotAddTransitionToSelectedClips(transition.cloneNode().toElement());
}
*/
}
void MainWindow::slotAddEffect(QAction *result)
{
qDebug() << "// EFFECTS MENU TRIGGERED: " << result->data().toString();
if (!result) {
return;
}
QString effectId = result->data().toString();
addEffect(effectId);
}
void MainWindow::addEffect(const QString &effectId)
{
if (m_assetPanel->effectStackOwner().first == ObjectType::TimelineClip) {
// Add effect to the current timeline selection
QVariantMap effectData;
effectData.insert(QStringLiteral("kdenlive/effect"), effectId);
pCore->window()->getMainTimeline()->controller()->addAsset(effectData);
} else if (m_assetPanel->effectStackOwner().first == ObjectType::TimelineTrack || m_assetPanel->effectStackOwner().first == ObjectType::BinClip || m_assetPanel->effectStackOwner().first == ObjectType::Master) {
if (!m_assetPanel->addEffect(effectId)) {
pCore->displayMessage(i18n("Cannot add effect to clip"), InformationMessage);
}
} else {
pCore->displayMessage(i18n("Select an item to add effect"), InformationMessage);
}
}
void MainWindow::slotZoomIn(bool zoomOnMouse)
{
slotSetZoom(m_zoomSlider->value() - 1, zoomOnMouse);
slotShowZoomSliderToolTip();
}
void MainWindow::slotZoomOut(bool zoomOnMouse)
{
slotSetZoom(m_zoomSlider->value() + 1, zoomOnMouse);
slotShowZoomSliderToolTip();
}
void MainWindow::slotFitZoom()
{
m_timelineTabs->fitZoom();
}
void MainWindow::slotSetZoom(int value, bool zoomOnMouse)
{
value = qBound(m_zoomSlider->minimum(), value, m_zoomSlider->maximum());
m_timelineTabs->changeZoom(value, zoomOnMouse);
updateZoomSlider(value);
}
void MainWindow::updateZoomSlider(int value)
{
slotUpdateZoomSliderToolTip(value);
KdenliveDoc *project = pCore->currentDoc();
if (project) {
project->setZoom(value);
}
m_zoomOut->setEnabled(value < m_zoomSlider->maximum());
m_zoomIn->setEnabled(value > m_zoomSlider->minimum());
QSignalBlocker blocker(m_zoomSlider);
m_zoomSlider->setValue(value);
}
void MainWindow::slotShowZoomSliderToolTip(int zoomlevel)
{
if (zoomlevel != -1) {
slotUpdateZoomSliderToolTip(zoomlevel);
}
QPoint global = m_zoomSlider->rect().topLeft();
global.ry() += m_zoomSlider->height() / 2;
QHelpEvent toolTipEvent(QEvent::ToolTip, QPoint(0, 0), m_zoomSlider->mapToGlobal(global));
QApplication::sendEvent(m_zoomSlider, &toolTipEvent);
}
void MainWindow::slotUpdateZoomSliderToolTip(int zoomlevel)
{
int max = m_zoomSlider->maximum() + 1;
m_zoomSlider->setToolTip(i18n("Zoom Level: %1/%2", max - zoomlevel, max));
}
void MainWindow::customEvent(QEvent *e)
{
if (e->type() == QEvent::User) {
m_messageLabel->setMessage(static_cast(e)->message(), MltError);
}
}
void MainWindow::slotSnapRewind()
{
if (m_projectMonitor->isActive()) {
getMainTimeline()->controller()->gotoPreviousSnap();
} else {
m_clipMonitor->slotSeekToPreviousSnap();
}
}
void MainWindow::slotSnapForward()
{
if (m_projectMonitor->isActive()) {
getMainTimeline()->controller()->gotoNextSnap();
} else {
m_clipMonitor->slotSeekToNextSnap();
}
}
void MainWindow::slotGuideRewind()
{
if (m_projectMonitor->isActive()) {
getMainTimeline()->controller()->gotoPreviousGuide();
} else {
m_clipMonitor->slotSeekToPreviousSnap();
}
}
void MainWindow::slotGuideForward()
{
if (m_projectMonitor->isActive()) {
getMainTimeline()->controller()->gotoNextGuide();
} else {
m_clipMonitor->slotSeekToNextSnap();
}
}
void MainWindow::slotClipStart()
{
if (m_projectMonitor->isActive()) {
getMainTimeline()->controller()->seekCurrentClip(false);
} else {
m_clipMonitor->slotStart();
}
}
void MainWindow::slotClipEnd()
{
if (m_projectMonitor->isActive()) {
getMainTimeline()->controller()->seekCurrentClip(true);
} else {
m_clipMonitor->slotEnd();
}
}
void MainWindow::slotChangeTool(QAction *action)
{
if (action == m_buttonSelectTool) {
slotSetTool(SelectTool);
} else if (action == m_buttonRazorTool) {
slotSetTool(RazorTool);
} else if (action == m_buttonSpacerTool) {
slotSetTool(SpacerTool);
}
}
void MainWindow::slotChangeEdit(QAction *action)
{
TimelineMode::EditMode mode = TimelineMode::NormalEdit;
if (action == m_overwriteEditTool) {
mode = TimelineMode::OverwriteEdit;
m_trimLabel->setText(i18n("Overwrite"));
m_trimLabel->setStyleSheet(QStringLiteral("QLabel { padding-left: 2; padding-right: 2; background-color :darkGreen; }"));
} else if (action == m_insertEditTool) {
mode = TimelineMode::InsertEdit;
m_trimLabel->setText(i18n("Insert"));
m_trimLabel->setStyleSheet(QStringLiteral("QLabel { padding-left: 2; padding-right: 2; background-color :red; }"));
} else {
m_trimLabel->setText(QString());
m_trimLabel->setStyleSheet(QString());
}
getMainTimeline()->controller()->getModel()->setEditMode(mode);
}
void MainWindow::slotSetTool(ProjectTool tool)
{
if (pCore->currentDoc()) {
// pCore->currentDoc()->setTool(tool);
QString message;
switch (tool) {
case SpacerTool:
message = i18n("Ctrl + click to use spacer on current track only");
break;
case RazorTool:
message = i18n("Click on a clip to cut it, Shift + move to preview cut frame");
break;
default:
message = i18n("Shift + click to create a selection rectangle, Ctrl + click to add an item to selection");
break;
}
m_messageLabel->setMessage(message, InformationMessage);
getMainTimeline()->setTool(tool);
}
}
void MainWindow::slotCopy()
{
getMainTimeline()->controller()->copyItem();
}
void MainWindow::slotPaste()
{
getMainTimeline()->controller()->pasteItem();
}
void MainWindow::slotPasteEffects()
{
getMainTimeline()->controller()->pasteEffects();
}
void MainWindow::slotClipInTimeline(const QString &clipId, const QList &ids)
{
Q_UNUSED(clipId)
QMenu *inTimelineMenu = static_cast(factory()->container(QStringLiteral("clip_in_timeline"), this));
QList actionList;
for (int i = 0; i < ids.count(); ++i) {
QString track = getMainTimeline()->controller()->getTrackNameFromIndex(pCore->getItemTrack(ObjectId(ObjectType::TimelineClip, ids.at(i))));
QString start = pCore->currentDoc()->timecode().getTimecodeFromFrames(pCore->getItemPosition(ObjectId(ObjectType::TimelineClip, ids.at(i))));
int j = 0;
QAction *a = new QAction(track + QStringLiteral(": ") + start, inTimelineMenu);
a->setData(ids.at(i));
connect(a, &QAction::triggered, this, &MainWindow::slotSelectClipInTimeline);
while (j < actionList.count()) {
if (actionList.at(j)->text() > a->text()) {
break;
}
j++;
}
actionList.insert(j, a);
}
QList list = inTimelineMenu->actions();
unplugActionList(QStringLiteral("timeline_occurences"));
qDeleteAll(list);
plugActionList(QStringLiteral("timeline_occurences"), actionList);
if (actionList.isEmpty()) {
inTimelineMenu->setEnabled(false);
} else {
inTimelineMenu->setEnabled(true);
}
}
void MainWindow::slotClipInProjectTree()
{
QList ids = getMainTimeline()->controller()->selection();
if (!ids.isEmpty()) {
m_projectBinDock->raise();
ObjectId id(ObjectType::TimelineClip, ids.constFirst());
int start = pCore->getItemIn(id);
int duration = pCore->getItemDuration(id);
QPoint zone(start, start + duration);
qDebug() << " - - selecting clip on monitor, zone: " << zone;
int pos = m_projectMonitor->position();
int itemPos = pCore->getItemPosition(id);
if (pos >= itemPos && pos < itemPos + duration) {
pos -= (itemPos - start);
} else {
pos = -1;
}
pCore->selectBinClip(getMainTimeline()->controller()->getClipBinId(ids.constFirst()), pos, zone);
}
}
void MainWindow::slotSelectClipInTimeline()
{
pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor);
auto *action = qobject_cast(sender());
int clipId = action->data().toInt();
getMainTimeline()->controller()->focusItem(clipId);
}
/** Gets called when the window gets hidden */
void MainWindow::hideEvent(QHideEvent * /*event*/)
{
if (isMinimized() && pCore->monitorManager()) {
pCore->monitorManager()->pauseActiveMonitor();
}
}
/*void MainWindow::slotSaveZone(Render *render, const QPoint &zone, DocClipBase *baseClip, QUrl path)
{
QPointer dialog = new QDialog(this);
dialog->setWindowTitle("Save clip zone");
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
QVBoxLayout *mainLayout = new QVBoxLayout;
dialog->setLayout(mainLayout);
QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
okButton->setDefault(true);
okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
dialog->connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept()));
dialog->connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject()));
QLabel *label1 = new QLabel(i18n("Save clip zone as:"), this);
if (path.isEmpty()) {
QString tmppath = pCore->currentDoc()->projectFolder().path() + QDir::separator();
if (baseClip == nullptr) {
tmppath.append("untitled.mlt");
} else {
tmppath.append((baseClip->name().isEmpty() ? baseClip->fileURL().fileName() : baseClip->name()) + '-' + QString::number(zone.x()).rightJustified(4,
'0') + QStringLiteral(".mlt"));
}
path = QUrl(tmppath);
}
KUrlRequester *url = new KUrlRequester(path, this);
url->setFilter("video/mlt-playlist");
QLabel *label2 = new QLabel(i18n("Description:"), this);
QLineEdit *edit = new QLineEdit(this);
mainLayout->addWidget(label1);
mainLayout->addWidget(url);
mainLayout->addWidget(label2);
mainLayout->addWidget(edit);
mainLayout->addWidget(buttonBox);
if (dialog->exec() == QDialog::Accepted) {
if (QFile::exists(url->url().path())) {
if (KMessageBox::questionYesNo(this, i18n("File %1 already exists.\nDo you want to overwrite it?", url->url().path())) == KMessageBox::No) {
slotSaveZone(render, zone, baseClip, url->url());
delete dialog;
return;
}
}
if (baseClip && !baseClip->fileURL().isEmpty()) {
// create zone from clip url, so that we don't have problems with proxy clips
QProcess p;
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
env.remove("MLT_PROFILE");
p.setProcessEnvironment(env);
p.start(KdenliveSettings::rendererpath(), QStringList() << baseClip->fileURL().toLocalFile() << "in=" + QString::number(zone.x()) << "out=" +
QString::number(zone.y()) << "-consumer" << "xml:" + url->url().path());
if (!p.waitForStarted(3000)) {
KMessageBox::sorry(this, i18n("Cannot start MLT's renderer:\n%1", KdenliveSettings::rendererpath()));
}
else if (!p.waitForFinished(5000)) {
KMessageBox::sorry(this, i18n("Timeout while creating xml output"));
}
}
else render->saveZone(url->url(), edit->text(), zone);
}
delete dialog;
}*/
void MainWindow::slotResizeItemStart()
{
getMainTimeline()->controller()->setInPoint();
}
void MainWindow::slotResizeItemEnd()
{
getMainTimeline()->controller()->setOutPoint();
}
int MainWindow::getNewStuff(const QString &configFile)
{
KNS3::Entry::List entries;
QPointer dialog = new KNS3::DownloadDialog(configFile);
if (dialog->exec() != 0) {
entries = dialog->changedEntries();
}
for (const KNS3::Entry &entry : entries) {
if (entry.status() == KNS3::Entry::Installed) {
qCDebug(KDENLIVE_LOG) << "// Installed files: " << entry.installedFiles();
}
}
delete dialog;
return entries.size();
}
void MainWindow::slotGetNewKeyboardStuff(QComboBox *schemesList)
{
if (getNewStuff(QStringLiteral(":data/kdenlive_keyboardschemes.knsrc")) > 0) {
// Refresh keyboard schemes list (schemes list creation code copied from KShortcutSchemesEditor)
QStringList schemes;
schemes << QStringLiteral("Default");
// List files in the shortcuts subdir, each one is a scheme. See KShortcutSchemesHelper::{shortcutSchemeFileName,exportActionCollection}
const QStringList shortcutsDirs = QStandardPaths::locateAll(
QStandardPaths::GenericDataLocation, QCoreApplication::applicationName() + QStringLiteral("/shortcuts"), QStandardPaths::LocateDirectory);
qCDebug(KDENLIVE_LOG) << "shortcut scheme dirs:" << shortcutsDirs;
Q_FOREACH (const QString &dir, shortcutsDirs) {
Q_FOREACH (const QString &file, QDir(dir).entryList(QDir::Files | QDir::NoDotAndDotDot)) {
qCDebug(KDENLIVE_LOG) << "shortcut scheme file:" << file;
schemes << file;
}
}
schemesList->clear();
schemesList->addItems(schemes);
}
}
void MainWindow::slotAutoTransition()
{
// TODO refac
/*
if (pCore->projectManager()->currentTimeline()) {
pCore->projectManager()->currentTimeline()->projectView()->autoTransition();
}
*/
}
void MainWindow::slotSplitAV()
{
getMainTimeline()->controller()->splitAV();
}
void MainWindow::slotSwitchClip()
{
getMainTimeline()->controller()->switchEnableState();
}
void MainWindow::slotSetAudioAlignReference()
{
getMainTimeline()->controller()->setAudioRef();
}
void MainWindow::slotAlignAudio()
{
getMainTimeline()->controller()->alignAudio();
}
void MainWindow::slotUpdateClipType(QAction *action)
{
Q_UNUSED(action)
// TODO refac
/*
if (pCore->projectManager()->currentTimeline()) {
PlaylistState::ClipState state = (PlaylistState::ClipState)action->data().toInt();
pCore->projectManager()->currentTimeline()->projectView()->setClipType(state);
}
*/
}
void MainWindow::slotUpdateTimelineView(QAction *action)
{
int viewMode = action->data().toInt();
KdenliveSettings::setAudiotracksbelow(viewMode);
getMainTimeline()->controller()->getModel()->_resetView();
}
void MainWindow::slotDvdWizard(const QString &url)
{
// We must stop the monitors since we create a new on in the dvd wizard
QPointer w = new DvdWizard(pCore->monitorManager(), url, this);
w->exec();
delete w;
pCore->monitorManager()->activateMonitor(Kdenlive::ClipMonitor);
}
void MainWindow::slotShowTimeline(bool show)
{
if (!show) {
m_timelineState = saveState();
centralWidget()->setHidden(true);
} else {
centralWidget()->setHidden(false);
restoreState(m_timelineState);
}
}
void MainWindow::loadClipActions()
{
unplugActionList(QStringLiteral("add_effect"));
plugActionList(QStringLiteral("add_effect"), m_effectsMenu->actions());
QList clipJobActions = getExtraActions(QStringLiteral("clipjobs"));
unplugActionList(QStringLiteral("clip_jobs"));
plugActionList(QStringLiteral("clip_jobs"), clipJobActions);
QList atcActions = getExtraActions(QStringLiteral("audiotranscoderslist"));
unplugActionList(QStringLiteral("audio_transcoders_list"));
plugActionList(QStringLiteral("audio_transcoders_list"), atcActions);
QList tcActions = getExtraActions(QStringLiteral("transcoderslist"));
unplugActionList(QStringLiteral("transcoders_list"));
plugActionList(QStringLiteral("transcoders_list"), tcActions);
}
void MainWindow::loadDockActions()
{
QList list = kdenliveCategoryMap.value(QStringLiteral("interface"))->actions();
// Sort actions
QMap sorted;
QStringList sortedList;
for (QAction *a : list) {
sorted.insert(a->text(), a);
sortedList << a->text();
}
QList orderedList;
sortedList.sort(Qt::CaseInsensitive);
for (const QString &text : sortedList) {
orderedList << sorted.value(text);
}
unplugActionList(QStringLiteral("dock_actions"));
plugActionList(QStringLiteral("dock_actions"), orderedList);
}
void MainWindow::buildDynamicActions()
{
KActionCategory *ts = nullptr;
if (kdenliveCategoryMap.contains(QStringLiteral("clipjobs"))) {
ts = kdenliveCategoryMap.take(QStringLiteral("clipjobs"));
delete ts;
}
ts = new KActionCategory(i18n("Clip Jobs"), m_extraFactory->actionCollection());
Mlt::Profile profile;
std::unique_ptr filter;
for (const QString &stab : {QStringLiteral("vidstab")}) {
filter = std::make_unique(profile, stab.toUtf8().constData());
if ((filter != nullptr) && filter->is_valid()) {
QAction *action = new QAction(i18n("Stabilize (%1)", stab), m_extraFactory->actionCollection());
ts->addAction(action->text(), action);
connect(action, &QAction::triggered, [stab]() {
pCore->jobManager()->startJob(pCore->bin()->selectedClipsIds(true), {},
i18np("Stabilize clip", "Stabilize clips", pCore->bin()->selectedClipsIds().size()), stab);
});
break;
}
}
filter = std::make_unique(profile, "motion_est");
if (filter) {
if (filter->is_valid()) {
QAction *action = new QAction(i18n("Automatic scene split"), m_extraFactory->actionCollection());
ts->addAction(action->text(), action);
connect(action, &QAction::triggered,
[&]() { pCore->jobManager()->startJob(pCore->bin()->selectedClipsIds(true), {}, i18n("Scene detection")); });
}
}
if (true /* TODO: check if timewarp producer is available */) {
QAction *action = new QAction(i18n("Duplicate clip with speed change"), m_extraFactory->actionCollection());
ts->addAction(action->text(), action);
connect(action, &QAction::triggered,
[&]() { pCore->jobManager()->startJob(pCore->bin()->selectedClipsIds(true), {}, i18n("Change clip speed")); });
}
// TODO refac reimplement analyseclipjob
/*
QAction *action = new QAction(i18n("Analyse keyframes"), m_extraFactory->actionCollection());
QStringList stabJob(QString::number((int)AbstractClipJob::ANALYSECLIPJOB));
action->setData(stabJob);
ts->addAction(action->text(), action);
connect(action, &QAction::triggered, pCore->bin(), &Bin::slotStartClipJob);
*/
kdenliveCategoryMap.insert(QStringLiteral("clipjobs"), ts);
if (kdenliveCategoryMap.contains(QStringLiteral("transcoderslist"))) {
ts = kdenliveCategoryMap.take(QStringLiteral("transcoderslist"));
delete ts;
}
if (kdenliveCategoryMap.contains(QStringLiteral("audiotranscoderslist"))) {
ts = kdenliveCategoryMap.take(QStringLiteral("audiotranscoderslist"));
delete ts;
}
// transcoders
ts = new KActionCategory(i18n("Transcoders"), m_extraFactory->actionCollection());
KActionCategory *ats = new KActionCategory(i18n("Extract Audio"), m_extraFactory->actionCollection());
KSharedConfigPtr config =
KSharedConfig::openConfig(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("kdenlivetranscodingrc")), KConfig::CascadeConfig);
KConfigGroup transConfig(config, "Transcoding");
// read the entries
QMap profiles = transConfig.entryMap();
QMapIterator i(profiles);
while (i.hasNext()) {
i.next();
QStringList transList;
transList << i.value().split(QLatin1Char(';'));
auto *a = new QAction(i.key(), m_extraFactory->actionCollection());
a->setData(transList);
if (transList.count() > 1) {
a->setToolTip(transList.at(1));
}
connect(a, &QAction::triggered, [&, a]() {
QStringList transcodeData = a->data().toStringList();
pCore->jobManager()->startJob(pCore->bin()->selectedClipsIds(true), -1, QString(), transcodeData.first());
});
if (transList.count() > 2 && transList.at(2) == QLatin1String("audio")) {
// This is an audio transcoding action
ats->addAction(i.key(), a);
} else {
ts->addAction(i.key(), a);
}
}
kdenliveCategoryMap.insert(QStringLiteral("transcoderslist"), ts);
kdenliveCategoryMap.insert(QStringLiteral("audiotranscoderslist"), ats);
// Populate View menu with show / hide actions for dock widgets
KActionCategory *guiActions = nullptr;
if (kdenliveCategoryMap.contains(QStringLiteral("interface"))) {
guiActions = kdenliveCategoryMap.take(QStringLiteral("interface"));
delete guiActions;
}
guiActions = new KActionCategory(i18n("Interface"), actionCollection());
QAction *showTimeline = new QAction(i18n("Timeline"), this);
showTimeline->setCheckable(true);
showTimeline->setChecked(true);
connect(showTimeline, &QAction::triggered, this, &MainWindow::slotShowTimeline);
guiActions->addAction(showTimeline->text(), showTimeline);
actionCollection()->addAction(showTimeline->text(), showTimeline);
QList docks = findChildren();
for (auto dock : docks) {
QAction *dockInformations = dock->toggleViewAction();
if (!dockInformations) {
continue;
}
dockInformations->setChecked(!dock->isHidden());
guiActions->addAction(dockInformations->text(), dockInformations);
}
kdenliveCategoryMap.insert(QStringLiteral("interface"), guiActions);
}
QList MainWindow::getExtraActions(const QString &name)
{
if (!kdenliveCategoryMap.contains(name)) {
return QList();
}
return kdenliveCategoryMap.value(name)->actions();
}
void MainWindow::slotTranscode(const QStringList &urls)
{
Q_ASSERT(!urls.isEmpty());
QString params;
QString desc;
ClipTranscode *d = new ClipTranscode(urls, params, QStringList(), desc, pCore->bin()->getCurrentFolder());
connect(d, &ClipTranscode::addClip, this, &MainWindow::slotAddProjectClip);
d->show();
}
void MainWindow::slotTranscodeClip()
{
const QString dialogFilter = ClipCreationDialog::getExtensionsFilter(QStringList() << i18n("All Files") + QStringLiteral(" (*)"));
QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveClipFolder"));
QStringList urls = QFileDialog::getOpenFileNames(this, i18n("Files to transcode"), clipFolder, dialogFilter);
if (urls.isEmpty()) {
return;
}
slotTranscode(urls);
}
void MainWindow::slotSetDocumentRenderProfile(const QMap &props)
{
KdenliveDoc *project = pCore->currentDoc();
bool modified = false;
QMapIterator i(props);
while (i.hasNext()) {
i.next();
if (project->getDocumentProperty(i.key()) == i.value()) {
continue;
}
project->setDocumentProperty(i.key(), i.value());
modified = true;
}
if (modified) {
project->setModified();
}
}
void MainWindow::slotUpdateTimecodeFormat(int ix)
{
KdenliveSettings::setFrametimecode(ix == 1);
m_clipMonitor->updateTimecodeFormat();
m_projectMonitor->updateTimecodeFormat();
// TODO refac: reimplement ?
// m_effectStack->transitionConfig()->updateTimecodeFormat();
// m_effectStack->updateTimecodeFormat();
pCore->bin()->updateTimecodeFormat();
getMainTimeline()->controller()->frameFormatChanged();
m_timeFormatButton->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
}
void MainWindow::slotRemoveFocus()
{
getMainTimeline()->setFocus();
}
void MainWindow::slotShutdown()
{
pCore->currentDoc()->setModified(false);
// Call shutdown
QDBusConnectionInterface *interface = QDBusConnection::sessionBus().interface();
if ((interface != nullptr) && interface->isServiceRegistered(QStringLiteral("org.kde.ksmserver"))) {
QDBusInterface smserver(QStringLiteral("org.kde.ksmserver"), QStringLiteral("/KSMServer"), QStringLiteral("org.kde.KSMServerInterface"));
smserver.call(QStringLiteral("logout"), 1, 2, 2);
} else if ((interface != nullptr) && interface->isServiceRegistered(QStringLiteral("org.gnome.SessionManager"))) {
QDBusInterface smserver(QStringLiteral("org.gnome.SessionManager"), QStringLiteral("/org/gnome/SessionManager"),
QStringLiteral("org.gnome.SessionManager"));
smserver.call(QStringLiteral("Shutdown"));
}
}
void MainWindow::slotSwitchMonitors()
{
pCore->monitorManager()->slotSwitchMonitors(!m_clipMonitor->isActive());
if (m_projectMonitor->isActive()) {
getMainTimeline()->setFocus();
} else {
pCore->bin()->focusBinView();
}
}
void MainWindow::slotSwitchMonitorOverlay(QAction *action)
{
if (pCore->monitorManager()->isActive(Kdenlive::ClipMonitor)) {
m_clipMonitor->switchMonitorInfo(action->data().toInt());
} else {
m_projectMonitor->switchMonitorInfo(action->data().toInt());
}
}
void MainWindow::slotSwitchDropFrames(bool drop)
{
m_clipMonitor->switchDropFrames(drop);
m_projectMonitor->switchDropFrames(drop);
}
void MainWindow::slotSetMonitorGamma(int gamma)
{
KdenliveSettings::setMonitor_gamma(gamma);
m_clipMonitor->updateMonitorGamma();
m_projectMonitor->updateMonitorGamma();
}
void MainWindow::slotInsertZoneToTree()
{
if (!m_clipMonitor->isActive() || m_clipMonitor->currentController() == nullptr) {
return;
}
QPoint info = m_clipMonitor->getZoneInfo();
QString id;
pCore->projectItemModel()->requestAddBinSubClip(id, info.x(), info.y(), {}, m_clipMonitor->activeClipId());
}
void MainWindow::slotMonitorRequestRenderFrame(bool request)
{
if (request) {
m_projectMonitor->sendFrameForAnalysis(true);
return;
}
for (int i = 0; i < m_gfxScopesList.count(); ++i) {
if (m_gfxScopesList.at(i)->isVisible() && tabifiedDockWidgets(m_gfxScopesList.at(i)).isEmpty() &&
static_cast(m_gfxScopesList.at(i)->widget())->autoRefreshEnabled()) {
request = true;
break;
}
}
#ifdef DEBUG_MAINW
qCDebug(KDENLIVE_LOG) << "Any scope accepting new frames? " << request;
#endif
if (!request) {
m_projectMonitor->sendFrameForAnalysis(false);
}
}
void MainWindow::slotUpdateProxySettings()
{
KdenliveDoc *project = pCore->currentDoc();
if (m_renderWidget) {
m_renderWidget->updateProxyConfig(project->useProxy());
}
pCore->bin()->refreshProxySettings();
}
void MainWindow::slotArchiveProject()
{
KdenliveDoc *doc = pCore->currentDoc();
pCore->projectManager()->prepareSave();
QString sceneData = pCore->projectManager()->projectSceneList(doc->url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile());
if (sceneData.isEmpty()) {
KMessageBox::error(this, i18n("Project file could not be saved for archiving."));
return;
}
QPointer d(new ArchiveWidget(doc->url().fileName(), sceneData, getMainTimeline()->controller()->extractCompositionLumas(), this));
if (d->exec() != 0) {
m_messageLabel->setMessage(i18n("Archiving project"), OperationCompletedMessage);
}
}
void MainWindow::slotDownloadResources()
{
QString currentFolder;
if (pCore->currentDoc()) {
currentFolder = pCore->currentDoc()->projectDataFolder();
} else {
currentFolder = KdenliveSettings::defaultprojectfolder();
}
auto *d = new ResourceWidget(currentFolder);
connect(d, &ResourceWidget::addClip, this, &MainWindow::slotAddProjectClip);
d->show();
}
void MainWindow::slotProcessImportKeyframes(GraphicsRectItem type, const QString &tag, const QString &keyframes)
{
Q_UNUSED(keyframes)
Q_UNUSED(tag)
if (type == AVWidget) {
// This data should be sent to the effect stack
// TODO REFAC reimplement
// m_effectStack->setKeyframes(tag, data);
} else if (type == TransitionWidget) {
// This data should be sent to the transition stack
// TODO REFAC reimplement
// m_effectStack->transitionConfig()->setKeyframes(tag, data);
} else {
// Error
}
}
void MainWindow::slotAlignPlayheadToMousePos()
{
pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor);
getMainTimeline()->controller()->seekToMouse();
}
void MainWindow::triggerKey(QKeyEvent *ev)
{
// Hack: The QQuickWindow that displays fullscreen monitor does not integrate quith QActions.
// so on keypress events we parse keys and check for shortcuts in all existing actions
QKeySequence seq;
// Remove the Num modifier or some shortcuts like "*" will not work
if (ev->modifiers() != Qt::KeypadModifier) {
seq = QKeySequence(ev->key() + static_cast(ev->modifiers()));
} else {
seq = QKeySequence(ev->key());
}
QList collections = KActionCollection::allCollections();
for (int i = 0; i < collections.count(); ++i) {
KActionCollection *coll = collections.at(i);
for (QAction *tempAction : coll->actions()) {
if (tempAction->shortcuts().contains(seq)) {
// Trigger action
tempAction->trigger();
ev->accept();
return;
}
}
}
}
QDockWidget *MainWindow::addDock(const QString &title, const QString &objectName, QWidget *widget, Qt::DockWidgetArea area)
{
QDockWidget *dockWidget = new QDockWidget(title, this);
dockWidget->setObjectName(objectName);
dockWidget->setWidget(widget);
addDockWidget(area, dockWidget);
return dockWidget;
}
void MainWindow::slotUpdateDockLocation(Qt::DockWidgetArea dockLocationArea)
{
qDebug()<<"== UPDATING DOCK LOCATION FOR: "<(factory()->container(QStringLiteral("monitor_config_overlay"), this));
if (!monitorOverlay) {
return;
}
QList actions = monitorOverlay->actions();
for (QAction *ac : actions) {
int mid = ac->data().toInt();
if (mid == 0x010) {
ac->setVisible(id == Kdenlive::ClipMonitor);
}
ac->setChecked(code & mid);
}
}
void MainWindow::slotChangeStyle(QAction *a)
{
QString style = a->data().toString();
KdenliveSettings::setWidgetstyle(style);
doChangeStyle();
// Monitor refresh is necessary
if (pCore->monitorManager()->isActive(Kdenlive::ClipMonitor)) {
m_clipMonitorDock->raise();
} else {
m_projectMonitorDock->raise();
}
}
void MainWindow::doChangeStyle()
{
QString newStyle = KdenliveSettings::widgetstyle();
if (newStyle.isEmpty() || newStyle == QStringLiteral("Default")) {
newStyle = defaultStyle("Breeze");
}
QApplication::setStyle(QStyleFactory::create(newStyle));
}
bool MainWindow::isTabbedWith(QDockWidget *widget, const QString &otherWidget)
{
QList tabbed = tabifiedDockWidgets(widget);
for (auto tab : tabbed) {
if (tab->objectName() == otherWidget) {
return true;
}
}
return false;
}
void MainWindow::updateDockTitleBars(bool isTopLevel)
{
if (!KdenliveSettings::showtitlebars() && !isTopLevel) {
return;
}
QList docks = findChildren();
//qDebug()<<"=== FOUND DOCKS: "<titleBarWidget();
if (dock->isFloating()) {
if (bar) {
dock->setTitleBarWidget(nullptr);
delete bar;
}
continue;
}
QList docked = pCore->window()->tabifiedDockWidgets(dock);
if (docked.isEmpty()) {
if (bar) {
dock->setTitleBarWidget(nullptr);
delete bar;
}
continue;
}
bool hasVisibleDockSibling = false;
for (QDockWidget *sub : docked) {
if (sub->toggleViewAction()->isChecked() && !sub->isTopLevel()) {
// we have another docked widget, so tabs are visible and can be used instead of title bars
hasVisibleDockSibling = true;
break;
}
}
if (!hasVisibleDockSibling) {
if (bar) {
dock->setTitleBarWidget(nullptr);
delete bar;
}
continue;
}
if (!bar) {
dock->setTitleBarWidget(new QWidget);
}
}
}
void MainWindow::slotToggleAutoPreview(bool enable)
{
KdenliveSettings::setAutopreview(enable);
if (enable && getMainTimeline()) {
getMainTimeline()->controller()->startPreviewRender();
}
}
void MainWindow::configureToolbars()
{
// Since our timeline toolbar is a non-standard toolbar (as it is docked in a custom widget, not
// in a QToolBarDockArea, we have to hack KXmlGuiWindow to avoid a crash when saving toolbar config.
// This is why we hijack the configureToolbars() and temporarily move the toolbar to a standard location
auto *ctnLay = (QVBoxLayout *)m_timelineToolBarContainer->layout();
ctnLay->removeWidget(m_timelineToolBar);
addToolBar(Qt::BottomToolBarArea, m_timelineToolBar);
auto *toolBarEditor = new KEditToolBar(guiFactory(), this);
toolBarEditor->setAttribute(Qt::WA_DeleteOnClose);
connect(toolBarEditor, SIGNAL(newToolBarConfig()), SLOT(saveNewToolbarConfig()));
connect(toolBarEditor, &QDialog::finished, this, &MainWindow::rebuildTimlineToolBar);
toolBarEditor->show();
}
void MainWindow::rebuildTimlineToolBar()
{
// Timeline toolbar settings changed, we can now re-add our toolbar to custom location
m_timelineToolBar = toolBar(QStringLiteral("timelineToolBar"));
removeToolBar(m_timelineToolBar);
m_timelineToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
auto *ctnLay = (QVBoxLayout *)m_timelineToolBarContainer->layout();
if (ctnLay) {
ctnLay->insertWidget(0, m_timelineToolBar);
}
m_timelineToolBar->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_timelineToolBar, &QWidget::customContextMenuRequested, this, &MainWindow::showTimelineToolbarMenu);
m_timelineToolBar->setVisible(true);
}
void MainWindow::showTimelineToolbarMenu(const QPoint &pos)
{
QMenu menu;
menu.addAction(actionCollection()->action(KStandardAction::name(KStandardAction::ConfigureToolbars)));
QMenu *contextSize = new QMenu(i18n("Icon Size"));
menu.addMenu(contextSize);
auto *sizeGroup = new QActionGroup(contextSize);
int currentSize = m_timelineToolBar->iconSize().width();
QAction *a = new QAction(i18nc("@item:inmenu Icon size", "Default"), contextSize);
a->setData(m_timelineToolBar->iconSizeDefault());
a->setCheckable(true);
if (m_timelineToolBar->iconSizeDefault() == currentSize) {
a->setChecked(true);
}
a->setActionGroup(sizeGroup);
contextSize->addAction(a);
KIconTheme *theme = KIconLoader::global()->theme();
QList avSizes;
if (theme) {
avSizes = theme->querySizes(KIconLoader::Toolbar);
}
std::sort(avSizes.begin(), avSizes.end());
if (avSizes.count() < 10) {
// Fixed or threshold type icons
Q_FOREACH (int it, avSizes) {
QString text;
if (it < 19) {
text = i18n("Small (%1x%2)", it, it);
} else if (it < 25) {
text = i18n("Medium (%1x%2)", it, it);
} else if (it < 35) {
text = i18n("Large (%1x%2)", it, it);
} else {
text = i18n("Huge (%1x%2)", it, it);
}
// save the size in the contextIconSizes map
auto *sizeAction = new QAction(text, contextSize);
sizeAction->setData(it);
sizeAction->setCheckable(true);
sizeAction->setActionGroup(sizeGroup);
if (it == currentSize) {
sizeAction->setChecked(true);
}
contextSize->addAction(sizeAction);
}
} else {
// Scalable icons.
const int progression[] = {16, 22, 32, 48, 64, 96, 128, 192, 256};
for (int i : progression) {
Q_FOREACH (int it, avSizes) {
if (it >= i) {
QString text;
if (it < 19) {
text = i18n("Small (%1x%2)", it, it);
} else if (it < 25) {
text = i18n("Medium (%1x%2)", it, it);
} else if (it < 35) {
text = i18n("Large (%1x%2)", it, it);
} else {
text = i18n("Huge (%1x%2)", it, it);
}
// save the size in the contextIconSizes map
auto *sizeAction = new QAction(text, contextSize);
sizeAction->setData(it);
sizeAction->setCheckable(true);
sizeAction->setActionGroup(sizeGroup);
if (it == currentSize) {
sizeAction->setChecked(true);
}
contextSize->addAction(sizeAction);
break;
}
}
}
}
connect(contextSize, &QMenu::triggered, this, &MainWindow::setTimelineToolbarIconSize);
menu.exec(m_timelineToolBar->mapToGlobal(pos));
contextSize->deleteLater();
}
void MainWindow::setTimelineToolbarIconSize(QAction *a)
{
if (!a) {
return;
}
int size = a->data().toInt();
m_timelineToolBar->setIconDimensions(size);
KSharedConfigPtr config = KSharedConfig::openConfig();
KConfigGroup mainConfig(config, QStringLiteral("MainWindow"));
KConfigGroup tbGroup(&mainConfig, QStringLiteral("Toolbar timelineToolBar"));
m_timelineToolBar->saveSettings(tbGroup);
}
void MainWindow::slotManageCache()
{
QDialog d(this);
d.setWindowTitle(i18n("Manage Cache Data"));
auto *lay = new QVBoxLayout;
TemporaryData tmp(pCore->currentDoc(), false, this);
connect(&tmp, &TemporaryData::disableProxies, this, &MainWindow::slotDisableProxies);
// TODO refac
/*
connect(&tmp, SIGNAL(disablePreview()), pCore->projectManager()->currentTimeline(), SLOT(invalidateRange()));
*/
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
connect(buttonBox, &QDialogButtonBox::rejected, &d, &QDialog::reject);
lay->addWidget(&tmp);
lay->addWidget(buttonBox);
d.setLayout(lay);
d.exec();
}
void MainWindow::slotUpdateCompositing(QAction *compose)
{
int mode = compose->data().toInt();
getMainTimeline()->controller()->switchCompositing(mode);
if (m_renderWidget) {
m_renderWidget->errorMessage(RenderWidget::CompositeError, mode == 1 ? i18n("Rendering using low quality track compositing") : QString());
}
pCore->currentDoc()->setModified();
}
void MainWindow::slotUpdateCompositeAction(int mode)
{
QList actions = m_compositeAction->actions();
for (int i = 0; i < actions.count(); i++) {
if (actions.at(i)->data().toInt() == mode) {
m_compositeAction->setCurrentAction(actions.at(i));
break;
}
}
if (m_renderWidget) {
m_renderWidget->errorMessage(RenderWidget::CompositeError, mode == 1 ? i18n("Rendering using low quality track compositing") : QString());
}
}
void MainWindow::showMenuBar(bool show)
{
if (!show) {
KMessageBox::information(this, i18n("This will hide the menu bar completely. You can show it again by typing Ctrl+M."), i18n("Hide menu bar"),
QStringLiteral("show-menubar-warning"));
}
menuBar()->setVisible(show);
}
void MainWindow::forceIconSet(bool force)
{
KdenliveSettings::setForce_breeze(force);
if (force) {
// Check current color theme
QColor background = qApp->palette().window().color();
bool useDarkIcons = background.value() < 100;
KdenliveSettings::setUse_dark_breeze(useDarkIcons);
}
if (KMessageBox::warningContinueCancel(this, i18n("Kdenlive needs to be restarted to apply the icon theme change. Restart now?")) ==
KMessageBox::Continue) {
slotRestart();
}
}
void MainWindow::slotSwitchTrimMode()
{
// TODO refac
/*
if (pCore->projectManager()->currentTimeline()) {
pCore->projectManager()->currentTimeline()->projectView()->switchTrimMode();
}
*/
}
void MainWindow::setTrimMode(const QString &mode){
Q_UNUSED(mode)
// TODO refac
/*
if (pCore->projectManager()->currentTimeline()) {
m_trimLabel->setText(mode);
m_trimLabel->setVisible(!mode.isEmpty());
}
*/
}
TimelineWidget *MainWindow::getMainTimeline() const
{
return m_timelineTabs->getMainTimeline();
}
TimelineWidget *MainWindow::getCurrentTimeline() const
{
return m_timelineTabs->getCurrentTimeline();
}
void MainWindow::resetTimelineTracks()
{
TimelineWidget *current = getCurrentTimeline();
if (current) {
current->controller()->resetTrackHeight();
}
}
void MainWindow::slotEditItemSpeed()
{
TimelineWidget *current = getCurrentTimeline();
if (current) {
current->controller()->changeItemSpeed(-1, -1);
}
}
void MainWindow::slotSwitchTimelineZone(bool active)
{
pCore->currentDoc()->setDocumentProperty(QStringLiteral("enableTimelineZone"), active ? QStringLiteral("1") : QStringLiteral("0"));
getCurrentTimeline()->controller()->useRulerChanged();
QSignalBlocker blocker(m_useTimelineZone);
m_useTimelineZone->setActive(active);
}
void MainWindow::slotGrabItem()
{
getCurrentTimeline()->controller()->grabCurrent();
}
void MainWindow::slotCollapse()
{
if ((QApplication::focusWidget() != nullptr) && (QApplication::focusWidget()->parentWidget() != nullptr) &&
QApplication::focusWidget()->parentWidget() == pCore->bin()) {
// Bin expand/collapse?
} else {
QWidget *widget = QApplication::focusWidget();
while ((widget != nullptr) && widget != this) {
if (widget == m_effectStackDock) {
m_assetPanel->collapseCurrentEffect();
return;
}
widget = widget->parentWidget();
}
// Collapse / expand track
getMainTimeline()->controller()->collapseActiveTrack();
}
}
void MainWindow::slotExpandClip()
{
getCurrentTimeline()->controller()->expandActiveClip();
}
bool MainWindow::timelineVisible() const
{
return !centralWidget()->isHidden();
}
#ifdef DEBUG_MAINW
#undef DEBUG_MAINW
#endif
diff --git a/src/monitor/glwidget.cpp b/src/monitor/glwidget.cpp
index 33e9ad3e6..e97eb0af9 100644
--- a/src/monitor/glwidget.cpp
+++ b/src/monitor/glwidget.cpp
@@ -1,1883 +1,1883 @@
/*
* Copyright (c) 2011-2016 Meltytech, LLC
* Original author: Dan Dennedy
* Modified for Kdenlive: Jean-Baptiste Mardelle
*
* GL shader based on BSD licensed code from Peter Bengtsson:
* https://www.fourcc.org/source/YUV420P-OpenGL-GLSLang.c
*
* 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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "core.h"
#include "glwidget.h"
#include "kdenlivesettings.h"
#include "monitorproxy.h"
#include "profiles/profilemodel.hpp"
#include "timeline2/view/qml/timelineitems.h"
#include
#ifndef GL_UNPACK_ROW_LENGTH
#ifdef GL_UNPACK_ROW_LENGTH_EXT
#define GL_UNPACK_ROW_LENGTH GL_UNPACK_ROW_LENGTH_EXT
#else
#error GL_UNPACK_ROW_LENGTH undefined
#endif
#endif
#if 1
#define check_error(fn) {}
#else
#define check_error(fn) { int err = fn->glGetError(); if (err != GL_NO_ERROR) { qCritical(KDENLIVE_LOG) << "GL error" << hex << err << dec << "at" << __FILE__ << ":" << __LINE__; } }
#endif
#ifndef GL_TIMEOUT_IGNORED
#define GL_TIMEOUT_IGNORED 0xFFFFFFFFFFFFFFFFull
#endif
using namespace Mlt;
GLWidget::GLWidget(int id, QObject *parent)
: QQuickView((QWindow *)parent)
, sendFrameForAnalysis(false)
, m_glslManager(nullptr)
, m_consumer(nullptr)
, m_producer(nullptr)
, m_id(id)
, m_rulerHeight(QFontInfo(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)).pixelSize() * 1.5)
, m_bgColor(KdenliveSettings::window_background())
, m_shader(nullptr)
, m_initSem(0)
, m_analyseSem(1)
, m_isInitialized(false)
, m_threadStartEvent(nullptr)
, m_threadStopEvent(nullptr)
, m_threadCreateEvent(nullptr)
, m_threadJoinEvent(nullptr)
, m_displayEvent(nullptr)
, m_frameRenderer(nullptr)
, m_projectionLocation(0)
, m_modelViewLocation(0)
, m_vertexLocation(0)
, m_texCoordLocation(0)
, m_colorspaceLocation(0)
, m_zoom(1.0f)
, m_profileSize(1920, 1080)
, m_colorSpace(601)
, m_dar(1.78)
, m_sendFrame(false)
, m_isZoneMode(false)
, m_isLoopMode(false)
, m_loopIn(0)
, m_offset(QPoint(0, 0))
, m_fbo(nullptr)
, m_shareContext(nullptr)
, m_openGLSync(false)
, m_ClientWaitSync(nullptr)
{
KDeclarative::KDeclarative kdeclarative;
kdeclarative.setDeclarativeEngine(engine());
kdeclarative.setupEngine(engine());
kdeclarative.setupContext();
m_texture[0] = m_texture[1] = m_texture[2] = 0;
qRegisterMetaType("Mlt::Frame");
qRegisterMetaType("SharedFrame");
if (m_id == Kdenlive::ClipMonitor && !(KdenliveSettings::displayClipMonitorInfo() & 0x01)) {
m_rulerHeight = 0;
} else if (!(KdenliveSettings::displayProjectMonitorInfo() & 0x01)) {
m_rulerHeight = 0;
}
setPersistentOpenGLContext(true);
setPersistentSceneGraph(true);
setClearBeforeRendering(false);
setResizeMode(QQuickView::SizeRootObjectToView);
m_offscreenSurface.setFormat(QWindow::format());
m_offscreenSurface.create();
m_refreshTimer.setSingleShot(true);
m_refreshTimer.setInterval(50);
m_blackClip.reset(new Mlt::Producer(pCore->getCurrentProfile()->profile(), "color:0"));
m_blackClip->set("kdenlive:id", "black");
m_blackClip->set("out", 3);
connect(&m_refreshTimer, &QTimer::timeout, this, &GLWidget::refresh);
m_producer = m_blackClip;
rootContext()->setContextProperty("markersModel", 0);
if (!initGPUAccel()) {
disableGPUAccel();
}
connect(this, &QQuickWindow::sceneGraphInitialized, this, &GLWidget::initializeGL, Qt::DirectConnection);
connect(this, &QQuickWindow::beforeRendering, this, &GLWidget::paintGL, Qt::DirectConnection);
connect(pCore.get(), &Core::updateMonitorProfile, this, &GLWidget::reloadProfile);
registerTimelineItems();
m_proxy = new MonitorProxy(this);
rootContext()->setContextProperty("controller", m_proxy);
}
GLWidget::~GLWidget()
{
// C & D
delete m_glslManager;
delete m_threadStartEvent;
delete m_threadStopEvent;
delete m_threadCreateEvent;
delete m_threadJoinEvent;
delete m_displayEvent;
if (m_frameRenderer) {
if (m_frameRenderer->isRunning()) {
QMetaObject::invokeMethod(m_frameRenderer, "cleanup");
m_frameRenderer->quit();
m_frameRenderer->wait();
m_frameRenderer->deleteLater();
} else {
delete m_frameRenderer;
}
}
m_blackClip.reset();
delete m_shareContext;
delete m_shader;
// delete pCore->getCurrentProfile();
}
void GLWidget::updateAudioForAnalysis()
{
if (m_frameRenderer) {
m_frameRenderer->sendAudioForAnalysis = KdenliveSettings::monitor_audio();
}
}
void GLWidget::initializeGL()
{
if (m_isInitialized) return;
openglContext()->makeCurrent(&m_offscreenSurface);
initializeOpenGLFunctions();
qCDebug(KDENLIVE_LOG) << "OpenGL vendor: " << QString::fromUtf8((const char *)glGetString(GL_VENDOR));
qCDebug(KDENLIVE_LOG) << "OpenGL renderer: " << QString::fromUtf8((const char *)glGetString(GL_RENDERER));
qCDebug(KDENLIVE_LOG) << "OpenGL Threaded: " << openglContext()->supportsThreadedOpenGL();
qCDebug(KDENLIVE_LOG) << "OpenGL ARG_SYNC: " << openglContext()->hasExtension("GL_ARB_sync");
qCDebug(KDENLIVE_LOG) << "OpenGL OpenGLES: " << openglContext()->isOpenGLES();
// C & D
if (onlyGLESGPUAccel()) {
disableGPUAccel();
}
createShader();
m_openGLSync = initGPUAccelSync();
// C & D
if (m_glslManager) {
// Create a context sharing with this context for the RenderThread context.
// This is needed because openglContext() is active in another thread
// at the time that RenderThread is created.
// See this Qt bug for more info: https://bugreports.qt.io/browse/QTBUG-44677
// TODO: QTBUG-44677 is closed. still applicable?
m_shareContext = new QOpenGLContext;
m_shareContext->setFormat(openglContext()->format());
m_shareContext->setShareContext(openglContext());
m_shareContext->create();
}
m_frameRenderer = new FrameRenderer(openglContext(), &m_offscreenSurface, m_ClientWaitSync);
m_frameRenderer->sendAudioForAnalysis = KdenliveSettings::monitor_audio();
openglContext()->makeCurrent(this);
connect(m_frameRenderer, &FrameRenderer::textureReady, this, &GLWidget::updateTexture, Qt::DirectConnection);
connect(m_frameRenderer, &FrameRenderer::frameDisplayed, this, &GLWidget::onFrameDisplayed, Qt::QueuedConnection);
connect(m_frameRenderer, &FrameRenderer::frameDisplayed, this, &GLWidget::frameDisplayed, Qt::QueuedConnection);
m_initSem.release();
m_isInitialized = true;
reconfigure();
}
void GLWidget::resizeGL(int width, int height)
{
int x, y, w, h;
height -= m_rulerHeight;
double this_aspect = (double)width / height;
// Special case optimization to negate odd effect of sample aspect ratio
// not corresponding exactly with image resolution.
if ((int)(this_aspect * 1000) == (int)(m_dar * 1000)) {
w = width;
h = height;
}
// Use OpenGL to normalise sample aspect ratio
else if (height * m_dar > width) {
w = width;
h = width / m_dar;
} else {
w = height * m_dar;
h = height;
}
x = (width - w) / 2;
y = (height - h) / 2;
m_rect.setRect(x, y, w, h);
QQuickItem *rootQml = rootObject();
if (rootQml) {
QSize s = pCore->getCurrentFrameSize();
double scalex = (double)m_rect.width() / s.width() * m_zoom;
double scaley = (double)m_rect.height() / s.height() * m_zoom;
rootQml->setProperty("center", m_rect.center());
rootQml->setProperty("scalex", scalex);
rootQml->setProperty("scaley", scaley);
if (rootQml->objectName() == QLatin1String("rootsplit")) {
// Adjust splitter pos
rootQml->setProperty("splitterPos", x + (rootQml->property("percentage").toDouble() * w));
}
}
emit rectChanged();
}
void GLWidget::resizeEvent(QResizeEvent *event)
{
resizeGL(event->size().width(), event->size().height());
QQuickView::resizeEvent(event);
}
void GLWidget::createGPUAccelFragmentProg()
{
m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment, "uniform sampler2D tex;"
"varying highp vec2 coordinates;"
"void main(void) {"
" gl_FragColor = texture2D(tex, coordinates);"
"}");
m_shader->link();
m_textureLocation[0] = m_shader->uniformLocation("tex");
}
void GLWidget::createShader()
{
m_shader = new QOpenGLShaderProgram;
m_shader->addShaderFromSourceCode(QOpenGLShader::Vertex, "uniform highp mat4 projection;"
"uniform highp mat4 modelView;"
"attribute highp vec4 vertex;"
"attribute highp vec2 texCoord;"
"varying highp vec2 coordinates;"
"void main(void) {"
" gl_Position = projection * modelView * vertex;"
" coordinates = texCoord;"
"}");
// C & D
if (m_glslManager) {
createGPUAccelFragmentProg();
} else {
// A & B
createYUVTextureProjectFragmentProg();
}
m_projectionLocation = m_shader->uniformLocation("projection");
m_modelViewLocation = m_shader->uniformLocation("modelView");
m_vertexLocation = m_shader->attributeLocation("vertex");
m_texCoordLocation = m_shader->attributeLocation("texCoord");
}
void GLWidget::createYUVTextureProjectFragmentProg()
{
m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment,
"uniform sampler2D Ytex, Utex, Vtex;"
"uniform lowp int colorspace;"
"varying highp vec2 coordinates;"
"void main(void) {"
" mediump vec3 texel;"
" texel.r = texture2D(Ytex, coordinates).r - 0.0625;" // Y
" texel.g = texture2D(Utex, coordinates).r - 0.5;" // U
" texel.b = texture2D(Vtex, coordinates).r - 0.5;" // V
" mediump mat3 coefficients;"
" if (colorspace == 601) {"
" coefficients = mat3("
" 1.1643, 1.1643, 1.1643," // column 1
" 0.0, -0.39173, 2.017," // column 2
" 1.5958, -0.8129, 0.0);" // column 3
" } else {" // ITU-R 709
" coefficients = mat3("
" 1.1643, 1.1643, 1.1643," // column 1
" 0.0, -0.213, 2.112," // column 2
" 1.793, -0.533, 0.0);" // column 3
" }"
" gl_FragColor = vec4(coefficients * texel, 1.0);"
"}");
m_shader->link();
m_textureLocation[0] = m_shader->uniformLocation("Ytex");
m_textureLocation[1] = m_shader->uniformLocation("Utex");
m_textureLocation[2] = m_shader->uniformLocation("Vtex");
m_colorspaceLocation = m_shader->uniformLocation("colorspace");
}
static void uploadTextures(QOpenGLContext *context, const SharedFrame &frame, GLuint texture[])
{
int width = frame.get_image_width();
int height = frame.get_image_height();
const uint8_t *image = frame.get_image();
QOpenGLFunctions *f = context->functions();
// The planes of pixel data may not be a multiple of the default 4 bytes.
f->glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// Upload each plane of YUV to a texture.
if (texture[0] != 0u) {
f->glDeleteTextures(3, texture);
}
check_error(f);
f->glGenTextures(3, texture);
check_error(f);
f->glBindTexture(GL_TEXTURE_2D, texture[0]);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
check_error(f);
f->glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, image);
check_error(f);
f->glBindTexture(GL_TEXTURE_2D, texture[1]);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
check_error(f);
f->glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width / 2, height / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, image + width * height);
check_error(f);
f->glBindTexture(GL_TEXTURE_2D, texture[2]);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
check_error(f);
f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
check_error(f);
f->glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width / 2, height / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, image + width * height + width / 2 * height / 2);
check_error(f);
}
void GLWidget::clear()
{
stopGlsl();
update();
}
void GLWidget::releaseAnalyse()
{
m_analyseSem.release();
}
bool GLWidget::acquireSharedFrameTextures()
{
// A
if ((m_glslManager == nullptr) && !openglContext()->supportsThreadedOpenGL()) {
QMutexLocker locker(&m_contextSharedAccess);
if (!m_sharedFrame.is_valid()) {
return false;
}
uploadTextures(openglContext(), m_sharedFrame, m_texture);
} else if (m_glslManager) {
// C & D
m_contextSharedAccess.lock();
if (m_sharedFrame.is_valid()) {
m_texture[0] = *((const GLuint *)m_sharedFrame.get_image());
}
}
if (!m_texture[0]) {
// C & D
if (m_glslManager) m_contextSharedAccess.unlock();
return false;
}
return true;
}
void GLWidget::bindShaderProgram()
{
m_shader->bind();
// C & D
if (m_glslManager) {
m_shader->setUniformValue(m_textureLocation[0], 0);
} else {
// A & B
m_shader->setUniformValue(m_textureLocation[0], 0);
m_shader->setUniformValue(m_textureLocation[1], 1);
m_shader->setUniformValue(m_textureLocation[2], 2);
m_shader->setUniformValue(m_colorspaceLocation, m_colorSpace);
}
}
void GLWidget::releaseSharedFrameTextures()
{
// C & D
if (m_glslManager) {
glFinish();
m_contextSharedAccess.unlock();
}
}
bool GLWidget::initGPUAccel()
{
if (!KdenliveSettings::gpu_accel()) return false;
m_glslManager = new Mlt::Filter(pCore->getCurrentProfile()->profile(), "glsl.manager");
return m_glslManager->is_valid();
}
// C & D
// TODO: insure safe, idempotent on all pipelines.
void GLWidget::disableGPUAccel()
{
delete m_glslManager;
m_glslManager = nullptr;
KdenliveSettings::setGpu_accel(false);
// Need to destroy MLT global reference to prevent filters from trying to use GPU.
mlt_properties_set_data(mlt_global_properties(), "glslManager", nullptr, 0, nullptr, nullptr);
emit gpuNotSupported();
}
bool GLWidget::onlyGLESGPUAccel() const
{
return (m_glslManager != nullptr) && openglContext()->isOpenGLES();
}
#if defined(Q_OS_WIN)
bool GLWidget::initGPUAccelSync()
{
// no-op
// TODO: getProcAddress is not working on Windows?
return false;
}
#else
bool GLWidget::initGPUAccelSync()
{
if (!KdenliveSettings::gpu_accel()) return false;
if (m_glslManager == nullptr) return false;
if (!openglContext()->hasExtension("GL_ARB_sync")) return false;
m_ClientWaitSync = (ClientWaitSync_fp)openglContext()->getProcAddress("glClientWaitSync");
if (m_ClientWaitSync) {
return true;
} else {
qCDebug(KDENLIVE_LOG) << " / / // NO GL SYNC, ERROR";
// fallback on A || B
// TODO: fallback on A || B || C?
disableGPUAccel();
return false;
}
}
#endif
void GLWidget::paintGL()
{
QOpenGLFunctions *f = openglContext()->functions();
float width = this->width() * devicePixelRatio();
float height = this->height() * devicePixelRatio();
f->glDisable(GL_BLEND);
f->glDisable(GL_DEPTH_TEST);
f->glDepthMask(GL_FALSE);
f->glViewport(0, (m_rulerHeight * devicePixelRatio() * 0.5 + 0.5), width, height);
check_error(f);
f->glClearColor(m_bgColor.redF(), m_bgColor.greenF(), m_bgColor.blueF(), 0);
f->glClear(GL_COLOR_BUFFER_BIT);
check_error(f);
if (!acquireSharedFrameTextures()) return;
// Bind textures.
for (uint i = 0; i < 3; ++i) {
if (m_texture[i] != 0u) {
f->glActiveTexture(GL_TEXTURE0 + i);
f->glBindTexture(GL_TEXTURE_2D, m_texture[i]);
check_error(f);
}
}
bindShaderProgram();
check_error(f);
// Setup an orthographic projection.
QMatrix4x4 projection;
projection.scale(2.0f / width, 2.0f / height);
m_shader->setUniformValue(m_projectionLocation, projection);
check_error(f);
// Set model view.
QMatrix4x4 modelView;
if (!qFuzzyCompare(m_zoom, 1.0f)) {
if ((offset().x() != 0) || (offset().y() != 0)) modelView.translate(-offset().x() * devicePixelRatio(), offset().y() * devicePixelRatio());
modelView.scale(zoom(), zoom());
}
m_shader->setUniformValue(m_modelViewLocation, modelView);
check_error(f);
// Provide vertices of triangle strip.
QVector vertices;
width = m_rect.width() * devicePixelRatio();
height = m_rect.height() * devicePixelRatio();
vertices << QVector2D(-width / 2.0f, -height / 2.0f);
vertices << QVector2D(-width / 2.0f, height / 2.0f);
vertices << QVector2D(width / 2.0f, -height / 2.0f);
vertices << QVector2D(width / 2.0f, height / 2.0f);
m_shader->enableAttributeArray(m_vertexLocation);
check_error(f);
m_shader->setAttributeArray(m_vertexLocation, vertices.constData());
check_error(f);
// Provide texture coordinates.
QVector texCoord;
texCoord << QVector2D(0.0f, 1.0f);
texCoord << QVector2D(0.0f, 0.0f);
texCoord << QVector2D(1.0f, 1.0f);
texCoord << QVector2D(1.0f, 0.0f);
m_shader->enableAttributeArray(m_texCoordLocation);
check_error(f);
m_shader->setAttributeArray(m_texCoordLocation, texCoord.constData());
check_error(f);
// Render
glDrawArrays(GL_TRIANGLE_STRIP, 0, vertices.size());
check_error(f);
if (m_sendFrame && m_analyseSem.tryAcquire(1)) {
// Render RGB frame for analysis
if ((m_fbo == nullptr) || m_fbo->size() != m_profileSize) {
delete m_fbo;
QOpenGLFramebufferObjectFormat fmt;
fmt.setSamples(1);
fmt.setInternalTextureFormat(GL_RGB); // GL_RGBA32F); // which one is the fastest ?
m_fbo = new QOpenGLFramebufferObject(m_profileSize.width(), m_profileSize.height(), fmt); // GL_TEXTURE_2D);
}
m_fbo->bind();
glViewport(0, 0, m_profileSize.width(), m_profileSize.height());
QMatrix4x4 projection2;
projection2.scale(2.0f / (float)width, 2.0f / (float)height);
m_shader->setUniformValue(m_projectionLocation, projection2);
glDrawArrays(GL_TRIANGLE_STRIP, 0, vertices.size());
check_error(f);
m_fbo->release();
emit analyseFrame(m_fbo->toImage());
m_sendFrame = false;
}
// Cleanup
m_shader->disableAttributeArray(m_vertexLocation);
m_shader->disableAttributeArray(m_texCoordLocation);
m_shader->release();
for (uint i = 0; i < 3; ++i) {
if (m_texture[i] != 0u) {
f->glActiveTexture(GL_TEXTURE0 + i);
f->glBindTexture(GL_TEXTURE_2D, 0);
check_error(f);
}
}
glActiveTexture(GL_TEXTURE0);
check_error(f);
releaseSharedFrameTextures();
check_error(f);
}
void GLWidget::slotZoom(bool zoomIn)
{
if (zoomIn) {
if (qFuzzyCompare(m_zoom, 1.0f)) {
setZoom(2.0f);
} else if (qFuzzyCompare(m_zoom, 2.0f)) {
setZoom(3.0f);
} else if (m_zoom < 1.0f) {
setZoom(m_zoom * 2);
}
} else {
if (qFuzzyCompare(m_zoom, 3.0f)) {
setZoom(2.0);
} else if (qFuzzyCompare(m_zoom, 2.0f)) {
setZoom(1.0);
} else if (m_zoom > 0.2) {
setZoom(m_zoom / 2);
}
}
}
void GLWidget::wheelEvent(QWheelEvent *event)
{
if (((event->modifiers() & Qt::ControlModifier) != 0u) && ((event->modifiers() & Qt::ShiftModifier) != 0u)) {
slotZoom(event->delta() > 0);
return;
}
emit mouseSeek(event->delta(), (uint)event->modifiers());
event->accept();
}
void GLWidget::requestSeek(int position)
{
m_consumer->set("scrub_audio", 1);
m_producer->seek(position);
if (!qFuzzyIsNull(m_producer->get_speed())) {
m_consumer->purge();
}
if (m_consumer->is_stopped()) {
m_consumer->start();
}
m_consumer->set("refresh", 1);
}
void GLWidget::requestRefresh()
{
if (m_producer && qFuzzyIsNull(m_producer->get_speed())) {
m_consumer->set("scrub_audio", 0);
m_refreshTimer.start();
}
}
QString GLWidget::frameToTime(int frames) const
{
return m_consumer ? m_consumer->frames_to_time(frames, mlt_time_smpte_df) : QStringLiteral("-");
}
void GLWidget::refresh()
{
m_refreshTimer.stop();
QMutexLocker locker(&m_mltMutex);
if (m_consumer->is_stopped()) {
m_consumer->start();
}
m_consumer->set("refresh", 1);
}
bool GLWidget::checkFrameNumber(int pos, int offset, bool isPlaying)
{
const double speed = m_producer->get_speed();
m_proxy->positionFromConsumer(pos, isPlaying);
int maxPos = m_producer->get_int("out");
if (m_isLoopMode || m_isZoneMode) {
if (isPlaying && pos >= maxPos) {
m_consumer->purge();
if (!m_isLoopMode) {
return false;
}
m_producer->seek(m_isZoneMode ? m_proxy->zoneIn() : m_loopIn);
m_producer->set_speed(1.0);
m_consumer->set("refresh", 1);
return true;
}
return true;
} else if (isPlaying) {
maxPos -= offset;
if (pos >= (maxPos - 1) && !(speed < 0.)) {
// Playing past last clip, pause
m_producer->set_speed(0);
m_consumer->set("refresh", 0);
m_consumer->purge();
m_proxy->setPosition(qMax(0, maxPos));
m_producer->seek(qMax(0, maxPos));
return false;
} else if (pos <= 0 && speed < 0.) {
// rewinding reached 0, pause
m_producer->set_speed(0);
m_consumer->set("refresh", 0);
m_consumer->purge();
m_proxy->setPosition(0);
m_producer->seek(0);
return false;
}
}
return isPlaying;
}
void GLWidget::mousePressEvent(QMouseEvent *event)
{
if ((rootObject() != nullptr) && rootObject()->objectName() != QLatin1String("root") && !(event->modifiers() & Qt::ControlModifier) &&
!(event->buttons() & Qt::MiddleButton)) {
event->ignore();
QQuickView::mousePressEvent(event);
return;
}
if ((event->button() & Qt::LeftButton) != 0u) {
if ((event->modifiers() & Qt::ControlModifier) != 0u) {
// Pan view
m_panStart = event->pos();
setCursor(Qt::ClosedHandCursor);
} else {
m_dragStart = event->pos();
}
} else if ((event->button() & Qt::RightButton) != 0u) {
emit showContextMenu(event->globalPos());
} else if ((event->button() & Qt::MiddleButton) != 0u) {
m_panStart = event->pos();
setCursor(Qt::ClosedHandCursor);
}
event->accept();
QQuickView::mousePressEvent(event);
}
void GLWidget::mouseMoveEvent(QMouseEvent *event)
{
if ((rootObject() != nullptr) && rootObject()->objectName() != QLatin1String("root") && !(event->modifiers() & Qt::ControlModifier) &&
!(event->buttons() & Qt::MiddleButton)) {
event->ignore();
QQuickView::mouseMoveEvent(event);
return;
}
/* if (event->modifiers() == Qt::ShiftModifier && m_producer) {
emit seekTo(m_producer->get_length() * event->x() / width());
return;
}*/
QQuickView::mouseMoveEvent(event);
if (!m_panStart.isNull()) {
emit panView(m_panStart - event->pos());
m_panStart = event->pos();
event->accept();
QQuickView::mouseMoveEvent(event);
return;
}
if (!(event->buttons() & Qt::LeftButton)) {
QQuickView::mouseMoveEvent(event);
return;
}
if (!event->isAccepted() && !m_dragStart.isNull() && (event->pos() - m_dragStart).manhattanLength() >= QApplication::startDragDistance()) {
m_dragStart = QPoint();
emit startDrag();
}
}
void GLWidget::keyPressEvent(QKeyEvent *event)
{
QQuickView::keyPressEvent(event);
if (!event->isAccepted()) {
emit passKeyEvent(event);
}
}
void GLWidget::createThread(RenderThread **thread, thread_function_t function, void *data)
{
#ifdef Q_OS_WIN
// On Windows, MLT event consumer-thread-create is fired from the Qt main thread.
while (!m_isInitialized) {
qApp->processEvents();
}
#else
if (!m_isInitialized) {
m_initSem.acquire();
}
#endif
(*thread) = new RenderThread(function, data, m_shareContext, &m_offscreenSurface);
(*thread)->start();
}
static void onThreadCreate(mlt_properties owner, GLWidget *self, RenderThread **thread, int *priority, thread_function_t function, void *data)
{
Q_UNUSED(owner)
Q_UNUSED(priority)
// self->clearFrameRenderer();
self->createThread(thread, function, data);
self->lockMonitor();
}
static void onThreadJoin(mlt_properties owner, GLWidget *self, RenderThread *thread)
{
Q_UNUSED(owner)
if (thread) {
thread->quit();
thread->wait();
delete thread;
// self->clearFrameRenderer();
self->releaseMonitor();
}
}
void GLWidget::startGlsl()
{
// C & D
if (m_glslManager) {
// clearFrameRenderer();
m_glslManager->fire_event("init glsl");
if (m_glslManager->get_int("glsl_supported") == 0) {
disableGPUAccel();
} else {
emit started();
}
}
}
static void onThreadStarted(mlt_properties owner, GLWidget *self)
{
Q_UNUSED(owner)
self->startGlsl();
}
void GLWidget::releaseMonitor()
{
emit lockMonitor(false);
}
void GLWidget::lockMonitor()
{
emit lockMonitor(true);
}
void GLWidget::stopGlsl()
{
if (m_consumer) {
m_consumer->purge();
}
// C & D
// TODO This is commented out for now because it is causing crashes.
// Technically, this should be the correct thing to do, but it appears
// some changes have created regression (see shotcut)
// with respect to restarting the consumer in GPU mode.
// m_glslManager->fire_event("close glsl");
m_texture[0] = 0;
}
static void onThreadStopped(mlt_properties owner, GLWidget *self)
{
Q_UNUSED(owner)
self->stopGlsl();
}
int GLWidget::setProducer(const QString &file)
{
if (m_producer) {
m_producer.reset();
}
qDebug()<<"==== OPENING PROIDUCER FILE: "<(new Mlt::Producer(pCore->getCurrentProfile()->profile(), nullptr, file.toUtf8().constData()));
if (m_consumer) {
//m_consumer->stop();
if (!m_consumer->is_stopped()) {
m_consumer->stop();
}
}
int error = reconfigure();
if (error == 0) {
// The profile display aspect ratio may have changed.
resizeGL(width(), height());
startConsumer();
}
return error;
}
int GLWidget::setProducer(const std::shared_ptr &producer, bool isActive, int position)
{
int error = 0;
QString currentId;
int consumerPosition = 0;
currentId = m_producer->parent().get("kdenlive:id");
if (m_consumer) {
consumerPosition = m_consumer->position();
}
stop();
if (producer) {
m_producer = producer;
} else {
if (currentId == QLatin1String("black")) {
return 0;
}
m_producer = m_blackClip;
// Reset markersModel
rootContext()->setContextProperty("markersModel", 0);
}
// redundant check. postcondition of above is m_producer != null
m_producer->set_speed(0);
error = reconfigure();
if (error == 0) {
// The profile display aspect ratio may have changed.
resizeGL(width(), height());
} else {
return error;
}
if (!m_consumer) {
return error;
}
if (position == -1 && m_producer->parent().get("kdenlive:id") == currentId) {
position = consumerPosition;
}
if (isActive) {
startConsumer();
}
m_consumer->set("scrub_audio", 0);
m_proxy->setPosition(position > 0 ? position : m_producer->position());
return error;
}
int GLWidget::droppedFrames() const
{
return (m_consumer ? m_consumer->get_int("drop_count") : 0);
}
void GLWidget::resetDrops()
{
if (m_consumer) {
m_consumer->set("drop_count", 0);
}
}
void GLWidget::stopCapture()
{
if (strcmp(m_consumer->get("mlt_service"), "multi") == 0) {
m_consumer->set("refresh", 0);
m_consumer->purge();
m_consumer->stop();
}
}
int GLWidget::reconfigureMulti(const QString ¶ms, const QString &path, Mlt::Profile *profile)
{
Q_UNUSED(params);
Q_UNUSED(path);
Q_UNUSED(profile);
// TODO Fix or delete
/*
QString serviceName = property("mlt_service").toString();
if ((m_consumer == nullptr) || !m_consumer->is_valid() || strcmp(m_consumer->get("mlt_service"), "multi") != 0) {
if (m_consumer) {
m_consumer->purge();
m_consumer->stop();
m_consumer.reset();
}
m_consumer.reset(new Mlt::FilteredConsumer(*profile, "multi"));
delete m_threadStartEvent;
m_threadStartEvent = nullptr;
delete m_threadStopEvent;
m_threadStopEvent = nullptr;
delete m_threadCreateEvent;
delete m_threadJoinEvent;
if (m_consumer) {
m_threadCreateEvent = m_consumer->listen("consumer-thread-create", this, (mlt_listener)onThreadCreate);
m_threadJoinEvent = m_consumer->listen("consumer-thread-join", this, (mlt_listener)onThreadJoin);
}
}
if (m_consumer->is_valid()) {
// build sub consumers
// m_consumer->set("mlt_image_format", "yuv422");
reloadProfile();
int volume = KdenliveSettings::volume();
m_consumer->set("0", serviceName.toUtf8().constData());
m_consumer->set("0.mlt_image_format", "yuv422");
m_consumer->set("0.terminate_on_pause", 0);
// m_consumer->set("0.preview_off", 1);
m_consumer->set("0.real_time", 0);
m_consumer->set("0.volume", (double)volume / 100);
if (serviceName.startsWith(QLatin1String("sdl_audio"))) {
#ifdef Q_OS_WIN
m_consumer->set("0.audio_buffer", 2048);
#else
m_consumer->set("0.audio_buffer", 512);
#endif
QString audioDevice = KdenliveSettings::audiodevicename();
if (!audioDevice.isEmpty()) {
m_consumer->set("audio_device", audioDevice.toUtf8().constData());
}
QString audioDriver = KdenliveSettings::audiodrivername();
if (!audioDriver.isEmpty()) {
m_consumer->set("audio_driver", audioDriver.toUtf8().constData());
}
}
m_consumer->set("1", "avformat");
m_consumer->set("1.target", path.toUtf8().constData());
// m_consumer->set("1.real_time", -KdenliveSettings::mltthreads());
m_consumer->set("terminate_on_pause", 0);
m_consumer->set("1.terminate_on_pause", 0);
// m_consumer->set("1.terminate_on_pause", 0);// was commented out. restoring it fixes mantis#3415 - FFmpeg recording freezes
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
QStringList paramList = params.split(' ', QString::SkipEmptyParts);
#else
QStringList paramList = params.split(' ', Qt::SkipEmptyParts);
#endif
for (int i = 0; i < paramList.count(); ++i) {
QString key = "1." + paramList.at(i).section(QLatin1Char('='), 0, 0);
QString value = paramList.at(i).section(QLatin1Char('='), 1, 1);
if (value == QLatin1String("%threads")) {
value = QString::number(QThread::idealThreadCount());
}
m_consumer->set(key.toUtf8().constData(), value.toUtf8().constData());
}
// Connect the producer to the consumer - tell it to "run" later
delete m_displayEvent;
// C & D
if (m_glslManager) {
// D
if (m_openGLSync) {
m_displayEvent = m_consumer->listen("consumer-frame-show", this, (mlt_listener)on_gl_frame_show);
} else {
// C
m_displayEvent = m_consumer->listen("consumer-frame-show", this, (mlt_listener)on_gl_nosync_frame_show);
}
} else {
// A & B
m_displayEvent = m_consumer->listen("consumer-frame-show", this, (mlt_listener)on_frame_show);
}
m_consumer->connect(*m_producer.get());
m_consumer->start();
return 0;
}
*/
return -1;
}
int GLWidget::reconfigure()
{
int error = 0;
// use SDL for audio, OpenGL for video
QString serviceName = property("mlt_service").toString();
if ((m_consumer == nullptr) || !m_consumer->is_valid() || strcmp(m_consumer->get("mlt_service"), "multi") == 0) {
if (m_consumer) {
m_consumer->purge();
m_consumer->stop();
m_consumer.reset();
}
QString audioBackend = (KdenliveSettings::external_display()) ? QString("decklink:%1").arg(KdenliveSettings::blackmagic_output_device())
: KdenliveSettings::audiobackend();
if (m_consumer == nullptr || serviceName.isEmpty() || serviceName != audioBackend) {
m_consumer.reset(new Mlt::FilteredConsumer(*pCore->getProjectProfile(), audioBackend.toLatin1().constData()));
if (m_consumer->is_valid()) {
serviceName = audioBackend;
} else {
// Warning, audio backend unavailable on system
m_consumer.reset();
QStringList backends = {"sdl2_audio", "sdl_audio", "rtaudio"};
for (const QString &bk : backends) {
if (bk == audioBackend) {
// Already tested
continue;
}
m_consumer.reset(new Mlt::FilteredConsumer(*pCore->getProjectProfile(), bk.toLatin1().constData()));
if (m_consumer->is_valid()) {
if (audioBackend == KdenliveSettings::sdlAudioBackend()) {
// switch sdl audio backend
KdenliveSettings::setSdlAudioBackend(bk);
}
qDebug() << "++++++++\nSwitching audio backend to: " << bk << "\n++++++++++";
KdenliveSettings::setAudiobackend(bk);
serviceName = bk;
break;
} else {
m_consumer.reset();
}
}
}
if (!m_consumer || !m_consumer->is_valid()) {
qWarning() << "WARNING, NO AUDIO BACKEND FOUND";
return -1;
}
setProperty("mlt_service", serviceName);
if (KdenliveSettings::external_display()) {
m_consumer->set("terminate_on_pause", 0);
}
m_consumer->set("width", m_profileSize.width());
m_consumer->set("height", m_profileSize.height());
m_colorSpace = pCore->getCurrentProfile()->colorspace();
m_dar = pCore->getCurrentDar();
}
delete m_threadStartEvent;
m_threadStartEvent = nullptr;
delete m_threadStopEvent;
m_threadStopEvent = nullptr;
delete m_threadCreateEvent;
delete m_threadJoinEvent;
if (m_consumer) {
m_threadCreateEvent = m_consumer->listen("consumer-thread-create", this, (mlt_listener)onThreadCreate);
m_threadJoinEvent = m_consumer->listen("consumer-thread-join", this, (mlt_listener)onThreadJoin);
}
}
if (m_consumer->is_valid()) {
// Connect the producer to the consumer - tell it to "run" later
if (m_producer) {
m_consumer->connect(*m_producer.get());
// m_producer->set_speed(0.0);
}
int dropFrames = realTime();
if (!KdenliveSettings::monitor_dropframes()) {
dropFrames = -dropFrames;
}
m_consumer->set("real_time", dropFrames);
m_consumer->set("channels", pCore->audioChannels());
if (KdenliveSettings::previewScaling() > 1) {
m_consumer->set("scale", 1.0 / KdenliveSettings::previewScaling());
}
// C & D
if (m_glslManager) {
if (!m_threadStartEvent) {
m_threadStartEvent = m_consumer->listen("consumer-thread-started", this, (mlt_listener)onThreadStarted);
}
if (!m_threadStopEvent) {
m_threadStopEvent = m_consumer->listen("consumer-thread-stopped", this, (mlt_listener)onThreadStopped);
}
if (!serviceName.startsWith(QLatin1String("decklink"))) {
m_consumer->set("mlt_image_format", "glsl");
}
} else {
// A & B
m_consumer->set("mlt_image_format", "yuv422");
}
delete m_displayEvent;
// C & D
if (m_glslManager) {
m_displayEvent = m_consumer->listen("consumer-frame-show", this, (mlt_listener)on_gl_frame_show);
} else {
// A & B
m_displayEvent = m_consumer->listen("consumer-frame-show", this, (mlt_listener)on_frame_show);
}
int volume = KdenliveSettings::volume();
if (serviceName.startsWith(QLatin1String("sdl"))) {
QString audioDevice = KdenliveSettings::audiodevicename();
if (!audioDevice.isEmpty()) {
m_consumer->set("audio_device", audioDevice.toUtf8().constData());
}
QString audioDriver = KdenliveSettings::audiodrivername();
if (!audioDriver.isEmpty()) {
m_consumer->set("audio_driver", audioDriver.toUtf8().constData());
}
}
/*if (!pCore->getCurrentProfile()->progressive())
m_consumer->set("progressive", property("progressive").toBool());*/
m_consumer->set("volume", volume / 100.0);
// m_consumer->set("progressive", 1);
m_consumer->set("rescale", KdenliveSettings::mltinterpolation().toUtf8().constData());
m_consumer->set("deinterlace_method", KdenliveSettings::mltdeinterlacer().toUtf8().constData());
/*
#ifdef Q_OS_WIN
m_consumer->set("audio_buffer", 2048);
#else
m_consumer->set("audio_buffer", 512);
#endif
*/
int fps = qRound(pCore->getCurrentFps());
m_consumer->set("buffer", qMax(25, fps));
m_consumer->set("prefill", qMax(1, fps / 25));
m_consumer->set("drop_max", fps / 4);
m_consumer->set("scrub_audio", 1);
m_consumer->set("channels", 2);
if (KdenliveSettings::monitor_gamma() == 0) {
m_consumer->set("color_trc", "iec61966_2_1");
} else {
m_consumer->set("color_trc", "bt709");
}
} else {
// Cleanup on error
error = 2;
}
return error;
}
float GLWidget::zoom() const
{
return m_zoom;
}
void GLWidget::reloadProfile()
{
// The profile display aspect ratio may have changed.
bool existingConsumer = false;
if (m_consumer) {
// Make sure to delete and rebuild consumer to match profile
m_consumer->purge();
m_consumer->stop();
m_consumer.reset();
existingConsumer = true;
}
m_blackClip.reset(new Mlt::Producer(pCore->getCurrentProfile()->profile(), "color:0"));
m_blackClip->set("kdenlive:id", "black");
if (existingConsumer) {
reconfigure();
}
resizeGL(width(), height());
refreshSceneLayout();
}
QSize GLWidget::profileSize() const
{
return m_profileSize;
}
QRect GLWidget::displayRect() const
{
return m_rect;
}
QPoint GLWidget::offset() const
{
return {m_offset.x() - ((int)((float)m_profileSize.width() * m_zoom) - width()) / 2,
m_offset.y() - ((int)((float)m_profileSize.height() * m_zoom) - height()) / 2};
}
void GLWidget::setZoom(float zoom)
{
double zoomRatio = zoom / m_zoom;
m_zoom = zoom;
emit zoomChanged();
if (rootObject()) {
rootObject()->setProperty("zoom", m_zoom);
double scalex = rootObject()->property("scalex").toDouble() * zoomRatio;
rootObject()->setProperty("scalex", scalex);
double scaley = rootObject()->property("scaley").toDouble() * zoomRatio;
rootObject()->setProperty("scaley", scaley);
}
update();
}
void GLWidget::onFrameDisplayed(const SharedFrame &frame)
{
m_contextSharedAccess.lock();
m_sharedFrame = frame;
m_sendFrame = sendFrameForAnalysis;
m_contextSharedAccess.unlock();
update();
}
void GLWidget::mouseReleaseEvent(QMouseEvent *event)
{
QQuickView::mouseReleaseEvent(event);
if (m_dragStart.isNull() && m_panStart.isNull() && (rootObject() != nullptr) && rootObject()->objectName() != QLatin1String("root") &&
!(event->modifiers() & Qt::ControlModifier)) {
event->ignore();
return;
}
if (!m_dragStart.isNull() && m_panStart.isNull() && ((event->button() & Qt::LeftButton) != 0u) && !event->isAccepted()) {
emit monitorPlay();
}
m_dragStart = QPoint();
m_panStart = QPoint();
setCursor(Qt::ArrowCursor);
}
void GLWidget::purgeCache()
{
if (m_consumer) {
m_consumer->purge();
m_producer->seek(m_proxy->getPosition() + 1);
}
}
void GLWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
QQuickView::mouseDoubleClickEvent(event);
if (event->isAccepted()) {
return;
}
if ((rootObject() == nullptr) || rootObject()->objectName() != QLatin1String("rooteffectscene")) {
emit switchFullScreen();
}
event->accept();
}
void GLWidget::setOffsetX(int x, int max)
{
m_offset.setX(x);
emit offsetChanged();
if (rootObject()) {
rootObject()->setProperty("offsetx", m_zoom > 1.0f ? x - max / 2.0 - 10 : 0);
}
update();
}
void GLWidget::setOffsetY(int y, int max)
{
m_offset.setY(y);
if (rootObject()) {
rootObject()->setProperty("offsety", m_zoom > 1.0f ? y - max / 2.0 - 10 : 0);
}
update();
}
int GLWidget::realTime() const
{
// C & D
if (m_glslManager) {
return 1;
}
return KdenliveSettings::mltthreads();
}
std::shared_ptr GLWidget::consumer()
{
return m_consumer;
}
void GLWidget::updateGamma()
{
reconfigure();
}
void GLWidget::resetConsumer(bool fullReset)
{
if (fullReset && m_consumer) {
m_consumer->purge();
m_consumer->stop();
m_consumer.reset();
}
reconfigure();
}
const QString GLWidget::sceneList(const QString &root, const QString &fullPath)
{
QString playlist;
qCDebug(KDENLIVE_LOG) << " * * *Setting document xml root: " << root;
Mlt::Consumer xmlConsumer(pCore->getCurrentProfile()->profile(), "xml", fullPath.isEmpty() ? "kdenlive_playlist" : fullPath.toUtf8().constData());
if (!root.isEmpty()) {
xmlConsumer.set("root", root.toUtf8().constData());
}
if (!xmlConsumer.is_valid()) {
return QString();
}
xmlConsumer.set("store", "kdenlive");
xmlConsumer.set("time_format", "clock");
// Disabling meta creates cleaner files, but then we don't have access to metadata on the fly (meta channels, etc)
// And we must use "avformat" instead of "avformat-novalidate" on project loading which causes a big delay on project opening
// xmlConsumer.set("no_meta", 1);
xmlConsumer.connect(*m_producer.get());
xmlConsumer.run();
playlist = fullPath.isEmpty() ? QString::fromUtf8(xmlConsumer.get("kdenlive_playlist")) : fullPath;
return playlist;
}
void GLWidget::updateTexture(GLuint yName, GLuint uName, GLuint vName)
{
m_texture[0] = yName;
m_texture[1] = uName;
m_texture[2] = vName;
}
void GLWidget::on_frame_show(mlt_consumer, void *self, mlt_frame frame_ptr)
{
Mlt::Frame frame(frame_ptr);
if (frame.get_int("rendered") != 0) {
auto *widget = static_cast(self);
int timeout = (widget->consumer()->get_int("real_time") > 0) ? 0 : 1000;
if ((widget->m_frameRenderer != nullptr) && widget->m_frameRenderer->semaphore()->tryAcquire(1, timeout)) {
QMetaObject::invokeMethod(widget->m_frameRenderer, "showFrame", Qt::QueuedConnection, Q_ARG(Mlt::Frame, frame));
}
}
}
void GLWidget::on_gl_nosync_frame_show(mlt_consumer, void *self, mlt_frame frame_ptr)
{
Mlt::Frame frame(frame_ptr);
if (frame.get_int("rendered") != 0) {
auto *widget = static_cast(self);
int timeout = (widget->consumer()->get_int("real_time") > 0) ? 0 : 1000;
if ((widget->m_frameRenderer != nullptr) && widget->m_frameRenderer->semaphore()->tryAcquire(1, timeout)) {
QMetaObject::invokeMethod(widget->m_frameRenderer, "showGLNoSyncFrame", Qt::QueuedConnection, Q_ARG(Mlt::Frame, frame));
}
}
}
void GLWidget::on_gl_frame_show(mlt_consumer, void *self, mlt_frame frame_ptr)
{
Mlt::Frame frame(frame_ptr);
if (frame.get_int("rendered") != 0) {
auto *widget = static_cast(self);
int timeout = (widget->consumer()->get_int("real_time") > 0) ? 0 : 1000;
if ((widget->m_frameRenderer != nullptr) && widget->m_frameRenderer->semaphore()->tryAcquire(1, timeout)) {
QMetaObject::invokeMethod(widget->m_frameRenderer, "showGLFrame", Qt::QueuedConnection, Q_ARG(Mlt::Frame, frame));
}
}
}
RenderThread::RenderThread(thread_function_t function, void *data, QOpenGLContext *context, QSurface *surface)
: QThread(nullptr)
, m_function(function)
, m_data(data)
, m_context(nullptr)
, m_surface(surface)
{
if (context) {
m_context = new QOpenGLContext;
m_context->setFormat(context->format());
m_context->setShareContext(context);
m_context->create();
m_context->moveToThread(this);
}
}
RenderThread::~RenderThread()
{
// would otherwise leak if RenderThread is allocated with a context but not run.
// safe post-run
delete m_context;
}
// TODO: missing some exception handling?
void RenderThread::run()
{
if (m_context) {
m_context->makeCurrent(m_surface);
}
m_function(m_data);
if (m_context) {
m_context->doneCurrent();
delete m_context;
m_context = nullptr;
}
}
FrameRenderer::FrameRenderer(QOpenGLContext *shareContext, QSurface *surface, GLWidget::ClientWaitSync_fp clientWaitSync)
: QThread(nullptr)
, m_semaphore(3)
, m_context(nullptr)
, m_surface(surface)
, m_ClientWaitSync(clientWaitSync)
, m_gl32(nullptr)
, sendAudioForAnalysis(false)
{
Q_ASSERT(shareContext);
m_renderTexture[0] = m_renderTexture[1] = m_renderTexture[2] = 0;
m_displayTexture[0] = m_displayTexture[1] = m_displayTexture[2] = 0;
// B & C & D
if (KdenliveSettings::gpu_accel() || shareContext->supportsThreadedOpenGL()) {
m_context = new QOpenGLContext;
m_context->setFormat(shareContext->format());
m_context->setShareContext(shareContext);
m_context->create();
m_context->moveToThread(this);
}
setObjectName(QStringLiteral("FrameRenderer"));
moveToThread(this);
start();
}
FrameRenderer::~FrameRenderer()
{
delete m_context;
delete m_gl32;
}
void FrameRenderer::showFrame(Mlt::Frame frame)
{
int width = 0;
int height = 0;
mlt_image_format format = mlt_image_yuv420p;
frame.get_image(format, width, height);
// Save this frame for future use and to keep a reference to the GL Texture.
m_displayFrame = SharedFrame(frame);
if ((m_context != nullptr) && m_context->isValid()) {
m_context->makeCurrent(m_surface);
// Upload each plane of YUV to a texture.
QOpenGLFunctions *f = m_context->functions();
uploadTextures(m_context, m_displayFrame, m_renderTexture);
f->glBindTexture(GL_TEXTURE_2D, 0);
check_error(f);
f->glFinish();
for (int i = 0; i < 3; ++i) {
std::swap(m_renderTexture[i], m_displayTexture[i]);
}
emit textureReady(m_displayTexture[0], m_displayTexture[1], m_displayTexture[2]);
m_context->doneCurrent();
}
// The frame is now done being modified and can be shared with the rest
// of the application.
emit frameDisplayed(m_displayFrame);
m_semaphore.release();
}
void FrameRenderer::showGLFrame(Mlt::Frame frame)
{
if ((m_context != nullptr) && m_context->isValid()) {
int width = 0;
int height = 0;
frame.set("movit.convert.use_texture", 1);
mlt_image_format format = mlt_image_glsl_texture;
frame.get_image(format, width, height);
m_context->makeCurrent(m_surface);
pipelineSyncToFrame(frame);
m_context->functions()->glFinish();
m_context->doneCurrent();
// Save this frame for future use and to keep a reference to the GL Texture.
m_displayFrame = SharedFrame(frame);
}
// The frame is now done being modified and can be shared with the rest
// of the application.
emit frameDisplayed(m_displayFrame);
m_semaphore.release();
}
void FrameRenderer::showGLNoSyncFrame(Mlt::Frame frame)
{
if ((m_context != nullptr) && m_context->isValid()) {
int width = 0;
int height = 0;
frame.set("movit.convert.use_texture", 1);
mlt_image_format format = mlt_image_glsl_texture;
frame.get_image(format, width, height);
m_context->makeCurrent(m_surface);
m_context->functions()->glFinish();
m_context->doneCurrent();
// Save this frame for future use and to keep a reference to the GL Texture.
m_displayFrame = SharedFrame(frame);
}
// The frame is now done being modified and can be shared with the rest
// of the application.
emit frameDisplayed(m_displayFrame);
m_semaphore.release();
}
void FrameRenderer::cleanup()
{
if ((m_renderTexture[0] != 0u) && (m_renderTexture[1] != 0u) && (m_renderTexture[2] != 0u)) {
m_context->makeCurrent(m_surface);
m_context->functions()->glDeleteTextures(3, m_renderTexture);
if ((m_displayTexture[0] != 0u) && (m_displayTexture[1] != 0u) && (m_displayTexture[2] != 0u)) {
m_context->functions()->glDeleteTextures(3, m_displayTexture);
}
m_context->doneCurrent();
m_renderTexture[0] = m_renderTexture[1] = m_renderTexture[2] = 0;
m_displayTexture[0] = m_displayTexture[1] = m_displayTexture[2] = 0;
}
}
// D
void FrameRenderer::pipelineSyncToFrame(Mlt::Frame &frame)
{
auto sync = (GLsync)frame.get_data("movit.convert.fence");
if (!sync) return;
#ifdef Q_OS_WIN
// On Windows, use QOpenGLFunctions_3_2_Core instead of getProcAddress.
// TODO: move to initialization of m_ClientWaitSync
if (!m_gl32) {
m_gl32 = m_context->versionFunctions();
if (m_gl32) {
m_gl32->initializeOpenGLFunctions();
}
}
if (m_gl32) {
m_gl32->glClientWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
check_error(m_context->functions());
}
#else
if (m_ClientWaitSync) {
m_ClientWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
check_error(m_context->functions());
}
#endif // Q_OS_WIN
}
void GLWidget::refreshSceneLayout()
{
if (!rootObject()) {
return;
}
QSize s = pCore->getCurrentFrameSize();
m_proxy->profileChanged();
rootObject()->setProperty("scalex", (double)m_rect.width() / s.width() * m_zoom);
rootObject()->setProperty("scaley", (double)m_rect.height() / s.height() * m_zoom);
}
void GLWidget::switchPlay(bool play, double speed)
{
if (!m_producer || !m_consumer) {
return;
}
if (m_isZoneMode || m_isLoopMode) {
resetZoneMode();
}
if (play) {
if (m_id == Kdenlive::ClipMonitor && m_consumer->position() == m_producer->get_out() && speed > 0) {
m_producer->seek(0);
}
m_producer->set_speed(speed);
if (speed <= 1. || speed > 6.) {
m_consumer->set("scrub_audio", 0);
} else {
m_consumer->set("scrub_audio", 1);
}
m_consumer->start();
m_consumer->set("refresh", 1);
} else {
emit paused();
m_producer->set_speed(0);
m_producer->seek(m_consumer->position() + 1);
m_consumer->purge();
m_consumer->start();
}
}
bool GLWidget::playZone(bool loop)
{
if (!m_producer || m_proxy->zoneOut() <= m_proxy->zoneIn()) {
pCore->displayMessage(i18n("Select a zone to play"), InformationMessage, 500);
return false;
}
m_producer->seek(m_proxy->zoneIn());
m_producer->set_speed(0);
m_consumer->purge();
m_producer->set("out", m_proxy->zoneOut());
m_producer->set_speed(1.0);
if (m_consumer->is_stopped()) {
m_consumer->start();
}
m_consumer->set("scrub_audio", 0);
m_consumer->set("refresh", 1);
m_isZoneMode = true;
m_isLoopMode = loop;
return true;
}
bool GLWidget::loopClip(QPoint inOut)
{
if (!m_producer || inOut.y() <= inOut.x()) {
pCore->displayMessage(i18n("Select a clip to play"), InformationMessage, 500);
return false;
}
m_loopIn = inOut.x();
m_producer->seek(inOut.x());
m_producer->set_speed(0);
m_consumer->purge();
m_producer->set("out", inOut.y());
m_producer->set_speed(1.0);
if (m_consumer->is_stopped()) {
m_consumer->start();
}
m_consumer->set("scrub_audio", 0);
m_consumer->set("refresh", 1);
m_isZoneMode = false;
m_isLoopMode = true;
return true;
}
void GLWidget::resetZoneMode()
{
if (!m_isZoneMode && !m_isLoopMode) {
return;
}
m_producer->set("out", m_producer->get_length());
m_loopIn = 0;
m_isZoneMode = false;
m_isLoopMode = false;
}
MonitorProxy *GLWidget::getControllerProxy()
{
return m_proxy;
}
int GLWidget::getCurrentPos() const
{
return m_proxy->getPosition();
}
void GLWidget::setRulerInfo(int duration, const std::shared_ptr &model)
{
rootObject()->setProperty("duration", duration);
if (model != nullptr) {
// we are resetting marker/snap model, reset zone
rootContext()->setContextProperty("markersModel", model.get());
}
}
void GLWidget::startConsumer()
{
if (m_consumer == nullptr) {
return;
}
if (m_consumer->is_stopped() && m_consumer->start() == -1) {
// ARGH CONSUMER BROKEN!!!!
KMessageBox::error(
qApp->activeWindow(),
i18n("Could not create the video preview window.\nThere is something wrong with your Kdenlive install or your driver settings, please fix it."));
if (m_displayEvent) {
delete m_displayEvent;
}
m_displayEvent = nullptr;
m_consumer.reset();
return;
}
m_consumer->set("refresh", 1);
}
void GLWidget::stop()
{
m_refreshTimer.stop();
// why this lock?
QMutexLocker locker(&m_mltMutex);
if (m_producer) {
if (m_isZoneMode || m_isLoopMode) {
resetZoneMode();
}
m_producer->set_speed(0.0);
}
if (m_consumer) {
m_consumer->purge();
if (!m_consumer->is_stopped()) {
m_consumer->stop();
}
}
}
double GLWidget::playSpeed() const
{
if (m_producer) {
return m_producer->get_speed();
}
return 0.0;
}
void GLWidget::setDropFrames(bool drop)
{
// why this lock?
QMutexLocker locker(&m_mltMutex);
if (m_consumer) {
int dropFrames = realTime();
if (!drop) {
dropFrames = -dropFrames;
}
m_consumer->stop();
m_consumer->set("real_time", dropFrames);
if (m_consumer->start() == -1) {
qCWarning(KDENLIVE_LOG) << "ERROR, Cannot start monitor";
}
}
}
int GLWidget::volume() const
{
if ((!m_consumer) || (!m_producer)) {
return -1;
}
if (m_consumer->get("mlt_service") == QStringLiteral("multi")) {
return ((int)100 * m_consumer->get_double("0.volume"));
}
return ((int)100 * m_consumer->get_double("volume"));
}
void GLWidget::setVolume(double volume)
{
if (m_consumer) {
if (m_consumer->get("mlt_service") == QStringLiteral("multi")) {
m_consumer->set("0.volume", volume);
} else {
m_consumer->set("volume", volume);
}
}
}
int GLWidget::duration() const
{
if (!m_producer) {
return 0;
}
return m_producer->get_playtime();
}
void GLWidget::setConsumerProperty(const QString &name, const QString &value)
{
QMutexLocker locker(&m_mltMutex);
if (m_consumer) {
m_consumer->set(name.toUtf8().constData(), value.toUtf8().constData());
if (m_consumer->start() == -1) {
qCWarning(KDENLIVE_LOG) << "ERROR, Cannot start monitor";
}
}
}
void GLWidget::updateScaling()
{
-#if LIBMLT_VERSION_INT >= MLT_VERSION_PREVIEW_SCALE
+#if LIBMLT_VERSION_INT >= QT_VERSION_CHECK(6,20,0)
int previewHeight = pCore->getCurrentFrameSize().height();
switch (KdenliveSettings::previewScaling()) {
case 2:
previewHeight = qMin(previewHeight, 720);
break;
case 4:
previewHeight = qMin(previewHeight, 540);
break;
case 8:
previewHeight = qMin(previewHeight, 360);
break;
case 16:
previewHeight = qMin(previewHeight, 270);
break;
default:
break;
}
int pWidth = previewHeight * pCore->getCurrentDar() / pCore->getCurrentSar();
if (pWidth% 2 > 0) {
pWidth ++;
}
m_profileSize = QSize(pWidth, previewHeight);
if (m_consumer) {
m_consumer->set("width", m_profileSize.width());
m_consumer->set("height", m_profileSize.height());
resizeGL(width(), height());
}
#else
int previewHeight = pCore->getCurrentFrameSize().height();
int pWidth = previewHeight * pCore->getCurrentDar() / pCore->getCurrentSar();
if (pWidth% 2 > 0) {
pWidth ++;
}
m_profileSize = QSize(pWidth, previewHeight);
if (m_consumer) {
resizeGL(width(), height());
}
#endif
}
void GLWidget::switchRuler(bool show)
{
m_rulerHeight = show ? QFontInfo(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)).pixelSize() * 1.5 : 0;
resizeGL(width(), height());
m_proxy->rulerHeightChanged();
}
diff --git a/src/titler/titlewidget.cpp b/src/titler/titlewidget.cpp
index 4bfdada34..237fda254 100644
--- a/src/titler/titlewidget.cpp
+++ b/src/titler/titlewidget.cpp
@@ -1,3180 +1,3182 @@
/***************************************************************************
titlewidget.cpp - description
-------------------
begin : Feb 28 2008
copyright : (C) 2008 by Marco Gittler
email : g.marco@freenet.de
***************************************************************************/
/***************************************************************************
* *
* 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 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "titlewidget.h"
#include "core.h"
#include "doc/kthumb.h"
#include "gradientwidget.h"
#include "kdenlivesettings.h"
#include "monitor/monitor.h"
#include
#include
#include
#include
#include
#include
#include "kdenlive_debug.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static QList titletemplates;
// What exactly is this variable good for?
int settingUp = 0;
const int IMAGEITEM = 7;
const int RECTITEM = 3;
const int TEXTITEM = 8;
+/*
const int NOEFFECT = 0;
const int BLUREFFECT = 1;
const int SHADOWEFFECT = 2;
const int TYPEWRITEREFFECT = 3;
+*/
void TitleWidget::refreshTemplateBoxContents()
{
templateBox->clear();
templateBox->addItem(QString());
for (const TitleTemplate &t : titletemplates) {
templateBox->addItem(t.icon, t.name, t.file);
}
}
TitleWidget::TitleWidget(const QUrl &url, const Timecode &tc, QString projectTitlePath, Monitor *monitor, QWidget *parent)
: QDialog(parent)
, Ui::TitleWidget_UI()
, m_startViewport(nullptr)
, m_endViewport(nullptr)
, m_count(0)
, m_unicodeDialog(new UnicodeDialog(UnicodeDialog::InputHex))
, m_missingMessage(nullptr)
, m_projectTitlePath(std::move(projectTitlePath))
, m_tc(tc)
, m_fps(pCore->getCurrentFps())
, m_guides(QList())
{
setupUi(this);
setMinimumSize(200, 200);
setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
frame_properties->setEnabled(false);
frame_properties->setFixedHeight(frame_toolbar->height());
int size = style()->pixelMetric(QStyle::PM_SmallIconSize);
QSize iconSize(size, size);
rectBColor->setAlphaChannelEnabled(true);
rectFColor->setAlphaChannelEnabled(true);
fontColorButton->setAlphaChannelEnabled(true);
textOutlineColor->setAlphaChannelEnabled(true);
shadowColor->setAlphaChannelEnabled(true);
auto *colorGroup = new QButtonGroup(this);
colorGroup->addButton(gradient_color);
colorGroup->addButton(plain_color);
auto *alignGroup = new QButtonGroup(this);
alignGroup->addButton(buttonAlignLeft);
alignGroup->addButton(buttonAlignCenter);
alignGroup->addButton(buttonAlignRight);
textOutline->setMinimum(0);
textOutline->setMaximum(200);
// textOutline->setDecimals(0);
textOutline->setValue(0);
textOutline->setToolTip(i18n("Outline width"));
backgroundAlpha->setMinimum(0);
backgroundAlpha->setMaximum(255);
bgAlphaSlider->setMinimum(0);
bgAlphaSlider->setMaximum(255);
backgroundAlpha->setValue(0);
backgroundAlpha->setToolTip(i18n("Background color opacity"));
itemrotatex->setMinimum(-360);
itemrotatex->setMaximum(360);
// itemrotatex->setDecimals(0);
itemrotatex->setValue(0);
itemrotatex->setToolTip(i18n("Rotation around the X axis"));
itemrotatey->setMinimum(-360);
itemrotatey->setMaximum(360);
// itemrotatey->setDecimals(0);
itemrotatey->setValue(0);
itemrotatey->setToolTip(i18n("Rotation around the Y axis"));
itemrotatez->setMinimum(-360);
itemrotatez->setMaximum(360);
// itemrotatez->setDecimals(0);
itemrotatez->setValue(0);
itemrotatez->setToolTip(i18n("Rotation around the Z axis"));
rectLineWidth->setMinimum(0);
rectLineWidth->setMaximum(500);
// rectLineWidth->setDecimals(0);
rectLineWidth->setValue(0);
rectLineWidth->setToolTip(i18n("Border width"));
itemzoom->setSuffix(i18n("%"));
QSize profileSize = pCore->getCurrentFrameSize();
m_frameWidth = (int)(profileSize.height() * pCore->getCurrentDar() + 0.5);
m_frameHeight = profileSize.height();
showToolbars(TITLE_SELECT);
splitter->setStretchFactor(0, 20);
// If project is drop frame, set the input mask as such.
title_duration->setInputMask(m_tc.mask());
title_duration->setText(m_tc.reformatSeparators(KdenliveSettings::title_duration()));
connect(backgroundColor, &KColorButton::changed, this, &TitleWidget::slotChangeBackground);
connect(backgroundAlpha, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotChangeBackground);
connect(shadowBox, &QGroupBox::toggled, this, &TitleWidget::slotUpdateShadow);
connect(shadowColor, &KColorButton::changed, this, &TitleWidget::slotUpdateShadow);
connect(blur_radius, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateShadow);
connect(shadowX, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateShadow);
connect(shadowY, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateShadow);
connect(fontColorButton, &KColorButton::changed, this, &TitleWidget::slotUpdateText);
connect(plain_color, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText);
connect(gradient_color, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText);
connect(gradients_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateText()));
connect(textOutlineColor, &KColorButton::changed, this, &TitleWidget::slotUpdateText);
connect(font_family, &QFontComboBox::currentFontChanged, this, &TitleWidget::slotUpdateText);
connect(font_size, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateText);
connect(letter_spacing, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateText);
connect(line_spacing, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateText);
connect(textOutline, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateText);
connect(font_weight_box, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateText()));
connect(rectFColor, &KColorButton::changed, this, &TitleWidget::rectChanged);
connect(rectBColor, &KColorButton::changed, this, &TitleWidget::rectChanged);
connect(plain_rect, &QAbstractButton::clicked, this, &TitleWidget::rectChanged);
connect(gradient_rect, &QAbstractButton::clicked, this, &TitleWidget::rectChanged);
connect(gradients_rect_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(rectChanged()));
connect(rectLineWidth, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::rectChanged);
connect(zValue, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::zIndexChanged);
connect(itemzoom, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::itemScaled);
connect(itemrotatex, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::itemRotateX);
connect(itemrotatey, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::itemRotateY);
connect(itemrotatez, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::itemRotateZ);
connect(itemhcenter, &QAbstractButton::clicked, this, &TitleWidget::itemHCenter);
connect(itemvcenter, &QAbstractButton::clicked, this, &TitleWidget::itemVCenter);
connect(itemtop, &QAbstractButton::clicked, this, &TitleWidget::itemTop);
connect(itembottom, &QAbstractButton::clicked, this, &TitleWidget::itemBottom);
connect(itemleft, &QAbstractButton::clicked, this, &TitleWidget::itemLeft);
connect(itemright, &QAbstractButton::clicked, this, &TitleWidget::itemRight);
connect(origin_x_left, &QAbstractButton::clicked, this, &TitleWidget::slotOriginXClicked);
connect(origin_y_top, &QAbstractButton::clicked, this, &TitleWidget::slotOriginYClicked);
connect(monitor, &Monitor::frameUpdated, this, &TitleWidget::slotGotBackground);
connect(this, &TitleWidget::requestBackgroundFrame, monitor, &Monitor::slotGetCurrentImage);
// Position and size
connect(value_w, static_cast(&QSpinBox::valueChanged), this, [this](int){slotValueChanged(ValueWidth);});
connect(value_h, static_cast(&QSpinBox::valueChanged), this, [this](int){slotValueChanged(ValueHeight);});
connect(value_x, static_cast(&QSpinBox::valueChanged), this, [this](int){slotValueChanged(ValueX);});
connect(value_y, static_cast(&QSpinBox::valueChanged), this, [this](int){slotValueChanged(ValueY);});
connect(buttonFitZoom, &QAbstractButton::clicked, this, &TitleWidget::slotAdjustZoom);
connect(buttonRealSize, &QAbstractButton::clicked, this, &TitleWidget::slotZoomOneToOne);
connect(buttonItalic, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText);
connect(buttonUnder, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText);
connect(buttonAlignLeft, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText);
connect(buttonAlignRight, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText);
connect(buttonAlignCenter, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText);
connect(edit_gradient, &QAbstractButton::clicked, this, &TitleWidget::slotEditGradient);
connect(edit_rect_gradient, &QAbstractButton::clicked, this, &TitleWidget::slotEditGradient);
connect(preserveAspectRatio, static_cast(&QCheckBox::stateChanged), [&] () {
slotValueChanged(ValueWidth);
});
displayBg->setChecked(KdenliveSettings::titlerShowbg());
connect(displayBg, static_cast(&QCheckBox::stateChanged), [&] (int state) {
KdenliveSettings::setTitlerShowbg(state == Qt::Checked);
displayBackgroundFrame();
});
connect(m_unicodeDialog, &UnicodeDialog::charSelected, this, &TitleWidget::slotInsertUnicodeString);
// mbd
connect(this, &QDialog::accepted, this, &TitleWidget::slotAccepted);
font_weight_box->blockSignals(true);
font_weight_box->addItem(i18nc("Font style", "Light"), QFont::Light);
font_weight_box->addItem(i18nc("Font style", "Normal"), QFont::Normal);
font_weight_box->addItem(i18nc("Font style", "Demi-Bold"), QFont::DemiBold);
font_weight_box->addItem(i18nc("Font style", "Bold"), QFont::Bold);
font_weight_box->addItem(i18nc("Font style", "Black"), QFont::Black);
font_weight_box->setToolTip(i18n("Font weight"));
font_weight_box->setCurrentIndex(1);
font_weight_box->blockSignals(false);
buttonFitZoom->setIconSize(iconSize);
buttonRealSize->setIconSize(iconSize);
buttonItalic->setIconSize(iconSize);
buttonUnder->setIconSize(iconSize);
buttonAlignCenter->setIconSize(iconSize);
buttonAlignLeft->setIconSize(iconSize);
buttonAlignRight->setIconSize(iconSize);
buttonFitZoom->setIcon(QIcon::fromTheme(QStringLiteral("zoom-fit-best")));
buttonRealSize->setIcon(QIcon::fromTheme(QStringLiteral("zoom-original")));
buttonItalic->setIcon(QIcon::fromTheme(QStringLiteral("format-text-italic")));
buttonUnder->setIcon(QIcon::fromTheme(QStringLiteral("format-text-underline")));
buttonAlignCenter->setIcon(QIcon::fromTheme(QStringLiteral("format-justify-center")));
buttonAlignLeft->setIcon(QIcon::fromTheme(QStringLiteral("format-justify-left")));
buttonAlignRight->setIcon(QIcon::fromTheme(QStringLiteral("format-justify-right")));
edit_gradient->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
edit_rect_gradient->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
buttonAlignRight->setToolTip(i18n("Align right"));
buttonAlignLeft->setToolTip(i18n("Align left"));
buttonAlignCenter->setToolTip(i18n("Align center"));
if (qApp->isLeftToRight()) {
buttonAlignRight->setChecked(true);
} else {
buttonAlignLeft->setChecked(true);
}
m_unicodeAction = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-insert-unicode")), QString(), this);
m_unicodeAction->setShortcut(Qt::SHIFT + Qt::CTRL + Qt::Key_U);
m_unicodeAction->setToolTip(getTooltipWithShortcut(i18n("Insert Unicode character"), m_unicodeAction));
connect(m_unicodeAction, &QAction::triggered, this, &TitleWidget::slotInsertUnicode);
buttonInsertUnicode->setDefaultAction(m_unicodeAction);
m_zUp = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-zindex-up")), QString(), this);
m_zUp->setShortcut(Qt::Key_PageUp);
m_zUp->setToolTip(i18n("Raise object"));
connect(m_zUp, &QAction::triggered, this, &TitleWidget::slotZIndexUp);
zUp->setDefaultAction(m_zUp);
m_zDown = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-zindex-down")), QString(), this);
m_zDown->setShortcut(Qt::Key_PageDown);
m_zDown->setToolTip(i18n("Lower object"));
connect(m_zDown, &QAction::triggered, this, &TitleWidget::slotZIndexDown);
zDown->setDefaultAction(m_zDown);
m_zTop = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-zindex-top")), QString(), this);
// TODO mbt 1414: Shortcut should change z index only if
// cursor is NOT in a text field ...
// m_zTop->setShortcut(Qt::Key_Home);
m_zTop->setToolTip(i18n("Raise object to top"));
connect(m_zTop, &QAction::triggered, this, &TitleWidget::slotZIndexTop);
zTop->setDefaultAction(m_zTop);
m_zBottom = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-zindex-bottom")), QString(), this);
// TODO mbt 1414
// m_zBottom->setShortcut(Qt::Key_End);
m_zBottom->setToolTip(i18n("Lower object to bottom"));
connect(m_zBottom, &QAction::triggered, this, &TitleWidget::slotZIndexBottom);
zBottom->setDefaultAction(m_zBottom);
m_selectAll = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-select-all")), QString(), this);
m_selectAll->setShortcut(Qt::CTRL + Qt::Key_A);
connect(m_selectAll, &QAction::triggered, this, &TitleWidget::slotSelectAll);
buttonSelectAll->setDefaultAction(m_selectAll);
m_selectText = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-select-texts")), QString(), this);
m_selectText->setShortcut(Qt::CTRL + Qt::Key_T);
connect(m_selectText, &QAction::triggered, this, &TitleWidget::slotSelectText);
buttonSelectText->setDefaultAction(m_selectText);
buttonSelectText->setEnabled(false);
m_selectRects = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-select-rects")), QString(), this);
m_selectRects->setShortcut(Qt::CTRL + Qt::Key_R);
connect(m_selectRects, &QAction::triggered, this, &TitleWidget::slotSelectRects);
buttonSelectRects->setDefaultAction(m_selectRects);
buttonSelectRects->setEnabled(false);
m_selectImages = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-select-images")), QString(), this);
m_selectImages->setShortcut(Qt::CTRL + Qt::Key_I);
connect(m_selectImages, &QAction::triggered, this, &TitleWidget::slotSelectImages);
buttonSelectImages->setDefaultAction(m_selectImages);
buttonSelectImages->setEnabled(false);
m_unselectAll = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-unselect-all")), QString(), this);
m_unselectAll->setShortcut(Qt::SHIFT + Qt::CTRL + Qt::Key_A);
connect(m_unselectAll, &QAction::triggered, this, &TitleWidget::slotSelectNone);
buttonUnselectAll->setDefaultAction(m_unselectAll);
buttonUnselectAll->setEnabled(false);
zDown->setIconSize(iconSize);
zTop->setIconSize(iconSize);
zBottom->setIconSize(iconSize);
zDown->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-zindex-down")));
zTop->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-zindex-top")));
zBottom->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-zindex-bottom")));
connect(zDown, &QAbstractButton::clicked, this, &TitleWidget::slotZIndexDown);
connect(zTop, &QAbstractButton::clicked, this, &TitleWidget::slotZIndexTop);
connect(zBottom, &QAbstractButton::clicked, this, &TitleWidget::slotZIndexBottom);
origin_x_left->setToolTip(i18n("Invert x axis and change 0 point"));
origin_y_top->setToolTip(i18n("Invert y axis and change 0 point"));
rectBColor->setToolTip(i18n("Select fill color"));
rectFColor->setToolTip(i18n("Select border color"));
zoom_slider->setToolTip(i18n("Zoom"));
buttonRealSize->setToolTip(i18n("Original size (1:1)"));
buttonFitZoom->setToolTip(i18n("Fit zoom"));
backgroundColor->setToolTip(i18n("Select background color"));
backgroundAlpha->setToolTip(i18n("Background opacity"));
buttonSelectAll->setToolTip(getTooltipWithShortcut(i18n("Select all"), m_selectAll));
buttonSelectText->setToolTip(getTooltipWithShortcut(i18n("Select text items in current selection"), m_selectText));
buttonSelectRects->setToolTip(getTooltipWithShortcut(i18n("Select rect items in current selection"), m_selectRects));
buttonSelectImages->setToolTip(getTooltipWithShortcut(i18n("Select image items in current selection"), m_selectImages));
buttonUnselectAll->setToolTip(getTooltipWithShortcut(i18n("Unselect all"), m_unselectAll));
itemhcenter->setIconSize(iconSize);
itemvcenter->setIconSize(iconSize);
itemtop->setIconSize(iconSize);
itembottom->setIconSize(iconSize);
itemright->setIconSize(iconSize);
itemleft->setIconSize(iconSize);
itemhcenter->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-align-hor")));
itemhcenter->setToolTip(i18n("Align item horizontally"));
itemvcenter->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-align-vert")));
itemvcenter->setToolTip(i18n("Align item vertically"));
itemtop->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-align-top")));
itemtop->setToolTip(i18n("Align item to top"));
itembottom->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-align-bottom")));
itembottom->setToolTip(i18n("Align item to bottom"));
itemright->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-align-right")));
itemright->setToolTip(i18n("Align item to right"));
itemleft->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-align-left")));
itemleft->setToolTip(i18n("Align item to left"));
auto *layout = new QHBoxLayout;
frame_toolbar->setLayout(layout);
layout->setContentsMargins(0, 0, 0, 0);
QToolBar *m_toolbar = new QToolBar(QStringLiteral("titleToolBar"), this);
m_toolbar->setIconSize(iconSize);
m_buttonCursor = m_toolbar->addAction(QIcon::fromTheme(QStringLiteral("transform-move")), i18n("Selection Tool"));
m_buttonCursor->setCheckable(true);
m_buttonCursor->setShortcut(Qt::ALT + Qt::Key_S);
m_buttonCursor->setToolTip(i18n("Selection Tool") + QLatin1Char(' ') + m_buttonCursor->shortcut().toString());
connect(m_buttonCursor, &QAction::triggered, this, &TitleWidget::slotSelectTool);
m_buttonText = m_toolbar->addAction(QIcon::fromTheme(QStringLiteral("insert-text")), i18n("Add Text"));
m_buttonText->setCheckable(true);
m_buttonText->setShortcut(Qt::ALT + Qt::Key_T);
m_buttonText->setToolTip(i18n("Add Text") + QLatin1Char(' ') + m_buttonText->shortcut().toString());
connect(m_buttonText, &QAction::triggered, this, &TitleWidget::slotTextTool);
m_buttonRect = m_toolbar->addAction(QIcon::fromTheme(QStringLiteral("kdenlive-insert-rect")), i18n("Add Rectangle"));
m_buttonRect->setCheckable(true);
m_buttonRect->setShortcut(Qt::ALT + Qt::Key_R);
m_buttonRect->setToolTip(i18n("Add Rectangle") + QLatin1Char(' ') + m_buttonRect->shortcut().toString());
connect(m_buttonRect, &QAction::triggered, this, &TitleWidget::slotRectTool);
m_buttonImage = m_toolbar->addAction(QIcon::fromTheme(QStringLiteral("insert-image")), i18n("Add Image"));
m_buttonImage->setCheckable(false);
m_buttonImage->setShortcut(Qt::ALT + Qt::Key_I);
m_buttonImage->setToolTip(i18n("Add Image") + QLatin1Char(' ') + m_buttonImage->shortcut().toString());
connect(m_buttonImage, &QAction::triggered, this, &TitleWidget::slotImageTool);
m_toolbar->addSeparator();
m_buttonLoad = m_toolbar->addAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("Open Document"));
m_buttonLoad->setCheckable(false);
m_buttonLoad->setShortcut(Qt::CTRL + Qt::Key_O);
m_buttonLoad->setToolTip(i18n("Open Document") + QLatin1Char(' ') + m_buttonLoad->shortcut().toString());
connect(m_buttonLoad, SIGNAL(triggered()), this, SLOT(loadTitle()));
m_buttonSave = m_toolbar->addAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n("Save As"));
m_buttonSave->setCheckable(false);
m_buttonSave->setShortcut(Qt::CTRL + Qt::Key_S);
m_buttonSave->setToolTip(i18n("Save As") + QLatin1Char(' ') + m_buttonSave->shortcut().toString());
connect(m_buttonSave, SIGNAL(triggered()), this, SLOT(saveTitle()));
m_buttonDownload = m_toolbar->addAction(QIcon::fromTheme(QStringLiteral("edit-download")), i18n("Download New Title Templates..."));
m_buttonDownload->setCheckable(false);
m_buttonDownload->setShortcut(Qt::ALT + Qt::Key_D);
m_buttonDownload->setToolTip(i18n("Download New Title Templates...") + QLatin1Char(' ') + m_buttonDownload->shortcut().toString());
connect(m_buttonDownload, &QAction::triggered, this, &TitleWidget::downloadTitleTemplates);
layout->addWidget(m_toolbar);
// initialize graphic scene
m_scene = new GraphicsSceneRectMove(this);
graphicsView->setScene(m_scene);
graphicsView->setMouseTracking(true);
graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
graphicsView->setDragMode(QGraphicsView::RubberBandDrag);
graphicsView->setRubberBandSelectionMode(Qt::ContainsItemBoundingRect);
m_titledocument.setScene(m_scene, m_frameWidth, m_frameHeight);
connect(m_scene, &QGraphicsScene::changed, this, &TitleWidget::slotChanged);
connect(font_size, static_cast(&QSpinBox::valueChanged), m_scene, &GraphicsSceneRectMove::slotUpdateFontSize);
connect(use_grid, &QAbstractButton::toggled, m_scene, &GraphicsSceneRectMove::slotUseGrid);
// Video frame rect
QPen framepen;
framepen.setColor(Qt::red);
m_frameBorder = new QGraphicsRectItem(QRectF(0, 0, m_frameWidth, m_frameHeight));
m_frameBorder->setPen(framepen);
m_frameBorder->setZValue(1000);
m_frameBorder->setBrush(Qt::transparent);
m_frameBorder->setFlags(nullptr);
m_frameBorder->setData(-1, -1);
graphicsView->scene()->addItem(m_frameBorder);
// Guides
connect(show_guides, &QCheckBox::stateChanged, this, &TitleWidget::showGuides);
show_guides->setChecked(KdenliveSettings::titlerShowGuides());
hguides->setValue(KdenliveSettings::titlerHGuides());
vguides->setValue(KdenliveSettings::titlerVGuides());
guideColor->setColor(KdenliveSettings::titleGuideColor());
connect(guideColor, &KColorButton::changed, this, &TitleWidget::guideColorChanged);
connect(hguides, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::updateGuides);
connect(vguides, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::updateGuides);
updateGuides(0);
// semi transparent safe zones
framepen.setColor(QColor(255, 0, 0, 100));
QGraphicsRectItem *safe1 = new QGraphicsRectItem(QRectF(m_frameWidth * 0.05, m_frameHeight * 0.05, m_frameWidth * 0.9, m_frameHeight * 0.9), m_frameBorder);
safe1->setBrush(Qt::transparent);
safe1->setPen(framepen);
safe1->setFlags(nullptr);
safe1->setData(-1, -1);
QGraphicsRectItem *safe2 = new QGraphicsRectItem(QRectF(m_frameWidth * 0.1, m_frameHeight * 0.1, m_frameWidth * 0.8, m_frameHeight * 0.8), m_frameBorder);
safe2->setBrush(Qt::transparent);
safe2->setPen(framepen);
safe2->setFlags(nullptr);
safe2->setData(-1, -1);
m_frameBackground = new QGraphicsRectItem(QRectF(0, 0, m_frameWidth, m_frameHeight));
m_frameBackground->setZValue(-1100);
m_frameBackground->setBrush(Qt::transparent);
m_frameBackground->setFlags(nullptr);
graphicsView->scene()->addItem(m_frameBackground);
m_frameImage = new QGraphicsPixmapItem();
QTransform qtrans;
qtrans.scale(2.0, 2.0);
m_frameImage->setTransform(qtrans);
m_frameImage->setZValue(-1200);
m_frameImage->setFlags(nullptr);
displayBackgroundFrame();
graphicsView->scene()->addItem(m_frameImage);
bgBox->setCurrentIndex(KdenliveSettings::titlerbg());
connect(bgBox, static_cast(&QComboBox::currentIndexChanged), [&] (int ix) {
KdenliveSettings::setTitlerbg(ix);
displayBackgroundFrame();
});
connect(m_scene, &QGraphicsScene::selectionChanged, this, &TitleWidget::selectionChanged);
connect(m_scene, &GraphicsSceneRectMove::itemMoved, this, &TitleWidget::selectionChanged);
connect(m_scene, &GraphicsSceneRectMove::sceneZoom, this, &TitleWidget::slotZoom);
connect(m_scene, &GraphicsSceneRectMove::actionFinished, this, &TitleWidget::slotSelectTool);
connect(m_scene, &GraphicsSceneRectMove::newRect, this, &TitleWidget::slotNewRect);
connect(m_scene, &GraphicsSceneRectMove::newText, this, &TitleWidget::slotNewText);
connect(zoom_slider, &QAbstractSlider::valueChanged, this, &TitleWidget::slotUpdateZoom);
connect(zoom_spin, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateZoom);
// mbd: load saved settings
loadGradients();
readChoices();
graphicsView->show();
graphicsView->setInteractive(true);
// qCDebug(KDENLIVE_LOG) << "// TITLE WIDGWT: " << graphicsView->viewport()->width() << 'x' << graphicsView->viewport()->height();
m_startViewport = new QGraphicsRectItem(QRectF(0, 0, m_frameWidth, m_frameHeight));
// Setting data at -1 so that the item is recognized as undeletable by graphicsscenerectmove
m_startViewport->setData(-1, -1);
m_endViewport = new QGraphicsRectItem(QRectF(0, 0, m_frameWidth, m_frameHeight));
m_endViewport->setData(-1, -1);
m_startViewport->setData(0, m_frameWidth);
m_startViewport->setData(1, m_frameHeight);
m_endViewport->setData(0, m_frameWidth);
m_endViewport->setData(1, m_frameHeight);
// scale the view so that the title widget is not too big at startup
graphicsView->scale(.5, .5);
if (url.isValid()) {
loadTitle(url);
} else {
prepareTools(nullptr);
slotTextTool();
QTimer::singleShot(200, this, &TitleWidget::slotAdjustZoom);
}
initAnimation();
QColor color = backgroundColor->color();
m_scene->setBackgroundBrush(QBrush(color));
color.setAlpha(backgroundAlpha->value());
m_frameBackground->setBrush(color);
connect(anim_start, &QAbstractButton::toggled, this, &TitleWidget::slotAnimStart);
connect(anim_end, &QAbstractButton::toggled, this, &TitleWidget::slotAnimEnd);
connect(templateBox, SIGNAL(currentIndexChanged(int)), this, SLOT(templateIndexChanged(int)));
buttonBox->button(QDialogButtonBox::Ok)->setEnabled(KdenliveSettings::hastitleproducer());
if (titletemplates.isEmpty()) {
refreshTitleTemplates(m_projectTitlePath);
}
// templateBox->setIconSize(QSize(60,60));
refreshTemplateBoxContents();
m_lastDocumentHash = QCryptographicHash::hash(xml().toString().toLatin1(), QCryptographicHash::Md5).toHex();
}
TitleWidget::~TitleWidget()
{
m_scene->blockSignals(true);
delete m_buttonRect;
delete m_buttonText;
delete m_buttonImage;
delete m_buttonCursor;
delete m_buttonSave;
delete m_buttonLoad;
delete m_unicodeAction;
delete m_zUp;
delete m_zDown;
delete m_zTop;
delete m_zBottom;
delete m_selectAll;
delete m_selectText;
delete m_selectRects;
delete m_selectImages;
delete m_unselectAll;
delete m_unicodeDialog;
delete m_frameBorder;
delete m_frameImage;
delete m_startViewport;
delete m_endViewport;
delete m_scene;
}
// static
QStringList TitleWidget::extractImageList(const QString &xml)
{
QStringList result;
if (xml.isEmpty()) {
return result;
}
QDomDocument doc;
doc.setContent(xml);
QDomNodeList images = doc.elementsByTagName(QStringLiteral("content"));
for (int i = 0; i < images.count(); ++i) {
if (images.at(i).toElement().hasAttribute(QStringLiteral("url"))) {
result.append(images.at(i).toElement().attribute(QStringLiteral("url")));
}
}
return result;
}
// static
QStringList TitleWidget::extractFontList(const QString &xml)
{
QStringList result;
if (xml.isEmpty()) {
return result;
}
QDomDocument doc;
doc.setContent(xml);
QDomNodeList images = doc.elementsByTagName(QStringLiteral("content"));
for (int i = 0; i < images.count(); ++i) {
if (images.at(i).toElement().hasAttribute(QStringLiteral("font"))) {
result.append(images.at(i).toElement().attribute(QStringLiteral("font")));
}
}
return result;
}
// static
void TitleWidget::refreshTitleTemplates(const QString &projectPath)
{
QStringList filters = QStringList() << QStringLiteral("*.kdenlivetitle");
titletemplates.clear();
// project templates
QDir dir(projectPath);
QStringList templateFiles = dir.entryList(filters, QDir::Files);
for (const QString &fname : templateFiles) {
TitleTemplate t;
t.name = fname;
t.file = dir.absoluteFilePath(fname);
t.icon = QIcon(KThumb::getImage(QUrl::fromLocalFile(t.file), 0, 60, -1));
titletemplates.append(t);
}
// system templates
QStringList titleTemplates = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("titles/"), QStandardPaths::LocateDirectory);
for (const QString &folderpath : titleTemplates) {
QDir folder(folderpath);
QStringList filesnames = folder.entryList(filters, QDir::Files);
for (const QString &fname : filesnames) {
TitleTemplate t;
t.name = fname;
t.file = folder.absoluteFilePath(fname);
t.icon = QIcon(KThumb::getImage(QUrl::fromLocalFile(t.file), 0, 60, -1));
titletemplates.append(t);
}
}
}
void TitleWidget::templateIndexChanged(int index)
{
QString item = templateBox->itemData(index).toString();
if (!item.isEmpty()) {
if (m_lastDocumentHash != QCryptographicHash::hash(xml().toString().toLatin1(), QCryptographicHash::Md5).toHex()) {
if (KMessageBox::questionYesNo(this, i18n("Do you really want to load a new template? Changes in this title will be lost!")) == KMessageBox::No) {
return;
}
}
loadTitle(QUrl::fromLocalFile(item));
// mbt 1607: Add property to distinguish between unchanged template titles and user titles.
// Text of unchanged template titles should be selected when clicked.
QList list = graphicsView->scene()->items();
for (QGraphicsItem *qgItem : list) {
if (qgItem->type() == TEXTITEM) {
auto *i = static_cast(qgItem);
i->setProperty("isTemplate", "true");
i->setProperty("templateText", i->toHtml());
}
}
m_lastDocumentHash = QCryptographicHash::hash(xml().toString().toLatin1(), QCryptographicHash::Md5).toHex();
}
}
// virtual
void TitleWidget::resizeEvent(QResizeEvent * /*event*/)
{
// slotAdjustZoom();
}
// virtual
void TitleWidget::keyPressEvent(QKeyEvent *e)
{
if (e->key() != Qt::Key_Escape && e->key() != Qt::Key_Return && e->key() != Qt::Key_Enter) {
QDialog::keyPressEvent(e);
}
}
void TitleWidget::slotTextTool()
{
m_scene->setTool(TITLE_TEXT);
showToolbars(TITLE_TEXT);
checkButton(TITLE_TEXT);
}
void TitleWidget::slotRectTool()
{
m_scene->setTool(TITLE_RECTANGLE);
showToolbars(TITLE_RECTANGLE);
checkButton(TITLE_RECTANGLE);
// Disable dragging mode, would make dragging a rect impossible otherwise ;)
graphicsView->setDragMode(QGraphicsView::NoDrag);
}
void TitleWidget::slotSelectTool()
{
m_scene->setTool(TITLE_SELECT);
// Enable rubberband selecting mode.
graphicsView->setDragMode(QGraphicsView::RubberBandDrag);
// Find out which toolbars need to be shown, depending on selected item
TITLETOOL t = TITLE_SELECT;
QList l = graphicsView->scene()->selectedItems();
if (!l.isEmpty()) {
switch (l.at(0)->type()) {
case TEXTITEM:
t = TITLE_TEXT;
break;
case RECTITEM:
t = TITLE_RECTANGLE;
break;
case IMAGEITEM:
t = TITLE_IMAGE;
break;
}
}
enableToolbars(t);
if (t == TITLE_RECTANGLE && (l.at(0) == m_endViewport || l.at(0) == m_startViewport)) {
// graphicsView->centerOn(l.at(0));
t = TITLE_SELECT;
}
showToolbars(t);
if (!l.isEmpty()) {
updateCoordinates(l.at(0));
updateDimension(l.at(0));
updateRotZoom(l.at(0));
}
checkButton(TITLE_SELECT);
}
void TitleWidget::slotImageTool()
{
QList supported = QImageReader::supportedImageFormats();
QStringList mimeTypeFilters;
QString allExtensions = i18n("All Images") + QStringLiteral(" (");
for (const QByteArray &mimeType : supported) {
mimeTypeFilters.append(i18n("%1 Image", QString(mimeType)) + QStringLiteral("( *.") + QString(mimeType) + QLatin1Char(')'));
allExtensions.append(QStringLiteral("*.") + mimeType + QLatin1Char(' '));
}
mimeTypeFilters.sort();
allExtensions.append(QLatin1Char(')'));
mimeTypeFilters.prepend(allExtensions);
QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveImageFolder"));
if (clipFolder.isEmpty()) {
clipFolder = QDir::homePath();
}
QFileDialog dialog(this, i18n("Add Image"), clipFolder);
dialog.setAcceptMode(QFileDialog::AcceptOpen);
dialog.setNameFilters(mimeTypeFilters);
if (dialog.exec() != QDialog::Accepted) {
return;
}
QUrl url = QUrl::fromLocalFile(dialog.selectedFiles().at(0));
if (url.isValid()) {
KRecentDirs::add(QStringLiteral(":KdenliveImageFolder"), url.adjusted(QUrl::RemoveFilename).toLocalFile());
if (url.toLocalFile().endsWith(QLatin1String(".svg"))) {
MySvgItem *svg = new MySvgItem(url.toLocalFile());
svg->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemSendsGeometryChanges);
svg->setZValue(m_count++);
svg->setData(Qt::UserRole, url.toLocalFile());
m_scene->addNewItem(svg);
prepareTools(svg);
} else {
QPixmap pix(url.toLocalFile());
auto *image = new MyPixmapItem(pix);
image->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
image->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemSendsGeometryChanges);
image->setData(Qt::UserRole, url.toLocalFile());
image->setZValue(m_count++);
m_scene->addNewItem(image);
prepareTools(image);
}
}
m_scene->setTool(TITLE_SELECT);
showToolbars(TITLE_SELECT);
checkButton(TITLE_SELECT);
}
void TitleWidget::showToolbars(TITLETOOL toolType)
{
toolbar_stack->setEnabled(toolType != TITLE_SELECT);
switch (toolType) {
case TITLE_IMAGE:
toolbar_stack->setCurrentIndex(2);
break;
case TITLE_RECTANGLE:
toolbar_stack->setCurrentIndex(1);
break;
case TITLE_TEXT:
default:
toolbar_stack->setCurrentIndex(0);
break;
}
}
void TitleWidget::enableToolbars(TITLETOOL toolType)
{
// TITLETOOL is defined in effectstack/graphicsscenerectmove.h
bool enable = false;
if (toolType == TITLE_RECTANGLE || toolType == TITLE_IMAGE) {
enable = true;
}
value_w->setEnabled(enable);
value_h->setEnabled(enable);
}
void TitleWidget::checkButton(TITLETOOL toolType)
{
bool bSelect = false;
bool bText = false;
bool bRect = false;
bool bImage = false;
switch (toolType) {
case TITLE_SELECT:
bSelect = true;
break;
case TITLE_TEXT:
bText = true;
break;
case TITLE_RECTANGLE:
bRect = true;
break;
case TITLE_IMAGE:
bImage = true;
break;
default:
break;
}
m_buttonCursor->setChecked(bSelect);
m_buttonText->setChecked(bText);
m_buttonRect->setChecked(bRect);
m_buttonImage->setChecked(bImage);
}
void TitleWidget::displayBackgroundFrame()
{
QRectF r = m_frameBorder->sceneBoundingRect();
if (!displayBg->isChecked()) {
switch (KdenliveSettings::titlerbg()) {
case 0: {
QPixmap pattern(20, 20);
pattern.fill(Qt::gray);
QColor bgcolor(180, 180, 180);
QPainter p(&pattern);
p.fillRect(QRect(0, 0, 10, 10), bgcolor);
p.fillRect(QRect(10, 10, 20, 20), bgcolor);
p.end();
QBrush br(pattern);
QPixmap bg((int)(r.width() / 2), (int)(r.height() / 2));
QPainter p2(&bg);
p2.fillRect(bg.rect(), br);
p2.end();
m_frameImage->setPixmap(bg);
break;
}
default: {
QColor col = KdenliveSettings::titlerbg() == 1 ? Qt::black : Qt::white;
QPixmap bg((int)(r.width() / 2), (int)(r.height() / 2));
QPainter p2(&bg);
p2.fillRect(bg.rect(), col);
p2.end();
m_frameImage->setPixmap(bg);
}
}
} else {
emit requestBackgroundFrame(true);
}
}
void TitleWidget::slotGotBackground(const QImage &img)
{
QRectF r = m_frameBorder->sceneBoundingRect();
m_frameImage->setPixmap(QPixmap::fromImage(img.scaled(r.width() / 2, r.height() / 2)));
emit requestBackgroundFrame(false);
}
void TitleWidget::initAnimation()
{
align_box->setEnabled(false);
QPen startpen(Qt::DotLine);
QPen endpen(Qt::DashDotLine);
startpen.setColor(QColor(100, 200, 100, 140));
endpen.setColor(QColor(200, 100, 100, 140));
m_startViewport->setPen(startpen);
m_endViewport->setPen(endpen);
m_startViewport->setZValue(-1000);
m_endViewport->setZValue(-1000);
m_startViewport->setFlags(nullptr);
m_endViewport->setFlags(nullptr);
graphicsView->scene()->addItem(m_startViewport);
graphicsView->scene()->addItem(m_endViewport);
connect(keep_aspect, &QAbstractButton::toggled, this, &TitleWidget::slotKeepAspect);
connect(resize50, &QAbstractButton::clicked, this, &TitleWidget::slotResize50);
connect(resize100, &QAbstractButton::clicked, this, &TitleWidget::slotResize100);
connect(resize200, &QAbstractButton::clicked, this, &TitleWidget::slotResize200);
}
void TitleWidget::slotUpdateZoom(int pos)
{
zoom_spin->setValue(pos);
zoom_slider->setValue(pos);
m_scene->setZoom((double)pos / 100);
}
void TitleWidget::slotZoom(bool up)
{
int pos = zoom_slider->value();
if (up) {
pos++;
} else {
pos--;
}
zoom_slider->setValue(pos);
}
void TitleWidget::slotAdjustZoom()
{
/*double scalex = graphicsView->width() / (double)(m_frameWidth * 1.2);
double scaley = graphicsView->height() / (double)(m_frameHeight * 1.2);
if (scalex > scaley) scalex = scaley;
int zoompos = (int)(scalex * 7 + 0.5);*/
graphicsView->fitInView(m_frameBorder, Qt::KeepAspectRatio);
int zoompos = graphicsView->matrix().m11() * 100;
zoom_slider->setValue(zoompos);
graphicsView->centerOn(m_frameBorder);
}
void TitleWidget::slotZoomOneToOne()
{
zoom_slider->setValue(100);
graphicsView->centerOn(m_frameBorder);
}
void TitleWidget::slotNewRect(QGraphicsRectItem *rect)
{
updateAxisButtons(rect); // back to default
if (rectLineWidth->value() == 0) {
rect->setPen(Qt::NoPen);
} else {
QPen penf(rectFColor->color());
penf.setWidth(rectLineWidth->value());
penf.setJoinStyle(Qt::RoundJoin);
rect->setPen(penf);
}
if (plain_rect->isChecked()) {
rect->setBrush(QBrush(rectBColor->color()));
rect->setData(TitleDocument::Gradient, QVariant());
} else {
// gradient
QString gradientData = gradients_rect_combo->currentData().toString();
rect->setData(TitleDocument::Gradient, gradientData);
QLinearGradient gr = GradientWidget::gradientFromString(gradientData, rect->boundingRect().width(), rect->boundingRect().height());
rect->setBrush(QBrush(gr));
}
rect->setZValue(m_count++);
rect->setData(TitleDocument::ZoomFactor, 100);
prepareTools(rect);
// setCurrentItem(rect);
// graphicsView->setFocus();
}
void TitleWidget::slotNewText(MyTextItem *tt)
{
updateAxisButtons(tt); // back to default
letter_spacing->blockSignals(true);
line_spacing->blockSignals(true);
letter_spacing->setValue(0);
line_spacing->setValue(0);
letter_spacing->blockSignals(false);
line_spacing->blockSignals(false);
letter_spacing->setEnabled(true);
line_spacing->setEnabled(true);
QFont font = font_family->currentFont();
font.setPixelSize(font_size->value());
// mbd: issue 551:
font.setWeight(font_weight_box->itemData(font_weight_box->currentIndex()).toInt());
font.setItalic(buttonItalic->isChecked());
font.setUnderline(buttonUnder->isChecked());
tt->setFont(font);
QColor color = fontColorButton->color();
QColor outlineColor = textOutlineColor->color();
tt->setTextColor(color);
tt->document()->setDocumentMargin(0);
QTextCursor cur(tt->document());
cur.select(QTextCursor::Document);
QTextBlockFormat format = cur.blockFormat();
QTextCharFormat cformat = cur.charFormat();
double outlineWidth = textOutline->value() / 10.0;
tt->setData(TitleDocument::OutlineWidth, outlineWidth);
tt->setData(TitleDocument::OutlineColor, outlineColor);
if (outlineWidth > 0.0) {
cformat.setTextOutline(QPen(outlineColor, outlineWidth));
}
tt->updateShadow(shadowBox->isChecked(), blur_radius->value(), shadowX->value(), shadowY->value(), shadowColor->color());
if (gradient_color->isChecked()) {
QString gradientData = gradients_combo->currentData().toString();
tt->setData(TitleDocument::Gradient, gradientData);
QLinearGradient gr = GradientWidget::gradientFromString(gradientData, tt->boundingRect().width(), tt->boundingRect().height());
cformat.setForeground(QBrush(gr));
} else {
cformat.setForeground(QBrush(color));
}
cur.setCharFormat(cformat);
cur.setBlockFormat(format);
tt->setTextCursor(cur);
tt->setZValue(m_count++);
setCurrentItem(tt);
prepareTools(tt);
}
void TitleWidget::setFontBoxWeight(int weight)
{
int index = font_weight_box->findData(weight);
if (index < 0) {
index = font_weight_box->findData(QFont::Normal);
}
font_weight_box->setCurrentIndex(index);
}
void TitleWidget::setCurrentItem(QGraphicsItem *item)
{
m_scene->setSelectedItem(item);
}
void TitleWidget::zIndexChanged(int v)
{
QList l = graphicsView->scene()->selectedItems();
for (auto &i : l) {
i->setZValue(v);
}
}
void TitleWidget::selectionChanged()
{
if (m_scene->tool() != TITLE_SELECT) {
return;
}
// qCDebug(KDENLIVE_LOG) << "Number of selected items: " << graphicsView->scene()->selectedItems().length() << '\n';
QList l;
// mbt 1607: One text item might have grabbed the keyboard.
// Ungrab it for all items that are not selected, otherwise
// text input would only work for the text item that grabbed
// the keyboard last.
l = graphicsView->scene()->items();
for (QGraphicsItem *item : l) {
if (item->type() == TEXTITEM && !item->isSelected()) {
auto *i = static_cast(item);
i->clearFocus();
}
}
l = graphicsView->scene()->selectedItems();
if (!l.isEmpty()) {
buttonUnselectAll->setEnabled(true);
// Enable all z index buttons if items selected.
// We can selectively disable them later.
zUp->setEnabled(true);
zDown->setEnabled(true);
zTop->setEnabled(true);
zBottom->setEnabled(true);
} else {
buttonUnselectAll->setEnabled(false);
}
if (l.size() >= 2) {
buttonSelectText->setEnabled(true);
buttonSelectRects->setEnabled(true);
buttonSelectImages->setEnabled(true);
} else {
buttonSelectText->setEnabled(false);
buttonSelectRects->setEnabled(false);
buttonSelectImages->setEnabled(false);
}
if (l.size() == 0) {
prepareTools(nullptr);
} else if (l.size() == 1) {
prepareTools(l.at(0));
} else {
/*
For multiple selected objects we need to decide which tools to show.
*/
int firstType = l.at(0)->type();
bool allEqual = true;
for (auto i : l) {
if (i->type() != firstType) {
allEqual = false;
break;
}
}
// qCDebug(KDENLIVE_LOG) << "All equal? " << allEqual << ".\n";
if (allEqual) {
prepareTools(l.at(0));
} else {
// Get the default toolset, but enable the property frame (x,y,w,h)
prepareTools(nullptr);
frame_properties->setEnabled(true);
// Enable x/y/w/h if it makes sense.
value_x->setEnabled(true);
value_y->setEnabled(true);
bool containsTextitem = false;
for (auto i : l) {
if (i->type() == TEXTITEM) {
containsTextitem = true;
break;
}
}
if (!containsTextitem) {
value_w->setEnabled(true);
value_h->setEnabled(true);
}
}
// Disable z index buttons if they don't make sense for the current selection
int firstZindex = l.at(0)->zValue();
allEqual = true;
for (auto &i : l) {
if ((int)i->zValue() != firstZindex) {
allEqual = false;
break;
}
}
if (!allEqual) {
zUp->setEnabled(false);
zDown->setEnabled(false);
}
}
}
void TitleWidget::slotValueChanged(int type)
{
/*
type tells us which QSpinBox value has changed.
*/
QList l = graphicsView->scene()->selectedItems();
// qCDebug(KDENLIVE_LOG) << l.size() << " items to be resized\n";
// Get the updated value here already to do less coding afterwards
int val = 0;
switch (type) {
case ValueWidth:
val = value_w->value();
break;
case ValueHeight:
val = value_h->value();
break;
case ValueX:
val = value_x->value();
break;
case ValueY:
val = value_y->value();
break;
}
for (int k = 0; k < l.size(); ++k) {
// qCDebug(KDENLIVE_LOG) << "Type of item " << k << ": " << l.at(k)->type() << '\n';
if (l.at(k)->type() == TEXTITEM) {
// Just update the position. We don't allow setting width/height for text items yet.
switch (type) {
case ValueX:
updatePosition(l.at(k), val, l.at(k)->pos().y());
break;
case ValueY:
updatePosition(l.at(k), l.at(k)->pos().x(), val);
break;
}
} else if (l.at(k)->type() == RECTITEM) {
auto *rec = static_cast