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"