übernahme Code Shortcut

This commit is contained in:
georg0480
2026-01-31 15:28:10 +01:00
parent 6f4d6b9301
commit ef46c21291
1787 changed files with 1126465 additions and 0 deletions

2984
src/docks/encodedock.cpp Normal file

File diff suppressed because it is too large Load Diff

205
src/docks/encodedock.h Normal file
View File

@@ -0,0 +1,205 @@
/*
* Copyright (c) 2012-2026 Meltytech, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ENCODEDOCK_H
#define ENCODEDOCK_H
#include "settings.h"
#include <MltProperties.h>
#include <QDockWidget>
#include <QDomElement>
#include <QSortFilterProxyModel>
#include <QStandardItemModel>
#include <QStringList>
class QTreeWidgetItem;
class QTemporaryFile;
namespace Ui {
class EncodeDock;
}
class AbstractJob;
class MeltJob;
namespace Mlt {
class Service;
class Producer;
class Filter;
} // namespace Mlt
class PresetsProxyModel : public QSortFilterProxyModel
{
protected:
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const;
};
class EncodeDock : public QDockWidget
{
Q_OBJECT
public:
explicit EncodeDock(QWidget *parent = 0);
~EncodeDock();
void loadPresetFromProperties(Mlt::Properties &);
bool isExportInProgress() const;
signals:
void captureStateChanged(bool);
void createOrEditFilterOnOutput(Mlt::Filter *, const QStringList & = {});
public slots:
void onAudioChannelsChanged();
void onProducerOpened();
void onProfileChanged();
void onReframeChanged();
void on_hwencodeButton_clicked();
bool detectHardwareEncoders();
private slots:
void on_presetsTree_clicked(const QModelIndex &index);
void on_presetsTree_activated(const QModelIndex &index);
void on_encodeButton_clicked();
void on_streamButton_clicked();
void on_addPresetButton_clicked();
void on_removePresetButton_clicked();
void onFinished(AbstractJob *, bool isSuccess);
void on_stopCaptureButton_clicked();
void on_videoRateControlCombo_activated(int index);
void on_audioRateControlCombo_activated(int index);
void on_scanModeCombo_currentIndexChanged(int index);
void on_presetsSearch_textChanged(const QString &search);
void on_resetButton_clicked();
void openCaptureFile();
void on_formatCombo_currentIndexChanged(int index);
void on_videoBufferDurationChanged();
void on_gopSpinner_valueChanged(int value);
void on_fromCombo_currentIndexChanged(int index);
void on_videoCodecCombo_currentIndexChanged(int index);
void on_audioCodecCombo_currentIndexChanged(int index);
void setAudioChannels(int channels);
void on_widthSpinner_editingFinished();
void on_heightSpinner_editingFinished();
void on_advancedButton_clicked(bool checked);
void on_hwencodeCheckBox_clicked(bool checked);
void on_hwdecodeCheckBox_clicked(bool checked);
void on_advancedCheckBox_clicked(bool checked);
void on_fpsSpinner_editingFinished();
void on_fpsComboBox_activated(int arg1);
void on_videoQualitySpinner_valueChanged(int vq);
void on_audioQualitySpinner_valueChanged(int aq);
void on_parallelCheckbox_clicked(bool checked);
void on_resolutionComboBox_activated(int arg1);
void on_reframeButton_clicked();
void on_aspectNumSpinner_valueChanged(int value);
void on_aspectDenSpinner_valueChanged(int value);
private:
enum {
RateControlAverage = 0,
RateControlConstant,
RateControlQuality,
RateControlConstrained
};
enum {
AudioChannels1 = 0,
AudioChannels2,
AudioChannels4,
AudioChannels6,
};
Ui::EncodeDock *ui;
Mlt::Properties *m_presets;
QScopedPointer<MeltJob> m_immediateJob;
QString m_extension;
Mlt::Properties *m_profiles;
PresetsProxyModel m_presetsModel;
QStringList m_outputFilenames;
bool m_isDefaultSettings;
double m_fps;
QStringList m_intraOnlyCodecs;
QStringList m_losslessVideoCodecs;
QStringList m_losslessAudioCodecs;
void loadPresets();
Mlt::Properties *collectProperties(int realtime, bool includeProfile = false);
void collectProperties(QDomElement &node, int realtime);
void setSubtitleProperties(QDomElement &node, Mlt::Producer *service);
QPoint addConsumerElement(
Mlt::Producer *service, QDomDocument &dom, const QString &target, int realtime, int pass);
MeltJob *convertReframe(Mlt::Producer *service,
QTemporaryFile *tmp,
const QString &target,
int realtime,
int pass,
const QThread::Priority priority);
MeltJob *createMeltJob(Mlt::Producer *service,
const QString &target,
int realtime,
int pass = 0,
const QThread::Priority priority = Settings.jobPriority());
void runMelt(const QString &target, int realtime = -1);
void enqueueAnalysis();
void enqueueMelt(const QStringList &targets, int realtime);
void encode(const QString &target);
void resetOptions();
Mlt::Producer *fromProducer(bool usePlaylistBin = false) const;
static void filterCodecParams(const QString &vcodec, QStringList &other);
void onVideoCodecComboChanged(int index, bool ignorePreset = false, bool resetBframes = true);
bool checkForMissingFiles();
QString &defaultFormatExtension();
void initSpecialCodecLists();
void setReframeEnabled(bool enabled);
void showResampleWarning(const QString &message);
void hideResampleWarning(bool hide = true);
void checkFrameRate();
void setResolutionAspectFromProfile();
};
#endif // ENCODEDOCK_H

2129
src/docks/encodedock.ui Normal file

File diff suppressed because it is too large Load Diff

1400
src/docks/filesdock.cpp Normal file

File diff suppressed because it is too large Load Diff

111
src/docks/filesdock.h Normal file
View File

@@ -0,0 +1,111 @@
/*
* Copyright (c) 2024-2025 Meltytech, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef FILESDOCK_H
#define FILESDOCK_H
#include <QDockWidget>
#include <QFileSystemModel>
#include <QHash>
#include <QMutex>
#include <QTimer>
#include <QUndoCommand>
namespace Ui {
class FilesDock;
}
class QAbstractItemView;
class QItemSelectionModel;
class QMenu;
class PlaylistIconView;
class FilesModel;
class FilesProxyModel;
class QSortFilterProxyModel;
class LineEditClear;
class QLabel;
class FilesDock : public QDockWidget
{
Q_OBJECT
public:
explicit FilesDock(QWidget *parent = 0);
~FilesDock();
struct CacheItem
{
int mediaType{-1}; // -1 = unknown
};
int getCacheMediaType(const QString &key);
void setCacheMediaType(const QString &key, int mediaType);
signals:
void selectionChanged();
public slots:
void onOpenActionTriggered();
void changeDirectory(const QString &path, bool updateLocation = true);
void changeFilesDirectory(const QModelIndex &index);
private slots:
void viewCustomContextMenuRequested(const QPoint &pos);
void onMediaTypeClicked();
void onOpenOtherAdd();
void onOpenOtherRemove();
void clearStatus();
void updateStatus();
void onLocationsEditingFinished();
void on_locationsCombo_activated(int index);
void on_addLocationButton_clicked();
void on_removeLocationButton_clicked();
protected:
void keyPressEvent(QKeyEvent *event);
void keyReleaseEvent(QKeyEvent *event);
private:
void setupActions();
void emitDataChanged(const QVector<int> &roles);
void updateViewMode();
void onUpdateThumbnailsActionTriggered();
void onSelectAllActionTriggered();
void incrementIndex(int step);
void addOpenWithMenu(QMenu *menu);
QString firstSelectedFilePath();
QString firstSelectedMediaType();
void openClip(const QString &filePath);
Ui::FilesDock *ui;
QAbstractItemView *m_view;
PlaylistIconView *m_iconsView;
std::unique_ptr<QFileSystemModel> m_dirsModel;
FilesModel *m_filesModel;
QItemSelectionModel *m_selectionModel;
QMenu *m_mainMenu;
FilesProxyModel *m_filesProxyModel;
QHash<QString, CacheItem> m_cache;
QMutex m_cacheMutex;
LineEditClear *m_searchField;
QLabel *m_label;
};
#endif // FILESDOCK_H

210
src/docks/filesdock.ui Normal file
View File

@@ -0,0 +1,210 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FilesDock</class>
<widget class="QDockWidget" name="FilesDock">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>460</width>
<height>278</height>
</rect>
</property>
<property name="windowTitle">
<string>Files</string>
</property>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="locationsLayout">
<property name="spacing">
<number>5</number>
</property>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Policy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>5</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Location</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="locationsCombo">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>true</bool>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon</enum>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="addLocationButton">
<property name="maximumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="toolTip">
<string>Add the current folder to the saved locations</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="list-add" resource="../../icons/resources.qrc">
<normaloff>:/icons/oxygen/32x32/actions/list-add.png</normaloff>:/icons/oxygen/32x32/actions/list-add.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeLocationButton">
<property name="maximumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="toolTip">
<string>Remove the selected location</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="list-remove" resource="../../icons/resources.qrc">
<normaloff>:/icons/oxygen/32x32/actions/list-remove.png</normaloff>:/icons/oxygen/32x32/actions/list-remove.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="filtersLayout"/>
</item>
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<widget class="QTreeView" name="treeView">
<property name="acceptDrops">
<bool>true</bool>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
<property name="animated">
<bool>true</bool>
</property>
</widget>
<widget class="QWidget" name="layoutWidget_2">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="PlaylistTable" name="tableView">
<property name="contextMenuPolicy">
<enum>Qt::ContextMenuPolicy::CustomContextMenu</enum>
</property>
<property name="toolTip">
<string/>
</property>
<property name="dragEnabled">
<bool>false</bool>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SelectionMode::ExtendedSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectionBehavior::SelectRows</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollMode::ScrollPerPixel</enum>
</property>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>200</number>
</attribute>
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<widget class="PlaylistListView" name="listView">
<property name="contextMenuPolicy">
<enum>Qt::ContextMenuPolicy::CustomContextMenu</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SelectionMode::ExtendedSelection</enum>
</property>
<property name="movement">
<enum>QListView::Movement::Static</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>PlaylistTable</class>
<extends>QTableView</extends>
<header>widgets/playlisttable.h</header>
</customwidget>
<customwidget>
<class>PlaylistListView</class>
<extends>QListView</extends>
<header>widgets/playlistlistview.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../../icons/resources.qrc"/>
</resources>
<connections/>
</ui>

271
src/docks/filtersdock.cpp Normal file
View File

@@ -0,0 +1,271 @@
/*
* Copyright (c) 2013-2026 Meltytech, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "filtersdock.h"
#include "Logger.h"
#include "actions.h"
#include "controllers/filtercontroller.h"
#include "mainwindow.h"
#include "mltcontroller.h"
#include "models/attachedfiltersmodel.h"
#include "models/metadatamodel.h"
#include "models/motiontrackermodel.h"
#include "models/subtitlesmodel.h"
#include "qmltypes/qmlapplication.h"
#include "qmltypes/qmlfilter.h"
#include "qmltypes/qmlutilities.h"
#include "qmltypes/qmlview.h"
#include <QAction>
#include <QDir>
#include <QIcon>
#include <QMenu>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQuickItem>
#include <QUrl>
#include <QtWidgets/QScrollArea>
FiltersDock::FiltersDock(MetadataModel *metadataModel,
AttachedFiltersModel *attachedModel,
MotionTrackerModel *motionTrackerModel,
SubtitlesModel *subtitlesModel,
QWidget *parent)
: QDockWidget(tr("Filters"), parent)
, m_qview(QmlUtilities::sharedEngine(), this)
{
LOG_DEBUG() << "begin";
setObjectName("FiltersDock");
setWhatsThis("https://forum.shotcut.org/t/about-filters/48127/1");
QIcon icon = QIcon::fromTheme("view-filter",
QIcon(":/icons/oxygen/32x32/actions/view-filter.png"));
toggleViewAction()->setIcon(icon);
setMinimumSize(200, 200);
setupActions();
m_qview.setFocusPolicy(Qt::StrongFocus);
m_qview.quickWindow()->setPersistentSceneGraph(false);
#ifndef Q_OS_MAC
m_qview.setAttribute(Qt::WA_AcceptTouchEvents);
#endif
setWidget(&m_qview);
QmlUtilities::setCommonProperties(m_qview.rootContext());
m_qview.rootContext()->setContextProperty("view", new QmlView(&m_qview));
m_qview.rootContext()->setContextProperty("metadatamodel", metadataModel);
m_qview.rootContext()->setContextProperty("motionTrackerModel", motionTrackerModel);
m_qview.rootContext()->setContextProperty("subtitlesModel", subtitlesModel);
m_qview.rootContext()->setContextProperty("attachedfiltersmodel", attachedModel);
m_qview.rootContext()->setContextProperty("producer", &m_producer);
connect(&m_producer, SIGNAL(seeked(int)), SIGNAL(seeked(int)));
connect(this, SIGNAL(producerInChanged(int)), &m_producer, SIGNAL(inChanged(int)));
connect(this, SIGNAL(producerOutChanged(int)), &m_producer, SIGNAL(outChanged(int)));
connect(m_qview.quickWindow(),
&QQuickWindow::sceneGraphInitialized,
this,
&FiltersDock::load,
Qt::QueuedConnection);
setCurrentFilter(0, 0, QmlFilter::NoCurrentFilter);
LOG_DEBUG() << "end";
}
void FiltersDock::setCurrentFilter(QmlFilter *filter, QmlMetadata *meta, int index)
{
if (filter && filter->producer().is_valid()) {
m_producer.setProducer(filter->producer());
if (mlt_service_playlist_type != filter->producer().type() && MLT.producer()
&& MLT.producer()->is_valid())
onSeeked(MLT.producer()->position());
} else {
Mlt::Producer emptyProducer(mlt_producer(0));
m_producer.setProducer(emptyProducer);
}
m_qview.rootContext()->setContextProperty("filter", filter);
m_qview.rootContext()->setContextProperty("metadata", meta);
if (filter)
connect(filter, SIGNAL(changed(QString)), SIGNAL(changed()));
else
disconnect(this, SIGNAL(changed()));
QMetaObject::invokeMethod(m_qview.rootObject(),
"setCurrentFilter",
Q_ARG(QVariant, QVariant(index)));
m_qview.setWhatsThis(meta ? meta->helpText() : QString());
}
bool FiltersDock::event(QEvent *event)
{
bool result = QDockWidget::event(event);
if (event->type() == QEvent::PaletteChange || event->type() == QEvent::StyleChange
|| event->type() == QEvent::Show) {
load();
}
return result;
}
void FiltersDock::keyPressEvent(QKeyEvent *event)
{
QDockWidget::keyPressEvent(event);
if (event->key() == Qt::Key_F) {
event->ignore();
} else if (event->key() == Qt::Key_Left || event->key() == Qt::Key_Right) {
event->accept();
}
}
void FiltersDock::onSeeked(int position)
{
if (m_producer.producer().is_valid()) {
if (MLT.isMultitrack()) {
// Make the position relative to clip's position on a timeline track.
position -= m_producer.producer().get_int(kPlaylistStartProperty);
} else {
// Make the position relative to the clip's in point.
position -= m_producer.in();
}
m_producer.seek(position);
}
}
void FiltersDock::onShowFrame(const SharedFrame &frame)
{
if (m_producer.producer().is_valid()) {
int position = frame.get_position();
onSeeked(position);
}
}
void FiltersDock::openFilterMenu() const
{
QMetaObject::invokeMethod(m_qview.rootObject(), "openFilterMenu");
}
void FiltersDock::showCopyFilterMenu()
{
QMenu menu;
menu.addAction(Actions["filtersCopyCurrentFilterAction"]);
menu.addAction(Actions["filtersCopyFiltersAction"]);
menu.addAction(Actions["filtersCopyAllFilterAction"]);
menu.exec(QCursor::pos());
}
void FiltersDock::onServiceInChanged(int delta, Mlt::Service *service)
{
if (delta && service && m_producer.producer().is_valid()
&& service->get_service() == m_producer.producer().get_service()) {
emit producerInChanged(delta);
}
}
void FiltersDock::load()
{
if (!m_qview.quickWindow()->isSceneGraphInitialized() && loadTries++ < 5) {
LOG_WARNING() << "scene graph not yet initialized";
QTimer::singleShot(300, this, &FiltersDock::load);
}
LOG_DEBUG() << "begin"
<< "isVisible" << isVisible() << "qview.status" << m_qview.status();
emit currentFilterRequested(QmlFilter::NoCurrentFilter);
QDir viewPath = QmlUtilities::qmlDir();
viewPath.cd("views");
viewPath.cd("filter");
m_qview.engine()->addImportPath(viewPath.path());
QDir modulePath = QmlUtilities::qmlDir();
modulePath.cd("modules");
m_qview.engine()->addImportPath(modulePath.path());
m_qview.setResizeMode(QQuickWidget::SizeRootObjectToView);
m_qview.quickWindow()->setColor(palette().window().color());
QUrl source = QUrl::fromLocalFile(viewPath.absoluteFilePath("filterview.qml"));
m_qview.setSource(source);
QObject::connect(m_qview.rootObject(),
SIGNAL(currentFilterRequested(int)),
SIGNAL(currentFilterRequested(int)));
QObject::connect(m_qview.rootObject(),
SIGNAL(copyFilterRequested()),
SLOT(showCopyFilterMenu()));
}
void FiltersDock::setupActions()
{
QIcon icon;
QAction *action;
action = new QAction(tr("Add"), this);
action->setShortcut(QKeySequence(Qt::Key_F));
action->setToolTip(tr("Choose a filter to add"));
icon = QIcon::fromTheme("list-add", QIcon(":/icons/oxygen/32x32/actions/list-add.png"));
action->setIcon(icon);
connect(action, &QAction::triggered, this, [=]() {
show();
raise();
m_qview.setFocus();
openFilterMenu();
});
addAction(action);
Actions.add("filtersAddFilterAction", action, windowTitle());
action = new QAction(tr("Remove"), this);
action->setShortcut(QKeySequence(Qt::SHIFT | Qt::Key_F));
action->setToolTip(tr("Remove selected filter"));
icon = QIcon::fromTheme("list-remove", QIcon(":/icons/oxygen/32x32/actions/list-remove.png"));
action->setIcon(icon);
connect(action, &QAction::triggered, this, [=]() { MAIN.filterController()->removeCurrent(); });
addAction(action);
Actions.add("filtersRemoveFilterAction", action, windowTitle());
action = new QAction(tr("Copy Enabled"), this);
action->setToolTip(tr("Copy checked filters to the clipboard"));
connect(action, &QAction::triggered, this, [=]() {
QmlApplication::singleton().copyEnabledFilters();
});
addAction(action);
Actions.add("filtersCopyFiltersAction", action, windowTitle());
action = new QAction(tr("Copy Current"), this);
action->setToolTip(tr("Copy current filter to the clipboard"));
connect(action, &QAction::triggered, this, [=]() {
QmlApplication::singleton().copyCurrentFilter();
});
addAction(action);
Actions.add("filtersCopyCurrentFilterAction", action, windowTitle());
action = new QAction(tr("Copy All"), this);
action->setToolTip(tr("Copy all filters to the clipboard"));
connect(action, &QAction::triggered, this, [=]() {
QmlApplication::singleton().copyAllFilters();
});
addAction(action);
Actions.add("filtersCopyAllFilterAction", action, windowTitle());
action = new QAction(tr("Paste Filters"), this);
action->setToolTip(tr("Paste the filters from the clipboard"));
icon = QIcon::fromTheme("edit-paste", QIcon(":/icons/oxygen/32x32/actions/edit-paste.png"));
action->setIcon(icon);
connect(action, &QAction::triggered, this, [=]() {
MAIN.filterController()->attachedModel()->pasteFilters();
});
addAction(action);
Actions.add("filtersPasteFiltersAction", action, windowTitle());
}

75
src/docks/filtersdock.h Normal file
View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2013-2024 Meltytech, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef FILTERSDOCK_H
#define FILTERSDOCK_H
#include "qmltypes/qmlproducer.h"
#include "sharedframe.h"
#include <QDockWidget>
#include <QObject>
#include <QQuickWidget>
class QmlFilter;
class QmlMetadata;
class MetadataModel;
class AttachedFiltersModel;
class MotionTrackerModel;
class SubtitlesModel;
class FiltersDock : public QDockWidget
{
Q_OBJECT
public:
explicit FiltersDock(MetadataModel *metadataModel,
AttachedFiltersModel *attachedModel,
MotionTrackerModel *motionTrackerModel,
SubtitlesModel *subtitlesModel,
QWidget *parent = 0);
QmlProducer *qmlProducer() { return &m_producer; }
signals:
void currentFilterRequested(int attachedIndex);
void changed(); /// Notifies when a filter parameter changes.
void seeked(int);
void producerInChanged(int delta);
void producerOutChanged(int delta);
public slots:
void setCurrentFilter(QmlFilter *filter, QmlMetadata *meta, int index);
void onSeeked(int position);
void onShowFrame(const SharedFrame &frame);
void openFilterMenu() const;
void showCopyFilterMenu();
void onServiceInChanged(int delta, Mlt::Service *service);
void load();
protected:
bool event(QEvent *event);
void keyPressEvent(QKeyEvent *event);
private:
void setupActions();
QQuickWidget m_qview;
QmlProducer m_producer;
unsigned loadTries{0};
};
#endif // FILTERSDOCK_H

View File

@@ -0,0 +1,93 @@
/*
* Copyright (c) 2025 Meltytech, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef FIND_ANALYSIS_FILTER_PARSER_H
#define FIND_ANALYSIS_FILTER_PARSER_H
#include "qmltypes/qmlapplication.h"
#include <Mlt.h>
#include <QFile>
#include <QList>
#include <QString>
class FindAnalysisFilterParser : public Mlt::Parser
{
private:
QList<Mlt::Filter> m_filters;
bool m_skipAnalyzed;
public:
FindAnalysisFilterParser()
: Mlt::Parser()
, m_skipAnalyzed(true)
{}
QList<Mlt::Filter> &filters() { return m_filters; }
void skipAnalyzed(bool skip) { m_skipAnalyzed = skip; }
int on_start_filter(Mlt::Filter *filter)
{
QString serviceName = filter->get("mlt_service");
if (serviceName == "loudness" || serviceName == "vidstab") {
// If the results property does not exist, empty, or file does not exist.
QString results = filter->get("results");
if (results.isEmpty() || !m_skipAnalyzed) {
if (serviceName == "vidstab") {
// vidstab requires a filename, which is only available when using a project folder.
QString filename = filter->get("filename");
if (filename.isEmpty() || filename.endsWith("vidstab.trf")) {
filename = QmlApplication::getNextProjectFile("stab-");
}
if (!filename.isEmpty()) {
filter->set("filename", filename.toUtf8().constData());
m_filters << Mlt::Filter(*filter);
// Touch file to prevent overwriting the same file
QFile file(filename);
file.open(QIODevice::WriteOnly);
file.resize(0);
file.close();
}
} else {
m_filters << Mlt::Filter(*filter);
}
}
}
return 0;
}
int on_start_producer(Mlt::Producer *) { return 0; }
int on_end_producer(Mlt::Producer *) { return 0; }
int on_start_playlist(Mlt::Playlist *) { return 0; }
int on_end_playlist(Mlt::Playlist *) { return 0; }
int on_start_tractor(Mlt::Tractor *) { return 0; }
int on_end_tractor(Mlt::Tractor *) { return 0; }
int on_start_multitrack(Mlt::Multitrack *) { return 0; }
int on_end_multitrack(Mlt::Multitrack *) { return 0; }
int on_start_track() { return 0; }
int on_end_track() { return 0; }
int on_end_filter(Mlt::Filter *) { return 0; }
int on_start_transition(Mlt::Transition *) { return 0; }
int on_end_transition(Mlt::Transition *) { return 0; }
int on_start_chain(Mlt::Chain *) { return 0; }
int on_end_chain(Mlt::Chain *) { return 0; }
int on_start_link(Mlt::Link *) { return 0; }
int on_end_link(Mlt::Link *) { return 0; }
};
#endif // FIND_ANALYSIS_FILTER_PARSER_H

224
src/docks/jobsdock.cpp Normal file
View File

@@ -0,0 +1,224 @@
/*
* Copyright (c) 2012-2026 Meltytech, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "jobsdock.h"
#include "ui_jobsdock.h"
#include "Logger.h"
#include "dialogs/textviewerdialog.h"
#include "jobqueue.h"
#include <QtWidgets>
JobsDock::JobsDock(QWidget *parent)
: QDockWidget(parent)
, ui(new Ui::JobsDock)
{
LOG_DEBUG() << "begin";
ui->setupUi(this);
QIcon icon = QIcon::fromTheme("run-build", QIcon(":/icons/oxygen/32x32/actions/run-builld.png"));
toggleViewAction()->setIcon(icon);
ui->treeView->setModel(&JOBS);
QHeaderView *header = ui->treeView->header();
header->setStretchLastSection(false);
header->setSectionResizeMode(JobQueue::COLUMN_ICON, QHeaderView::Fixed);
ui->treeView->setColumnWidth(JobQueue::COLUMN_ICON, 24);
header->setSectionResizeMode(JobQueue::COLUMN_OUTPUT, QHeaderView::Stretch);
header->setSectionResizeMode(JobQueue::COLUMN_STATUS, QHeaderView::ResizeToContents);
ui->cleanButton->hide();
LOG_DEBUG() << "end";
}
JobsDock::~JobsDock()
{
JOBS.cleanup();
delete ui;
}
AbstractJob *JobsDock::currentJob() const
{
QModelIndex index = ui->treeView->currentIndex();
if (!index.isValid())
return 0;
return JOBS.jobFromIndex(index);
}
void JobsDock::onJobAdded()
{
QModelIndex index = JOBS.index(JOBS.rowCount() - 1, JobQueue::COLUMN_OUTPUT);
QProgressBar *progressBar = new QProgressBar;
progressBar->setMinimum(0);
progressBar->setMaximum(100);
progressBar->setAutoFillBackground(true);
progressBar->setTextVisible(false);
QHBoxLayout *layout = new QHBoxLayout(progressBar);
QLabel *label = new QLabel;
layout->addWidget(label);
layout->setContentsMargins(0, 0, 0, 0);
ui->treeView->setIndexWidget(index, progressBar);
ui->treeView->resizeColumnToContents(JobQueue::COLUMN_STATUS);
label->setToolTip(JOBS.data(index).toString());
label->setText(
label->fontMetrics().elidedText(JOBS.data(index).toString(),
Qt::ElideMiddle,
ui->treeView->columnWidth(JobQueue::COLUMN_OUTPUT)));
connect(JOBS.jobFromIndex(index),
SIGNAL(progressUpdated(QStandardItem *, int)),
SLOT(onProgressUpdated(QStandardItem *, int)));
show();
raise();
}
void JobsDock::onProgressUpdated(QStandardItem *item, int percent)
{
if (item) {
QModelIndex index = JOBS.index(item->row(), JobQueue::COLUMN_OUTPUT);
QProgressBar *progressBar = qobject_cast<QProgressBar *>(ui->treeView->indexWidget(index));
if (progressBar && percent > progressBar->value())
progressBar->setValue(percent);
}
}
void JobsDock::resizeEvent(QResizeEvent *event)
{
QDockWidget::resizeEvent(event);
foreach (QLabel *label, ui->treeView->findChildren<QLabel *>()) {
label->setText(
label->fontMetrics().elidedText(label->toolTip(),
Qt::ElideMiddle,
ui->treeView->columnWidth(JobQueue::COLUMN_OUTPUT)));
}
}
void JobsDock::on_treeView_customContextMenuRequested(const QPoint &pos)
{
QModelIndex index = ui->treeView->currentIndex();
QMenu menu(this);
AbstractJob *job = index.isValid() ? JOBS.jobFromIndex(index) : nullptr;
if (job) {
if (job->ran() && job->state() == QProcess::NotRunning
&& job->exitStatus() == QProcess::NormalExit) {
menu.addActions(job->successActions());
}
if (job->stopped() || (JOBS.isPaused() && !job->ran()))
menu.addAction(ui->actionRun);
if (job->state() == QProcess::Running)
menu.addAction(ui->actionStopJob);
else
menu.addAction(ui->actionRemove);
if (job->ran())
menu.addAction(ui->actionViewLog);
menu.addActions(job->standardActions());
}
for (auto job : JOBS.jobs()) {
if (job->ran() && job->state() != QProcess::Running) {
menu.addAction(ui->actionRemoveFinished);
break;
}
}
menu.exec(mapToGlobal(pos));
}
void JobsDock::on_actionStopJob_triggered()
{
QModelIndex index = ui->treeView->currentIndex();
if (!index.isValid())
return;
AbstractJob *job = JOBS.jobFromIndex(index);
if (job)
job->stop();
}
void JobsDock::on_actionViewLog_triggered()
{
QModelIndex index = ui->treeView->currentIndex();
if (!index.isValid())
return;
AbstractJob *job = JOBS.jobFromIndex(index);
if (job) {
TextViewerDialog dialog(this);
dialog.setWindowTitle(tr("Job Log"));
dialog.setText(job->log());
auto connection = connect(job, &AbstractJob::progressUpdated, this, [&]() {
dialog.setText(job->log(), true);
});
dialog.exec();
disconnect(connection);
}
}
void JobsDock::on_pauseButton_toggled(bool checked)
{
if (checked)
JOBS.pause();
else
JOBS.resume();
}
void JobsDock::on_actionRun_triggered()
{
QModelIndex index = ui->treeView->currentIndex();
if (!index.isValid())
return;
AbstractJob *job = JOBS.jobFromIndex(index);
if (job)
job->start();
}
void JobsDock::on_menuButton_clicked()
{
on_treeView_customContextMenuRequested(ui->menuButton->mapToParent(QPoint(0, 0)));
}
void JobsDock::on_treeView_doubleClicked(const QModelIndex &index)
{
AbstractJob *job = JOBS.jobFromIndex(index);
if (job && job->ran() && job->state() == QProcess::NotRunning
&& job->exitStatus() == QProcess::NormalExit) {
foreach (QAction *action, job->successActions()) {
if (action->data() == "Open") {
action->trigger();
break;
}
}
}
}
void JobsDock::on_actionRemove_triggered()
{
QModelIndex index = ui->treeView->currentIndex();
if (!index.isValid())
return;
JOBS.remove(index);
}
void JobsDock::on_actionRemoveFinished_triggered()
{
JOBS.removeFinished();
}
void JobsDock::on_JobsDock_visibilityChanged(bool visible)
{
if (visible) {
foreach (QLabel *label, ui->treeView->findChildren<QLabel *>()) {
label->setText(label->fontMetrics().elidedText(label->toolTip(),
Qt::ElideMiddle,
ui->treeView->columnWidth(
JobQueue::COLUMN_OUTPUT)));
}
}
}

62
src/docks/jobsdock.h Normal file
View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2012-2020 Meltytech, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef JOBSDOCK_H
#define JOBSDOCK_H
#include <QDockWidget>
class AbstractJob;
class QStandardItem;
namespace Ui {
class JobsDock;
}
class JobsDock : public QDockWidget
{
Q_OBJECT
public:
explicit JobsDock(QWidget *parent = 0);
~JobsDock();
AbstractJob *currentJob() const;
public slots:
void onJobAdded();
void onProgressUpdated(QStandardItem *item, int percent);
protected:
void resizeEvent(QResizeEvent *event);
private:
Ui::JobsDock *ui;
private slots:
void on_treeView_customContextMenuRequested(const QPoint &pos);
void on_actionStopJob_triggered();
void on_actionViewLog_triggered();
void on_pauseButton_toggled(bool checked);
void on_actionRun_triggered();
void on_menuButton_clicked();
void on_treeView_doubleClicked(const QModelIndex &index);
void on_actionRemove_triggered();
void on_actionRemoveFinished_triggered();
void on_JobsDock_visibilityChanged(bool visible);
};
#endif // JOBSDOCK_H

195
src/docks/jobsdock.ui Normal file
View File

@@ -0,0 +1,195 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>JobsDock</class>
<widget class="QDockWidget" name="JobsDock">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>283</width>
<height>279</height>
</rect>
</property>
<property name="whatsThis">
<string notr="true">https://forum.shotcut.org/t/jobs-panel/12945/1</string>
</property>
<property name="windowTitle">
<string>Jobs</string>
</property>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="frameShape">
<enum>QFrame::Shape::NoFrame</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>283</width>
<height>219</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTreeView" name="treeView">
<property name="contextMenuPolicy">
<enum>Qt::ContextMenuPolicy::CustomContextMenu</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="textElideMode">
<enum>Qt::TextElideMode::ElideMiddle</enum>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>6</number>
</property>
<item>
<widget class="QPushButton" name="menuButton">
<property name="toolTip">
<string>Jobs Menu</string>
</property>
<property name="icon">
<iconset theme="show-menu" resource="../../icons/resources.qrc">
<normaloff>:/icons/light/32x32/show-menu.png</normaloff>:/icons/light/32x32/show-menu.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pauseButton">
<property name="toolTip">
<string>Stop automatically processing the next pending job in
the list. This does not stop a currently running job. Right-
-click a job to open a menu to stop a currently running job.</string>
</property>
<property name="text">
<string>Pause Queue</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cleanButton">
<property name="toolTip">
<string>Remove all of the completed and failed jobs from the list</string>
</property>
<property name="text">
<string>Clean</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<action name="actionStopJob">
<property name="text">
<string>Stop This Job</string>
</property>
<property name="toolTip">
<string>Stop the currently selected job</string>
</property>
</action>
<action name="actionViewLog">
<property name="text">
<string>View Log</string>
</property>
<property name="toolTip">
<string>View the messages of MLT and FFmpeg </string>
</property>
</action>
<action name="actionRun">
<property name="text">
<string>Run</string>
</property>
<property name="toolTip">
<string>Restart a stopped job</string>
</property>
</action>
<action name="actionRemove">
<property name="text">
<string>Remove</string>
</property>
</action>
<action name="actionRemoveFinished">
<property name="text">
<string>Remove Finished</string>
</property>
<property name="toolTip">
<string>Remove Finished</string>
</property>
</action>
</widget>
<resources>
<include location="../../icons/resources.qrc"/>
</resources>
<connections/>
</ui>

1235
src/docks/keyframesdock.cpp Normal file

File diff suppressed because it is too large Load Diff

93
src/docks/keyframesdock.h Normal file
View File

@@ -0,0 +1,93 @@
/*
* Copyright (c) 2016-2023 Meltytech, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEYFRAMESDOCK_H
#define KEYFRAMESDOCK_H
#include "models/keyframesmodel.h"
#include "qmltypes/qmlfilter.h"
#include <QDockWidget>
#include <QQuickWidget>
#include <QScopedPointer>
class QmlFilter;
class QmlMetadata;
class AttachedFiltersModel;
class QmlProducer;
class QMenu;
class KeyframesDock : public QDockWidget
{
Q_OBJECT
Q_PROPERTY(double timeScale READ timeScale WRITE setTimeScale NOTIFY timeScaleChanged)
public:
explicit KeyframesDock(QmlProducer *qmlProducer, QWidget *parent = 0);
KeyframesModel &model() { return m_model; }
Q_INVOKABLE int seekPrevious();
Q_INVOKABLE int seekNext();
int currentParameter() const;
double timeScale() const { return m_timeScale; }
void setTimeScale(double value);
signals:
void changed(); /// Notifies when a filter parameter changes.
void setZoom(double value);
void zoomIn();
void zoomOut();
void zoomToFit();
void resetZoom();
void seekPreviousSimple();
void seekNextSimple();
void newFilter(); // Notifies when the filter itself has been changed
void timeScaleChanged();
void dockClicked();
public slots:
void setCurrentFilter(QmlFilter *filter, QmlMetadata *meta);
void load(bool force = false);
void reload();
void onProducerModified();
protected:
bool event(QEvent *event);
void keyPressEvent(QKeyEvent *event);
void keyReleaseEvent(QKeyEvent *event);
private slots:
void onDockRightClicked();
void onKeyframeRightClicked();
void onClipRightClicked();
private:
void setupActions();
QQuickWidget m_qview;
KeyframesModel m_model;
QmlMetadata *m_metadata;
QmlFilter *m_filter;
QmlProducer *m_qmlProducer;
QMenu *m_mainMenu;
QMenu *m_keyMenu;
QMenu *m_keyTypePrevMenu;
QMenu *m_keyTypeNextMenu;
QMenu *m_clipMenu;
double m_timeScale{1.0};
};
#endif // KEYFRAMESDOCK_H

464
src/docks/markersdock.cpp Normal file
View File

@@ -0,0 +1,464 @@
/*
* Copyright (c) 2021-2026 Meltytech, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "markersdock.h"
#include "Logger.h"
#include "actions.h"
#include "mainwindow.h"
#include "models/markersmodel.h"
#include "settings.h"
#include "util.h"
#include "widgets/docktoolbar.h"
#include "widgets/editmarkerwidget.h"
#include <QAction>
#include <QDebug>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QIcon>
#include <QLineEdit>
#include <QMenu>
#include <QMouseEvent>
#include <QPainter>
#include <QSortFilterProxyModel>
#include <QSpacerItem>
#include <QStyledItemDelegate>
#include <QToolButton>
#include <QTreeView>
#include <QVBoxLayout>
#include <QtWidgets/QScrollArea>
class ColorItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
ColorItemDelegate(QAbstractItemView *view, QWidget *parent = nullptr)
: QStyledItemDelegate(parent)
, m_view(view)
{}
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
const auto color = index.data(MarkersModel::ColorRole).value<QColor>();
const auto textColor(Util::textColor(color));
painter->fillRect(option.rect, color);
const auto point = option.rect.topLeft()
+ QPoint(2 * m_view->devicePixelRatioF(),
option.fontMetrics.ascent() + m_view->devicePixelRatioF());
painter->setPen(textColor);
painter->drawText(point, color.name());
}
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(index);
return QSize(m_view->viewport()->width(),
option.fontMetrics.height() + 2 * m_view->devicePixelRatioF());
}
private:
QAbstractItemView *m_view;
};
class MarkerTreeView : public QTreeView
{
Q_OBJECT
public:
// Make this function public
using QTreeView::selectedIndexes;
void blockSelectionEvent(bool block) { m_blockSelectionEvent = block; }
signals:
void rowClicked(const QModelIndex &index);
void markerSelected(QModelIndex &index);
protected:
void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
QTreeView::selectionChanged(selected, deselected);
if (!m_blockSelectionEvent) {
QModelIndex signalIndex;
QModelIndexList indices = selectedIndexes();
if (indices.size() > 0) {
signalIndex = indices[0];
}
emit markerSelected(signalIndex);
}
}
void mouseReleaseEvent(QMouseEvent *event)
{
QTreeView::mouseReleaseEvent(event);
QModelIndex signalIndex = indexAt(event->pos());
if (signalIndex.isValid()) {
emit rowClicked(signalIndex);
}
}
private:
bool m_blockSelectionEvent = false;
};
// Include this so that MarkerTreeView can be declared in the source file.
#include "markersdock.moc"
MarkersDock::MarkersDock(QWidget *parent)
: QDockWidget(parent)
, m_model(nullptr)
, m_proxyModel(nullptr)
, m_editInProgress(false)
{
LOG_DEBUG() << "begin";
setObjectName("MarkersDock");
QDockWidget::setWindowTitle(tr("Markers"));
QIcon icon = QIcon::fromTheme("marker", QIcon(":/icons/oxygen/32x32/actions/marker.png"));
toggleViewAction()->setIcon(icon);
setWhatsThis("https://forum.shotcut.org/t/timeline-markers/30535/1");
QScrollArea *scrollArea = new QScrollArea();
scrollArea->setFrameShape(QFrame::NoFrame);
scrollArea->setWidgetResizable(true);
QDockWidget::setWidget(scrollArea);
QVBoxLayout *vboxLayout = new QVBoxLayout();
scrollArea->setLayout(vboxLayout);
m_treeView = new MarkerTreeView();
m_treeView->setItemDelegateForColumn(0, new ColorItemDelegate(m_treeView));
m_treeView->setItemsExpandable(false);
m_treeView->setRootIsDecorated(false);
m_treeView->setUniformRowHeights(true);
m_treeView->setSortingEnabled(true);
connect(m_treeView,
SIGNAL(markerSelected(QModelIndex &)),
this,
SLOT(onSelectionChanged(QModelIndex &)));
connect(m_treeView,
SIGNAL(rowClicked(const QModelIndex &)),
this,
SLOT(onRowClicked(const QModelIndex &)));
vboxLayout->addWidget(m_treeView, 1);
QMenu *mainMenu = new QMenu("Markers", this);
mainMenu->addAction(Actions["timelineMarkerAction"]);
mainMenu->addAction(Actions["timelinePrevMarkerAction"]);
mainMenu->addAction(Actions["timelineNextMarkerAction"]);
mainMenu->addAction(Actions["timelineDeleteMarkerAction"]);
mainMenu->addAction(Actions["timelineMarkSelectedClipAction"]);
mainMenu->addAction(Actions["timelineCycleMarkerColorAction"]);
mainMenu->addAction(tr("Remove All Markers"), this, SLOT(onRemoveAllRequested()));
QAction *action;
QMenu *columnsMenu = new QMenu(tr("Columns"), this);
action = columnsMenu->addAction(tr("Color"), this, SLOT(onColorColumnToggled(bool)));
action->setCheckable(true);
action->setChecked(Settings.markersShowColumn("color"));
action = columnsMenu->addAction(tr("Name"), this, SLOT(onTextColumnToggled(bool)));
action->setCheckable(true);
action->setChecked(Settings.markersShowColumn("text"));
action = columnsMenu->addAction(tr("Start"), this, SLOT(onStartColumnToggled(bool)));
action->setCheckable(true);
action->setChecked(Settings.markersShowColumn("start"));
action = columnsMenu->addAction(tr("End"), this, SLOT(onEndColumnToggled(bool)));
action->setCheckable(true);
action->setChecked(Settings.markersShowColumn("end"));
action = columnsMenu->addAction(tr("Duration"), this, SLOT(onDurationColumnToggled(bool)));
action->setCheckable(true);
action->setChecked(Settings.markersShowColumn("duration"));
mainMenu->addMenu(columnsMenu);
Actions.loadFromMenu(mainMenu);
DockToolBar *toolbar = new DockToolBar(tr("Markers Controls"));
toolbar->setAreaHint(Qt::BottomToolBarArea);
QToolButton *menuButton = new QToolButton(this);
menuButton->setIcon(
QIcon::fromTheme("show-menu", QIcon(":/icons/oxygen/32x32/actions/show-menu.png")));
menuButton->setToolTip(tr("Markers Menu"));
menuButton->setAutoRaise(true);
menuButton->setMenu(mainMenu);
menuButton->setPopupMode(QToolButton::QToolButton::InstantPopup);
toolbar->addWidget(menuButton);
m_addButton = new QToolButton(this);
m_addButton->setIcon(
QIcon::fromTheme("list-add", QIcon(":/icons/oxygen/32x32/actions/list-add.png")));
m_addButton->setToolTip(tr("Add a marker at the current time"));
m_addButton->setAutoRaise(true);
if (!connect(m_addButton, &QAbstractButton::clicked, this, &MarkersDock::onAddRequested))
connect(m_addButton, SIGNAL(clicked()), SLOT(onAddRequested()));
toolbar->addWidget(m_addButton);
m_removeButton = new QToolButton(this);
m_removeButton->setIcon(
QIcon::fromTheme("list-remove", QIcon(":/icons/oxygen/32x32/actions/list-remove.png")));
m_removeButton->setToolTip(tr("Remove the selected marker"));
m_removeButton->setAutoRaise(true);
if (!connect(m_removeButton, &QAbstractButton::clicked, this, &MarkersDock::onRemoveRequested))
connect(m_removeButton, SIGNAL(clicked()), SLOT(onRemoveRequested()));
toolbar->addWidget(m_removeButton);
m_clearButton = new QToolButton(this);
m_clearButton->setIcon(
QIcon::fromTheme("window-close", QIcon(":/icons/oxygen/32x32/actions/window-close.png")));
m_clearButton->setToolTip(tr("Deselect the marker"));
m_clearButton->setAutoRaise(true);
if (!connect(m_clearButton,
&QAbstractButton::clicked,
this,
&MarkersDock::onClearSelectionRequested))
connect(m_clearButton, SIGNAL(clicked()), SLOT(onClearSelectionRequested()));
toolbar->addWidget(m_clearButton);
m_searchField = new QLineEdit(this);
m_searchField->setPlaceholderText(tr("search"));
if (!connect(m_searchField, &QLineEdit::textChanged, this, &MarkersDock::onSearchChanged))
connect(m_searchField, SIGNAL(textChanged(const QString &)), SLOT(onSearchChanged()));
toolbar->addWidget(m_searchField);
m_clearSearchButton = new QToolButton(this);
m_clearSearchButton->setIcon(
QIcon::fromTheme("edit-clear", QIcon(":/icons/oxygen/32x32/actions/edit-clear.png")));
m_clearSearchButton->setToolTip(tr("Clear search"));
m_clearSearchButton->setAutoRaise(true);
if (!connect(m_clearSearchButton, &QAbstractButton::clicked, m_searchField, &QLineEdit::clear))
connect(m_clearSearchButton, SIGNAL(clicked()), m_searchField, SLOT(clear()));
toolbar->addWidget(m_clearSearchButton);
vboxLayout->addWidget(toolbar);
enableButtons(false);
m_editMarkerWidget = new EditMarkerWidget(this, "", "", 0, 0, 0);
m_editMarkerWidget->setVisible(false);
connect(m_editMarkerWidget, SIGNAL(valuesChanged()), SLOT(onValuesChanged()));
vboxLayout->addWidget(m_editMarkerWidget);
vboxLayout->addStretch();
LOG_DEBUG() << "end";
}
MarkersDock::~MarkersDock() {}
void MarkersDock::setModel(MarkersModel *model)
{
m_treeView->blockSelectionEvent(true);
m_model = model;
m_proxyModel = new QSortFilterProxyModel(this);
m_proxyModel->setSourceModel(m_model);
m_proxyModel->setFilterKeyColumn(1);
m_treeView->setModel(m_proxyModel);
m_treeView->setColumnHidden(0, !Settings.markersShowColumn("color"));
m_treeView->setColumnHidden(1, !Settings.markersShowColumn("text"));
m_treeView->setColumnHidden(2, !Settings.markersShowColumn("start"));
m_treeView->setColumnHidden(3, !Settings.markersShowColumn("end"));
m_treeView->setColumnHidden(4, !Settings.markersShowColumn("duration"));
m_treeView->sortByColumn(Settings.getMarkerSortColumn(), Settings.getMarkerSortOrder());
connect(m_model,
SIGNAL(rowsInserted(const QModelIndex &, int, int)),
this,
SLOT(onRowsInserted(const QModelIndex &, int, int)));
connect(m_model,
SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &, const QVector<int> &)),
this,
SLOT(onDataChanged(const QModelIndex &, const QModelIndex &, const QVector<int> &)));
connect(m_model, SIGNAL(modelReset()), this, SLOT(onModelReset()));
connect(m_treeView->header(),
SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)),
this,
SLOT(onSortIndicatorChanged(int, Qt::SortOrder)));
m_treeView->blockSelectionEvent(false);
}
void MarkersDock::onMarkerSelectionRequest(int markerIndex)
{
QModelIndex sourceIndex = m_model->modelIndexForRow(markerIndex);
QModelIndex insertedIndex = m_proxyModel->mapFromSource(sourceIndex);
if (insertedIndex.isValid()) {
m_treeView->setCurrentIndex(insertedIndex);
}
}
void MarkersDock::onSelectionChanged(QModelIndex &index)
{
if (m_model && m_proxyModel && MAIN.multitrack() && index.isValid()) {
QModelIndex realIndex = m_proxyModel->mapToSource(index);
if (realIndex.isValid()) {
Markers::Marker marker = m_model->getMarker(realIndex.row());
enableButtons(true);
m_editMarkerWidget->setVisible(true);
QSignalBlocker editBlocker(m_editMarkerWidget);
m_editMarkerWidget->setValues(marker.text,
marker.color,
marker.start,
marker.end,
MAIN.multitrack()->get_length() - 1);
return;
}
}
m_editMarkerWidget->setVisible(false);
enableButtons(false);
}
void MarkersDock::onRowClicked(const QModelIndex &index)
{
if (m_model && m_proxyModel && MAIN.multitrack() && index.isValid()) {
QModelIndex realIndex = m_proxyModel->mapToSource(index);
if (realIndex.isValid()) {
Markers::Marker marker = m_model->getMarker(realIndex.row());
emit seekRequested(marker.start);
}
}
}
void MarkersDock::onAddRequested()
{
emit addRequested();
}
void MarkersDock::onRemoveRequested()
{
if (m_model && m_proxyModel) {
QModelIndexList indices = m_treeView->selectedIndexes();
if (indices.size() > 0) {
QModelIndex realIndex = m_proxyModel->mapToSource(indices[0]);
if (realIndex.isValid()) {
m_model->remove(realIndex.row());
}
}
}
}
void MarkersDock::onClearSelectionRequested()
{
m_treeView->clearSelection();
}
void MarkersDock::onRemoveAllRequested()
{
m_model->clear();
}
void MarkersDock::onSearchChanged()
{
if (m_proxyModel) {
m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
m_proxyModel->setFilterFixedString(m_searchField->text());
}
}
void MarkersDock::onColorColumnToggled(bool checked)
{
Settings.setMarkersShowColumn("color", checked);
m_treeView->setColumnHidden(0, !checked);
}
void MarkersDock::onTextColumnToggled(bool checked)
{
Settings.setMarkersShowColumn("text", checked);
m_treeView->setColumnHidden(1, !checked);
}
void MarkersDock::onStartColumnToggled(bool checked)
{
Settings.setMarkersShowColumn("start", checked);
m_treeView->setColumnHidden(2, !checked);
}
void MarkersDock::onEndColumnToggled(bool checked)
{
Settings.setMarkersShowColumn("end", checked);
m_treeView->setColumnHidden(3, !checked);
}
void MarkersDock::onDurationColumnToggled(bool checked)
{
Settings.setMarkersShowColumn("duration", checked);
m_treeView->setColumnHidden(4, !checked);
}
void MarkersDock::onRowsInserted(const QModelIndex &parent, int first, int last)
{
Q_UNUSED(parent);
Q_UNUSED(last);
QModelIndex sourceIndex = m_model->modelIndexForRow(first);
QModelIndex insertedIndex = m_proxyModel->mapFromSource(sourceIndex);
m_treeView->setCurrentIndex(insertedIndex);
}
void MarkersDock::onDataChanged(const QModelIndex &topLeft,
const QModelIndex &bottomRight,
const QVector<int> &roles)
{
Q_UNUSED(topLeft);
Q_UNUSED(bottomRight);
Q_UNUSED(roles);
if (m_model && m_proxyModel && !m_editInProgress) {
QModelIndexList indices = m_treeView->selectedIndexes();
if (indices.size() > 0) {
QModelIndex realIndex = m_proxyModel->mapToSource(indices[0]);
if (realIndex.isValid()) {
Markers::Marker marker = m_model->getMarker(realIndex.row());
m_editMarkerWidget->setVisible(true);
QSignalBlocker editBlocker(m_editMarkerWidget);
m_editMarkerWidget->setValues(marker.text,
marker.color,
marker.start,
marker.end,
MAIN.multitrack()->get_length() - 1);
return;
}
}
}
}
void MarkersDock::onValuesChanged()
{
if (m_model && m_proxyModel) {
QModelIndexList indices = m_treeView->selectedIndexes();
if (indices.size() > 0) {
QModelIndex realIndex = m_proxyModel->mapToSource(indices[0]);
if (realIndex.isValid()) {
Markers::Marker marker;
marker.text = m_editMarkerWidget->getText();
marker.color = m_editMarkerWidget->getColor();
marker.start = m_editMarkerWidget->getStart();
marker.end = m_editMarkerWidget->getEnd();
m_editInProgress = true;
m_model->update(realIndex.row(), marker);
m_editInProgress = false;
}
}
}
}
void MarkersDock::onModelReset()
{
m_treeView->clearSelection();
m_editMarkerWidget->setVisible(false);
}
void MarkersDock::onSortIndicatorChanged(int logicalIndex, Qt::SortOrder order)
{
Settings.setMarkerSort(logicalIndex, order);
}
void MarkersDock::enableButtons(bool enable)
{
m_removeButton->setEnabled(enable);
m_clearButton->setEnabled(enable);
}

84
src/docks/markersdock.h Normal file
View File

@@ -0,0 +1,84 @@
/*
* Copyright (c) 2021 Meltytech, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MARKERSDOCK_H
#define MARKERSDOCK_H
#include <QDockWidget>
#include <QItemSelectionModel>
class EditMarkerWidget;
class MarkerTreeView;
class MarkersModel;
class QLineEdit;
class QSortFilterProxyModel;
class QToolButton;
class MarkersDock : public QDockWidget
{
Q_OBJECT
public:
explicit MarkersDock(QWidget *parent = 0);
~MarkersDock();
void setModel(MarkersModel *model);
signals:
void seekRequested(int pos);
void addRequested();
void addAroundSelectionRequested();
public slots:
void onMarkerSelectionRequest(int markerIndex);
private slots:
void onSelectionChanged(QModelIndex &index);
void onRowClicked(const QModelIndex &index);
void onAddRequested();
void onRemoveRequested();
void onClearSelectionRequested();
void onRemoveAllRequested();
void onSearchChanged();
void onColorColumnToggled(bool checked);
void onTextColumnToggled(bool checked);
void onStartColumnToggled(bool checked);
void onEndColumnToggled(bool checked);
void onDurationColumnToggled(bool checked);
void onRowsInserted(const QModelIndex &parent, int first, int last);
void onDataChanged(const QModelIndex &topLeft,
const QModelIndex &bottomRight,
const QVector<int> &roles = QVector<int>());
void onValuesChanged();
void onModelReset();
void onSortIndicatorChanged(int logicalIndex, Qt::SortOrder order);
private:
void enableButtons(bool enable);
MarkersModel *m_model;
QSortFilterProxyModel *m_proxyModel;
MarkerTreeView *m_treeView;
QToolButton *m_addButton;
QToolButton *m_removeButton;
QToolButton *m_clearButton;
QLineEdit *m_searchField;
QToolButton *m_clearSearchButton;
EditMarkerWidget *m_editMarkerWidget;
bool m_editInProgress;
};
#endif // MARKERSDOCK_H

191
src/docks/notesdock.cpp Normal file
View File

@@ -0,0 +1,191 @@
/*
* Copyright (c) 2022-2026 Meltytech, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "notesdock.h"
#include "Logger.h"
#include "actions.h"
#include "dialogs/speechdialog.h"
#include "jobqueue.h"
#include "jobs/kokorodokijob.h"
#include "mltcontroller.h"
#include "settings.h"
#include "util.h"
#include "widgets/docktoolbar.h"
#include <QAction>
#include <QApplication>
#include <QIcon>
#include <QMenu>
#include <QMessageBox>
#include <QPlainTextEdit>
#include <QTimer>
#include <QVBoxLayout>
#include <QWheelEvent>
class TextEditor : public QPlainTextEdit
{
Q_DECLARE_TR_FUNCTIONS(TextEditor)
public:
explicit TextEditor(QWidget *parent = nullptr)
: QPlainTextEdit()
{
setObjectName("Notes");
zoomIn(Settings.notesZoom());
setTabChangesFocus(false);
setTabStopDistance(fontMetrics().horizontalAdvance("XXXX")); // Tabstop = 4 spaces
setContextMenuPolicy(Qt::CustomContextMenu);
auto icon = QIcon::fromTheme("zoom-out", QIcon(":/icons/oxygen/32x32/actions/zoom-out.png"));
auto action = new QAction(icon, tr("Decrease Text Size"), this);
action->setShortcut(Qt::CTRL | Qt::ALT | Qt::Key_Minus);
addAction(action);
Actions.add("notesDecreaseTextSize", action, objectName());
connect(action, &QAction::triggered, this, [=]() { setZoom(-4); });
icon = QIcon::fromTheme("zoom-in", QIcon(":/icons/oxygen/32x32/actions/zoom-in.png"));
action = new QAction(icon, tr("Increase Text Size"), this);
action->setShortcut(Qt::CTRL | Qt::ALT | Qt::Key_Equal);
addAction(action);
Actions.add("notesIncreaseTextSize", action, objectName());
connect(action, &QAction::triggered, this, [=]() { setZoom(4); });
#ifdef EXTERNAL_LAUNCHERS
icon = QIcon::fromTheme("text-speak", QIcon(":/icons/oxygen/32x32/actions/text-speak.png"));
action = new QAction(icon, tr("Text to Speech..."), this);
addAction(action);
Actions.add("notesTextToSpeech", action, objectName());
connect(action, &QAction::triggered, this, [=] {
if (toPlainText().isEmpty() || !KokorodokiJob::checkDockerImage(this))
return;
m_speechDialog.reset(new SpeechDialog(this));
if (m_speechDialog->exec() != QDialog::Accepted)
return;
KokorodokiJob::prepareAndRun(this, [=]() {
const auto outFile = m_speechDialog->outputFile();
if (outFile.isEmpty())
return;
QFileInfo outInfo(outFile);
auto txtFile = new QTemporaryFile(outInfo.dir().filePath("XXXXXX.txt"));
if (!txtFile->open()) {
LOG_ERROR() << "Failed to create temp text file" << txtFile->fileName();
txtFile->deleteLater();
return;
}
txtFile->write(toPlainText().toUtf8());
txtFile->close();
const auto lang = m_speechDialog->languageCode();
const auto voice = m_speechDialog->voiceCode();
const auto spd = m_speechDialog->speed();
auto job = new KokorodokiJob(txtFile->fileName(), outFile, lang, voice, spd);
txtFile->setParent(job); // auto-delete with job
job->setPostJobAction(new OpenPostJobAction(outFile, outFile, QString()));
JOBS.add(job);
});
});
#endif
connect(this, &QWidget::customContextMenuRequested, this, [=](const QPoint &pos) {
std::unique_ptr<QMenu> menu{createStandardContextMenu()};
actions().at(0)->setEnabled(Settings.notesZoom() > 0);
menu->addActions(actions());
menu->exec(mapToGlobal(pos));
});
}
void setZoom(int delta)
{
auto zoom = Settings.notesZoom();
zoomIn((zoom + delta >= 0) ? delta : -zoom);
Settings.setNotesZoom(std::max<int>(0, zoom + delta));
}
protected:
void wheelEvent(QWheelEvent *event) override
{
if (event->modifiers() & Qt::ControlModifier) {
setZoom((event->angleDelta().y() < 0) ? -1 : 1);
event->accept();
} else {
QPlainTextEdit::wheelEvent(event);
}
}
private:
std::unique_ptr<SpeechDialog> m_speechDialog;
};
NotesDock::NotesDock(QWidget *parent)
: QDockWidget(tr("Notes"), parent)
, m_textEdit(new TextEditor(this))
, m_blockUpdate(false)
{
LOG_DEBUG() << "begin";
setObjectName("NotesDock");
QIcon icon = QIcon::fromTheme("document-edit",
QIcon(":/icons/oxygen/32x32/actions/document-edit.png"));
toggleViewAction()->setIcon(icon);
setWhatsThis("https://forum.shotcut.org/t/notes-panel/33110/1");
QObject::connect(m_textEdit, SIGNAL(textChanged()), SLOT(onTextChanged()));
// Wrap the text editor with a container so we can place a toolbar beneath it.
auto container = new QWidget(this);
auto layout = new QVBoxLayout(container);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
layout->addWidget(m_textEdit, /*stretch*/ 1);
// Create a DockToolBar to hold custom actions defined in TextEditor.
auto toolbar = new DockToolBar(tr("Notes Controls"), container);
toolbar->setAreaHint(Qt::BottomToolBarArea);
const auto actions = m_textEdit->actions();
toolbar->addAction(actions.at(0)); // Decrease Text Size
toolbar->addAction(actions.at(1)); // Increase Text Size
#ifdef EXTERNAL_LAUNCHERS
toolbar->addSeparator();
toolbar->addAction(actions.at(2)); // Text to Speech
#endif
layout->addWidget(toolbar, /*stretch*/ 0);
QDockWidget::setWidget(container);
LOG_DEBUG() << "end";
}
QString NotesDock::getText()
{
return m_textEdit->toPlainText();
}
void NotesDock::setText(const QString &text)
{
m_blockUpdate = true;
m_textEdit->setPlainText(text);
m_blockUpdate = false;
}
void NotesDock::onTextChanged()
{
if (!m_blockUpdate) {
emit modified();
}
}

