Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_paintop_presets_editor.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2 * SPDX-FileCopyrightText: 2008 Boudewijn Rempt <boud@valdyas.org>
3 * SPDX-FileCopyrightText: 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
4 * SPDX-FileCopyrightText: 2011 Silvio Heinrich <plassy@web.de>
5 *
6 * SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8
10
11#include <QList>
12#include <QHBoxLayout>
13#include <QToolButton>
14#include <QGridLayout>
15#include <QFont>
16#include <QMenu>
17#include <QAction>
18#include <QShowEvent>
19#include <QFontDatabase>
20#include <QWidgetAction>
21#include <QScreen>
22#include <QSplitter>
23#include <QActionGroup>
24
25#include <kconfig.h>
26#include <klocalizedstring.h>
27
28#include <KoDockRegistry.h>
29
30#include <kis_icon.h>
37
38#include <ui_wdgpaintopsettings.h>
39#include <kis_node.h>
40#include "kis_config.h"
41
45
49
50// ones from brush engine selector
53
54#include <lager/state.hpp>
55
78
80 KisFavoriteResourceManager* favoriteResourceManager,
81 KisPresetSaveWidget* savePresetWidget,
82 QWidget * parent)
83 : QWidget(parent)
84 , m_d(new Private())
85{
86 setObjectName("KisPaintOpPresetsEditor");
87
88 KisConfig cfg(true);
89
91
92 m_d->resourceProvider = resourceProvider;
93 m_d->favoriteResManager = favoriteResourceManager;
94
95 m_d->uiWdgPaintOpPresetSettings.setupUi(this);
96
97 m_d->layout = new QGridLayout(m_d->uiWdgPaintOpPresetSettings.frmOptionWidgetContainer);
98
99 m_d->uiWdgPaintOpPresetSettings.scratchPad->setupScratchPad(resourceProvider, Qt::white);
100 m_d->uiWdgPaintOpPresetSettings.scratchPad->setCutoutOverlayRect(QRect(25, 25, 200, 200));
101
102 m_d->uiWdgPaintOpPresetSettings.dirtyPresetIndicatorButton->setToolTip(i18n("The settings for this preset have changed from their default."));
103
104 m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setToolTip(i18n("Toggle showing presets"));
105
106 m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setToolTip(i18n("Toggle showing scratchpad"));
107
108 m_d->uiWdgPaintOpPresetSettings.reloadPresetButton->setToolTip(i18n("Reload the brush preset"));
109 m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setToolTip(i18n("Rename the brush preset"));
110
111
112 // creating a new preset from scratch. Part of the brush presets area
113 // the menu options will get filled up later when we are generating all available paintops
114 // in the filter drop-down
115 newPresetBrushEnginesMenu = new QMenu();
116
117 // overwrite existing preset and saving a new preset use the same dialog
118 saveDialog = savePresetWidget;
119 saveDialog->scratchPadSetup(resourceProvider);
120 saveDialog->setFavoriteResourceManager(m_d->favoriteResManager); // this is needed when saving the preset
121 saveDialog->hide();
122
123 // the area on the brush editor for renaming the brush. make sure edit fields are hidden by default
125
126 // Brush Preset Left Panel
127 {
128 // Container
129 QVBoxLayout* containerLayout = dynamic_cast<QVBoxLayout*>(m_d->uiWdgPaintOpPresetSettings.presetsContainer->layout());
130 containerLayout->setAlignment(Qt::AlignmentFlag::AlignTop); // spacers are not required
131
132 // Show Panel Button
133 m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setCheckable(true);
134 m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setChecked(cfg.presetStripVisible());
135
136 connect(m_d->uiWdgPaintOpPresetSettings.showPresetsButton, SIGNAL(clicked(bool)), this, SLOT(slotSwitchShowPresets(bool)));
137
138 // Brush Engine Filter
139 connect(m_d->uiWdgPaintOpPresetSettings.brushEngineComboBox, SIGNAL(currentIndexChanged(int)),
140 this, SLOT(slotUpdatePaintOpFilter()));
141
142 // Brush Presets
143 KisPresetChooser* presetChooser = m_d->uiWdgPaintOpPresetSettings.presetWidget;
144 presetChooser->showTaggingBar(true);
145
146 // Brush Presets ViewMode Menu
147 QMenu* viewModeMenu = new QMenu(this);
148 {
149 viewModeMenu->setStyleSheet("margin: 6px");
150
151 // View Modes Btns
152 viewModeMenu->addSection(i18nc("@title Which elements to display (e.g., thumbnails or details)", "Display"));
154 QActionGroup *actionGroup = new QActionGroup(this);
155
156 QAction* action = viewModeMenu->addAction(KisIconUtils::loadIcon("view-preview"), i18n("Thumbnails"));
157 action->setCheckable(true);
158 action->setChecked(mode == KisPresetChooser::THUMBNAIL);
159 action->setActionGroup(actionGroup);
160
161 connect(action, &QAction::triggered,
163
164 action = viewModeMenu->addAction(KisIconUtils::loadIcon("view-list-details"), i18n("Details"));
165 action->setCheckable(true);
166 action->setChecked(mode == KisPresetChooser::DETAIL);
167 action->setActionGroup(actionGroup);
168
169 connect(action, &QAction::triggered,
171
172 // Icon Size Slider
173 viewModeMenu->addSection(i18n("Icon Size"));
174
175 QSlider* iconSizeSlider = new QSlider(this);
176 iconSizeSlider->setOrientation(Qt::Horizontal);
177 iconSizeSlider->setRange(30, 80);
178 iconSizeSlider->setValue(m_d->uiWdgPaintOpPresetSettings.presetWidget->iconSize());
179 iconSizeSlider->setMinimumHeight(20);
180 iconSizeSlider->setMinimumWidth(40);
181 iconSizeSlider->setTickInterval(10);
182
183 connect(iconSizeSlider, &QSlider::valueChanged,
185
186 connect(iconSizeSlider, &QSlider::sliderReleased,
188
189 QWidgetAction *sliderAction= new QWidgetAction(this);
190 sliderAction->setDefaultWidget(iconSizeSlider);
191
192 viewModeMenu->addAction(sliderAction);
193 }
194
195 KisResourceItemChooser* resourceChooser = presetChooser->itemChooser();
196 resourceChooser->viewModeButton()->setPopupWidget(viewModeMenu);
197
198 // Bottom Bar Buttons
199 m_d->uiWdgPaintOpPresetSettings.newPresetEngineButton->setPopupMode(QToolButton::InstantPopup); // loading preset from scratch option
200 connect(m_d->uiWdgPaintOpPresetSettings.bnBlacklistPreset,SIGNAL(clicked()),
201 this, SLOT(slotBlackListCurrentPreset())); // TODO: add confirm dialog
202 }
203
204 // show/hide buttons
205 m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setCheckable(true);
206 m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setChecked(cfg.scratchpadVisible());
207
208 QMenu *viewOptionsMenu = new QMenu(this);
209 QAction *detachBrushEditorAction = viewOptionsMenu->addAction(i18n("Detach Brush Editor"));
210 detachBrushEditorAction->setCheckable(true);
211 detachBrushEditorAction->setChecked(cfg.paintopPopupDetached());
212 m_d->uiWdgPaintOpPresetSettings.viewOptionButton->setMenu(viewOptionsMenu);
213
214 // Connections
215 connect(m_d->uiWdgPaintOpPresetSettings.paintPresetIcon, SIGNAL(clicked()),
216 m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(paintPresetImage()));
217
219
220 connect (m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton, SIGNAL(clicked(bool)),
221 this, SLOT(slotRenameBrushActivated()));
222
223 connect (m_d->uiWdgPaintOpPresetSettings.cancelBrushNameUpdateButton, SIGNAL(clicked(bool)),
224 this, SLOT(slotRenameBrushDeactivated()));
225
226 connect(m_d->uiWdgPaintOpPresetSettings.updateBrushNameButton, SIGNAL(clicked(bool)),
227 this, SLOT(slotSaveRenameCurrentBrush()));
228
229 connect(m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField, SIGNAL(returnPressed()),
231
232 connect(m_d->uiWdgPaintOpPresetSettings.showScratchpadButton, SIGNAL(clicked(bool)),
233 this, SLOT(slotSwitchScratchpad(bool)));
234
235 connect(detachBrushEditorAction, SIGNAL(toggled(bool)),
236 this, SLOT(slotToggleDetach(bool)));
237
238 connect(m_d->uiWdgPaintOpPresetSettings.eraseScratchPad, SIGNAL(clicked()),
239 m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillDefault()));
240
241 connect(m_d->uiWdgPaintOpPresetSettings.fillLayer, SIGNAL(clicked()),
242 m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillDocument()));
243
244 connect(m_d->uiWdgPaintOpPresetSettings.fillGradient, SIGNAL(clicked()),
245 m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillGradient()));
246
247 connect(m_d->uiWdgPaintOpPresetSettings.fillSolid, SIGNAL(clicked()),
248 m_d->uiWdgPaintOpPresetSettings.scratchPad, SLOT(fillBackground()));
249
250
251 m_d->settingsWidget = 0;
252
253 connect(m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton, SIGNAL(clicked()),
254 this, SLOT(slotSaveBrushPreset()));
255
256 connect(m_d->uiWdgPaintOpPresetSettings.saveNewBrushPresetButton, SIGNAL(clicked()),
257 this, SLOT(slotSaveNewBrushPreset()));
258
259 connect(m_d->uiWdgPaintOpPresetSettings.reloadPresetButton, SIGNAL(clicked()),
260 this, SIGNAL(reloadPresetClicked()));
261
262 connect(m_d->uiWdgPaintOpPresetSettings.dirtyPresetCheckBox, SIGNAL(toggled(bool)),
263 this, SIGNAL(dirtyPresetToggled(bool)));
264
265 connect(m_d->uiWdgPaintOpPresetSettings.eraserBrushSizeCheckBox, SIGNAL(toggled(bool)),
266 this, SIGNAL(eraserBrushSizeToggled(bool)));
267
268 connect(m_d->uiWdgPaintOpPresetSettings.eraserBrushOpacityCheckBox, SIGNAL(toggled(bool)),
269 this, SIGNAL(eraserBrushOpacityToggled(bool)));
270
271
272 // preset widget connections
274 this, SIGNAL(signalResourceSelected(KoResourceSP )));
275
276 connect(m_d->uiWdgPaintOpPresetSettings.reloadPresetButton, SIGNAL(clicked()),
277 m_d->uiWdgPaintOpPresetSettings.presetWidget, SLOT(updateViewSettings()));
278
279 connect(m_d->uiWdgPaintOpPresetSettings.reloadPresetButton, SIGNAL(clicked()), SLOT(slotUpdatePresetSettings()));
280
281 m_d->ignoreHideEvents = false;
282
283 m_d->uiWdgPaintOpPresetSettings.dirtyPresetCheckBox->setChecked(cfg.useDirtyPresets());
284 m_d->uiWdgPaintOpPresetSettings.eraserBrushSizeCheckBox->setChecked(cfg.useEraserBrushSize());
285 m_d->uiWdgPaintOpPresetSettings.eraserBrushOpacityCheckBox->setChecked(cfg.useEraserBrushOpacity());
286
287 connect(m_d->uiWdgPaintOpPresetSettings.brushEngineComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdatePaintOpFilter()));
288 connect(m_d->uiWdgPaintOpPresetSettings.bnBlacklistPreset, SIGNAL(clicked()), this, SLOT(slotBlackListCurrentPreset()));
289
291
292 // setup things like the scene construct images, layers, etc that is a one-time thing
293 m_d->uiWdgPaintOpPresetSettings.liveBrushPreviewView->setup(resourceProvider->resourceManager());
294
295 // Responsive Layout
296 m_d->horzSplitter = new QSplitter(this); // you can't add QSplitter to a UI file
297 m_d->horzSplitter->setChildrenCollapsible(false);
298
299 m_d->horzSplitter->addWidget(m_d->uiWdgPaintOpPresetSettings.presetsContainer);
300 m_d->horzSplitter->setStretchFactor(0, 0);
301 m_d->horzSplitter->addWidget(m_d->uiWdgPaintOpPresetSettings.brushEditorSettingsControls);
302 m_d->horzSplitter->setStretchFactor(1, 2);
303 m_d->horzSplitter->addWidget(m_d->uiWdgPaintOpPresetSettings.scratchpadControls);
304 m_d->horzSplitter->setStretchFactor(2, 0);
305
306 m_d->uiWdgPaintOpPresetSettings.gridLayout->addWidget(m_d->horzSplitter, 3, 0, 1, 3);
307
308 // The side panels' sizeHints must be retrieved while they are visible.
309 // The center widget's sizeHint must be retrieved later.
310 m_d->defaultSplitterSizes << m_d->horzSplitter->widget(0)->sizeHint().width()
311 << m_d->horzSplitter->widget(1)->sizeHint().width()
312 << m_d->horzSplitter->widget(2)->sizeHint().width();
313
314 // Default Configuration
317}
318
325
330
335
337{
338 // This function doesn't really do anything except get the UI in a state to rename a brush preset
339 m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField->setVisible(isRenaming);
340 m_d->uiWdgPaintOpPresetSettings.updateBrushNameButton->setVisible(isRenaming);
341 m_d->uiWdgPaintOpPresetSettings.cancelBrushNameUpdateButton->setVisible(isRenaming);
342
343
344 // hide these below areas while renaming
345 m_d->uiWdgPaintOpPresetSettings.currentBrushNameLabel->setVisible(!isRenaming);
346 m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setVisible(!isRenaming);
347 m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton->setEnabled(!isRenaming);
348 m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton->setVisible(!isRenaming);
349 m_d->uiWdgPaintOpPresetSettings.saveNewBrushPresetButton->setEnabled(!isRenaming);
350 m_d->uiWdgPaintOpPresetSettings.saveNewBrushPresetButton->setVisible(!isRenaming);
351
352 // if the presets area is shown, only then can you show/hide the load default brush
353 // need to think about weird state when you are in the middle of renaming a brush
354 // what happens if you try to change presets. maybe we should auto-hide (or disable)
355 // the presets area in this case
356 if (m_d->uiWdgPaintOpPresetSettings.presetWidget->isVisible()) {
357 m_d->uiWdgPaintOpPresetSettings.newPresetEngineButton->setVisible(!isRenaming);
358 m_d->uiWdgPaintOpPresetSettings.bnBlacklistPreset->setVisible(!isRenaming);
359 }
360
361}
362
364{
365 // if you are renaming a brush, that is different than updating the settings
366 // make sure we are in a clean state before renaming. This logic might change,
367 // but that is what we are going with for now
368 KisPaintOpSettingsSP prevSettings = m_d->resourceProvider->currentPreset()->settings()->clone();
369 bool isDirty = m_d->resourceProvider->currentPreset()->isDirty();
370
371 // this returns the UI to its original state after saving
373 slotUpdatePresetSettings(); // update visibility of dirty preset and icon
374
375 // get a reference to the existing (and new) file name and path that we are working with
377 // in case the preset is dirty, we need an id to get the actual non-dirty preset to save just the name change
378 // into the database
379 int currentPresetResourceId = curPreset->resourceId();
380
381 QString renamedPresetName = m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField->text();
382
383 // If the id < 0, this is a new preset that hasn't been added to the storage and the database yet.
384 if (currentPresetResourceId < 0) {
385 curPreset->setName(renamedPresetName);
386 slotUpdatePresetSettings(); // update visibility of dirty preset and icon
387 return;
388 }
389
390 Q_EMIT reloadPresetClicked();
391
392 // create a new brush preset with the name specified and add to resource provider
394 KoResourceSP properCleanResource = model.resourceForId(currentPresetResourceId);
395 const bool success = KisResourceUserOperations::renameResourceWithUserInput(this, properCleanResource, renamedPresetName);
396
397 if (isDirty) {
398 properCleanResource.dynamicCast<KisPaintOpPreset>()->setSettings(prevSettings);
399 properCleanResource.dynamicCast<KisPaintOpPreset>()->setDirty(isDirty);
400 }
401
402 // refresh and select our freshly renamed resource
403 if (success) resourceSelected(properCleanResource);
404
406
407 slotUpdatePresetSettings(); // update visibility of dirty preset and icon
408}
409
411{
412 if (m_d->settingsWidget) {
413 m_d->layout->removeWidget(m_d->settingsWidget);
414 m_d->settingsWidget->hide();
415 m_d->settingsWidget->setParent(0);
416 m_d->settingsWidget = 0;
417 }
418 delete m_d;
420}
421
423{
424 if (m_d->settingsWidget) {
425 m_d->layout->removeWidget(m_d->settingsWidget);
426 m_d->uiWdgPaintOpPresetSettings.frmOptionWidgetContainer->updateGeometry();
427 }
428 m_d->layout->update();
429 updateGeometry();
430
432 m_d->settingsWidget = 0;
433
434 if (widget) {
435
436 m_d->settingsWidget = dynamic_cast<KisPaintOpConfigWidget*>(widget);
438
439 KisConfig cfg(true);
442 } else {
444 }
445
447
452 m_d->uiWdgPaintOpPresetSettings.wdgLodAvailability->setLodAvailabilityModel(model);
453
454 widget->setFont(m_d->smallFont);
455
456 m_d->layout->addWidget(widget);
457
458 // hook up connections that will monitor if our preset is dirty or not. Show a notification if it is
460
462 m_d->widgetConnections.addConnection(preset->updateProxy(), SIGNAL(sigSettingsChanged()),
463 this, SLOT(slotUpdatePresetSettings()));
464 }
465
466 m_d->widgetConnections.addConnection(model, SIGNAL(effectiveLodAvailableChanged(bool)),
467 this, SLOT(slotUpdateEffectiveLodAvailable(bool)));
468 slotUpdateEffectiveLodAvailable(model->effectiveLodAvailable());
469
470 m_d->widgetConnections.addConnection(model, SIGNAL(sigConfigurationItemChanged()),
471 widget, SIGNAL(sigConfigurationItemChanged()));
472
473 m_d->layout->update();
474 widget->show();
475
476 }
477}
478
480{
481 return m_d->uiWdgPaintOpPresetSettings.scratchPad->cutoutOverlay();
482}
483
485{
486 Q_UNUSED(e);
487}
488
493
503
511
513{
514 // this gets called every time the brush editor window is opened
515 // TODO: this gets called multiple times whenever the preset is changed in the presets area
516 // the connections probably need to be thought about with this a bit more to keep things in sync
517
518 m_d->uiWdgPaintOpPresetSettings.presetWidget->setCurrentResource(resource);
519
520 // find the display name of the brush engine and append it to the selected preset display
521 QString currentBrushEngineName;
522 QPixmap currentBrushEngineIcon = QPixmap(26, 26);
523 currentBrushEngineIcon.fill(Qt::transparent);
524 for(int i=0; i < sortedBrushEnginesList.length(); i++) {
525 if (sortedBrushEnginesList.at(i).id == currentPaintOpId() ) {
526 currentBrushEngineName = sortedBrushEnginesList.at(i).name;
527 currentBrushEngineIcon = sortedBrushEnginesList.at(i).icon.pixmap(26, 26);
528 }
529 }
530
531 // brush names have underscores as part of the file name (to help with building). We don't really need underscores
532 // when viewing the names, so replace them with spaces
533 QString formattedBrushName = resource->name().replace("_", " ");
534
535 m_d->uiWdgPaintOpPresetSettings.currentBrushNameLabel->setToolTip(formattedBrushName);
536 formattedBrushName = this->fontMetrics().elidedText(formattedBrushName, Qt::ElideRight, m_d->uiWdgPaintOpPresetSettings.currentBrushNameLabel->width());
537 m_d->uiWdgPaintOpPresetSettings.currentBrushNameLabel->setText(formattedBrushName);
538 m_d->uiWdgPaintOpPresetSettings.currentBrushEngineLabel->setText(i18nc("%1 is the name of a brush engine", "%1 Engine", currentBrushEngineName));
539 m_d->uiWdgPaintOpPresetSettings.currentBrushEngineIcon->setPixmap(currentBrushEngineIcon);
540 m_d->uiWdgPaintOpPresetSettings.renameBrushNameTextField->setText(resource->name());
541
542 // get the preset image and pop it into the thumbnail area on the top of the brush editor
543 QSize thumbSize = QSize(55, 55)*devicePixelRatioF();
544 QImage thumbImage = resource->image();
545
546 m_d->uiWdgPaintOpPresetSettings.scratchPad->setPresetImage(thumbImage);
547
548 QPixmap thumbnail;
549 if (!thumbImage.isNull()) {
550 thumbnail = QPixmap::fromImage(thumbImage.scaled(thumbSize, Qt::KeepAspectRatio, Qt::SmoothTransformation));
551 } else {
552 thumbnail = QPixmap();
553 }
554
555 thumbnail.setDevicePixelRatio(devicePixelRatioF());
556 m_d->uiWdgPaintOpPresetSettings.presetThumbnailicon->setPixmap(thumbnail);
557
558 toggleBrushRenameUIActive(false); // reset the UI state of renaming a brush if we are changing brush presets
559 slotUpdatePresetSettings(); // check to see if the dirty preset icon needs to be shown
560}
561
563{
564 return v1.priority < v2.priority;
565}
566
568{
569 m_d->uiWdgPaintOpPresetSettings.brushEngineComboBox->clear(); // reset combobox list just in case
570
571
572 // create a new list so we can sort it and populate the brush engine combo box
573 sortedBrushEnginesList.clear(); // just in case this function is called again, don't keep adding to the list
574
575 for(int i=0; i < list.length(); i++) {
576 KisPaintOpInfo paintOpInfo;
577 paintOpInfo.id = list.at(i)->id();
578 paintOpInfo.name = list.at(i)->name();
579 paintOpInfo.icon = list.at(i)->icon();
580 paintOpInfo.priority = list.at(i)->priority();
581
582 sortedBrushEnginesList.append(paintOpInfo);
583 }
584
585 std::stable_sort(sortedBrushEnginesList.begin(), sortedBrushEnginesList.end(), variantLessThan );
586
587 // add an "All" option at the front to show all presets
588 QPixmap emptyPixmap = QPixmap(22,22);
589 emptyPixmap.fill(Qt::transparent);
590
591 // if we create a new brush from scratch, we need a full list of paintops to choose from
592 // we don't want "All", so populate the list before that is added
593 newPresetBrushEnginesMenu->actions().clear(); // clean out list in case we run this again
594 newBrushEngineOptions.clear();
595
596 for (int j = 0; j < sortedBrushEnginesList.length(); j++) {
597 auto * newEngineAction = newPresetBrushEnginesMenu->addAction(sortedBrushEnginesList[j].name);
598 newEngineAction->setObjectName(sortedBrushEnginesList[j].id); // we need the ID for changing the paintop when action triggered
599 newEngineAction->setIcon(sortedBrushEnginesList[j].icon);
600 newBrushEngineOptions.append(newEngineAction);
601 connect(newEngineAction, SIGNAL(triggered()), this, SLOT(slotCreateNewBrushPresetEngine()));
602 }
603 m_d->uiWdgPaintOpPresetSettings.newPresetEngineButton->setMenu(newPresetBrushEnginesMenu);
604
605 // fill the list into the brush combo box
606 sortedBrushEnginesList.push_front(KisPaintOpInfo(QString("all_options"), i18n("All"), QString(""), QIcon(emptyPixmap), 0 ));
607 for (int m = 0; m < sortedBrushEnginesList.length(); m++) {
608 m_d->uiWdgPaintOpPresetSettings.brushEngineComboBox->addItem(sortedBrushEnginesList[m].icon, sortedBrushEnginesList[m].name, QVariant(sortedBrushEnginesList[m].id));
609 }
610}
611
612
614{
615 current_paintOpId = paintOpId;
616}
617
618
622
624{
625 if (m_d->ignoreHideEvents) {
626 return;
627 }
628
629 KisConfig cfg(false);
630
631 QList<int> splitterSizes = m_d->horzSplitter->sizes();
632 if (!cfg.presetStripVisible()) {
633 splitterSizes[0] = m_d->presetPanelWidth;
634 }
635
636 if (!cfg.scratchpadVisible()) {
637 splitterSizes[2] = m_d->scratchPanelWidth;
638 }
639
640 cfg.writeList<int>("brushEditorSplitterSizes", splitterSizes);
641
642 QWidget *frame = this->parentWidget();
643 cfg.writeEntry("brushEditorWindowGeometry", frame->saveGeometry());
644
645 QWidget::hideEvent(event);
646}
647
649{
650 KisConfig cfg(false);
651
652 // The center widget's size will be cut off if not re-retrieved.
653 m_d->defaultSplitterSizes[1] = m_d->horzSplitter->widget(1)->sizeHint().width();
654 QList<int> splitterSizes = cfg.readList<int>("brushEditorSplitterSizes", m_d->defaultSplitterSizes);
655
656 m_d->presetPanelWidth = splitterSizes[0];
657 m_d->scratchPanelWidth = splitterSizes[2];
658
659 if (!cfg.presetStripVisible()) {
660 splitterSizes[0] = 0;
661 }
662 if (!cfg.scratchpadVisible()) {
663 splitterSizes[2] = 0;
664 }
665
666 QWidget *frame = this->parentWidget();
667 QByteArray frameGeometry = cfg.readEntry("brushEditorWindowGeometry", QByteArray());
668 if (!frameGeometry.isEmpty()) {
669 frame->restoreGeometry(frameGeometry);
670 }
671 else {
672 int presetPanelWidth = splitterSizes[0] != 0 ? splitterSizes[0] : m_d->uiWdgPaintOpPresetSettings.showPresetsButton->width();
673 int scratchPanelWidth =
674 splitterSizes[2] != 0 ? splitterSizes[2] : m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->width();
675 const QMargins margins = m_d->layout->contentsMargins();
676 int width = presetPanelWidth + splitterSizes[1] + scratchPanelWidth +
677 margins.bottom() + margins.left() + margins.top() + margins.right();
678 QRect defaultGeometry = QRect(frame->geometry().x(), frame->geometry().y(), width, frame->geometry().height());
679 frame->setGeometry(defaultGeometry);
680 }
681 m_d->horzSplitter->setSizes(splitterSizes);
682
683 Q_EMIT brushEditorShown();
684}
685
687{
688 QWidget::resizeEvent(event);
689 if (parentWidget()) {
690 // Make sure resizing doesn't push this widget out of the screen
691 QRect screenRect = this->screen()->availableGeometry();
692 QRect newPositionRect = kisEnsureInRect(parentWidget()->geometry(), screenRect);
693 parentWidget()->setGeometry(newPositionRect);
694 }
695}
696
698{
699 bool sameVisibility = m_d->uiWdgPaintOpPresetSettings.scratchPad->isVisible() == visible;
700
701 // hide all the internal controls except the toggle button
702 m_d->uiWdgPaintOpPresetSettings.scratchPad->setVisible(visible);
703 m_d->uiWdgPaintOpPresetSettings.paintPresetIcon->setVisible(visible);
704 m_d->uiWdgPaintOpPresetSettings.fillGradient->setVisible(visible);
705 m_d->uiWdgPaintOpPresetSettings.fillLayer->setVisible(visible);
706 m_d->uiWdgPaintOpPresetSettings.fillSolid->setVisible(visible);
707 m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setVisible(visible);
708 m_d->uiWdgPaintOpPresetSettings.scratchpadSidebarLabel->setVisible(visible);
709
710 if (sameVisibility) {
711 return;
712 }
713
714 QPushButton* showBtn = m_d->uiWdgPaintOpPresetSettings.showScratchpadButton;
715 QGroupBox *container = m_d->uiWdgPaintOpPresetSettings.scratchpadControls;
716
717 const QMargins margins = m_d->layout->contentsMargins();
718 int emptyContainerWidth = showBtn->width() + margins.left() + margins.right();
719
720 if (visible) {
721 showBtn->setIcon(KisIconUtils::loadIcon("arrow-left"));
722
723 container->setMinimumWidth(scratchPadPanelMinWidth);
724 container->setMaximumWidth(0xFF'FFFF);
725
726 QList<int> splitterSizes = m_d->horzSplitter->sizes();
728 QWidget *frame = this->parentWidget();
729 QRect currentGeometry = frame->geometry();
730 currentGeometry.setRight(currentGeometry.right() + (splitterSizes[2] - emptyContainerWidth));
731 frame->setGeometry(currentGeometry);
732 m_d->horzSplitter->setSizes(splitterSizes);
733 } else {
734 showBtn->setIcon(KisIconUtils::loadIcon("arrow-right"));
735
736 container->setMinimumWidth(emptyContainerWidth);
737 container->setMaximumWidth(emptyContainerWidth);
738
739 QList<int> splitterSizes = m_d->horzSplitter->sizes();
741 QWidget *frame = this->parentWidget();
742 QRect currentGeometry = frame->geometry();
743 currentGeometry.setRight(currentGeometry.right() - (m_d->scratchPanelWidth - emptyContainerWidth));
744 frame->setGeometry(currentGeometry);
745
746 splitterSizes[1] = 0xFF'FFFF;
747 splitterSizes[2] = emptyContainerWidth;
748 m_d->horzSplitter->setSizes(splitterSizes);
749 }
750
751 KisConfig cfg(false);
752 cfg.setScratchpadVisible(visible);
753}
754
756 m_d->uiWdgPaintOpPresetSettings.brushEditorSettingsControls->setVisible(visible);
757}
758
760{
761 bool sameVisibility = m_d->uiWdgPaintOpPresetSettings.presetsSidebarLabel->isVisible() == visible;
762
763 m_d->uiWdgPaintOpPresetSettings.presetsSidebarLabel->setVisible(visible);
764 m_d->uiWdgPaintOpPresetSettings.engineFilterLabel->setVisible(visible);
765 m_d->uiWdgPaintOpPresetSettings.brushEngineComboBox->setVisible(visible);
766 m_d->uiWdgPaintOpPresetSettings.presetWidget->setVisible(visible);
767 m_d->uiWdgPaintOpPresetSettings.newPresetEngineButton->setVisible(visible);
768 m_d->uiWdgPaintOpPresetSettings.bnBlacklistPreset->setVisible(visible);
769
770 if (sameVisibility) {
771 return;
772 }
773
774 QPushButton* showBtn = m_d->uiWdgPaintOpPresetSettings.showPresetsButton;
775 QGroupBox *container = m_d->uiWdgPaintOpPresetSettings.presetsContainer;
776
777 const QMargins margins = m_d->layout->contentsMargins();
778 int emptyContainerWidth = showBtn->width() + margins.left() + margins.right();
779
780 if (visible) {
781 showBtn->setIcon(KisIconUtils::loadIcon("arrow-right"));
782
783 container->setMinimumWidth(brushPresetsPanelMinWidth);
784 container->setMaximumWidth(0xFF'FFFF);
785
786 QList<int> splitterSizes = m_d->horzSplitter->sizes();
787 splitterSizes[0] = m_d->presetPanelWidth;
788 QWidget *frame = this->parentWidget();
789 QRect currentGeometry = frame->geometry();
790 currentGeometry.setLeft(currentGeometry.left() - (splitterSizes[0] - emptyContainerWidth));
791 frame->setGeometry(currentGeometry);
792 m_d->horzSplitter->setSizes(splitterSizes);
793 } else {
794 showBtn->setIcon(KisIconUtils::loadIcon("arrow-left"));
795
796 container->setMinimumWidth(emptyContainerWidth);
797 container->setMaximumWidth(emptyContainerWidth);
798
799 QList<int> splitterSizes = m_d->horzSplitter->sizes();
801 QWidget *frame = this->parentWidget();
802 QRect currentGeometry = frame->geometry();
803 currentGeometry.setLeft(currentGeometry.left() + (m_d->presetPanelWidth - emptyContainerWidth));
804 frame->setGeometry(currentGeometry);
805
806 splitterSizes[0] = emptyContainerWidth;
807 splitterSizes[1] = 0xFF'FFFF;
808 m_d->horzSplitter->setSizes(splitterSizes);
809 }
810
811 KisConfig cfg(false);
812 cfg.setPresetStripVisible(visible);
813}
814
816 QVariant userData = m_d->uiWdgPaintOpPresetSettings.brushEngineComboBox->currentData(); // grab paintOpID from data
817 QString filterPaintOpId = userData.toString();
818
819 if (filterPaintOpId == "all_options") {
820 filterPaintOpId = "";
821 }
822 m_d->uiWdgPaintOpPresetSettings.presetWidget->setPresetFilter(filterPaintOpId);
823}
824
826 // here we are assuming that people want to keep their existing preset icon. We will just update the
827 // settings and save a new copy with the same name.
828 // there is a dialog with save options, but we don't need to show it in this situation
829
830 saveDialog->useNewBrushDialog(false); // this mostly just makes sure we keep the existing brush preset name when saving
831 const QImage thumbImage = m_d->resourceProvider->currentPreset() ? m_d->resourceProvider->currentPreset()->image() : QImage();
832 saveDialog->brushPresetThumbnailWidget->setPresetImage(thumbImage);
834 saveDialog->loadExistingThumbnail(); // This makes sure we use the existing preset icon when updating the existing brush preset
836
837 // refresh the view settings so the brush doesn't appear dirty
838 // tiar 2021: I'm not sure if it's needed anymore; seems to work without it...
840}
841
844 const QImage thumbImage = m_d->resourceProvider->currentPreset() ? m_d->resourceProvider->currentPreset()->image() : QImage();
845 saveDialog->brushPresetThumbnailWidget->setPresetImage(thumbImage);
848}
849
851{
852 Q_EMIT toggleDetachState(detach);
853 KisConfig cfg(false);
854 cfg.setPaintopPopupDetached(detach);
855}
856
862
864{
865 Q_EMIT createPresetFromScratch(sender()->objectName());
866}
867
869{
870 m_d->uiWdgPaintOpPresetSettings.presetWidget->updateViewSettings();
871}
872
874{
875 if (preset) {
876 m_d->uiWdgPaintOpPresetSettings.presetWidget->setCurrentResource(preset);
877 setCurrentPaintOpId(preset->paintOp().id());
878 }
879}
880
882{
883 m_d->uiWdgPaintOpPresetSettings.viewOptionButton->setIcon(KisIconUtils::loadIcon("view-choose"));
884
885 m_d->uiWdgPaintOpPresetSettings.paintPresetIcon->setIcon(KisIconUtils::loadIcon("krita_tool_freehand"));
886 m_d->uiWdgPaintOpPresetSettings.fillLayer->setIcon(KisIconUtils::loadIcon("document-new"));
887 m_d->uiWdgPaintOpPresetSettings.fillLayer->hide();
888 m_d->uiWdgPaintOpPresetSettings.fillGradient->setIcon(KisIconUtils::loadIcon("krita_tool_gradient"));
889 m_d->uiWdgPaintOpPresetSettings.fillSolid->setIcon(KisIconUtils::loadIcon("krita_tool_color_fill"));
890 m_d->uiWdgPaintOpPresetSettings.eraseScratchPad->setIcon(KisIconUtils::loadIcon("edit-delete"));
891
892 m_d->uiWdgPaintOpPresetSettings.newPresetEngineButton->setIcon(KisIconUtils::loadIcon("list-add"));
893 m_d->uiWdgPaintOpPresetSettings.bnBlacklistPreset->setIcon(KisIconUtils::loadIcon("deletelayer"));
894 m_d->uiWdgPaintOpPresetSettings.reloadPresetButton->setIcon(KisIconUtils::loadIcon("reload-preset-16"));
895 m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setIcon(KisIconUtils::loadIcon("document-edit"));
896 m_d->uiWdgPaintOpPresetSettings.dirtyPresetIndicatorButton->setIcon(KisIconUtils::loadIcon("warning"));
897 m_d->uiWdgPaintOpPresetSettings.brokenPresetIndicatorButton->setIcon(KisIconUtils::loadIcon("broken-preset"));
898
899 m_d->uiWdgPaintOpPresetSettings.newPresetEngineButton->setIcon(KisIconUtils::loadIcon("list-add"));
900 m_d->uiWdgPaintOpPresetSettings.bnBlacklistPreset->setIcon(KisIconUtils::loadIcon("deletelayer"));
901 //m_d->uiWdgPaintOpPresetSettings.presetChangeViewToolButton->setIcon(KisIconUtils::loadIcon("view-choose"));
902
903 // store if the scratchpad or brush presets are visible in the config
904 KisConfig cfg(true);
905 if (cfg.presetStripVisible()) {
906 //m_d->uiWdgPaintOpPresetSettings.presetsSpacer->changeSize(0,0, QSizePolicy::Ignored,QSizePolicy::Ignored);
907 m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setIcon(KisIconUtils::loadIcon("arrow-right"));
908 } else {
909 //m_d->uiWdgPaintOpPresetSettings.presetsSpacer->changeSize(0,0, QSizePolicy::MinimumExpanding,QSizePolicy::MinimumExpanding);
910 m_d->uiWdgPaintOpPresetSettings.showPresetsButton->setIcon(KisIconUtils::loadIcon("arrow-left"));
911 }
912
913 if (cfg.scratchpadVisible()) {
914 m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setIcon(KisIconUtils::loadIcon("arrow-left"));
915 } else {
916 m_d->uiWdgPaintOpPresetSettings.showScratchpadButton->setIcon(KisIconUtils::loadIcon("arrow-right"));
917 }
918}
919
921{
922 if (!m_d->resourceProvider) {
923 return;
924 }
925
927 return;
928 }
929
930 // hide options on UI if we are creating a brush preset from scratch to prevent confusion
932 m_d->uiWdgPaintOpPresetSettings.dirtyPresetIndicatorButton->setVisible(false);
933 m_d->uiWdgPaintOpPresetSettings.brokenPresetIndicatorButton->setVisible(false);
934 m_d->uiWdgPaintOpPresetSettings.reloadPresetButton->setVisible(false);
935 m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton->setVisible(false);
936 m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setVisible(false);
937 } else {
938 const bool isPresetDirty = m_d->resourceProvider->currentPreset()->isDirty();
939
940 // don't need to reload or overwrite a clean preset
941 m_d->uiWdgPaintOpPresetSettings.dirtyPresetIndicatorButton->setVisible(isPresetDirty);
942
943 {
944 bool isBroken = false;
945 QString brokenReason;
946 const int resourceId = m_d->resourceProvider->currentPreset()->resourceId();
947 if (resourceId >= 0) {
949 QModelIndex index = model.indexForResourceId(resourceId);
950 if (index.isValid()) {
951 isBroken = index.data(Qt::UserRole + KisAbstractResourceModel::BrokenStatus).toBool();
952 brokenReason =
953 QString(
954 "<html><body style=\"margin: 20px;\"><h3>%1</h3>"
955 "%2"
956 "</body></html>")
957 .arg(i18n("Resource is broken!"),
958 index.data(Qt::UserRole + KisAbstractResourceModel::BrokenStatusMessage).toString());
959 }
960 }
961
962 m_d->uiWdgPaintOpPresetSettings.brokenPresetIndicatorButton->setVisible(isBroken);
963 m_d->uiWdgPaintOpPresetSettings.brokenPresetIndicatorButton->setToolTip(brokenReason);
964 }
965
966 m_d->uiWdgPaintOpPresetSettings.reloadPresetButton->setVisible(isPresetDirty);
967 m_d->uiWdgPaintOpPresetSettings.saveBrushPresetButton->setEnabled(isPresetDirty);
968 m_d->uiWdgPaintOpPresetSettings.renameBrushPresetButton->setVisible(true);
969 }
970
971 // update live preview area in here...
972 // don't update the live preview if the widget is not visible.
973 if (m_d->uiWdgPaintOpPresetSettings.liveBrushPreviewView->isVisible()) {
974 m_d->uiWdgPaintOpPresetSettings.liveBrushPreviewView->setCurrentPreset(m_d->resourceProvider->currentPreset());
975 m_d->uiWdgPaintOpPresetSettings.liveBrushPreviewView->requestUpdateStroke();
976 }
977}
float value(const T *src, size_t ch)
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
@ BrokenStatus
Whether the resource is broken (bool)
KoCanvasResourceProvider * resourceManager()
KisPaintOpPresetSP currentPreset() const
void writeList(const QString &name, const QList< T > &value)
Definition kis_config.h:784
QList< T > readList(const QString &name, const QList< T > &defaultValue=QList< T >())
Definition kis_config.h:794
bool useDirtyPresets(bool defaultValue=false) const
void writeEntry(const QString &name, const T &value)
Definition kis_config.h:779
void setScratchpadVisible(bool visible)
void setPresetStripVisible(bool visible)
bool paintopPopupDetached(bool defaultValue=false) const
bool scratchpadVisible(bool defaultValue=false) const
int presetChooserViewMode(bool defaultValue=false) const
bool useEraserBrushOpacity(bool defaultValue=false) const
T readEntry(const QString &name, const T &defaultValue=T())
Definition kis_config.h:789
void setPaintopPopupDetached(bool detached) const
bool useEraserBrushSize(bool defaultValue=false) const
bool presetStripVisible(bool defaultValue=false) const
bool read(const KisPropertiesConfiguration *setting)
void setConfigurationSafe(const KisPropertiesConfigurationSP config)
virtual lager::reader< qreal > effectiveBrushSize() const =0
virtual lager::reader< KisPaintopLodLimitations > lodLimitationsReader() const =0
void writeConfigurationSafe(KisPropertiesConfigurationSP config) const
void createPresetFromScratch(const QString &paintOpName)
QList< KisPaintOpInfo > sortedBrushEnginesList
void setPaintOpList(const QList< KisPaintOpFactory * > &list)
void writeOptionSetting(KisPropertiesConfigurationSP setting) const
void toggleDetachState(bool detach)
QString currentPaintOpId()
returns the internal ID for the paint op (brush engine)
void showEvent(QShowEvent *) override
void eraserBrushSizeToggled(bool value)
void resourceSelected(KoResourceSP resource)
void eraserBrushOpacityToggled(bool value)
void setCurrentPaintOpId(const QString &paintOpId)
void currentPresetChanged(KisPaintOpPresetSP preset)
void readOptionSetting(const KisPropertiesConfigurationSP setting)
void slotBlackListCurrentPreset()
we do not delete brush presets, but blacklist them so they disappear from the interface
void contextMenuEvent(QContextMenuEvent *) override
void signalResourceSelected(KoResourceSP resource)
void toggleBrushRenameUIActive(bool isRenaming)
void setPaintOpSettingsWidget(QWidget *widget)
void hideEvent(QHideEvent *) override
void resizeEvent(QResizeEvent *) override
KisPaintOpPresetsEditor(KisCanvasResourceProvider *resourceProvider, KisFavoriteResourceManager *favoriteResourceManager, KisPresetSaveWidget *savePresetWidget, QWidget *parent=0)
void dirtyPresetToggled(bool value)
void setPopupWidget(QWidget *widget)
@ DETAIL
Shows thumbnails.
void saveIconSize()
saves the icon size for the presets. called by the horizontal slider release event.
void setIconSize(int newSize)
sets the icon size. Used by slider in view menu
void showTaggingBar(bool show)
Sets the visibility of tagging klineEdits.
KisResourceItemChooser * itemChooser()
void scratchPadSetup(KisCanvasResourceProvider *resourceProvider)
void setFavoriteResourceManager(KisFavoriteResourceManager *favManager)
void saveScratchPadThumbnailArea(const QImage image)
void useNewBrushDialog(bool show)
determines if we should show the save as dialog (true) or save in the background (false)
The KisResourceModel class provides the main access to resources. It is possible to filter the resour...
KoResourceSP resourceForId(int id) const
QModelIndex indexForResourceId(int resourceId) const override
indexFromResource
static KisResourceServerProvider * instance()
KisPaintOpPresetResourceServer * paintOpPresetServer()
static bool renameResourceWithUserInput(QWidget *widgetParent, KoResourceSP resource, QString resourceName)
void addConnection(Sender sender, Signal signal, Receiver receiver, Method method, Qt::ConnectionType type=Qt::AutoConnection)
void setResource(int key, const QVariant &value)
bool removeResourceFromServer(QSharedPointer< T > resource)
Remove a resource from Resource Server but not from a file.
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:75
QRect kisEnsureInRect(QRect rc, const QRect &bounds)
Definition kis_global.h:267
bool variantLessThan(const KisPaintOpInfo v1, const KisPaintOpInfo v2)
QIcon loadIcon(const QString &name)
const QString PaintOpPresets
lager::state< KisLodAvailabilityData, lager::automatic_tag > lodAvailabilityData
KisFavoriteResourceManager * favoriteResManager
KisSignalAutoConnectionsStore widgetConnections