Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_wdg_seexpr.cpp
Go to the documentation of this file.
1/*
2 * This file is part of Krita
3 *
4 * SPDX-FileCopyrightText: 2020 L. E. Segovia <amy@amyspark.me>
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9#include <QCheckBox>
10
11#include <KSeExprUI/ErrorMessages.h>
12#include <KisDialogStateSaver.h>
15#include <KoColor.h>
16#include <KoResourceServer.h>
18
20#include <kis_assert.h>
21#include <kis_config.h>
22#include <kis_debug.h>
23#include <kis_icon.h>
24#include <kis_signals_blocker.h>
25
27#include "generator.h"
28#include "kis_wdg_seexpr.h"
29#include "ui_wdgseexpr.h"
30
32 : KisConfigWidget(parent)
33 , updateCompressor(1000, KisSignalCompressor::Mode::POSTPONE)
34 , m_currentPreset(new KisSeExprScript(i18n("Untitled")))
35 , m_saveDialog(new KisWdgSeExprPresetsSave(this))
36 , m_isCreatingPresetFromScratch(true)
37{
38 m_widget = new Ui_WdgSeExpr();
39 m_widget->setupUi(this);
40 m_widget->txtEditor->setControlCollectionWidget(m_widget->wdgControls);
41
42 m_widget->renameBrushPresetButton->setIcon(KisIconUtils::loadIcon("document-edit"));
43
44 m_widget->reloadPresetButton->setIcon(KisIconUtils::loadIcon("reload-preset-16"));
45 m_widget->reloadPresetButton->setToolTip(i18n("Reload the preset"));
46 m_widget->dirtyPresetIndicatorButton->setIcon(KisIconUtils::loadIcon("warning"));
47 m_widget->dirtyPresetIndicatorButton->setToolTip(i18n("The settings for this preset have changed from their default."));
48
49 KisDialogStateSaver::restoreState(m_widget->txtEditor, "krita/generators/seexpr");
50 // Manually restore SeExpr state. KisDialogStateSaver uses setPlainText, not text itself
51 m_widget->txtEditor->setExpr(m_widget->txtEditor->exprTe->toPlainText());
52
53 m_widget->txtEditor->registerExtraVariable("$u", i18nc("SeExpr variable", "Normalized X axis coordinate of the image from its top-left corner"));
54 m_widget->txtEditor->registerExtraVariable("$v", i18nc("SeExpr variable", "Normalized Y axis coordinate of the image from its top-left corner"));
55 m_widget->txtEditor->registerExtraVariable("$w", i18nc("SeExpr variable", "Image width"));
56 m_widget->txtEditor->registerExtraVariable("$h", i18nc("SeExpr variable", "Image height"));
57
58 m_widget->txtEditor->updateCompleter();
59
60 m_widget->txtEditor->exprTe->setFont(QFontDatabase().systemFont(QFontDatabase::FixedFont));
61 const QFontMetricsF fntTe(m_widget->txtEditor->exprTe->fontMetrics());
62 m_widget->txtEditor->exprTe->setTabStopDistance(fntTe.horizontalAdvance(" "));
63
64 connect(m_widget->scriptSelectorWidget, SIGNAL(resourceSelected(KoResourceSP)), this, SLOT(slotResourceSelected(KoResourceSP)));
65 connect(m_saveDialog, SIGNAL(resourceSelected(KoResourceSP)), this, SLOT(slotResourceSaved(KoResourceSP)));
66
67 connect(m_widget->renameBrushPresetButton, SIGNAL(clicked(bool)),
68 this, SLOT(slotRenamePresetActivated()));
69 connect(m_widget->cancelBrushNameUpdateButton, SIGNAL(clicked(bool)),
70 this, SLOT(slotRenamePresetDeactivated()));
71 connect(m_widget->updateBrushNameButton, SIGNAL(clicked(bool)),
72 this, SLOT(slotSaveRenameCurrentPreset()));
73 connect(m_widget->renameBrushNameTextField, SIGNAL(returnPressed()),
74 this, SLOT(slotSaveRenameCurrentPreset()));
75
76 connect(m_widget->saveBrushPresetButton, SIGNAL(clicked()),
77 this, SLOT(slotSaveBrushPreset()));
78 connect(m_widget->saveNewBrushPresetButton, SIGNAL(clicked()),
79 this, SLOT(slotSaveNewBrushPreset()));
80
81 connect(m_widget->reloadPresetButton, SIGNAL(clicked()),
82 this, SLOT(slotReloadPresetClicked()));
83
84 connect(m_widget->txtEditor, SIGNAL(apply()),
85 &updateCompressor, SLOT(start()));
86 connect(m_widget->txtEditor, SIGNAL(preview()),
87 &updateCompressor, SLOT(start()));
88 connect(m_widget->txtEditor, &ExprEditor::preview, this, &KisWdgSeExpr::slotHideCheckboxes); // HACK: hide disney checkboxes
89
90 connect(&updateCompressor, SIGNAL(timeout()), this, SLOT(isValid()));
91
92 togglePresetRenameUIActive(false); // reset the UI state of renaming a preset if we are changing presets
93 slotUpdatePresetSettings(); // disable everything until a preset is selected
94
95 m_widget->splitter->restoreState(KisConfig(true).readEntry("seExpr/splitLayoutState", QByteArray())); // restore splitter state
96 m_widget->tabWidget->setCurrentIndex(KisConfig(true).readEntry("seExpr/selectedTab", -1)); // save currently selected tab
97}
98
100{
101 KisDialogStateSaver::saveState(m_widget->txtEditor, "krita/generators/seexpr");
102 KisConfig(false).writeEntry("seExpr/splitLayoutState", m_widget->splitter->saveState()); // save splitter state
103 KisConfig(false).writeEntry("seExpr/selectedTab", m_widget->tabWidget->currentIndex()); // save currently selected tab
104
105 delete m_saveDialog;
106 delete m_widget;
107}
108
109inline const Ui_WdgSeExpr *KisWdgSeExpr::widget() const
110{
111 return m_widget;
112}
113
115{
116 auto rserver = KoResourceServerProvider::instance()->seExprScriptServer();
117 auto name = config->getString("seexpr", "Disney_noisecolor2");
118 auto pattern = rserver->resource("", "", name);
119 if (pattern) {
120 m_widget->scriptSelectorWidget->setCurrentScript(pattern);
121 }
122
123 QString script = config->getString("script");
124
125 if (!script.isNull()) {
126 m_widget->txtEditor->setExpr(script, true);
127 }
128}
129
131{
133
134 if (m_widget->scriptSelectorWidget->currentResource()) {
135 QVariant v;
136 v.setValue(m_widget->scriptSelectorWidget->currentResource()->name());
137 config->setProperty("pattern", v);
138 }
139 config->setProperty("script", QVariant(m_widget->txtEditor->getExpr()));
140
141 return config;
142}
143
145{
146 if (resource) {
147 m_widget->scriptSelectorWidget->setCurrentScript(resource);
148 slotResourceSelected(resource);
149 }
150}
151
153{
154 KisSeExprScriptSP preset = resource.dynamicCast<KisSeExprScript>();
155 if (preset) {
156 m_currentPreset = preset;
157
159
160 m_widget->txtEditor->setExpr(m_currentPreset->script(), true);
161
162 QString formattedBrushName = m_currentPreset->name().replace("_", " ");
163 m_widget->currentBrushNameLabel->setText(formattedBrushName);
164 m_widget->renameBrushNameTextField->setText(formattedBrushName);
165 // get the preset image and pop it into the thumbnail area on the top of the brush editor
166 QSize thumbSize = QSize(55, 55)*devicePixelRatioF();
167 QPixmap thumbnail = QPixmap::fromImage(m_currentPreset->image().scaled(thumbSize, Qt::KeepAspectRatio, Qt::SmoothTransformation));
168 thumbnail.setDevicePixelRatio(devicePixelRatioF());
169 m_widget->presetThumbnailicon->setPixmap(thumbnail);
170
171 togglePresetRenameUIActive(false); // reset the UI state of renaming a brush if we are changing brush presets
172 slotUpdatePresetSettings(); // check to see if the dirty preset icon needs to be shown
173
175 }
176}
177
182
187
189{
190 // This function doesn't really do anything except get the UI in a state to rename a brush preset
191 m_widget->renameBrushNameTextField->setVisible(isRenaming);
192 m_widget->updateBrushNameButton->setVisible(isRenaming);
193 m_widget->cancelBrushNameUpdateButton->setVisible(isRenaming);
194
195 // hide these below areas while renaming
196 m_widget->currentBrushNameLabel->setVisible(!isRenaming);
197 m_widget->renameBrushPresetButton->setVisible(!isRenaming);
198 m_widget->saveBrushPresetButton->setEnabled(!isRenaming);
199 m_widget->saveBrushPresetButton->setVisible(!isRenaming);
200 m_widget->saveNewBrushPresetButton->setEnabled(!isRenaming);
201 m_widget->saveNewBrushPresetButton->setVisible(!isRenaming);
202}
203
205{
207
208 // if you are renaming a brush, that is different than updating the settings
209 // make sure we are in a clean state before renaming. This logic might change,
210 // but that is what we are going with for now
211 const auto prevScript = m_currentPreset->script();
212 bool isDirty = m_currentPreset->isDirty();
213
214 // this returns the UI to its original state after saving
216 slotUpdatePresetSettings(); // update visibility of dirty preset and icon
217
218 // in case the preset is dirty, we need an id to get the actual non-dirty preset to save just the name change
219 // into the database
220 int currentPresetResourceId = m_currentPreset->resourceId();
221
222 QString renamedPresetName = m_widget->renameBrushNameTextField->text();
223
224 // If the id < 0, this is a new preset that hasn't been added to the storage and the database yet.
225 if (currentPresetResourceId < 0) {
226 m_currentPreset->setName(renamedPresetName);
227 slotUpdatePresetSettings(); // update visibility of dirty preset and icon
228 return;
229 }
230
232
233 // create a new brush preset with the name specified and add to resource provider
235 KoResourceSP properCleanResource = model.resourceForId(currentPresetResourceId);
236 const bool success = KisResourceUserOperations::renameResourceWithUserInput(this, properCleanResource, renamedPresetName);
237
238 if (isDirty) {
239 properCleanResource.dynamicCast<KisSeExprScript>()->setScript(prevScript);
240 properCleanResource.dynamicCast<KisSeExprScript>()->setDirty(isDirty);
241 }
242
243 // refresh and select our freshly renamed resource
244 if (success) slotResourceSelected(properCleanResource);
245
246
247 slotUpdatePresetSettings(); // update visibility of dirty preset and icon
248}
249
251{
252 // hide options on UI if we are creating a brush preset from scratch to prevent confusion
254 m_widget->presetThumbnailicon->setVisible(false);
255 m_widget->dirtyPresetIndicatorButton->setVisible(false);
256 m_widget->reloadPresetButton->setVisible(false);
257 m_widget->saveBrushPresetButton->setVisible(false);
258 m_widget->saveNewBrushPresetButton->setEnabled(false);
259 m_widget->renameBrushPresetButton->setVisible(false);
260 } else {
261 // In SeExpr's case, there is never a default preset -- amyspark
262 if (!m_currentPreset) {
263 return;
264 }
265
266 bool isPresetDirty = m_currentPreset->isDirty();
267
268 m_widget->presetThumbnailicon->setVisible(true);
269 // don't need to reload or overwrite a clean preset
270 m_widget->dirtyPresetIndicatorButton->setVisible(isPresetDirty);
271 m_widget->reloadPresetButton->setVisible(isPresetDirty);
272 m_widget->saveBrushPresetButton->setEnabled(isPresetDirty);
273 m_widget->saveNewBrushPresetButton->setEnabled(true);
274 m_widget->renameBrushPresetButton->setVisible(true);
275 }
276}
277
279{
280 KisFilterConfigurationSP currentConfiguration = static_cast<KisFilterConfiguration *>(configuration().data());
281
282 m_saveDialog->useNewPresetDialog(false); // this mostly just makes sure we keep the existing brush preset name when saving
284 m_saveDialog->setCurrentRenderConfiguration(currentConfiguration);
285 m_saveDialog->loadExistingThumbnail(); // This makes sure we use the existing preset icon when updating the existing brush preset
286 m_saveDialog->showDialog(); // apply tiar's suggestion and let the user decide
287}
288
299
301{
302 KisSignalsBlocker blocker(this);
303
305 const bool success = model.reloadResource(m_currentPreset);
306
307 KIS_SAFE_ASSERT_RECOVER_NOOP(success && "couldn't reload preset");
308
309 warnPlugins << "resourceSelected: preset" << m_currentPreset
310 << (m_currentPreset ? QString("%1").arg(m_currentPreset->valid()) : "");
311
312 KIS_ASSERT(!m_currentPreset->isDirty());
313
314 // refresh and select our freshly renamed resource
316}
317
319{
320 for (auto controls : m_widget->wdgControls->findChildren<ExprControl *>()) {
321 for (auto wdg : controls->findChildren<QCheckBox *>(QString(), Qt::FindDirectChildrenOnly)) {
322 wdg->setCheckState(Qt::Unchecked);
323 wdg->setVisible(false);
324 }
325 }
326}
327
329{
330 QString script = m_widget->txtEditor->getExpr();
331 SeExprExpressionContext expression(script);
332
333 expression.setDesiredReturnType(KSeExpr::ExprType().FP(3));
334
335 expression.m_vars["u"] = new SeExprVariable();
336 expression.m_vars["v"] = new SeExprVariable();
337 expression.m_vars["w"] = new SeExprVariable();
338 expression.m_vars["h"] = new SeExprVariable();
339
340 m_widget->txtEditor->clearErrors();
341
342 if (!expression.isValid()) {
343 const auto &errors = expression.getErrors();
344
345 for (const auto &occurrence : errors) {
346 QString message = ErrorMessages::message(occurrence.error);
347 for (const auto &arg : occurrence.ids) {
348 message = message.arg(QString::fromStdString(arg));
349 }
350 m_widget->txtEditor->addError(occurrence.startPos, occurrence.endPos, message);
351 }
352
353 m_widget->saveBrushPresetButton->setEnabled(false);
354 m_widget->saveNewBrushPresetButton->setEnabled(false);
355 }
356 // Should not happen now, but I've left it for completeness's sake
357 else if (!expression.returnType().isFP(3)) {
358 QString type = QString::fromStdString(expression.returnType().toString());
359 m_widget->txtEditor->addError(1, 1, tr2i18n("Expected this script to output color, got '%1'").arg(type));
360
361 m_widget->saveBrushPresetButton->setEnabled(false);
362 m_widget->saveNewBrushPresetButton->setEnabled(false);
363 } else {
364 m_widget->txtEditor->clearErrors();
366
367 if (m_currentPreset->script() != m_widget->txtEditor->getExpr()) {
368 m_currentPreset->setScript(m_widget->txtEditor->getExpr());
369 m_currentPreset->setDirty(true);
370 }
371
373
374 // Override preset settings with the following
376 m_widget->saveNewBrushPresetButton->setEnabled(true);
377 }
378 }
379}
qreal v
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
void sigConfigurationItemChanged()
void writeEntry(const QString &name, const T &value)
Definition kis_config.h:779
static KisResourcesInterfaceSP instance()
The KisResourceModel class provides the main access to resources. It is possible to filter the resour...
KoResourceSP resourceForId(int id) const
bool reloadResource(KoResourceSP resource) override
reloadResource
static bool renameResourceWithUserInput(QWidget *widgetParent, KoResourceSP resource, QString resourceName)
void setCurrentPreset(KisSeExprScriptSP resource)
void setCurrentRenderConfiguration(KisFilterConfigurationSP config)
void useNewPresetDialog(bool show)
determines if we should show the save as dialog (true) or save in the background (false)
KisPropertiesConfigurationSP configuration() const override
void slotHideCheckboxes()
~KisWdgSeExpr() override
KisWdgSeExprPresetsSave * m_saveDialog
void slotResourceSelected(KoResourceSP resource)
KisSignalCompressor updateCompressor
KisSeExprScriptSP m_currentPreset
void slotReloadPresetClicked()
void slotSaveNewBrushPreset()
void slotSaveRenameCurrentPreset()
void slotRenamePresetActivated()
const Ui_WdgSeExpr * widget() const
KisWdgSeExpr(QWidget *parent=0)
void slotUpdatePresetSettings()
void slotSaveBrushPreset()
bool m_isCreatingPresetFromScratch
Ui_WdgSeExpr * m_widget
void setConfiguration(const KisPropertiesConfigurationSP) override
void slotResourceSaved(KoResourceSP resource)
void togglePresetRenameUIActive(bool isRenaming)
void slotRenamePresetDeactivated()
#define KIS_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:75
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
#define warnPlugins
Definition kis_debug.h:93
KRITAWIDGETUTILS_EXPORT void restoreState(QWidget *parent, const QString &dialogName, const QMap< QString, QVariant > &defaults=QMap< QString, QVariant >())
restoreState restores the state of the dialog
KRITAWIDGETUTILS_EXPORT void saveState(QWidget *parent, const QString &dialogName)
saveState saves the state for the specified widgets
QIcon loadIcon(const QString &name)
const QString SeExprScripts
static KoResourceServerProvider * instance()