Krita Source Code Documentation
Loading...
Searching...
No Matches
gamutmask_dock.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2018 Anna Medonosova <anna.medonosova@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6#include "gamutmask_dock.h"
7
8#include <kis_debug.h>
9
10#include <klocalizedstring.h>
13#include <KoResourceServer.h>
14#include <KoCanvasBase.h>
15#include <KoColor.h>
17#include <kis_icon_utils.h>
18#include <KisPart.h>
19#include <kis_shape_layer.h>
20#include <kis_types.h>
21#include <KisDocument.h>
23#include <kis_group_layer.h>
24#include <KisView.h>
27
28#include <QWidget>
29#include <QButtonGroup>
30#include <QRegularExpressionValidator>
31#include <QRegularExpression>
32#include <QFileInfo>
33#include <QMessageBox>
34#include <QInputDialog>
35
36#include <KisViewManager.h>
38#include <KoColorBackground.h>
39#include <KoShapeStroke.h>
40#include "KisMainWindow.h"
41
42#include <ctime>
43
44#include "ui_wdgGamutMaskChooser.h"
45#include <kis_layer_utils.h>
46
47struct GamutMaskChooserUI: public QWidget, public Ui_wdgGamutMaskChooser
48{
50 setupUi(this);
51 }
52};
53
54
56 : QDockWidget(i18n("Gamut Masks"))
57{
59
60 m_dockerUI->bnMaskEditor->setIcon(KisIconUtils::loadIcon("document-edit"));
61 m_dockerUI->bnMaskDelete->setIcon(KisIconUtils::loadIcon("edit-delete"));
62 m_dockerUI->bnMaskNew->setIcon(KisIconUtils::loadIcon("list-add"));
63 m_dockerUI->bnMaskDuplicate->setIcon(KisIconUtils::loadIcon("duplicateitem"));
64
65 m_dockerUI->maskPropertiesBox->setVisible(false);
66 m_dockerUI->bnSaveMask->setIcon(KisIconUtils::loadIcon("document-save-16"));
67 m_dockerUI->bnCancelMaskEdit->setIcon(KisIconUtils::loadIcon("dialog-cancel"));
68 m_dockerUI->bnPreviewMask->setIcon(KisIconUtils::loadIcon("visible"));
69
70 QRegularExpression maskTitleRegex("^[-_\\(\\)\\sA-Za-z0-9]+$");
71 QRegularExpressionValidator* m_maskTitleValidator = new QRegularExpressionValidator(maskTitleRegex, this);
72 m_dockerUI->maskTitleEdit->setValidator(m_maskTitleValidator);
73
75 rServer->addObserver(this);
76
77 // gamut mask connections
78 connect(m_dockerUI->bnSaveMask , SIGNAL(clicked()) , SLOT(slotGamutMaskSave()));
79 connect(m_dockerUI->bnCancelMaskEdit , SIGNAL(clicked()) , SLOT(slotGamutMaskCancelEdit()));
80 connect(m_dockerUI->bnPreviewMask , SIGNAL(clicked()) , SLOT(slotGamutMaskPreview()));
81
82 connect(m_dockerUI->bnMaskEditor , SIGNAL(clicked()) , SLOT(slotGamutMaskEdit()));
83 connect(m_dockerUI->maskChooser, SIGNAL(sigGamutMaskSelected(KoGamutMaskSP)), SLOT(slotGamutMaskSelected(KoGamutMaskSP)));
84 connect(m_dockerUI->bnMaskNew , SIGNAL(clicked()) , SLOT(slotGamutMaskCreateNew()));
85 connect(m_dockerUI->bnMaskDelete , SIGNAL(clicked()) , SLOT(slotGamutMaskDelete()));
86 connect(m_dockerUI->bnMaskDuplicate , SIGNAL(clicked()) , SLOT(slotGamutMaskDuplicate()));
87
88 setWidget(m_dockerUI);
89}
90
96
98{
100
102
103 connect(this, SIGNAL(sigGamutMaskSet(KoGamutMaskSP)), m_resourceProvider, SLOT(slotGamutMaskActivated(KoGamutMaskSP)), Qt::UniqueConnection);
104 connect(this, SIGNAL(sigGamutMaskChanged(KoGamutMaskSP)), m_resourceProvider, SLOT(slotGamutMaskActivated(KoGamutMaskSP)), Qt::UniqueConnection);
105 connect(this, SIGNAL(sigGamutMaskUnset()), m_resourceProvider, SLOT(slotGamutMaskUnset()), Qt::UniqueConnection);
106 connect(this, SIGNAL(sigGamutMaskPreviewUpdate()), m_resourceProvider, SLOT(slotGamutMaskPreviewUpdate()), Qt::UniqueConnection);
107 connect(KisPart::instance(), SIGNAL(sigDocumentRemoved(QString)), this, SLOT(slotDocumentRemoved(QString)), Qt::UniqueConnection);
108}
109
111{
112 if (!m_selectedMask) {
113 return;
114 }
116}
117
119{
120 if (!m_selectedMask) {
121 return false;
122 }
123
124 // find the template resource first, so we can abort the action early on
125 QString maskTemplateFile = ":/gamutmasks/GamutMaskTemplate.kra";
126 if (maskTemplateFile.isEmpty() || maskTemplateFile.isNull() || !QFile::exists(maskTemplateFile)) {
127 dbgPlugins << "GamutMaskDock::openMaskEditor(): maskTemplateFile (" << maskTemplateFile << ") was not found on the system";
128 getUserFeedback(i18n("Could not open gamut mask for editing."),
129 i18n("The editor template was not found."),
130 QMessageBox::Ok, QMessageBox::Ok, QMessageBox::Critical);
131 return false;
132 }
133
134 m_dockerUI->maskChooser->setEnabled(false);
135 m_dockerUI->maskPropertiesBox->setVisible(true);
136 m_dockerUI->maskPropertiesBox->setEnabled(true);
137 m_dockerUI->editControlsBox->setEnabled(false);
138 m_dockerUI->editControlsBox->setVisible(false);
139
140 m_dockerUI->maskTitleEdit->setText(m_selectedMask->title());
141 m_dockerUI->maskDescriptionEdit->setPlainText(m_selectedMask->description());
142
146
147 // template document needs a proper autogenerated filename,
148 // to avoid collision with other documents,
149 // otherwise bugs happen when slotDocumentRemoved is called
150 // (e.g. user closes another view, the template stays open, but the edit operation is canceled)
152 QString maskPath =
153 QString("%1%2%3_%4.kra")
154 .arg(QDir::tempPath(), "/", "GamutMaskTemplate", QString::number(std::time(nullptr)));
155 m_maskDocument->setPath(maskPath);
157
158 KisShapeLayerSP shapeLayer = getShapeLayer();
159
160 // pass only copies of shapes to the layer,
161 // so the originals don't disappear from the mask later
162 for (KoShape *shape: m_selectedMask->koShapes()) {
163 KoShape* newShape = shape->cloneShape();
164 newShape->setStroke(KoShapeStrokeModelSP());
165 newShape->setBackground(QSharedPointer<KoColorBackground>(new KoColorBackground(QColor(255,255,255))));
166 shapeLayer->addShape(newShape);
167 }
168
170
171 // set document as active
173 KIS_ASSERT(mainWindow);
174
177
178 for(KisView *view: KisPart::instance()->views()) {
179 if (view->document() == m_maskDocument) {
180 view->activateWindow();
181 break;
182 }
183 }
184
185 connect(m_view->viewManager(), SIGNAL(viewChanged()), this, SLOT(slotViewChanged()));
186 connect(m_maskDocument, SIGNAL(completed()), this, SLOT(slotDocumentSaved()));
187
188 m_dockerUI->maskChooser->setEnabled(true);
189
190 return true;
191}
192
194{
195 if (m_creatingNewMask) {
196 deleteMask();
197 }
198
199 if (m_selectedMask) {
200 m_selectedMask->clearPreview();
201
204 }
205 }
206
208}
209
210void GamutMaskDock::selectMask(KoGamutMaskSP mask, bool notifyItemChooser)
211{
212 if (!mask) {
213 return;
214 }
215
216 m_selectedMask = mask;
217
218 if (notifyItemChooser) {
219 m_selfSelectingMask = true;
220 m_dockerUI->maskChooser->setCurrentResource(m_selectedMask);
221 m_selfSelectingMask = false;
222 }
223
225}
226
228{
230 return false;
231 }
232
233 bool maskSaved = false;
234
235 if (m_selectedMask) {
237
238 if (shapes.count() > 0) {
239 m_selectedMask->setMaskShapes(shapes);
240
241 m_selectedMask->setImage(
244 )
245 );
246
247 m_selectedMask->setDescription(m_dockerUI->maskDescriptionEdit->toPlainText());
248
249 m_selectedMask->clearPreview();
251 QModelIndex idx = model.indexForResourceId(m_selectedMask->resourceId());
252 bool r = true;
253 if (idx.isValid()) {
254 // don't add, only update
256 } else {
258 }
259
260 maskSaved = r;
261 } else {
262 getUserFeedback(i18n("Saving of gamut mask '%1' was aborted.", m_selectedMask->title()),
263 i18n("<p>The mask template is invalid.</p>"
264 "<p>Please check that:"
265 "<ul>"
266 "<li>your template contains a vector layer named 'maskShapesLayer'</li>"
267 "<li>there are one or more vector shapes on the 'maskShapesLayer'</li>"
268 "</ul></p>"
269 ),
270 QMessageBox::Ok, QMessageBox::Ok);
271 }
272 }
273
274
275 return maskSaved;
276}
277
279{
281 QModelIndex idx = model.indexForResource(m_selectedMask);
282 if (idx.isValid()) {
283 model.setResourceInactive(idx);
284 }
285 m_selectedMask = nullptr;
286}
287
288int GamutMaskDock::getUserFeedback(QString text, QString informativeText,
289 QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton,
290 QMessageBox::Icon severity)
291{
292 QMessageBox msgBox;
293 msgBox.setWindowTitle(i18nc("@title:window", "Krita"));
294 msgBox.setText(QString("<p><b>%1</b></p>").arg(text));
295 msgBox.setInformativeText(informativeText);
296 msgBox.setStandardButtons(buttons);
297 msgBox.setDefaultButton(defaultButton);
298 msgBox.setIcon(severity);
299 int res = msgBox.exec();
300
301 return res;
302}
303
304int GamutMaskDock::saveOrCancel(QMessageBox::StandardButton defaultAction)
305{
306 int response = 0;
307
308 if (m_maskDocument->isModified()) {
309 response = getUserFeedback(i18n("Gamut mask <b>'%1'</b> has been modified.", m_selectedMask->title()),
310 i18n("Do you want to save it?"),
311 QMessageBox::Cancel | QMessageBox::Close | QMessageBox::Save, defaultAction);
312
313 } else if (m_templatePrevSaved && defaultAction != QMessageBox::Close) {
314 response = QMessageBox::Save;
315
316 } else if (!m_templatePrevSaved) {
317 response = QMessageBox::Close;
318
319 } else {
320 response = defaultAction;
321 }
322
323 switch (response) {
324 case QMessageBox::Save : {
326 break;
327 }
328 case QMessageBox::Close : {
330 break;
331 }
332 }
333
334 return response;
335}
336
338{
339 m_creatingNewMask = true;
340
341 KoGamutMaskSP newMask;
342 if (sourceMask) {
343 newMask = KoGamutMaskSP(new KoGamutMask(sourceMask.data()));
344 newMask->setVersion(-1);
345 newMask->setResourceId(-1);
346 newMask->setImage(sourceMask->image());
347 } else {
348 newMask = KoGamutMaskSP(new KoGamutMask());
349
350 QString defaultPreviewPath = ":/gamutmasks/empty_mask_preview.png";
351 KIS_SAFE_ASSERT_RECOVER_NOOP(!(defaultPreviewPath.isEmpty() || defaultPreviewPath.isNull() || !QFile::exists(defaultPreviewPath)));
352
353 newMask->setImage(QImage(defaultPreviewPath, "PNG"));
354 }
355
357
358 QString saveLocation = rServer->saveLocation();
359 QString name = newTitle;
360
361 QFileInfo fileInfo(saveLocation + "/" + name + newMask->defaultFileExtension());
362
363 newMask->setTitle(name);
364 newMask->setFilename(fileInfo.fileName());
365 newMask->setValid(true);
366
367 // don't save just yet; saving will happen when the user presses Save button on editing
368
369 return newMask;
370}
371
373{
374 if (m_maskDocument) {
375
376 disconnect(m_view->viewManager(), SIGNAL(viewChanged()), this, SLOT(slotViewChanged()));
377 disconnect(m_maskDocument, SIGNAL(completed()), this, SLOT(slotDocumentSaved()));
378
379 // the template file is meant as temporary, if the user saved it, delete now
380 if (QFile::exists(m_maskDocument->localFilePath())) {
381 QFile::remove(m_maskDocument->localFilePath());
382 }
383
385 // set the document to not modified to bypass confirmation dialog
386 // the close is already confirmed
388
390 m_view->closeView();
391 m_view->deleteLater();
392
393 // set a flag that we are doing it ourselves, so the docker does not react to
394 // removing signal from KisPart
398 m_selfClosingTemplate = false;
399 }
400 }
401
402 m_dockerUI->maskPropertiesBox->setVisible(false);
403 m_dockerUI->editControlsBox->setVisible(true);
404 m_dockerUI->editControlsBox->setEnabled(true);
405
406 m_maskDocument = nullptr;
407 m_view = nullptr;
408 m_creatingNewMask = false;
409 m_templatePrevSaved = false;
410}
411
413{
414 KisShapeLayerSP shapeLayer = getShapeLayer();
415
416 // create a deep copy of the shapes to save in the mask,
417 // otherwise they vanish when the template closes
418 QList<KoShape*> newShapes;
419
420 if (shapeLayer) {
421 for (KoShape* sh: shapeLayer->shapes()) {
422 KoShape* newShape = sh->cloneShape();
423 KoShapeStrokeSP border(new KoShapeStroke(0.5f, Qt::white));
424 newShape->setStroke(border);
425 newShape->setBackground(QSharedPointer<KoColorBackground>(new KoColorBackground(QColor(255,255,255,0))));
426 newShapes.append(newShape);
427 }
428 }
429
430 return newShapes;
431}
432
438
440{
442 return;
443 }
444
445 QString newTitle = m_dockerUI->maskTitleEdit->text();
446
447 if (m_selectedMask->title() != newTitle) {
448 // title has changed, rename
450
451 // delete old mask and select new
452 deleteMask();
453 selectMask(newMask);
454 }
455
456 bool maskSaved = saveSelectedMaskResource();
457 if (maskSaved) {
460 }
461}
462
464{
465 if (!m_selectedMask) {
466 return;
467 }
468
469 saveOrCancel(QMessageBox::Close);
470}
471
473{
474 if (!m_selectedMask) {
475 return;
476 }
477
478 m_selectedMask->setPreviewMaskShapes(getShapesFromLayer());
480}
481
483{
484 if (!m_selfSelectingMask) {
485 if (m_maskDocument) {
486 int res = saveOrCancel();
487 if (res == QMessageBox::Cancel) {
488 return;
489 }
490 }
491
492 selectMask(mask, false);
493 }
494}
495
497{
498 setEnabled(canvas != 0);
499}
500
502{
503 setEnabled(false);
504}
505
506
512
514{
515 // if deleting previously set mask, notify selectors to unset their mask
516 if (resource == m_resourceProvider->currentGamutMask()) {
517 Q_EMIT sigGamutMaskUnset();
518 m_selectedMask = nullptr;
519 }
520}
521
523{
524 // if currently set mask has been changed, notify selectors
525 if (resource == m_resourceProvider->currentGamutMask()) {
526 selectMask(resource);
527 }
528}
529
531{
532 KoGamutMaskSP newMask = createMaskResource(nullptr, "new mask");
533 selectMask(newMask);
534
535 bool editorOpened = openMaskEditor();
536 if (!editorOpened) {
537 deleteMask();
538 }
539}
540
542{
543 if (!m_selectedMask) {
544 return;
545 }
546
547 KoGamutMaskSP newMask = createMaskResource(m_selectedMask, m_selectedMask->title() + QString(" (Copy)"));
548 selectMask(newMask);
549
550 bool editorOpened = openMaskEditor();
551 if (!editorOpened) {
552 deleteMask();
553 }
554}
555
557{
558 if (!m_selectedMask) {
559 return;
560 }
561
562 int res = getUserFeedback(i18n("Are you sure you want to delete mask <b>'%1'</b>?"
563 , m_selectedMask->title()));
564
565 if (res == QMessageBox::Yes) {
566 deleteMask();
567 }
568}
569
571{
572 if (!m_maskDocument) {
573 return;
574 }
575
577
578 // we do not want to run this if it is we who close the file
580 // KisPart called, that a document will be removed
581 // if it's ours, cancel the mask edit operation
582 if (m_maskDocument->path() == filename) {
584 saveOrCancel();
585 }
586 }
587
589}
590
592{
593 if (!m_maskDocument || !m_view) {
594 return;
595 }
596
598 m_dockerUI->maskPropertiesBox->setEnabled(true);
599 } else {
600 m_dockerUI->maskPropertiesBox->setEnabled(false);
601 }
602}
603
QSharedPointer< KoShapeStrokeModel > KoShapeStrokeModelSP
QSharedPointer< KoGamutMask > KoGamutMaskSP
Definition KoGamutMask.h:98
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
int saveOrCancel(QMessageBox::StandardButton defaultAction=QMessageBox::Save)
~GamutMaskDock() override
void setViewManager(KisViewManager *kisview) override
void resourceChanged(KoGamutMaskSP resource) override
bool m_externalTemplateClose
void slotGamutMaskPreview()
void unsetResourceServer() override
void removingResource(KoGamutMaskSP resource) override
void selectMask(KoGamutMaskSP mask, bool notifyItemChooser=true)
GamutMaskChooserUI * m_dockerUI
QList< KoShape * > getShapesFromLayer()
void sigGamutMaskUnset()
void setCanvas(KoCanvasBase *canvas) override
KoGamutMaskSP m_selectedMask
KisShapeLayerSP getShapeLayer()
KisDocument * m_maskDocument
bool saveSelectedMaskResource()
void slotGamutMaskCreateNew()
void sigGamutMaskChanged(KoGamutMaskSP mask)
void sigGamutMaskPreviewUpdate()
void slotGamutMaskSelected(KoGamutMaskSP mask)
void slotDocumentRemoved(QString filename)
void unsetCanvas() override
KisCanvasResourceProvider * m_resourceProvider
void sigGamutMaskSet(KoGamutMaskSP mask)
void slotGamutMaskCancelEdit()
void slotGamutMaskDuplicate()
int getUserFeedback(QString text, QString informativeText="", QMessageBox::StandardButtons buttons=QMessageBox::Yes|QMessageBox::No, QMessageBox::StandardButton defaultButton=QMessageBox::Yes, QMessageBox::Icon severity=QMessageBox::Warning)
KoGamutMaskSP createMaskResource(KoGamutMaskSP sourceMask, QString newTitle)
bool m_selfClosingTemplate
bool setResourceInactive(const QModelIndex &index)
void waitForSavingToComplete()
KisImageSP image
QString localFilePath() const
bool isModified() const
void setModified(bool _mod)
void setPath(const QString &path)
void setLocalFilePath(const QString &localFilePath)
void setPreActivatedNode(KisNodeSP activatedNode)
QString path() const
bool closePath(bool promptToSave=true)
void setInfiniteAutoSaveInterval()
bool openPath(const QString &path, OpenFlags flags=None)
openPath Open a Path
QImage convertToQImage(qint32 x1, qint32 y1, qint32 width, qint32 height, const KoColorProfile *profile)
KisGroupLayerSP rootLayer() const
QRect bounds() const override
const KoColorProfile * profile() const
Main window for Krita.
KisView * addViewAndNotifyLoadingCompleted(KisDocument *document, QMdiSubWindow *subWindow=0)
static KisPart * instance()
Definition KisPart.cpp:131
void removeView(KisView *view)
Definition KisPart.cpp:344
void addDocument(KisDocument *document, bool notify=true)
Definition KisPart.cpp:211
KisMainWindow * currentMainwindow() const
Definition KisPart.cpp:483
void removeDocument(KisDocument *document, bool deleteDocument=true)
Definition KisPart.cpp:248
KisDocument * createDocument() const
Definition KisPart.cpp:230
QList< QPointer< KisView > > views
Definition KisPart.cpp:106
The KisResourceModel class provides the main access to resources. It is possible to filter the resour...
QModelIndex indexForResource(KoResourceSP resource) const override
indexFromResource
QModelIndex indexForResourceId(int resourceId) const override
indexFromResource
static bool addResourceWithUserInput(QWidget *widgetParent, KoResourceSP resource, QString storageLocation="")
static bool updateResourceWithUserInput(QWidget *widgetParent, KoResourceSP resource)
KisDocument * document() const
KisCanvasResourceProvider * canvasResourceProvider()
void closeView()
Definition KisView.cpp:1441
KisViewManager * viewManager
Definition KisView.cpp:129
A simple solid color shape background.
The resource type for gamut masks used by the artistic color selector.
Definition KoGamutMask.h:44
void addObserver(ObserverType *observer)
void removeObserver(ObserverType *observer)
QString saveLocation()
Returns path where to save user defined and imported resources to.
QList< KoShape * > shapes() const
void addShape(KoShape *shape)
virtual void setStroke(KoShapeStrokeModelSP stroke)
Definition KoShape.cpp:1081
virtual void setBackground(QSharedPointer< KoShapeBackground > background)
Definition KoShape.cpp:918
virtual KoShape * cloneShape() const
creates a deep copy of the shape or shape's subtree
Definition KoShape.cpp:200
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
#define dbgPlugins
Definition kis_debug.h:51
QString buttons(const T &ev)
KisSharedPtr< KisShapeLayer > KisShapeLayerSP
Definition kis_types.h:125
QIcon loadIcon(const QString &name)
KisNodeSP findNodeByName(KisNodeSP root, const QString &name)
const QString GamutMasks
static KoResourceServerProvider * instance()
KoResourceServer< KoGamutMask > * gamutMaskServer