Warning, file /graphics/krita/plugins/tools/tool_lazybrush/kis_tool_lazy_brush_options_widget.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  *  SPDX-FileCopyrightText: 2016 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_tool_lazy_brush_options_widget.h"
0008 
0009 #include "ui_kis_tool_lazy_brush_options_widget.h"
0010 
0011 #include <QWheelEvent>
0012 #include <KoColorSpaceRegistry.h>
0013 #include "KisPaletteModel.h"
0014 #include <KisSpinBoxI18nHelper.h>
0015 
0016 #include "kis_config.h"
0017 #include <resources/KoColorSet.h>
0018 #include "kis_canvas_resource_provider.h"
0019 #include "kis_signal_auto_connection.h"
0020 #include "lazybrush/kis_colorize_mask.h"
0021 #include "kis_image.h"
0022 #include "kis_signals_blocker.h"
0023 #include "kis_signal_compressor.h"
0024 #include "kis_layer_properties_icons.h"
0025 
0026 struct KisToolLazyBrushOptionsWidget::Private
0027 {
0028     Private()
0029         : baseNodeChangedCompressor(500, KisSignalCompressor::FIRST_ACTIVE)
0030     {
0031     }
0032 
0033     Ui_KisToolLazyBrushOptionsWidget *ui {nullptr};
0034     KisPaletteModel *colorModel {nullptr};
0035     int preferredColumnCount {10};
0036     KisCanvasResourceProvider *provider {nullptr};
0037 
0038     KisSignalAutoConnectionsStore providerSignals;
0039     KisSignalAutoConnectionsStore maskSignals;
0040     KisColorizeMaskSP activeMask;
0041 
0042     KoColorSetSP colorSet {new KoColorSet(QString())};
0043     int transparentColorIndex {0};
0044 
0045     KisSignalCompressor baseNodeChangedCompressor;
0046 };
0047 
0048 struct PaletteEventFilter : public QObject
0049 {
0050     bool eventFilter(QObject *watched, QEvent *event) override
0051     {
0052         if (event->type() == QEvent::Wheel) {
0053             QWheelEvent *wevent = static_cast<QWheelEvent*>(event);
0054 
0055             if (wevent->modifiers() == Qt::ControlModifier) {
0056                 if (watched == m_parentView->viewport()) {
0057                     const int columnCountDelta = -wevent->delta() / QWheelEvent::DefaultDeltasPerStep;
0058                     const int newColumnCount = qMax(1, m_optionsWidget->m_d->preferredColumnCount + columnCountDelta);
0059 
0060                     m_optionsWidget->m_d->preferredColumnCount = newColumnCount;
0061                     m_optionsWidget->slotColorLabelsChanged();
0062                 }
0063 
0064                 return true;
0065             }
0066 
0067         }
0068 
0069         return QObject::eventFilter(watched, event);
0070     }
0071 
0072     PaletteEventFilter(KisPaletteView *parentView, KisToolLazyBrushOptionsWidget *optionsWidget)
0073         : QObject(optionsWidget),
0074           m_parentView(parentView),
0075           m_optionsWidget(optionsWidget)
0076 
0077     {}
0078 
0079     KisPaletteView *m_parentView;
0080     KisToolLazyBrushOptionsWidget *m_optionsWidget;
0081 };
0082 
0083 KisToolLazyBrushOptionsWidget::KisToolLazyBrushOptionsWidget(KisCanvasResourceProvider *provider, QWidget *parent)
0084     : QWidget(parent),
0085       m_d(new Private)
0086 {
0087     m_d->ui = new Ui_KisToolLazyBrushOptionsWidget();
0088     m_d->ui->setupUi(this);
0089 
0090     m_d->colorModel = new KisPaletteModel(this);
0091     m_d->ui->colorView->setPaletteModel(m_d->colorModel);
0092     m_d->ui->colorView->setAllowModification(false); //people probably shouldn't be able to edit the colorentries themselves.
0093     m_d->ui->colorView->setCrossedKeyword("transparent");
0094 
0095     PaletteEventFilter *filter = new PaletteEventFilter(m_d->ui->colorView, this);
0096     m_d->ui->colorView->viewport()->installEventFilter(filter);
0097 
0098     connect(m_d->ui->chkUseEdgeDetection, SIGNAL(toggled(bool)), SLOT(slotUseEdgeDetectionChanged(bool)));
0099     connect(m_d->ui->intEdgeDetectionSize, SIGNAL(valueChanged(int)), SLOT(slotEdgeDetectionSizeChanged(int)));
0100     connect(m_d->ui->intRadius, SIGNAL(valueChanged(int)), SLOT(slotRadiusChanged(int)));
0101     connect(m_d->ui->intCleanUp, SIGNAL(valueChanged(int)), SLOT(slotCleanUpChanged(int)));
0102     connect(m_d->ui->chkLimitToDevice, SIGNAL(toggled(bool)), SLOT(slotLimitToDeviceChanged(bool)));
0103 
0104     m_d->ui->intEdgeDetectionSize->setRange(0, 100);
0105     m_d->ui->intEdgeDetectionSize->setExponentRatio(2.0);
0106     m_d->ui->intEdgeDetectionSize->setSuffix(i18n(" px"));
0107     m_d->ui->intEdgeDetectionSize->setPrefix(i18n("Edge detection: "));
0108     m_d->ui->intEdgeDetectionSize->setToolTip(
0109         i18nc("@info:tooltip",
0110               "Activate for images with vast solid areas. "
0111               "Set the value to the width of the thinnest "
0112               "lines on the image"));
0113 
0114     m_d->ui->intRadius->setRange(0, 1000);
0115     m_d->ui->intRadius->setExponentRatio(3.0);
0116     m_d->ui->intRadius->setSuffix(i18n(" px"));
0117     m_d->ui->intRadius->setPrefix(i18n("Gap close hint: "));
0118     m_d->ui->intRadius->setToolTip(
0119         i18nc("@info:tooltip",
0120               "The mask will try to close non-closed contours "
0121               "if the gap is smaller than \"Gap close hint\" value"));
0122 
0123     m_d->ui->intCleanUp->setRange(0, 100);
0124     KisSpinBoxI18nHelper::setText(m_d->ui->intCleanUp,
0125                                   i18nc("{n} is the number value, % is the percent sign", "Clean up: {n}%"));
0126     m_d->ui->intCleanUp->setToolTip(
0127         i18nc("@info:tooltip",
0128               "The mask will try to remove parts of the key strokes "
0129               "that are placed outside the closed contours. 0% - no effect, 100% - max effect"));
0130 
0131 
0132     connect(m_d->ui->colorView, SIGNAL(sigIndexSelected(QModelIndex)), this, SLOT(entrySelected(QModelIndex)));
0133     connect(m_d->ui->btnTransparent, SIGNAL(toggled(bool)), this, SLOT(slotMakeTransparent(bool)));
0134     connect(m_d->ui->btnRemove, SIGNAL(clicked()), this, SLOT(slotRemove()));
0135 
0136     connect(m_d->ui->chkAutoUpdates, SIGNAL(toggled(bool)), m_d->ui->btnUpdate, SLOT(setDisabled(bool)));
0137 
0138     connect(m_d->ui->btnUpdate, SIGNAL(clicked()), this, SLOT(slotUpdate()));
0139     connect(m_d->ui->chkAutoUpdates, SIGNAL(toggled(bool)), this, SLOT(slotSetAutoUpdates(bool)));
0140     connect(m_d->ui->chkShowKeyStrokes, SIGNAL(toggled(bool)), this, SLOT(slotSetShowKeyStrokes(bool)));
0141     connect(m_d->ui->chkShowOutput, SIGNAL(toggled(bool)), this, SLOT(slotSetShowOutput(bool)));
0142 
0143     connect(&m_d->baseNodeChangedCompressor, SIGNAL(timeout()), this, SLOT(slotUpdateNodeProperties()));
0144 
0145     m_d->provider = provider;
0146 
0147     m_d->colorModel->setColorSet(m_d->colorSet);
0148 
0149     const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
0150 
0151     m_d->colorModel->addSwatch(KisSwatch(KoColor(Qt::red, cs), "color1"));
0152     m_d->colorModel->addSwatch(KisSwatch(KoColor(Qt::green, cs), "color2"));
0153     m_d->colorModel->addSwatch(KisSwatch(KoColor(Qt::blue, cs), "color3"));
0154 }
0155 
0156 KisToolLazyBrushOptionsWidget::~KisToolLazyBrushOptionsWidget()
0157 {
0158     delete m_d->ui;
0159     m_d->ui = nullptr;
0160 }
0161 
0162 void KisToolLazyBrushOptionsWidget::showEvent(QShowEvent *event)
0163 {
0164     QWidget::showEvent(event);
0165 
0166     m_d->providerSignals.addConnection(
0167         m_d->provider, SIGNAL(sigNodeChanged(KisNodeSP)),
0168         this, SLOT(slotCurrentNodeChanged(KisNodeSP)));
0169 
0170     m_d->providerSignals.addConnection(
0171         m_d->provider, SIGNAL(sigFGColorChanged(KoColor)),
0172         this, SLOT(slotCurrentFgColorChanged(KoColor)));
0173 
0174     slotCurrentNodeChanged(m_d->provider->currentNode());
0175     slotCurrentFgColorChanged(m_d->provider->fgColor());
0176 }
0177 
0178 void KisToolLazyBrushOptionsWidget::hideEvent(QHideEvent *event)
0179 {
0180     QWidget::hideEvent(event);
0181 
0182     m_d->providerSignals.clear();
0183 }
0184 
0185 void KisToolLazyBrushOptionsWidget::entrySelected(QModelIndex index)
0186 {
0187     if (!index.isValid()) return;
0188     if (!qvariant_cast<bool>(index.data(KisPaletteModel::CheckSlotRole))) return;
0189 
0190     KisSwatch entry = m_d->colorModel->getSwatch(index);
0191     m_d->provider->setFGColor(entry.color());
0192 
0193     int idxInList = m_d->activeMask->keyStrokesColors().colors.indexOf(entry.color());
0194 
0195     if (idxInList != -1) {
0196         const bool transparentChecked = idxInList == m_d->transparentColorIndex;
0197         KisSignalsBlocker b(m_d->ui->btnTransparent);
0198         m_d->ui->btnTransparent->setChecked(transparentChecked);
0199     }
0200 }
0201 
0202 void KisToolLazyBrushOptionsWidget::slotCurrentFgColorChanged(const KoColor &color)
0203 {
0204     bool found = false;
0205 
0206     QModelIndex candidateIdx = m_d->colorModel->indexForClosest(color);
0207     if (m_d->colorModel->getSwatch(candidateIdx).color() == color) {
0208         found = true;
0209     }
0210 
0211     m_d->ui->btnRemove->setEnabled(found);
0212     m_d->ui->btnTransparent->setEnabled(found);
0213 
0214     if (!found) {
0215         KisSignalsBlocker b(m_d->ui->btnTransparent);
0216         m_d->ui->btnTransparent->setChecked(false);
0217     }
0218 
0219     QModelIndex newIndex = found ? candidateIdx : QModelIndex();
0220 
0221     if (!found) {
0222         m_d->ui->colorView->selectionModel()->clear();
0223     }
0224     if (newIndex.isValid() && newIndex != m_d->ui->colorView->currentIndex()) {
0225         m_d->ui->colorView->setCurrentIndex(newIndex);
0226         m_d->ui->colorView->update(newIndex);
0227     }
0228 }
0229 
0230 void KisToolLazyBrushOptionsWidget::slotColorLabelsChanged()
0231 {
0232     m_d->colorModel->clear(m_d->preferredColumnCount);
0233     m_d->transparentColorIndex = -1;
0234 
0235     if (m_d->activeMask) {
0236         KisColorizeMask::KeyStrokeColors colors = m_d->activeMask->keyStrokesColors();
0237         m_d->transparentColorIndex = colors.transparentIndex;
0238 
0239         for (int i = 0; i < colors.colors.size(); i++) {
0240             const QString name = i == m_d->transparentColorIndex ? "transparent" : "";
0241             m_d->colorModel->addSwatch(KisSwatch(colors.colors[i], name));
0242         }
0243     }
0244 
0245     slotCurrentFgColorChanged(m_d->provider->fgColor());
0246 }
0247 
0248 void KisToolLazyBrushOptionsWidget::slotUpdateNodeProperties()
0249 {
0250     KisSignalsBlocker b1(m_d->ui->chkAutoUpdates,
0251                          m_d->ui->btnUpdate,
0252                          m_d->ui->chkShowKeyStrokes,
0253                          m_d->ui->chkShowOutput);
0254     KisSignalsBlocker b2(m_d->ui->chkUseEdgeDetection,
0255                          m_d->ui->intEdgeDetectionSize,
0256                          m_d->ui->intRadius,
0257                          m_d->ui->intCleanUp,
0258                          m_d->ui->chkLimitToDevice);
0259 
0260     // not implemented yet!
0261     //m_d->ui->chkAutoUpdates->setEnabled(m_d->activeMask);
0262     m_d->ui->chkAutoUpdates->setEnabled(false);
0263     m_d->ui->chkAutoUpdates->setVisible(false);
0264 
0265     bool value = false;
0266 
0267     value = m_d->activeMask && KisLayerPropertiesIcons::nodeProperty(m_d->activeMask, KisLayerPropertiesIcons::colorizeNeedsUpdate, true).toBool();
0268     m_d->ui->btnUpdate->setEnabled(m_d->activeMask && !m_d->ui->chkAutoUpdates->isChecked() && value);
0269 
0270     value = m_d->activeMask && KisLayerPropertiesIcons::nodeProperty(m_d->activeMask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool();
0271     m_d->ui->chkShowKeyStrokes->setEnabled(m_d->activeMask);
0272     m_d->ui->chkShowKeyStrokes->setChecked(value);
0273 
0274     value = m_d->activeMask && KisLayerPropertiesIcons::nodeProperty(m_d->activeMask, KisLayerPropertiesIcons::colorizeShowColoring, true).toBool();
0275     m_d->ui->chkShowOutput->setEnabled(m_d->activeMask);
0276     m_d->ui->chkShowOutput->setChecked(value);
0277 
0278     m_d->ui->chkUseEdgeDetection->setEnabled(m_d->activeMask);
0279     m_d->ui->chkUseEdgeDetection->setChecked(m_d->activeMask && m_d->activeMask->useEdgeDetection());
0280 
0281     m_d->ui->intEdgeDetectionSize->setEnabled(m_d->activeMask && m_d->ui->chkUseEdgeDetection->isChecked());
0282     m_d->ui->intEdgeDetectionSize->setValue(m_d->activeMask ? m_d->activeMask->edgeDetectionSize() : 4.0);
0283     m_d->ui->intRadius->setEnabled(m_d->activeMask);
0284     m_d->ui->intRadius->setValue(2 * (m_d->activeMask ? m_d->activeMask->fuzzyRadius() : 15));
0285     m_d->ui->intCleanUp->setEnabled(m_d->activeMask);
0286     m_d->ui->intCleanUp->setValue(100 * (m_d->activeMask ? m_d->activeMask->cleanUpAmount() : 0.7));
0287 
0288     m_d->ui->chkLimitToDevice->setEnabled(m_d->activeMask);
0289     m_d->ui->chkLimitToDevice->setChecked(m_d->activeMask && m_d->activeMask->limitToDeviceBounds());
0290 }
0291 
0292 void KisToolLazyBrushOptionsWidget::slotCurrentNodeChanged(KisNodeSP node)
0293 {
0294     m_d->maskSignals.clear();
0295 
0296     KisColorizeMask *mask = dynamic_cast<KisColorizeMask*>(node.data());
0297     m_d->activeMask = mask;
0298 
0299     if (m_d->activeMask) {
0300         m_d->maskSignals.addConnection(
0301             m_d->activeMask, SIGNAL(sigKeyStrokesListChanged()),
0302             this, SLOT(slotColorLabelsChanged()));
0303 
0304         m_d->maskSignals.addConnection(
0305             m_d->provider->currentImage(), SIGNAL(sigNodeChanged(KisNodeSP)),
0306             this, SLOT(slotUpdateNodeProperties()));
0307     }
0308 
0309     slotColorLabelsChanged();
0310     slotUpdateNodeProperties();
0311     m_d->ui->colorView->setEnabled(m_d->activeMask);
0312 }
0313 
0314 void KisToolLazyBrushOptionsWidget::slotMakeTransparent(bool enableTransparency)
0315 {
0316     KIS_ASSERT_RECOVER_RETURN(m_d->activeMask);
0317 
0318     QModelIndex index = m_d->ui->colorView->currentIndex();
0319     KisSwatch activeSwatch = m_d->colorModel->getSwatch(index);
0320     if (!index.isValid()) return;
0321 
0322     QVector<KisSwatchGroup::SwatchInfo> infoList;
0323     Q_FOREACH (const QString &groupName, m_d->colorSet->swatchGroupNames()) {
0324         KisSwatchGroupSP group = m_d->colorSet->getGroup(groupName);
0325         Q_FOREACH (const KisSwatchGroup::SwatchInfo &info, group->infoList()) {
0326             infoList.append(info);
0327         }
0328     }
0329 
0330     // We can't rely on the order of the colors returned, so we need to sort them row by row from left to right
0331     std::sort(infoList.begin(), infoList.end(), sortSwatchInfo);
0332     KisColorizeMask::KeyStrokeColors colors;
0333     int i = 0;
0334     for (const KisSwatchGroup::SwatchInfo &info : infoList) {
0335         if (activeSwatch == info.swatch && enableTransparency) {
0336             colors.transparentIndex = i;
0337         }
0338         colors.colors << info.swatch.color();
0339         i++;
0340     }
0341 
0342     m_d->activeMask->setKeyStrokesColors(colors);
0343 }
0344 
0345 void KisToolLazyBrushOptionsWidget::slotRemove()
0346 {
0347     KIS_ASSERT_RECOVER_RETURN(m_d->activeMask);
0348 
0349     QModelIndex index = m_d->ui->colorView->currentIndex();
0350     if (!index.isValid()) return;
0351 
0352     const KoColor color = m_d->colorModel->getSwatch(index).color();
0353     m_d->activeMask->removeKeyStroke(color);
0354 }
0355 
0356 void KisToolLazyBrushOptionsWidget::slotUpdate()
0357 {
0358     KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask);
0359     KisLayerPropertiesIcons::setNodePropertyAutoUndo(m_d->activeMask, KisLayerPropertiesIcons::colorizeNeedsUpdate, false, m_d->provider->currentImage());
0360 }
0361 
0362 void KisToolLazyBrushOptionsWidget::slotSetAutoUpdates(bool value)
0363 {
0364     // not implemented yet!
0365     ENTER_FUNCTION() << ppVar(value);
0366 }
0367 
0368 void KisToolLazyBrushOptionsWidget::slotSetShowKeyStrokes(bool value)
0369 {
0370     KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask);
0371     KisLayerPropertiesIcons::setNodePropertyAutoUndo(m_d->activeMask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, value, m_d->provider->currentImage());
0372 }
0373 
0374 void KisToolLazyBrushOptionsWidget::slotSetShowOutput(bool value)
0375 {
0376     KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask);
0377     KisLayerPropertiesIcons::setNodePropertyAutoUndo(m_d->activeMask, KisLayerPropertiesIcons::colorizeShowColoring, value, m_d->provider->currentImage());
0378 }
0379 
0380 void KisToolLazyBrushOptionsWidget::slotUseEdgeDetectionChanged(bool value)
0381 {
0382     KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask);
0383     m_d->activeMask->setUseEdgeDetection(value);
0384     m_d->ui->intEdgeDetectionSize->setEnabled(value);
0385 }
0386 
0387 void KisToolLazyBrushOptionsWidget::slotEdgeDetectionSizeChanged(int value)
0388 {
0389     KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask);
0390     m_d->activeMask->setEdgeDetectionSize(value);
0391 }
0392 
0393 void KisToolLazyBrushOptionsWidget::slotRadiusChanged(int value)
0394 {
0395     KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask);
0396     m_d->activeMask->setFuzzyRadius(0.5 * value);
0397 }
0398 
0399 void KisToolLazyBrushOptionsWidget::slotCleanUpChanged(int value)
0400 {
0401     KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask);
0402     m_d->activeMask->setCleanUpAmount(qreal(value) / 100.0);
0403 }
0404 
0405 void KisToolLazyBrushOptionsWidget::slotLimitToDeviceChanged(bool value)
0406 {
0407     KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->activeMask);
0408     m_d->activeMask->setLimitToDeviceBounds(value);
0409 }
0410 
0411 bool KisToolLazyBrushOptionsWidget::sortSwatchInfo(const KisSwatchGroup::SwatchInfo &first, const KisSwatchGroup::SwatchInfo &second)
0412 {
0413     if (first.row < second.row) { return true; }
0414     if (first.row > second.row) { return false; }
0415     return first.column < second.column;
0416 }