Skip to content

Commit

Permalink
Add "Copy Current Filter" action (#1620)
Browse files Browse the repository at this point in the history
* Add "Copy Current Filter" action

As suggested here:
https://forum.shotcut.org/t/copy-single-filters/13909

* Resolve review comments

Remove extra button and put copy options in a menu

* Do not show icons and remove "Filter" from action names.

This removes redundancy.
  • Loading branch information
bmatherly authored Jan 28, 2025
1 parent 94e720d commit aeb5ffb
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 19 deletions.
35 changes: 30 additions & 5 deletions src/docks/filtersdock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <QtWidgets/QScrollArea>
#include <QQmlEngine>
#include <QDir>
#include <QMenu>
#include <QUrl>
#include <QQmlContext>
#include <QAction>
Expand Down Expand Up @@ -148,6 +149,15 @@ 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()
Expand Down Expand Up @@ -183,6 +193,8 @@ void FiltersDock::load()

QObject::connect(m_qview.rootObject(), SIGNAL(currentFilterRequested(int)),
SIGNAL(currentFilterRequested(int)));
QObject::connect(m_qview.rootObject(), SIGNAL(copyFilterRequested()),
SLOT(showCopyFilterMenu()));
}

void FiltersDock::setupActions()
Expand Down Expand Up @@ -217,17 +229,30 @@ void FiltersDock::setupActions()
addAction(action);
Actions.add("filtersRemoveFilterAction", action, windowTitle());

