File indexing completed on 2025-01-26 04:11:28

0001 /*
0002  *  SPDX-FileCopyrightText: 2017 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "KisMaskingBrushOption.h"
0008 
0009 #include "kis_predefined_brush_chooser.h"
0010 #include "kis_brush_selection_widget.h"
0011 
0012 #include <QWidget>
0013 #include <QVBoxLayout>
0014 #include <QHBoxLayout>
0015 #include <QComboBox>
0016 
0017 #include <QDomDocument>
0018 #include "kis_brush.h"
0019 #include "kis_image.h"
0020 #include "kis_image_config.h"
0021 #include "kis_brush_option.h"
0022 
0023 #include "KisMaskingBrushOptionProperties.h"
0024 #include <strokes/KisMaskingBrushCompositeOpFactory.h>
0025 #include <KoCompositeOpRegistry.h>
0026 #include <brushengine/KisPaintopSettingsIds.h>
0027 #include <brushengine/kis_paintop_lod_limitations.h>
0028 #include <lager/state.hpp>
0029 #include <lager/constant.hpp>
0030 #include <KisWidgetConnectionUtils.h>
0031 #include <functional>
0032 #include "KisAutoBrushModel.h"
0033 #include "KisPredefinedBrushModel.h"
0034 #include "KisTextBrushModel.h"
0035 
0036 
0037 using namespace KisBrushModel;
0038 using namespace KisWidgetConnectionUtils;
0039 
0040 
0041 namespace detail {
0042 
0043 QString warningLabelText(qreal realBrushSize, qreal theoreticalMaskingBrushSize)
0044 {
0045     return
0046         i18nc("warning about too big size of the masked brush",
0047               "WARNING: Dependent size of the masked brush grew too big (%1 pixels). Its value has been cropped to %2 pixels.",
0048               theoreticalMaskingBrushSize,
0049               realBrushSize);
0050 }
0051 
0052 bool warningLabelVisible(qreal theoreticalBrushSize) {
0053     KisImageConfig cfg(true);
0054     return theoreticalBrushSize > cfg.maxMaskingBrushSize();
0055 }
0056 
0057 
0058 }
0059 
0060 class MaskingBrushModel : public QObject
0061 {
0062     Q_OBJECT
0063 public:
0064     MaskingBrushModel(lager::cursor<MaskingBrushData> maskingData, lager::cursor<qreal> commonBrushSizeData, lager::reader<qreal> masterBrushSize)
0065         : m_maskingData(maskingData),
0066           m_commonBrushSizeData(commonBrushSizeData),
0067           m_masterBrushSize(masterBrushSize),
0068           m_preserveMode(false),
0069           autoBrushModel(m_maskingData[&MaskingBrushData::brush][&BrushData::common],
0070                          m_maskingData[&MaskingBrushData::brush][&BrushData::autoBrush],
0071                          m_commonBrushSizeData),
0072           predefinedBrushModel(m_maskingData[&MaskingBrushData::brush][&BrushData::common],
0073                                m_maskingData[&MaskingBrushData::brush][&BrushData::predefinedBrush],
0074                                m_commonBrushSizeData,
0075                                false),
0076           textBrushModel(m_maskingData[&MaskingBrushData::brush][&BrushData::common],
0077                          m_maskingData[&MaskingBrushData::brush][&BrushData::textBrush]),
0078           LAGER_QT(isEnabled) {m_maskingData[&MaskingBrushData::isEnabled]},
0079           LAGER_QT(compositeOpId) {m_maskingData[&MaskingBrushData::compositeOpId]},
0080           LAGER_QT(theoreticalBrushSize) {
0081               lager::with(m_maskingData[&MaskingBrushData::masterSizeCoeff],
0082                           m_masterBrushSize)
0083                       .map(std::multiplies<qreal>{})},
0084           LAGER_QT(realBrushSize) {m_commonBrushSizeData},
0085           LAGER_QT(warningLabelVisible) {
0086               lager::with(m_preserveMode,
0087                           LAGER_QT(theoreticalBrushSize).map(&detail::warningLabelVisible))
0088                       .map(std::logical_and<bool>{})},
0089           LAGER_QT(warningLabelText) {
0090               lager::with(LAGER_QT(realBrushSize),
0091                           LAGER_QT(theoreticalBrushSize))
0092                       .map(&detail::warningLabelText)},
0093           m_maskingBrushCursor(m_maskingData[&MaskingBrushData::brush])
0094     {
0095         lager::watch(m_commonBrushSizeData, std::bind(&MaskingBrushModel::updatePreserveMode, this));
0096         lager::watch(m_maskingBrushCursor, std::bind(&MaskingBrushModel::updatePreserveMode, this));
0097         lager::watch(m_masterBrushSize, std::bind(&MaskingBrushModel::updatePreserveMode, this));
0098     }
0099 
0100     // the state must be declared **before** any cursors or readers
0101     lager::cursor<MaskingBrushData> m_maskingData;
0102     lager::cursor<qreal> m_commonBrushSizeData;
0103     lager::reader<qreal> m_masterBrushSize;
0104     lager::state<bool, lager::automatic_tag> m_preserveMode;
0105 
0106     KisAutoBrushModel autoBrushModel;
0107     KisPredefinedBrushModel predefinedBrushModel;
0108     KisTextBrushModel textBrushModel;
0109 
0110     LAGER_QT_CURSOR(bool, isEnabled);
0111     LAGER_QT_CURSOR(QString, compositeOpId);
0112     LAGER_QT_READER(qreal, theoreticalBrushSize);
0113     LAGER_QT_READER(qreal, realBrushSize);
0114     LAGER_QT_READER(bool, warningLabelVisible);
0115     LAGER_QT_READER(QString, warningLabelText);
0116 
0117     void updatePreserveMode()
0118     {
0119         if (!m_preserveMode.get() || !m_originalBrushInfo) return;
0120 
0121         if (m_originalBrushInfo->brush != m_maskingData->brush ||
0122             !qFuzzyCompare(m_originalBrushInfo->masterSize, m_masterBrushSize.get()) ||
0123             !qFuzzyCompare(m_originalBrushInfo->commonSize, m_commonBrushSizeData.get())) {
0124 
0125             m_originalBrushInfo = std::nullopt;
0126             m_preserveMode.set(false);
0127         }
0128     }
0129 
0130     void startPreserveMode()
0131     {
0132         m_originalBrushInfo =
0133             {m_maskingData->brush,
0134              m_masterBrushSize.get(),
0135              m_commonBrushSizeData.get()};
0136 
0137         m_preserveMode.set(true);
0138     }
0139 
0140     MaskingBrushData bakedOptionData() const {
0141         MaskingBrushData data = m_maskingData.get();
0142 
0143         data.brush.autoBrush = autoBrushModel.bakedOptionData();
0144         data.brush.predefinedBrush = predefinedBrushModel.bakedOptionData();
0145         return data;
0146     }
0147 
0148 private:
0149     struct OriginalBrushInfo {
0150         BrushData brush;
0151         qreal masterSize;
0152         qreal commonSize;
0153     };
0154 
0155     std::optional<OriginalBrushInfo> m_originalBrushInfo;
0156     lager::reader<BrushData> m_maskingBrushCursor;
0157 
0158 };
0159 
0160 
0161 struct KisMaskingBrushOption::Private
0162 {
0163     Private(lager::reader<qreal> effectiveBrushSize)
0164         : ui(new QWidget())
0165         ,  commonBrushSizeData(777.0)
0166         ,  masterBrushSize(effectiveBrushSize)
0167 
0168         ,  maskingModel(maskingData, commonBrushSizeData, effectiveBrushSize)
0169     {
0170         compositeSelector = new QComboBox(ui.data());
0171 
0172         const QStringList supportedComposites = KisMaskingBrushCompositeOpFactory::supportedCompositeOpIds();
0173         Q_FOREACH (const QString &id, supportedComposites) {
0174             const QString name = KoCompositeOpRegistry::instance().getKoID(id).name();
0175             compositeSelector->addItem(name, id);
0176         }
0177         compositeSelector->setCurrentIndex(0);
0178 
0179         QHBoxLayout *compositeOpLayout = new QHBoxLayout();
0180         compositeOpLayout->addWidget(new QLabel(i18n("Blending Mode:")), 0);
0181         compositeOpLayout->addWidget(compositeSelector, 1);
0182 
0183         brushSizeWarningLabel = new QLabel(ui.data());
0184         brushSizeWarningLabel->setVisible(false);
0185         brushSizeWarningLabel->setWordWrap(true);
0186 
0187         brushChooser = new KisBrushSelectionWidget(KisImageConfig(true).maxMaskingBrushSize(), &maskingModel.autoBrushModel, &maskingModel.predefinedBrushModel, &maskingModel.textBrushModel, maskingData[&MaskingBrushData::brush][&BrushData::type], brushPrecisionData, KisBrushOptionWidgetFlag::None, ui.data());
0188 
0189         QVBoxLayout *layout  = new QVBoxLayout(ui.data());
0190         layout->addLayout(compositeOpLayout, 0);
0191         layout->addWidget(brushSizeWarningLabel, 0);
0192         layout->addWidget(brushChooser, 1);
0193     }
0194 
0195     QScopedPointer<QWidget> ui;
0196     KisBrushSelectionWidget *brushChooser = 0;
0197     QComboBox *compositeSelector = 0;
0198     QLabel *brushSizeWarningLabel = 0;
0199 
0200     lager::state<KisBrushModel::MaskingBrushData, lager::automatic_tag> maskingData;
0201     lager::state<qreal, lager::automatic_tag> commonBrushSizeData;
0202     lager::reader<qreal> masterBrushSize;
0203     MaskingBrushModel maskingModel;
0204 
0205     /// we don't use precison data, we just need it to pass
0206     /// to the brush selection widget
0207     lager::state<KisBrushModel::PrecisionData, lager::automatic_tag> brushPrecisionData;
0208 };
0209 
0210 KisMaskingBrushOption::KisMaskingBrushOption(lager::reader<qreal> effectiveBrushSize)
0211     : KisPaintOpOption(i18n("Brush Tip"), KisPaintOpOption::MASKING_BRUSH, true)
0212     , m_d(new Private(effectiveBrushSize))
0213 {
0214     setObjectName("KisMaskingBrushOption");
0215     setConfigurationPage(m_d->ui.data());
0216 
0217     connect(&m_d->maskingModel, &MaskingBrushModel::isEnabledChanged,
0218             this, &KisMaskingBrushOption::setChecked);
0219     connect(this, &KisMaskingBrushOption::sigCheckedChanged,
0220             &m_d->maskingModel, &MaskingBrushModel::setisEnabled);
0221     m_d->maskingModel.LAGER_QT(isEnabled).nudge();
0222 
0223     connect(&m_d->maskingModel, &MaskingBrushModel::compositeOpIdChanged,
0224             this, &KisMaskingBrushOption::slotCompositeModePropertyChanged);
0225     connect(m_d->compositeSelector, qOverload<int>(&QComboBox::currentIndexChanged),
0226             this, &KisMaskingBrushOption::slotCompositeModeWidgetChanged);
0227     m_d->maskingModel.LAGER_QT(compositeOpId).nudge();
0228 
0229     connect(&m_d->maskingModel, &MaskingBrushModel::warningLabelVisibleChanged,
0230             m_d->brushSizeWarningLabel, &QLabel::setVisible);
0231     m_d->maskingModel.LAGER_QT(warningLabelVisible).nudge();
0232 
0233     connect(&m_d->maskingModel, &MaskingBrushModel::warningLabelTextChanged,
0234             m_d->brushSizeWarningLabel, &QLabel::setText);
0235     m_d->maskingModel.LAGER_QT(warningLabelText).nudge();
0236 
0237     m_d->maskingData.watch(std::bind(&KisMaskingBrushOption::emitSettingChanged, this));
0238     m_d->commonBrushSizeData.watch(std::bind(&KisMaskingBrushOption::emitSettingChanged, this));
0239 }
0240 
0241 KisMaskingBrushOption::~KisMaskingBrushOption()
0242 {
0243 
0244 }
0245 
0246 void KisMaskingBrushOption::writeOptionSetting(KisPropertiesConfigurationSP setting) const
0247 {
0248     using namespace KisBrushModel;
0249 
0250     if (m_d->maskingData->useMasterSize &&
0251         !m_d->maskingModel.m_preserveMode.get()) {
0252 
0253         MaskingBrushData tempData = m_d->maskingModel.bakedOptionData();
0254         tempData.masterSizeCoeff = m_d->commonBrushSizeData.get() / m_d->masterBrushSize.get();
0255         tempData.write(setting.data());
0256     } else {
0257         m_d->maskingModel.bakedOptionData().write(setting.data());
0258     }
0259 }
0260 
0261 void KisMaskingBrushOption::readOptionSetting(const KisPropertiesConfigurationSP setting)
0262 {
0263     MaskingBrushData data = MaskingBrushData::read(setting.data(), m_d->masterBrushSize.get(), resourcesInterface());
0264 
0265     m_d->commonBrushSizeData.set(effectiveSizeForBrush(data.brush.type,
0266                                                        data.brush.autoBrush,
0267                                                        data.brush.predefinedBrush,
0268                                                        data.brush.textBrush));
0269     m_d->maskingData.set(data);
0270     m_d->maskingModel.startPreserveMode();
0271 }
0272 
0273 void KisMaskingBrushOption::setImage(KisImageWSP image)
0274 {
0275     m_d->brushChooser->setImage(image);
0276 }
0277 
0278 void KisMaskingBrushOption::lodLimitations(KisPaintopLodLimitations *l) const
0279 {
0280     *l |= KisBrushModel::brushLodLimitations(m_d->maskingData->brush);
0281 }
0282 
0283 lager::reader<bool> KisMaskingBrushOption::maskingBrushEnabledReader() const
0284 {
0285     return m_d->maskingData[&MaskingBrushData::isEnabled];
0286 }
0287 
0288 void KisMaskingBrushOption::slotCompositeModeWidgetChanged(int index)
0289 {
0290     m_d->maskingModel.setcompositeOpId(m_d->compositeSelector->itemData(index).toString());
0291 }
0292 
0293 void KisMaskingBrushOption::slotCompositeModePropertyChanged(const QString &value)
0294 {
0295     const int index = m_d->compositeSelector->findData(QVariant::fromValue(value));
0296     KIS_SAFE_ASSERT_RECOVER_RETURN(index >= 0);
0297     m_d->compositeSelector->setCurrentIndex(index);
0298 }
0299 
0300 #include "KisMaskingBrushOption.moc"