Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_filter_manager.cc
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2007 Boudewijn Rempt <boud@valdyas.org>
3 * SPDX-FileCopyrightText: 2007 Cyrille Berger <cberger@cberger.net>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
9
10
11#include <QHash>
12#include <KisSignalMapper.h>
13
14#include <QMessageBox>
15#include <kactionmenu.h>
16#include <kactioncollection.h>
17
18#include <KoID.h>
19#include <KisMainWindow.h>
20
21// krita/image
22#include <filter/kis_filter.h>
25#include <kis_paint_device.h>
29#include <kis_time_span.h>
30#include <kis_image_config.h>
31#include <KisAnimAutoKey.h>
32
33// krita/ui
34#include "KisViewManager.h"
35#include "kis_canvas2.h"
37
38#include "kis_action.h"
39#include "kis_action_manager.h"
43#include "krita_utils.h"
44#include "kis_icon_utils.h"
45#include "kis_layer_utils.h"
47
73
75 : d(new Private)
76{
77 d->view = view;
78}
79
83
85{
86 Q_UNUSED(imageView);
87}
88
89
91{
92 d->actionCollection = ac;
93 d->actionManager = actionManager;
94
95 // Setup reapply action
96 d->reapplyAction = d->actionManager->createAction("filter_apply_again");
98 d->reapplyAction->setEnabled(false);
99
100 d->reapplyActionReprompt = d->actionManager->createAction("filter_apply_reprompt");
101 d->reapplyActionReprompt->setActivationFlags(KisAction::ACTIVE_DEVICE);
102 d->reapplyActionReprompt->setEnabled(false);
103
104 connect(d->reapplyAction, SIGNAL(triggered()), SLOT(reapplyLastFilter()));
105 connect(d->reapplyActionReprompt, SIGNAL(triggered()), SLOT(reapplyLastFilterReprompt()));
106
107 connect(&d->actionsMapper, SIGNAL(mapped(QString)), SLOT(showFilterDialog(QString)));
108
109 // Setup list of filters
111 keys.sort();
112 Q_FOREACH (const QString &filterName, keys) {
113 insertFilter(filterName);
114 }
115
116 connect(KisFilterRegistry::instance(), SIGNAL(filterAdded(QString)), SLOT(insertFilter(QString)));
117}
118
119void KisFilterManager::insertFilter(const QString & filterName)
120{
121 Q_ASSERT(d->actionCollection);
122
123 KisFilterSP filter = KisFilterRegistry::instance()->value(filterName);
124 Q_ASSERT(filter);
125
126 if (d->filters2Action.contains(filter.data())) {
127 warnKrita << "Filter" << filterName << " has already been inserted";
128 return;
129 }
130
131 KoID category = filter->menuCategory();
132 KActionMenu* actionMenu = d->filterActionMenus[ category.id()];
133 if (!actionMenu) {
134 actionMenu = new KActionMenu(category.name(), this);
135 d->actionCollection->addAction(category.id(), actionMenu);
136 d->filterActionMenus[category.id()] = actionMenu;
137 }
138
139 KisAction *action = new KisAction(filter->menuEntry(), this);
140 action->setDefaultShortcut(filter->shortcut());
142
143 d->actionManager->addAction(QString("krita_filter_%1").arg(filterName), action);
144 d->filters2Action[filter.data()] = action;
145
146 actionMenu->addAction(action);
147
148 d->actionsMapper.setMapping(action, filterName);
149 connect(action, SIGNAL(triggered()), &d->actionsMapper, SLOT(map()));
150}
151
153{
154 if (!d->view) return;
155
156 bool enable = false;
157
158 KisNodeSP activeNode = d->view->activeNode();
159 enable = activeNode && activeNode->hasEditablePaintDevice();
160
161 d->reapplyAction->setEnabled(enable);
162
163 for (QHash<KisFilter*, QAction *>::iterator it = d->filters2Action.begin();
164 it != d->filters2Action.end(); ++it) {
165
166 bool localEnable = enable;
167
168 it.value()->setEnabled(localEnable);
169 }
170}
171
173{
174 if (!d->lastConfiguration) return;
175
176 apply(d->lastConfiguration);
177 finish();
178}
179
181{
182 if (!d->lastConfiguration) return;
183
184 showFilterDialog(d->lastConfiguration->name(), d->lastConfiguration);
185}
186
187void KisFilterManager::showFilterDialog(const QString &filterId, KisFilterConfigurationSP overrideDefaultConfig)
188{
189 if (!d->view->activeNode()->isEditable()) {
190 d->view->showFloatingMessage(i18n("Cannot apply filter to locked layer."),
191 KisIconUtils::loadIcon("object-locked"));
192 return;
193 }
194
195 if (d->filterDialog && d->filterDialog->isVisible()) {
196 KisFilterSP filter = KisFilterRegistry::instance()->value(filterId);
197 d->filterDialog->setFilter(filter, overrideDefaultConfig);
198 return;
199 }
200
201 connect(d->view->image(),
202 SIGNAL(sigStrokeCancellationRequested()),
204 Qt::UniqueConnection);
205
206 connect(d->view->image(),
207 SIGNAL(sigStrokeEndRequested()),
209 Qt::UniqueConnection);
210
215 if (!d->view->blockUntilOperationsFinished(d->view->image())) {
216 return;
217 }
218
219 Q_ASSERT(d->view);
220 Q_ASSERT(d->view->activeNode());
221
222 KisPaintDeviceSP dev = d->view->activeNode()->paintDevice();
223 if (!dev) {
224 warnKrita << "KisFilterManager::showFilterDialog(): Filtering was requested for illegal active layer!" << d->view->activeNode();
225 return;
226 }
227
228 KisFilterSP filter = KisFilterRegistry::instance()->value(filterId);
229
230 if (dev->colorSpace()->willDegrade(filter->colorSpaceIndependence())) {
231 // Warning bells!
232 if (filter->colorSpaceIndependence() == TO_LAB16) {
233 if (QMessageBox::warning(d->view->mainWindow(),
234 i18nc("@title:window", "Krita"),
235 i18n("The %1 filter will convert your %2 data to 16-bit L*a*b* and vice versa. ",
236 filter->name(),
237 dev->colorSpace()->name()),
238 QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok)
239 != QMessageBox::Ok) return;
240
241 } else if (filter->colorSpaceIndependence() == TO_RGBA16) {
242 if (QMessageBox::warning(d->view->mainWindow(),
243 i18nc("@title:window", "Krita"),
244 i18n("The %1 filter will convert your %2 data to 16-bit RGBA and vice versa. ",
245 filter->name() , dev->colorSpace()->name()),
246 QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok)
247 != QMessageBox::Ok) return;
248 }
249 }
250
251 if (filter->showConfigurationWidget()) {
252 if (!d->filterDialog) {
253 d->filterDialog = new KisDlgFilter(d->view , d->view->activeNode(), this, d->view->mainWindow());
254 d->filterDialog->setAttribute(Qt::WA_DeleteOnClose); // make sure that the dialog is deleted when calling `done()`
255 connect(d->filterDialog, SIGNAL(finished(int)),
256 this, SLOT(filterDialogHasFinished(int)));
257 }
258
259 d->filterDialog->setFilter(filter, overrideDefaultConfig);
260 d->filterDialog->setVisible(true);
261 } else {
262 KisFilterConfigurationSP defaultConfiguration =
263 overrideDefaultConfig ? overrideDefaultConfig : filter->defaultConfiguration(KisGlobalResourcesInterface::instance());
264 apply(defaultConfiguration);
265 finish();
266 }
267}
268
270{
271 KisFilterConfigurationSP filterConfig = _filterConfig->cloneWithResourcesSnapshot();
272
273 KisFilterSP filter = KisFilterRegistry::instance()->value(filterConfig->name());
274 KisImageWSP image = d->view->image();
275
276 if (d->currentStrokeId) {
277 image->cancelStroke(d->currentStrokeId);
278
279 d->currentStrokeId.clear();
280 d->idleBarrierCookie.clear();
281 } else {
282 image->waitForDone();
283 }
284
285 if (!d->externalCancelUpdatesStorage) {
286 // Lazily initialize the cancel updates storage, just in case
287 // if the stroke has been cancelled in the meantime.
288
289 d->externalCancelUpdatesStorage.reset(new KisFilterStrokeStrategy::ExternalCancelUpdatesStorage());
290 }
291
292 KoCanvasResourceProvider *resourceManager =
293 d->view->canvasResourceProvider()->resourceManager();
294
295 KisResourcesSnapshotSP resources =
296 new KisResourcesSnapshot(image,
297 d->view->activeNode(),
298 resourceManager);
299
301 KisFilterConfigurationSP(filterConfig),
302 resources,
303 d->externalCancelUpdatesStorage.toWeakRef());
304 {
305 KConfigGroup group( KSharedConfig::openConfig(), "filterdialog");
306 strategy->setForceLodModeIfPossible(group.readEntry("forceLodMode", true));
307 }
308
309 d->currentStrokeId =
310 image->startStroke(strategy);
311
312 // Apply filter preview to active, visible frame only.
313 KisImageConfig imgConf(true);
314 image->addJob(d->currentStrokeId, new KisFilterStrokeStrategy::FilterJobData());
315
316 {
319 d->idleBarrierCookie = data->idleBarrierCookie();
320 image->addJob(d->currentStrokeId, data);
321 }
322
323 d->currentlyAppliedConfiguration = filterConfig;
324}
325
327{
328 Q_ASSERT(d->currentStrokeId);
329
330 if (d->filterAllSelectedFrames) { // Apply filter to the other selected frames...
331 KisImageSP image = d->view->image();
332 KisPaintDeviceSP paintDevice = d->view->activeNode()->paintDevice();
333 KisNodeSP node = d->view->activeNode();
334
335 // Filter selected times to only those with keyframes...
336 QSet<int> selectedTimes = image->animationInterface()->activeLayerSelectedTimes();
337 selectedTimes = KisLayerUtils::filterTimesForOnlyRasterKeyedTimes(node, selectedTimes);
338 QSet<int> uniqueFrames = KisLayerUtils::fetchUniqueFrameTimes(node, selectedTimes, true);
339
340 Q_FOREACH(const int& frameTime, uniqueFrames) {
341 image->addJob(d->currentStrokeId, new KisFilterStrokeStrategy::FilterJobData(frameTime));
342 }
343 }
344
345 d->view->image()->endStroke(d->currentStrokeId);
346
347 KisFilterSP filter = KisFilterRegistry::instance()->value(d->currentlyAppliedConfiguration->name());
348 if (filter->bookmarkManager()) {
350 d->currentlyAppliedConfiguration.data());
351 }
352
353 d->lastConfiguration = d->currentlyAppliedConfiguration;
354 d->reapplyAction->setEnabled(true);
355 d->reapplyAction->setText(i18n("Apply Filter Again: %1", filter->name()));
356
357 d->idleBarrierCookie.clear();
358 d->currentlyAppliedConfiguration.clear();
359}
360
362{
363 Q_ASSERT(d->currentStrokeId);
364
365 // we should to notify the stroke that it should do the updates itself.
366 d->externalCancelUpdatesStorage->shouldIssueCancellationUpdates.ref();
367 d->view->image()->cancelStroke(d->currentStrokeId);
368
369 d->currentStrokeId.clear();
370 d->idleBarrierCookie.clear();
371 d->currentlyAppliedConfiguration.clear();
372 d->externalCancelUpdatesStorage.clear();
373}
374
376{
378
379 d->filterDialog->reject();
380}
381
383{
384 return bool(d->currentStrokeId);
385}
386
388{
389 return !d->idleBarrierCookie;
390}
391
392void KisFilterManager::setFilterAllSelectedFrames(bool filterAllSelectedFrames)
393{
394 d->filterAllSelectedFrames = filterAllSelectedFrames;
395}
396
398{
399 return d->filterAllSelectedFrames;
400}
401
403{
404 if (d->currentStrokeId && d->filterDialog) {
405 d->filterDialog->accept();
406 }
407}
408
410{
411 if (d->currentStrokeId && d->filterDialog) {
412 d->filterDialog->reject();
413 }
414}
416{
417 // as far as we are concerned, filterDialog has been deleted
418 d->filterDialog = nullptr;
419}
@ TO_RGBA16
@ TO_LAB16
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
A KisActionManager class keeps track of KisActions. These actions are always associated with the GUI....
KisAction * createAction(const QString &name)
void setActivationFlags(ActivationFlags flags)
@ ACTIVE_DEVICE
Activate if the active node has a paint device, i.e. there are pixels to be modified.
Definition kis_action.h:47
void setDefaultShortcut(const QKeySequence &shortcut)
bool isStrokeRunning() const
void setup(KisKActionCollection *ac, KisActionManager *actionManager)
void insertFilter(const QString &name)
void showFilterDialog(const QString &filterId, KisFilterConfigurationSP overrideDefaultConfig=nullptr)
void apply(KisFilterConfigurationSP filterConfig)
KisFilterManager(KisViewManager *parent)
void cancelRunningStroke()
Cancel current running stroke.
void cancelDialog()
Call cancelRunningStroke() and then delete dialog d
QScopedPointer< Private > d
void setFilterAllSelectedFrames(bool filterAllSelectedFrames)
void setView(QPointer< KisView >imageView)
void filterDialogHasFinished(int)
Clean up after filter dialog has been accepted / rejected / closed.
static KisFilterRegistry * instance()
static KisResourcesInterfaceSP instance()
void waitForDone()
bool cancelStroke(KisStrokeId id) override
KisImageAnimationInterface * animationInterface() const
void addJob(KisStrokeId id, KisStrokeJobData *data) override
KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy) override
A container for a set of QAction objects.
const KoColorSpace * colorSpace() const
The KisResourcesSnapshot class takes a snapshot of the various resources like colors and settings use...
The KisSignalMapper class bundles signals from identifiable senders.
void setForceLodModeIfPossible(bool forceLodModeIfPossible)
virtual bool willDegrade(ColorSpaceIndependence independence) const =0
const T value(const QString &id) const
QList< QString > keys() const
Definition KoID.h:30
QString name() const
Definition KoID.cpp:68
QString id() const
Definition KoID.cpp:63
#define warnKrita
Definition kis_debug.h:87
KisPinnedSharedPtr< KisFilterConfiguration > KisFilterConfigurationSP
Definition kis_types.h:275
QIcon loadIcon(const QString &name)
QSet< int > fetchUniqueFrameTimes(KisNodeSP node, QSet< int > selectedTimes, bool filterActiveFrameID)
QSet< int > filterTimesForOnlyRasterKeyedTimes(KisNodeSP node, const QSet< int > &times)
bool hasEditablePaintDevice() const
QString menuEntry() const
KisBookmarkedConfigurationManager * bookmarkManager
ColorSpaceIndependence colorSpaceIndependence
virtual KisFilterConfigurationSP defaultConfiguration(KisResourcesInterfaceSP resourcesInterface) const
void save(const QString &configname, const KisSerializableConfigurationSP)
KisFilterConfigurationSP lastConfiguration
QHash< QString, KActionMenu * > filterActionMenus
KisFilterConfigurationSP currentlyAppliedConfiguration
KisKActionCollection * actionCollection
KisDlgFilter * filterDialog
The filter dialog shown to the user.
QHash< KisFilter *, QAction * > filters2Action
KisFilterStrokeStrategy::ExternalCancelUpdatesStorageSP externalCancelUpdatesStorage
KisActionManager * actionManager
KisFilterStrokeStrategy::IdleBarrierData::IdleBarrierCookie idleBarrierCookie