File indexing completed on 2025-12-07 04:19:08

0001 /*
0002     SPDX-FileCopyrightText: 2012 Dan Leinir Turthra Jensen <admin@leinir.dk>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 
0008 #include "CompositeOpModel.h"
0009 #include <kis_composite_ops_model.h>
0010 #include <KisViewManager.h>
0011 #include <kis_canvas_resource_provider.h>
0012 #include <kis_tool.h>
0013 #include <kis_canvas2.h>
0014 #include <input/kis_input_manager.h>
0015 #include <kis_node_manager.h>
0016 #include <kis_node.h>
0017 #include <kis_layer.h>
0018 #include <brushengine/kis_paintop_preset.h>
0019 #include <brushengine/kis_paintop_settings.h>
0020 #include <brushengine/kis_paintop_registry.h>
0021 #include <brushengine/kis_paintop_config_widget.h>
0022 #include <KoCompositeOpRegistry.h>
0023 #include <KoColorSpace.h>
0024 #include <KoToolManager.h>
0025 #include <KisGlobalResourcesInterface.h>
0026 
0027 class CompositeOpModel::Private
0028 {
0029 public:
0030     Private(CompositeOpModel* qq)
0031         : q(qq)
0032         , model(new KisCompositeOpListModel())
0033         , view(0)
0034         , eraserMode(0)
0035         , opacity(0)
0036         , opacityEnabled(false)
0037         , flow(0)
0038         , flowEnabled(false)
0039         , size(0)
0040         , sizeEnabled(false)
0041         , presetsEnabled(true)
0042     {};
0043 
0044     CompositeOpModel* q;
0045     KisCompositeOpListModel* model;
0046     KisViewManager* view;
0047     QString currentCompositeOpID;
0048     QString prevCompositeOpID;
0049     bool eraserMode;
0050     QMap<KisPaintOpPreset*, KisPaintOpConfigWidget*> settingsWidgets;
0051 
0052     qreal opacity;
0053     bool opacityEnabled;
0054     qreal flow;
0055     bool flowEnabled;
0056     qreal size;
0057     bool sizeEnabled;
0058     bool presetsEnabled;
0059     KisPaintOpPresetSP currentPreset;
0060 
0061     void updateCompositeOp(QString compositeOpID)
0062     {
0063         if (!view)
0064             return;
0065 
0066         KisNodeSP node = view->canvasResourceProvider()->currentNode();
0067 
0068         if (node && node->paintDevice())
0069         {
0070             if (!node->paintDevice()->colorSpace()->hasCompositeOp(compositeOpID))
0071                 compositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id();
0072 
0073             if (compositeOpID != currentCompositeOpID)
0074             {
0075                 q->setEraserMode(compositeOpID == COMPOSITE_ERASE);
0076                 currentPreset->settings()->setProperty("CompositeOp", compositeOpID);
0077                 //m_optionWidget->setConfiguration(m_activePreset->settings().data());
0078                 view->canvasResourceProvider()->setCurrentCompositeOp(compositeOpID);
0079                 prevCompositeOpID = currentCompositeOpID;
0080                 currentCompositeOpID = compositeOpID;
0081             }
0082         }
0083         emit q->currentCompositeOpIDChanged();
0084     }
0085 
0086     void ofsChanged()
0087     {
0088         if (presetsEnabled && !currentPreset.isNull() && !currentPreset->settings().isNull())
0089         {
0090             // IMPORTANT: set the PaintOp size before setting the other properties
0091             //            it wont work the other way
0092             //qreal sizeDiff = size - currentPreset->settings()->paintOpSize();
0093             //currentPreset->settings()->changePaintOpSize(sizeDiff, 0);
0094 
0095             if (currentPreset->settings()->hasProperty("OpacityValue"))
0096                 currentPreset->settings()->setProperty("OpacityValue", opacity);
0097 
0098             if (currentPreset->settings()->hasProperty("FlowValue"))
0099                 currentPreset->settings()->setProperty("FlowValue", flow);
0100 
0101             //m_optionWidget->setConfiguration(d->currentPreset->settings().data());
0102         }
0103         if (view)
0104         {
0105             view->canvasResourceProvider()->setOpacity(opacity);
0106         }
0107     }
0108 };
0109 
0110 CompositeOpModel::CompositeOpModel(QObject* parent)
0111     : QAbstractListModel(parent)
0112     , d(new Private(this))
0113 {
0114     connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*)),
0115             this, SLOT(slotToolChanged(KoCanvasController*)));
0116 
0117 }
0118 
0119 CompositeOpModel::~CompositeOpModel()
0120 {
0121     delete d;
0122 }
0123 
0124 QHash<int, QByteArray> CompositeOpModel::roleNames() const
0125 {
0126     QHash<int,QByteArray> roles;
0127     roles[TextRole] = "text";
0128     roles[IsCategoryRole] = "isCategory";
0129     return roles;
0130 }
0131 
0132 QVariant CompositeOpModel::data(const QModelIndex& index, int role) const
0133 {
0134     QVariant data;
0135     if (index.isValid())
0136     {
0137         QModelIndex otherIndex = d->model->index(index.row(), index.column(), QModelIndex());
0138         switch(role)
0139         {
0140             case TextRole:
0141                 data = d->model->data(otherIndex, Qt::DisplayRole);
0142                 break;
0143             case IsCategoryRole:
0144                 data = d->model->data(otherIndex, __CategorizedListModelBase::IsHeaderRole);
0145                 break;
0146             default:
0147                 break;
0148         }
0149     }
0150     return data;
0151 }
0152 
0153 int CompositeOpModel::rowCount(const QModelIndex& parent) const
0154 {
0155     if (parent.isValid())
0156         return 0;
0157     return d->model->rowCount(QModelIndex());
0158 }
0159 
0160 void CompositeOpModel::activateItem(int index)
0161 {
0162     if (index > -1 && index < d->model->rowCount(QModelIndex()))
0163     {
0164         KoID compositeOp;
0165         if (d->model->entryAt(compositeOp, d->model->index(index)))
0166             d->updateCompositeOp(compositeOp.id());
0167     }
0168 }
0169 
0170 QObject* CompositeOpModel::view() const
0171 {
0172     return d->view;
0173 }
0174 
0175 void CompositeOpModel::setView(QObject* newView)
0176 {
0177     if (d->view)
0178     {
0179         d->view->canvasBase()->disconnect(this);
0180         d->view->canvasBase()->globalInputManager()->disconnect(this);
0181         d->view->nodeManager()->disconnect(this);
0182     }
0183     d->view = qobject_cast<KisViewManager*>( newView );
0184     if (d->view)
0185     {
0186         if (d->view->canvasBase() && d->view->canvasBase()->resourceManager()) {
0187             connect(d->view->canvasBase()->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
0188                     this, SLOT(resourceChanged(int,QVariant)));
0189         }
0190 //        if (d->view->nodeManager()) {
0191 //            connect(d->view->nodeManager(), SIGNAL(sigLayerActivated(KisLayerSP)),
0192 //                    this, SLOT(currentNodeChanged(KisLayerSP)));
0193 //        }
0194         slotToolChanged(0);
0195     }
0196     emit viewChanged();
0197 }
0198 
0199 bool CompositeOpModel::eraserMode() const
0200 {
0201     return d->eraserMode;
0202 }
0203 
0204 void CompositeOpModel::setEraserMode(bool newEraserMode)
0205 {
0206     if (d->eraserMode != newEraserMode)
0207     {
0208         d->eraserMode = newEraserMode;
0209         if (d->eraserMode)
0210             d->updateCompositeOp(COMPOSITE_ERASE);
0211         else
0212             d->updateCompositeOp(d->prevCompositeOpID);
0213         emit eraserModeChanged();
0214     }
0215 }
0216 
0217 qreal CompositeOpModel::flow() const
0218 {
0219     return d->flow;
0220 }
0221 
0222 void CompositeOpModel::setFlow(qreal newFlow)
0223 {
0224     if (d->flow != newFlow)
0225     {
0226         d->flow = newFlow;
0227         d->ofsChanged();
0228         emit flowChanged();
0229     }
0230 }
0231 
0232 bool CompositeOpModel::flowEnabled() const
0233 {
0234     return d->flowEnabled;
0235 }
0236 
0237 void CompositeOpModel::setFlowEnabled(bool newFlowEnabled)
0238 {
0239     d->flowEnabled = newFlowEnabled;
0240     emit flowEnabledChanged();
0241 }
0242 
0243 qreal CompositeOpModel::opacity() const
0244 {
0245     return d->opacity;
0246 }
0247 
0248 void CompositeOpModel::setOpacity(qreal newOpacity)
0249 {
0250     if (d->opacity != newOpacity)
0251     {
0252         d->opacity = newOpacity;
0253         d->ofsChanged();
0254         emit opacityChanged();
0255     }
0256 }
0257 
0258 bool CompositeOpModel::opacityEnabled() const
0259 {
0260     return d->opacityEnabled;
0261 }
0262 
0263 void CompositeOpModel::setOpacityEnabled(bool newOpacityEnabled)
0264 {
0265     d->opacityEnabled = newOpacityEnabled;
0266     emit opacityEnabledChanged();
0267 }
0268 
0269 qreal CompositeOpModel::size() const
0270 {
0271     return d->size;
0272 }
0273 
0274 void CompositeOpModel::setSize(qreal newSize)
0275 {
0276     if (d->size != newSize)
0277     {
0278         d->size = newSize;
0279         d->ofsChanged();
0280         emit sizeChanged();
0281     }
0282 }
0283 
0284 bool CompositeOpModel::sizeEnabled() const
0285 {
0286     return d->sizeEnabled;
0287 }
0288 
0289 void CompositeOpModel::setSizeEnabled(bool newSizeEnabled)
0290 {
0291     d->sizeEnabled = newSizeEnabled;
0292     emit sizeEnabledChanged();
0293 }
0294 
0295 void CompositeOpModel::changePaintopValue(QString propertyName, QVariant value)
0296 {
0297     if (propertyName == "size" && value.toReal() != d->size)
0298         setSize(value.toReal());
0299     else if (propertyName == "opacity" && value.toReal() != d->opacity)
0300         setOpacity(value.toReal());
0301     else if (propertyName == "flow" && value.toReal() != d->flow)
0302         setFlow(value.toReal());
0303 }
0304 
0305 bool CompositeOpModel::mirrorHorizontally() const
0306 {
0307     if (d->view)
0308         return d->view->canvasResourceProvider()->mirrorHorizontal();
0309     return false;
0310 }
0311 
0312 void CompositeOpModel::setMirrorHorizontally(bool newMirrorHorizontally)
0313 {
0314     if (d->view && d->view->canvasResourceProvider()->mirrorHorizontal() != newMirrorHorizontally)
0315     {
0316         d->view->canvasResourceProvider()->setMirrorHorizontal(newMirrorHorizontally);
0317         emit mirrorHorizontallyChanged();
0318     }
0319 }
0320 
0321 bool CompositeOpModel::mirrorVertically() const
0322 {
0323     if (d->view)
0324         return d->view->canvasResourceProvider()->mirrorVertical();
0325     return false;
0326 }
0327 
0328 void CompositeOpModel::setMirrorVertically(bool newMirrorVertically)
0329 {
0330     if (d->view && d->view->canvasResourceProvider()->mirrorVertical() != newMirrorVertically)
0331     {
0332         d->view->canvasResourceProvider()->setMirrorVertical(newMirrorVertically);
0333         emit mirrorVerticallyChanged();
0334     }
0335 }
0336 
0337 void CompositeOpModel::slotToolChanged(KoCanvasController* canvas)
0338 {
0339     Q_UNUSED(canvas);
0340 
0341     if (!d->view) return;
0342     if (!d->view->canvasBase()) return;
0343 
0344     QString  id   = KoToolManager::instance()->activeToolId();
0345     KisTool* tool = dynamic_cast<KisTool*>(KoToolManager::instance()->toolById(d->view->canvasBase(), id));
0346 
0347     if (tool) {
0348         int flags = tool->flags();
0349 
0350         if (flags & KisTool::FLAG_USES_CUSTOM_COMPOSITEOP) {
0351             //setWidgetState(ENABLE_COMPOSITEOP|ENABLE_OPACITY);
0352             d->opacityEnabled = true;
0353         }
0354         else {
0355             //setWidgetState(DISABLE_COMPOSITEOP|DISABLE_OPACITY);
0356             d->opacityEnabled = false;
0357         }
0358 
0359         if (flags & KisTool::FLAG_USES_CUSTOM_PRESET) {
0360             d->flowEnabled = true;
0361             d->sizeEnabled = true;
0362             d->presetsEnabled = true;
0363         }
0364         else {
0365             d->flowEnabled = false;
0366             d->sizeEnabled = false;
0367             d->presetsEnabled = false;
0368         }
0369     }
0370     else {
0371         d->opacityEnabled = false;
0372         d->flowEnabled = false;
0373         d->sizeEnabled = false;
0374     }
0375     emit opacityEnabledChanged();
0376     emit flowEnabledChanged();
0377     emit sizeEnabledChanged();
0378 }
0379 
0380 void CompositeOpModel::resourceChanged(int key, const QVariant& /*v*/)
0381 {
0382     if (d->view && d->view->canvasBase() && d->view->canvasBase()->resourceManager() && d->view->canvasResourceProvider()) {
0383 
0384         if (key == KoCanvasResource::MirrorHorizontal) {
0385             emit mirrorHorizontallyChanged();
0386             return;
0387         }
0388         else if(key == KoCanvasResource::MirrorVertical) {
0389             emit mirrorVerticallyChanged();
0390             return;
0391         }
0392 
0393         KisPaintOpPresetSP preset = d->view->canvasBase()->resourceManager()->resource(KoCanvasResource::CurrentPaintOpPreset).value<KisPaintOpPresetSP>();
0394 
0395         if (preset && d->currentPreset.data() != preset.data()) {
0396             d->currentPreset = preset;
0397             if (!d->settingsWidgets.contains(preset.data())) {
0398                 d->settingsWidgets[preset.data()] = KisPaintOpRegistry::instance()->get(preset->paintOp().id())->createConfigWidget(0,
0399                                                                                                                                     KisGlobalResourcesInterface::instance(),
0400                                                                                                                                     d->view->canvasResourceProvider()->resourceManager()->canvasResourcesInterface());
0401                 d->settingsWidgets[preset.data()]->setImage(d->view->image());
0402                 d->settingsWidgets[preset.data()]->setConfiguration(preset->settings());
0403             }
0404 
0405             d->size = preset->settings()->paintOpSize();
0406             emit sizeChanged();
0407 
0408             if (preset->settings()->hasProperty("OpacityValue"))  {
0409                 d->opacityEnabled = true;
0410                 d->opacity = preset->settings()->getProperty("OpacityValue").toReal();
0411             }
0412             else {
0413                 d->opacityEnabled = false;
0414                 d->opacity = 1;
0415             }
0416 
0417             d->view->canvasResourceProvider()->setOpacity(d->opacity);
0418             emit opacityChanged();
0419             emit opacityEnabledChanged();
0420 
0421             if (preset->settings()->hasProperty("FlowValue")) {
0422                 d->flowEnabled = true;
0423                 d->flow = preset->settings()->getProperty("FlowValue").toReal();
0424             }
0425             else {
0426                 d->flowEnabled = false;
0427                 d->flow = 1;
0428             }
0429             emit flowChanged();
0430             emit flowEnabledChanged();
0431 
0432             QString compositeOp = preset->settings()->getString("CompositeOp");
0433 
0434             // This is a little odd, but the logic here is that the opposite of an eraser is a normal composite op (so we just select over, aka normal)
0435             // This means that you can switch your eraser over to being a painting tool by turning off the eraser again.
0436             if (compositeOp == COMPOSITE_ERASE) {
0437                 d->currentCompositeOpID = COMPOSITE_OVER;
0438                 d->eraserMode = true;
0439             }
0440             else {
0441                 d->eraserMode = false;
0442             }
0443             emit eraserModeChanged();
0444             d->updateCompositeOp(compositeOp);
0445         }
0446     }
0447 }
0448 
0449 void CompositeOpModel::currentNodeChanged(KisLayerSP newNode)
0450 {
0451     Q_UNUSED(newNode);
0452     if (d->eraserMode) {
0453         d->eraserMode = false;
0454         d->updateCompositeOp(d->prevCompositeOpID);
0455         emit eraserModeChanged();
0456     }
0457 }
0458 
0459 int CompositeOpModel::indexOf(QString compositeOpId)
0460 {
0461     return d->model->indexOf(KoID(compositeOpId)).row();
0462 }
0463 
0464 QString CompositeOpModel::currentCompositeOpID() const
0465 {
0466     return d->currentCompositeOpID;
0467 }
0468