46
src/docks/notesdock.h Normal file
View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2022-2025 Meltytech, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NOTESDOCK_H
#define NOTESDOCK_H
#include <QDockWidget>
#include <QObject>
class TextEditor;
class NotesDock : public QDockWidget
{
Q_OBJECT
public:
explicit NotesDock(QWidget *parent = 0);
QString getText();
void setText(const QString &text);
signals:
void modified();
private slots:
void onTextChanged();
private:
TextEditor *m_textEdit;
bool m_blockUpdate;
};
#endif // NOTESDOCK_H

2179
src/docks/playlistdock.cpp Normal file

File diff suppressed because it is too large Load Diff

166
src/docks/playlistdock.h Normal file
View File

@@ -0,0 +1,166 @@
/*
* Copyright (c) 2012-2025 Meltytech, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef PLAYLISTDOCK_H
#define PLAYLISTDOCK_H
#include "models/playlistmodel.h"
#include <QDockWidget>
#include <QTimer>
#include <QTreeWidget>
#include <QUndoCommand>
namespace Ui {
class PlaylistDock;
}
class QAbstractItemView;
class QItemSelectionModel;
class QLabel;
class QMenu;
class PlaylistIconView;
class PlaylistProxyModel;
class LineEditClear;
class BinTree : public QTreeWidget
{
Q_OBJECT
public:
explicit BinTree(QWidget *parent = nullptr)
: QTreeWidget(parent)
{}
signals:
void copied(QString);
void moved(QList<int>, QPointF);
protected:
void dropEvent(QDropEvent *event);
};
class PlaylistDock : public QDockWidget
{
Q_OBJECT
public:
enum SmartBin {
SmartBinNone = -1,
SmartBinAll,
SmartBinDuplicates,
SmartBinNotInBin,
SmartBinNotInTimeline,
SmartBinCount
};
explicit PlaylistDock(QWidget *parent = 0);
~PlaylistDock();
PlaylistModel *model() { return &m_model; }
int position();
void replaceClipsWithHash(const QString &hash, Mlt::Producer &producer);
void getSelectionRange(int *start, int *end);
Mlt::Playlist *binPlaylist();
static void sortBins(QTreeWidget *treeWidget);
signals:
void clipOpened(Mlt::Producer *producer, bool play = false);
void itemActivated(int start);
void showStatusMessage(QString);
void addAllTimeline(Mlt::Playlist *, bool skipProxy = false, bool emptyTrack = false);
void producerOpened();
void selectionChanged();
void enableUpdate(bool);
public slots:
void onOpenActionTriggered();
void onAppendCutActionTriggered();
void onProducerOpened();
void onInChanged();
void onOutChanged();
void onProducerChanged(Mlt::Producer *producer);
void onProducerModified();
void onPlayerDragStarted();
void onPlaylistModified();
void onPlaylistCreated();
void onPlaylistLoaded();
void onPlaylistCleared();
void onPlaylistClosed();
void refreshTimelineSmartBins();
private slots:
void viewCustomContextMenuRequested(const QPoint &pos);
void viewDoubleClicked(const QModelIndex &index);
void onDropped(const QMimeData *data, int row);
void onMoveClip(int from, int to);
void onMovedToEnd();
void onInTimerFired();
void onOutTimerFired();
void onMediaTypeClicked();
void on_treeWidget_itemSelectionChanged();
void clearStatus();
void updateStatus();
protected:
void keyPressEvent(QKeyEvent *event);
void keyReleaseEvent(QKeyEvent *event);
private:
void setupActions();
void resetPlaylistIndex();
void emitDataChanged(const QVector<int> &roles);
void setPlaylistIndex(Mlt::Producer *producer, int row);
void updateViewMode();
void onAddFilesActionTriggered();
void onUpdateThumbnailsActionTriggered();
void onAddToTimelineActionTriggered();
void onAddToSlideshowActionTriggered();
void onSetFileDateActionTriggered();
void onRemoveAllActionTriggered();
void onGotoActionTriggered();
void onCopyActionTriggered();
void onSelectAllActionTriggered();
void onInsertCutActionTriggered();
void onUpdateActionTriggered();
void onRemoveActionTriggered();
void incrementIndex(int step);
void setIndex(int row);
void moveClipUp();
void moveClipDown();
void addFiles(int row, const QList<QUrl> &urls);
void loadBins();
void sortBins();
void assignToBin(Mlt::Properties &properties, QString bin = QString());
Ui::PlaylistDock *ui;
QAbstractItemView *m_view;
PlaylistIconView *m_iconsView;
PlaylistModel m_model;
QItemSelectionModel *m_selectionModel;
int m_defaultRowHeight;
QTimer m_inChangedTimer;
QTimer m_outChangedTimer;
QMenu *m_mainMenu;
bool m_blockResizeColumnsToContents;
PlaylistProxyModel *m_proxyModel;
Mlt::Playlist m_binPlaylist;
LineEditClear *m_searchField;
QLabel *m_label;
};
#endif // PLAYLISTDOCK_H

196
src/docks/playlistdock.ui Normal file
View File

@@ -0,0 +1,196 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PlaylistDock</class>
<widget class="QDockWidget" name="PlaylistDock">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>460</width>
<height>278</height>
</rect>
</property>
<property name="whatsThis">
<string notr="true">https://forum.shotcut.org/t/about-the-playlist/12951/1</string>
</property>
<property name="windowTitle">
<string>Playlist</string>
</property>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
<number>1</number>
</property>
<widget class="QWidget" name="page">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTextBrowser" name="textBrowser">
<property name="html">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:16px; margin-left:-24px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Double-click&lt;/span&gt; a playlist item to open it in the player.&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:16px; margin-left:-24px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;You can freely preview clips without necessarily adding them to the playlist or closing it.&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:16px; margin-left:-24px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;To trim or adjust a playlist item &lt;span style=&quot; font-weight:600;&quot;&gt;Double-click&lt;/span&gt; to open it, make the changes, and click the &lt;span style=&quot; font-weight:600;&quot;&gt;Update&lt;/span&gt; icon.&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:16px; margin-left:-24px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Drag-n-drop&lt;/span&gt; to rearrange the items.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="filtersLayout"/>
</item>
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<widget class="BinTree" name="treeWidget">
<property name="acceptDrops">
<bool>true</bool>
</property>
<property name="styleSheet">
<string notr="true">QTreeWidget::item { padding: 10px }</string>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="PlaylistTable" name="tableView">
<property name="contextMenuPolicy">
<enum>Qt::ContextMenuPolicy::CustomContextMenu</enum>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Double-click a playlist item to open it in the player.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="dragEnabled">
<bool>false</bool>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SelectionMode::ExtendedSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectionBehavior::SelectRows</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollMode::ScrollPerPixel</enum>
</property>
<attribute name="horizontalHeaderHighlightSections">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<widget class="PlaylistListView" name="listView">
<property name="contextMenuPolicy">
<enum>Qt::ContextMenuPolicy::CustomContextMenu</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SelectionMode::ExtendedSelection</enum>
</property>
<property name="movement">
<enum>QListView::Movement::Static</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>PlaylistTable</class>
<extends>QTableView</extends>
<header>widgets/playlisttable.h</header>
</customwidget>
<customwidget>
<class>PlaylistListView</class>
<extends>QListView</extends>
<header>widgets/playlistlistview.h</header>
</customwidget>
<customwidget>
<class>BinTree</class>
<extends>QTreeWidget</extends>
<header>docks/playlistdock.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../../icons/resources.qrc"/>
</resources>
<connections/>
</ui>

167
src/docks/recentdock.cpp Normal file
View File

@@ -0,0 +1,167 @@
/*
* Copyright (c) 2012-2026 Meltytech, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "recentdock.h"
#include "ui_recentdock.h"
#include "Logger.h"
#include "settings.h"
#include "util.h"
#include <QAction>
#include <QDir>
#include <QKeyEvent>
#include <QMenu>
static const int MaxItems = 200;
RecentDock::RecentDock(QWidget *parent)
: QDockWidget(parent)
, ui(new Ui::RecentDock)
{
LOG_DEBUG() << "begin";
ui->setupUi(this);
QIcon icon = QIcon::fromTheme("document-open-recent",
QIcon(":/icons/oxygen/32x32/actions/document-open-recent.png"));
toggleViewAction()->setIcon(icon);
m_recent = Settings.recent();
#ifdef Q_OS_WIN
// Remove bad entries on Windows due to bug in v17.01.
QStringList newList;
bool isRepaired = false;
foreach (QString s, m_recent) {
if (s.size() >= 3 && s[0] == '/' && s[2] == ':') {
s.remove(0, 1);
isRepaired = true;
}
newList << s;
}
if (isRepaired) {
m_recent = newList;
Settings.setRecent(m_recent);
Settings.sync();
}
#endif
ui->listWidget->setDragEnabled(true);
ui->listWidget->setDragDropMode(QAbstractItemView::DragOnly);
foreach (QString s, m_recent) {
QStandardItem *item = new QStandardItem(Util::baseName(s));
item->setToolTip(QDir::toNativeSeparators(s));
m_model.appendRow(item);
}
m_proxyModel.setSourceModel(&m_model);
m_proxyModel.setFilterCaseSensitivity(Qt::CaseInsensitive);
ui->listWidget->setModel(&m_proxyModel);
LOG_DEBUG() << "end";
}
RecentDock::~RecentDock()
{
delete ui;
}
void RecentDock::add(const QString &s)
{
// Block anything big that can cause a performance problem
if (s.size() > ShotcutSettings::MaxPath)
return;
QString filePath = QDir::fromNativeSeparators(s);
if (filePath.startsWith(QDir::tempPath()))
return;
QString name = remove(s);
QStandardItem *item = new QStandardItem(name);
item->setToolTip(QDir::toNativeSeparators(s));
m_model.insertRow(0, item);
m_recent.prepend(filePath);
while (m_recent.count() > MaxItems)
m_recent.removeLast();
Settings.setRecent(m_recent);
if (filePath.endsWith(".mlt")) {
auto projects = Settings.projects();
projects.removeOne(filePath);
projects.prepend(filePath);
Settings.setProjects(projects);
}
}
void RecentDock::on_listWidget_activated(const QModelIndex &i)
{
ui->listWidget->setCurrentIndex(QModelIndex());
emit itemActivated(m_proxyModel.itemData(i)[Qt::ToolTipRole].toString());
}
QString RecentDock::remove(const QString &s)
{
QString filePath = QDir::fromNativeSeparators(s);
m_recent.removeOne(filePath);
Settings.setRecent(m_recent);
QString name = Util::baseName(filePath);
QList<QStandardItem *> items = m_model.findItems(name);
if (items.count() > 0)
m_model.removeRow(items.first()->row());
return name;
}
void RecentDock::find()
{
ui->lineEdit->setFocus();
ui->lineEdit->selectAll();
}
void RecentDock::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete) {
on_actionDelete_triggered();
} else {
QDockWidget::keyPressEvent(event);
}
}
void RecentDock::on_lineEdit_textChanged(const QString &search)
{
m_proxyModel.setFilterFixedString(search);
}
void RecentDock::on_actionDelete_triggered()
{
if (ui->listWidget->currentIndex().isValid()) {
auto row = ui->listWidget->currentIndex().row();
auto url = m_recent[row];
m_recent.removeAt(row);
Settings.setRecent(m_recent);
m_model.removeRow(row);
if (url.endsWith(".mlt")) {
auto ls = Settings.projects();
if (ls.removeAll(url) > 0)
Settings.setProjects(ls);
}
emit deleted(url);
}
}
void RecentDock::on_listWidget_customContextMenuRequested(const QPoint &pos)
{
if (ui->listWidget->currentIndex().isValid()) {
QMenu menu(this);
menu.addAction(ui->actionDelete);
menu.exec(mapToGlobal(pos + QPoint(0, ui->lineEdit->height())));
}
}

62
src/docks/recentdock.h Normal file
View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2012-2020 Meltytech, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef RECENTDOCK_H
#define RECENTDOCK_H
#include <QDockWidget>
#include <QSortFilterProxyModel>
#include <QStandardItemModel>
namespace Ui {
class RecentDock;
}
class RecentDock : public QDockWidget
{
Q_OBJECT
public:
explicit RecentDock(QWidget *parent = 0);
~RecentDock();
signals:
void itemActivated(const QString &url);
void deleted(const QString &url);
public slots:
void add(const QString &);
QString remove(const QString &s);
void find();
protected:
void keyPressEvent(QKeyEvent *event);
private:
Ui::RecentDock *ui;
QStringList m_recent;
QStandardItemModel m_model;
QSortFilterProxyModel m_proxyModel;
private slots:
void on_listWidget_activated(const QModelIndex &i);
void on_lineEdit_textChanged(const QString &search);
void on_actionDelete_triggered();
void on_listWidget_customContextMenuRequested(const QPoint &pos);
};
#endif // RECENTDOCK_H

93
src/docks/recentdock.ui Normal file
View File

@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RecentDock</class>
<widget class="QDockWidget" name="RecentDock">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>396</width>
<height>296</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>150</width>
<height>141</height>
</size>
</property>
<property name="whatsThis">
<string notr="true">https://forum.shotcut.org/t/recent-panel/12941/1</string>
</property>
<property name="windowTitle">
<string>Recent</string>
</property>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="LineEditClear" name="lineEdit">
<property name="toolTip">
<string>Show only files with name matching text</string>
</property>
<property name="placeholderText">
<string>search</string>
</property>
</widget>
</item>
<item>
<widget class="QListView" name="listWidget">
<property name="contextMenuPolicy">
<enum>Qt::ContextMenuPolicy::CustomContextMenu</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="spacing">
<number>2</number>
</property>
</widget>
</item>
</layout>
</widget>
<action name="actionDelete">
<property name="text">
<string>Remove</string>
</property>
<property name="toolTip">
<string>Remove</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>LineEditClear</class>
<extends>QLineEdit</extends>
<header>widgets/lineeditclear.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../../icons/resources.qrc"/>
</resources>
<connections/>
</ui>

70
src/docks/scopedock.cpp Normal file
View File

@@ -0,0 +1,70 @@
/*
* Copyright (c) 2015-2023 Meltytech, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "scopedock.h"
#include "Logger.h"
#include "controllers/scopecontroller.h"
#include "mltcontroller.h"
#include <QAction>
#include <QtWidgets/QScrollArea>
ScopeDock::ScopeDock(ScopeController *scopeController, ScopeWidget *scopeWidget)
: QDockWidget()
, m_scopeController(scopeController)
, m_scopeWidget(scopeWidget)
{
LOG_DEBUG() << "begin";
setObjectName(m_scopeWidget->objectName() + "Dock");
QScrollArea *scrollArea = new QScrollArea();
scrollArea->setFrameShape(QFrame::NoFrame);
scrollArea->setWidgetResizable(true);
scrollArea->setWidget(m_scopeWidget);
QDockWidget::setWidget(scrollArea);
QDockWidget::setWindowTitle(m_scopeWidget->getTitle());
connect(toggleViewAction(), SIGNAL(toggled(bool)), this, SLOT(onActionToggled(bool)));
connect(this, &QDockWidget::dockLocationChanged, m_scopeWidget, &ScopeWidget::moved);
LOG_DEBUG() << "end";
}
void ScopeDock::resizeEvent(QResizeEvent *e)
{
if (width() > height()) {
m_scopeWidget->setOrientation(Qt::Horizontal);
} else {
m_scopeWidget->setOrientation(Qt::Vertical);
}
QDockWidget::resizeEvent(e);
}
void ScopeDock::onActionToggled(bool checked)
{
if (checked) {
connect(m_scopeController,
SIGNAL(newFrame(const SharedFrame &)),
m_scopeWidget,
SLOT(onNewFrame(const SharedFrame &)));
MLT.refreshConsumer();
} else {
disconnect(m_scopeController,
SIGNAL(newFrame(const SharedFrame &)),
m_scopeWidget,
SLOT(onNewFrame(const SharedFrame &)));
}
}

48
src/docks/scopedock.h Normal file
View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) 2015 Meltytech, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SCOPEDOCK_H
#define SCOPEDOCK_H
#include "widgets/scopes/scopewidget.h"
#include <QDockWidget>
#include <QObject>
class ScopeController;
class ScopeDock Q_DECL_FINAL : public QDockWidget
{
Q_OBJECT
public:
explicit ScopeDock(ScopeController *scopeController, ScopeWidget *scopeWidget);
protected:
void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE;
private:
ScopeController *m_scopeController;
ScopeWidget *m_scopeWidget;
void setWidget(QWidget *widget); // Private to disallow use
private slots:
void onActionToggled(bool checked);
};
#endif // SCOPEDOCK_H

1388
src/docks/subtitlesdock.cpp Normal file

File diff suppressed because it is too large Load Diff

108
src/docks/subtitlesdock.h Normal file
View File

@@ -0,0 +1,108 @@
/*
* Copyright (c) 2024-2025 Meltytech, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SUBTITLESDOCK_H
#define SUBTITLESDOCK_H
#include <MltPlaylist.h>
#include <QDockWidget>
class SubtitlesModel;
class SubtitlesSelectionModel;
class QComboBox;
class QItemSelection;
class QLabel;
class QTextEdit;
class QTreeView;
class SpeechDialog;
class SubtitlesDock : public QDockWidget
{
Q_OBJECT
public:
explicit SubtitlesDock(QWidget *parent = 0);
~SubtitlesDock();
void setModel(SubtitlesModel *model, SubtitlesSelectionModel *selectionModel);
void importSrtFromFile(const QString &srtPath,
const QString &trackName,
const QString &lang,
bool includeNonspoken);
signals:
void seekRequested(int pos);
void addAllTimeline(Mlt::Playlist *, bool skipProxy, bool emptyTrack);
void createOrEditFilterOnOutput(Mlt::Filter *, const QStringList &key_properties);
private slots:
void onPositionChanged(int position);
void onStartColumnToggled(bool checked);
void onEndColumnToggled(bool checked);
void onDurationColumnToggled(bool checked);
protected:
void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE;
private:
void setupActions();
void onCreateOrEditRequested();
void onAddRequested();
void onRemoveRequested();
void onSetStartRequested();
void onSetEndRequested();
void onMoveRequested();
void onTextEdited();
void onModelReset();
void updateActionAvailablity();
void addSubtitleTrack();
void removeSubtitleTrack();
void editSubtitleTrack();
void refreshTracksCombo();
void importSubtitles();
void exportSubtitles();
void onItemDoubleClicked(const QModelIndex &index);
void resizeTextWidgets();
void updateTextWidgets();
void setCurrentItem(int trackIndex, int itemIndex);
void refreshWidgets();
void selectItemForTime();
QString availableTrackName();
bool trackNameExists(const QString &name);
void ensureTrackExists();
void burnInOnTimeline();
void generateTextOnTimeline();
void speechToText();
void textToSpeech();
bool findWhisperExe();
SubtitlesModel *m_model;
SubtitlesSelectionModel *m_selectionModel;
QLabel *m_addToTimelineLabel;
QComboBox *m_trackCombo;
QTreeView *m_treeView;
QTextEdit *m_text;
QTextEdit *m_prev;
QTextEdit *m_next;
QLabel *m_prevLabel;
QLabel *m_textLabel;
QLabel *m_nextLabel;
int m_pos;
bool m_textEditInProgress;
std::unique_ptr<SpeechDialog> m_speechDialog;
};
#endif // SUBTITLESDOCK_H

4472
src/docks/timelinedock.cpp Normal file

File diff suppressed because it is too large Load Diff

286
src/docks/timelinedock.h Normal file
View File

@@ -0,0 +1,286 @@
/*
* Copyright (c) 2013-2025 Meltytech, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TIMELINEDOCK_H
#define TIMELINEDOCK_H
#include "jobs/ffmpegjob.h"
#include "models/markersmodel.h"
#include "models/multitrackmodel.h"
#include "models/subtitlesmodel.h"
#include "models/subtitlesselectionmodel.h"
#include "sharedframe.h"
#include <QApplication>
#include <QDateTime>
#include <QDockWidget>
#include <QQuickWidget>
#include <QTimer>
namespace Timeline {
class UpdateCommand;
class TrimCommand;
} // namespace Timeline
class UndoHelper;
class QMenu;
class TimelineDock : public QDockWidget
{
Q_OBJECT
Q_PROPERTY(int position READ position WRITE setPosition NOTIFY positionChanged)
Q_PROPERTY(int currentTrack READ currentTrack WRITE setCurrentTrack NOTIFY currentTrackChanged)
Q_PROPERTY(
QVariantList selection READ selectionForJS WRITE setSelectionFromJS NOTIFY selectionChanged)
Q_PROPERTY(bool isRecording READ isRecording NOTIFY isRecordingChanged)
Q_PROPERTY(int loopStart READ loopStart NOTIFY loopChanged)
Q_PROPERTY(int loopEnd READ loopEnd NOTIFY loopChanged)
public:
explicit TimelineDock(QWidget *parent = 0);
~TimelineDock();
enum TrimLocation { TrimInPoint, TrimOutPoint };
MultitrackModel *model() { return &m_model; }
MarkersModel *markersModel() { return &m_markersModel; }
SubtitlesModel *subtitlesModel() { return &m_subtitlesModel; }
SubtitlesSelectionModel *subtitlesSelectionModel() { return &m_subtitlesSelectionModel; }
int position() const { return m_position; }
void setPosition(int position);
Mlt::Producer producerForClip(int trackIndex, int clipIndex);
int clipIndexAtPlayhead(int trackIndex = -1);
int clipIndexAtPosition(int trackIndex, int position);
void chooseClipAtPosition(int position, int &trackIndex, int &clipIndex);
void setCurrentTrack(int currentTrack);
int currentTrack() const;
int clipCount(int trackIndex) const;
void setSelectionFromJS(const QVariantList &list);
void setSelection(QList<QPoint> selection = QList<QPoint>(),
int trackIndex = -1,
bool isMultitrack = false);
QVariantList selectionForJS() const;
const QList<QPoint> selection() const;
const QVector<QUuid> selectionUuids();
const QList<QPoint> uuidsToSelection(QVector<QUuid> uuids) const;
void saveAndClearSelection();
Q_INVOKABLE void restoreSelection();
Q_INVOKABLE QVariantList getGroupForClip(int trackIndex, int clipIndex);
void selectClipUnderPlayhead();
int centerOfClip(int trackIndex, int clipIndex);
bool isTrackLocked(int trackIndex) const;
void trimClipAtPlayhead(TrimLocation location, bool ripple);
Q_INVOKABLE bool isMultitrackSelected() const { return m_selection.isMultitrackSelected; }
Q_INVOKABLE int selectedTrack() const { return m_selection.selectedTrack; }
Q_INVOKABLE bool isFloating() const { return QDockWidget::isFloating(); }
Q_INVOKABLE static void openProperties();
void emitSelectedChanged(const QVector<int> &roles);
void replaceClipsWithHash(const QString &hash, Mlt::Producer &producer);
Q_INVOKABLE void recordAudio();
Q_INVOKABLE void stopRecording();
bool isRecording() const { return m_isRecording; }
int addTrackIfNeeded(TrackType trackType);
void getSelectionRange(int *start, int *end);
int loopStart() const { return m_loopStart; }
int loopEnd() const { return m_loopEnd; }
signals:
void currentTrackChanged();
void selectionChanged();
void seeked(int position);
void positionChanged(int position);
void loopChanged();
void clipOpened(Mlt::Producer *producer);
void dragging(const QPointF &pos, int duration);
void dropped();
void dropAccepted(const QString &xml);
void gainChanged(double gain);
void fadeInChanged(int duration);
void fadeOutChanged(int duration);
void selected(Mlt::Producer *producer);
void clipClicked();
void showStatusMessage(QString);
void clipCopied();
void clipMoved(int fromTrack, int toTrack, int clipIndex, int position, bool ripple);
void filteredClicked();
void durationChanged();
void transitionAdded(int trackIndex, int clipIndex, int position, bool ripple);
void zoomIn();
void zoomOut();
void zoomToFit();
void setZoom(double value);
void markerRangesChanged();
void markerSeeked(int markerIndex);
void isRecordingChanged(bool);
void multitrackSelected();
void warnTrackLocked(int trackIndex);
void refreshWaveforms();
void updateThumbnails(int trackIndex, int clipIndex);
void trimStarted();
void trimEnded();
public slots:
int addAudioTrack();
int addVideoTrack();
void alignSelectedClips();
void onShowFrame(const SharedFrame &frame);
void onSeeked(int position);
void append(int trackIndex);
void remove(int trackIndex, int clipIndex, bool ignoreTransition = false);
bool mergeClipWithNext(int trackIndex, int clipIndex, bool dryrun);
void lift(int trackIndex, int clipIndex, bool ignoreTransition = false);
void removeSelection(bool withCopy = false);
void liftSelection();
void incrementCurrentTrack(int by);
void selectTrackHead(int trackIndex);
void selectMultitrack();
void copy(int trackIndex, int clipIndex);
void setTrackName(int trackIndex, const QString &value);
void toggleTrackMute(int trackIndex);
void toggleTrackHidden(int trackIndex);
void setTrackComposite(int trackIndex, bool composite);
void setTrackLock(int trackIndex, bool lock);
bool moveClip(int fromTrack, int toTrack, int clipIndex, int position, bool ripple);
void onClipMoved(int fromTrack, int toTrack, int clipIndex, int position, bool ripple);
bool trimClipIn(
int trackIndex, int clipIndex, int oldClipIndex, int delta, bool ripple, bool roll);
bool trimClipOut(int trackIndex, int clipIndex, int delta, bool ripple, bool roll);
void insert(int trackIndex, int position = -1, const QString &xml = QString(), bool seek = true);
void overwrite(int trackIndex,
int position = -1,
const QString &xml = QString(),
bool seek = true);
void appendFromPlaylist(Mlt::Playlist *playlist, bool skipProxy, bool emptyTrack);
bool changeGain(int trackIndex, int clipIndex, double gain);
void fadeIn(int trackIndex, int clipIndex = -1, int duration = -1);
void fadeOut(int trackIndex, int clipIndex = -1, int duration = -1);
void seekPreviousEdit();
void seekNextEdit();
void seekInPoint(int clipIndex);
void clearSelectionIfInvalid();
void insertTrack();
void insertAudioTrack();
void insertVideoTrack();
void removeTrack();
void moveTrack(int fromTrackIndex, int toTrackIndex);
void moveTrackUp();
void moveTrackDown();
void onProducerChanged(Mlt::Producer *);
void emitSelectedFromSelection();
void remakeAudioLevels(int trackIndex, int clipIndex, bool force = true);
void commitTrimCommand();
void onRowsInserted(const QModelIndex &parent, int first, int last);
void onRowsRemoved(const QModelIndex &parent, int first, int last);
void onRowsMoved(
const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row);
void detachAudio(int trackIndex, int clipIndex);
void selectAll();
void selectAllOnCurrentTrack();
void onProducerModified();
void replace(int trackIndex, int clipIndex, const QString &xml = QString());
void createOrEditMarker();
void createOrEditSelectionMarker();
void createMarker();
void editMarker(int markerIndex);
void deleteMarker(int markerIndex = -1);
void seekNextMarker();
void seekPrevMarker();
void onFilterModelChanged();
void trimClipIn(bool ripple = false);
void trimClipOut(bool ripple = false);
void initLoad();
void handleDrop(int trackIndex, int position, QString xml);
void onLoopChanged(int start, int end);
protected:
void dragEnterEvent(QDragEnterEvent *event);
void dragMoveEvent(QDragMoveEvent *event);
void dragLeaveEvent(QDragLeaveEvent *event);
void dropEvent(QDropEvent *event);
bool event(QEvent *event);
void keyPressEvent(QKeyEvent *event);
void keyReleaseEvent(QKeyEvent *event);
private:
bool isBlank(int trackIndex, int clipIndex);
bool clipsAreSelected();
bool blankIsSelected();
bool nothingIsSelected();
bool isTransition(int trackIndex, int clipIndex);
bool isEmptyTrack(Mlt::Playlist &playlist);
void emitNonSeekableWarning();
void addTrackIfNeeded(int mltTrackIndex, Mlt::Producer *srcTrack);
void setupActions();
bool isMultitrackValid() { return m_model.tractor() && !m_model.trackList().empty(); }
void reportSelectionChange();
void applyCopiedFiltersToSelectdClips();
void insertOrOverwriteDrop(int trackIndex, int position, const QString &xml);
void freezeFrame();
void addGenerator(QWidget *widget);
QQuickWidget m_quickView;
MultitrackModel m_model;
MarkersModel m_markersModel;
SubtitlesModel m_subtitlesModel;
SubtitlesSelectionModel m_subtitlesSelectionModel;
int m_position{-1};
std::unique_ptr<Timeline::UpdateCommand> m_updateCommand;
bool m_ignoreNextPositionChange{false};
struct Selection
{
QList<QPoint> selectedClips; // x is the clip index, y is the track index
int selectedTrack;
bool isMultitrackSelected;
};
Selection m_selection;
int m_savedSelectedTrack{-1};
bool m_savedIsMultitrackSelected{false};
QVector<QUuid> m_savedSelectionUuids;
QTimer m_selectionSignalTimer;
std::unique_ptr<Timeline::TrimCommand> m_trimCommand;
std::unique_ptr<UndoHelper> m_undoHelper;
int m_trimDelta{0};
int m_transitionDelta{0};
bool m_isRecording{false};
std::unique_ptr<AbstractJob> m_recordJob;
QTimer m_recordingTimer;
QDateTime m_recordingTime;
int m_recordingTrackIndex{-1};
int m_recordingClipIndex{-1};
int m_currentTrack{0};
QMenu *m_mainMenu{nullptr};
QMenu *m_clipMenu{nullptr};
int m_loopStart{-1};
int m_loopEnd{-1};
private slots:
void load(bool force);
void onTopLevelChanged(bool floating);
void onTransitionAdded(int trackIndex, int clipIndex, int position, bool ripple);
void selectClip(int trackIndex, int clipIndex);
void onMultitrackClosed();
void reloadTimelineModels();
void onRecordStarted();
void updateRecording();
void onRecordFinished(AbstractJob *, bool);
void onWarnTrackLocked();
void onTimelineRightClicked();
void onClipRightClicked();
void onNoMoreEmptyTracks(bool isAudio);
void addGenerator();
};
#endif // TIMELINEDOCK_H