action = new QAction(tr("Copy Filters"), this);
action = new QAction(tr("Copy Enabled"), this);
action->setToolTip(tr("Copy checked filters to the clipboard"));
icon = QIcon::fromTheme("edit-copy",
QIcon(":/icons/oxygen/32x32/actions/edit-copy.png"));
action->setIcon(icon);
connect(action, &QAction::triggered, this, [ = ]() {
QmlApplication::singleton().copyFilters();
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",
Expand Down
1 change: 1 addition & 0 deletions src/docks/filtersdock.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public slots:
void onSeeked(int position);
void onShowFrame(const SharedFrame &frame);
void openFilterMenu() const;
void showCopyFilterMenu();
void onServiceInChanged(int delta, Mlt::Service *service);
void load();

Expand Down
25 changes: 19 additions & 6 deletions src/mltcontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1117,13 +1117,14 @@ static int indexOfFirstNonGpu(Producer &toProducer)
}

void Controller::copyFilters(Producer &fromProducer, Producer &toProducer, bool fromClipboard,
bool includeDisabled)
int filterIndex)
{
int in = fromProducer.get(kFilterInProperty) ? fromProducer.get_int(kFilterInProperty) :
fromProducer.get_in();
int out = fromProducer.get(kFilterOutProperty) ? fromProducer.get_int(
kFilterOutProperty) : fromProducer.get_out();
int count = fromProducer.filter_count();
int filterCount = 0;

// Get the index of the first non-GPU filter or link in toProducer
int firstNonGpuService = fromClipboard ? indexOfFirstNonGpu(toProducer) : -1;
Expand All @@ -1132,8 +1133,16 @@ void Controller::copyFilters(Producer &fromProducer, Producer &toProducer, bool
QScopedPointer<Mlt::Filter> fromFilter(fromProducer.filter(i));
if (fromFilter && fromFilter->is_valid() && !fromFilter->get_int("_loader")
&& !fromFilter->get_int(kShotcutHiddenProperty)
&& fromFilter->get("mlt_service")
&& (includeDisabled || !fromFilter->get_int("disable"))) {
&& fromFilter->get("mlt_service")) {

filterCount++;
if (filterIndex >= 0 && filterIndex != (filterCount - 1)) {
continue;
}

if (filterIndex == FILTER_INDEX_ENABLED && fromFilter->get_int("disable")) {
continue;
}

// Determine if filter can be added
auto metadata = MAIN.filterController()->metadataForService(fromFilter.data());
Expand Down Expand Up @@ -1177,6 +1186,10 @@ void Controller::copyFilters(Producer &fromProducer, Producer &toProducer, bool
QScopedPointer<Mlt::Link> fromLink(fromChain.link(i));
if (fromLink && fromLink->is_valid() && fromLink->get("mlt_service")
&& !fromLink->get_int("_loader")) {
filterCount++;
if (filterIndex != -1 && filterIndex != (filterCount - 1)) {
continue;
}
Mlt::Link toLink(fromLink->get("mlt_service"));
if (toLink.is_valid()) {
toLink.inherit(*fromLink);
Expand All @@ -1190,14 +1203,14 @@ void Controller::copyFilters(Producer &fromProducer, Producer &toProducer, bool
}
}

void Controller::copyFilters(Mlt::Producer *producer)
void Controller::copyFilters(Mlt::Producer *producer, int filterIndex)
{
if (producer && producer->is_valid()) {
initFiltersClipboard();
copyFilters(*producer, *m_filtersClipboard, false, false);
copyFilters(*producer, *m_filtersClipboard, false, filterIndex);
} else if (m_producer && m_producer->is_valid()) {
initFiltersClipboard();
copyFilters(*m_producer, *m_filtersClipboard, false, false);
copyFilters(*m_producer, *m_filtersClipboard, false, filterIndex);
}
}

Expand Down
9 changes: 7 additions & 2 deletions src/mltcontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ class Controller
virtual int reconfigure(bool isMulti) = 0;

public:
enum {
FILTER_INDEX_ALL = -1,
FILTER_INDEX_ENABLED = -2,
};

static Controller &singleton(QObject *parent = nullptr);
virtual ~Controller();
static void destroy();
Expand Down Expand Up @@ -130,8 +135,8 @@ class Controller
void setUuid(Mlt::Properties &properties, QUuid uid) const;
QUuid ensureHasUuid(Mlt::Properties &properties) const;
static void copyFilters(Mlt::Producer &fromProducer, Mlt::Producer &toProducer,
bool fromClipboard = false, bool includeDisabled = true);
void copyFilters(Mlt::Producer *producer = nullptr);
bool fromClipboard = false, int filterIndex = FILTER_INDEX_ENABLED);
void copyFilters(Mlt::Producer *producer = nullptr, int filterIndex = FILTER_INDEX_ENABLED);
void pasteFilters(Mlt::Producer *producer = nullptr, Mlt::Producer *fromProducer = nullptr);
static void adjustFilters(Mlt::Producer &producer, int startIndex = 0);
static void adjustFilter(Mlt::Filter *filter, int in, int out, int inDelta, int outDelta,
Expand Down
2 changes: 1 addition & 1 deletion src/qml/views/filter/CopyFiltersDialog.qml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Window {
id: nameDialog

function acceptName() {
application.copyFilters();
application.copyEnabledFilters();
let name = nameField.text.trim();
if (name.length) {
metadatamodel.saveFilterSet(name);
Expand Down
5 changes: 3 additions & 2 deletions src/qml/views/filter/filterview.qml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Rectangle {
property int selectedIndex: Shotcut.Filter.NoCurrentFilter

signal currentFilterRequested(int attachedIndex)
signal copyFilterRequested()

function clearCurrentFilter() {
if (filterConfig.item) {
Expand Down Expand Up @@ -225,10 +226,10 @@ Rectangle {
icon.source: 'qrc:///icons/oxygen/32x32/actions/edit-copy.png'
enabled: selectedIndex > Shotcut.Filter.NoCurrentFilter
opacity: enabled ? 1 : 0.5
onClicked: application.copyFilters()
onClicked: root.copyFilterRequested()

Shotcut.HoverTip {
text: qsTr('Copy checked filters')
text: qsTr('Copy filters')
}
}

Expand Down
27 changes: 25 additions & 2 deletions src/qmltypes/qmlapplication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,34 @@ bool QmlApplication::hasFiltersOnClipboard()
return MLT.hasFiltersOnClipboard();
}

void QmlApplication::copyFilters()
void QmlApplication::copyEnabledFilters()
{
QScopedPointer<Mlt::Producer> producer(new Mlt::Producer(
MAIN.filterController()->attachedModel()->producer()));
MLT.copyFilters(producer.data());
MLT.copyFilters(producer.data(), MLT.FILTER_INDEX_ENABLED);
QGuiApplication::clipboard()->setText(MLT.filtersClipboardXML());
emit QmlApplication::singleton().filtersCopied();
}

void QmlApplication::copyAllFilters()
{
QScopedPointer<Mlt::Producer> producer(new Mlt::Producer(
MAIN.filterController()->attachedModel()->producer()));
MLT.copyFilters(producer.data(), MLT.FILTER_INDEX_ENABLED);
QGuiApplication::clipboard()->setText(MLT.filtersClipboardXML());
emit QmlApplication::singleton().filtersCopied();
}

void QmlApplication::copyCurrentFilter()
{
int currentIndex = MAIN.filterController()->currentIndex();
if (currentIndex < 0) {
MAIN.showStatusMessage(tr("Select a filter to copy"));
return;
}
QScopedPointer<Mlt::Producer> producer(new Mlt::Producer(
MAIN.filterController()->attachedModel()->producer()));
MLT.copyFilters(producer.data(), currentIndex);
QGuiApplication::clipboard()->setText(MLT.filtersClipboardXML());
emit QmlApplication::singleton().filtersCopied();
}
Expand Down
4 changes: 3 additions & 1 deletion src/qmltypes/qmlapplication.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ class QmlApplication : public QObject
static QString OS();
static QRect mainWinRect();
static bool hasFiltersOnClipboard();
Q_INVOKABLE static void copyFilters();
Q_INVOKABLE static void copyAllFilters();
Q_INVOKABLE static void copyEnabledFilters();
Q_INVOKABLE static void copyCurrentFilter();
Q_INVOKABLE static void pasteFilters();
Q_INVOKABLE static QString clockFromFrames(int frames);
Q_INVOKABLE static QString timeFromFrames(int frames);
Expand Down

0 comments on commit aeb5ffb

Please sign in to comment.