File indexing completed on 2024-12-22 04:14:51

0001 /*
0002   SPDX-FileCopyrightText: 2006 Gábor Lehel <illissius@gmail.com>
0003   SPDX-FileCopyrightText: 2008 Cyrille Berger <cberger@cberger.net>
0004   SPDX-FileCopyrightText: 2011 José Luis Vergara <pentalis@gmail.com>
0005 
0006   SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 #include "kis_config.h"
0009 #include "NodeDelegate.h"
0010 #include "kis_node_model.h"
0011 #include "NodeToolTip.h"
0012 #include "NodeView.h"
0013 #include "KisPart.h"
0014 #include "input/kis_input_manager.h"
0015 
0016 #include <QtDebug>
0017 #include <QApplication>
0018 #include <QKeyEvent>
0019 #include <QLineEdit>
0020 #include <QModelIndex>
0021 #include <QMouseEvent>
0022 #include <QPainter>
0023 #include <QPointer>
0024 #include <QStyle>
0025 #include <QStyleOptionViewItem>
0026 #include <QBitmap>
0027 #include <QToolTip>
0028 
0029 #include <klocalizedstring.h>
0030 #include "kis_node_view_color_scheme.h"
0031 #include "kis_icon_utils.h"
0032 #include "kis_layer_properties_icons.h"
0033 #include "krita_utils.h"
0034 #include "kis_config_notifier.h"
0035 #include <kis_painting_tweaks.h>
0036 
0037 typedef KisBaseNode::Property* OptionalProperty;
0038 
0039 #include <kis_base_node.h>
0040 
0041 class NodeDelegate::Private
0042 {
0043 public:
0044     Private(NodeDelegate *_q) : q(_q), view(0), edit(0) { }
0045 
0046     NodeDelegate *q;
0047 
0048     NodeView *view;
0049     QPointer<QWidget> edit;
0050     NodeToolTip tip;
0051 
0052     QImage checkers;
0053 
0054     QRect thumbnailGeometry;
0055     int thumbnailSize {-1};
0056     int rowHeight {-1};
0057 
0058     QList<QModelIndex> shiftClickedIndexes;
0059 
0060     enum StasisOperation {
0061         Record,
0062         Review,
0063         Restore
0064     };
0065 
0066     QList<OptionalProperty> rightmostProperties(const KisBaseNode::PropertyList &props) const;
0067     int numProperties(const QModelIndex &index) const;
0068     OptionalProperty findProperty(KisBaseNode::PropertyList &props, const OptionalProperty &refProp) const;
0069     OptionalProperty findVisibilityProperty(KisBaseNode::PropertyList &props) const;
0070 
0071     void toggleProperty(KisBaseNode::PropertyList &props, const OptionalProperty clickedProperty, const Qt::KeyboardModifiers modifier, const QModelIndex &index);
0072     void togglePropertyRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty, const QList<QModelIndex> &items, StasisOperation record, bool mode);
0073 
0074     bool stasisIsDirty(const QModelIndex &root, const OptionalProperty &clickedProperty, bool on = false, bool off = false);
0075     void resetPropertyStateRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty);
0076     void restorePropertyInStasisRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty);
0077 
0078     bool checkImmediateStasis(const QModelIndex &root, const OptionalProperty &clickedProperty);
0079 
0080     void getParentsIndex(QList<QModelIndex> &items, const QModelIndex &index);
0081     void getChildrenIndex(QList<QModelIndex> &items, const QModelIndex &index);
0082     void getSiblingsIndex(QList<QModelIndex> &items, const QModelIndex &index);
0083     boost::optional<KisBaseNode::Property>
0084     propForMousePos(const QModelIndex &index, const QPoint &mousePos, const QStyleOptionViewItem &option);
0085 };
0086 
0087 NodeDelegate::NodeDelegate(NodeView *view, QObject *parent)
0088     : QAbstractItemDelegate(parent)
0089     , d(new Private(this))
0090 {
0091     d->view = view;
0092 
0093     QApplication::instance()->installEventFilter(this);
0094     connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
0095     connect(this, SIGNAL(resetVisibilityStasis()), SLOT(slotResetState()));
0096     slotConfigChanged();
0097 }
0098 
0099 NodeDelegate::~NodeDelegate()
0100 {
0101     delete d;
0102 }
0103 
0104 QSize NodeDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
0105 {
0106     KisNodeViewColorScheme scm;
0107     if (index.column() == NodeView::VISIBILITY_COL) {
0108         return QSize(scm.visibilityColumnWidth(), d->rowHeight);
0109     }
0110     return QSize(option.rect.width(), d->rowHeight);
0111 }
0112 
0113 void NodeDelegate::paint(QPainter *p, const QStyleOptionViewItem &o, const QModelIndex &index) const
0114 {
0115     p->save();
0116 
0117     {
0118         QStyleOptionViewItem option = getOptions(o, index);
0119         QStyle *style = option.widget ? option.widget->style() : QApplication::style();
0120         style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, p, option.widget);
0121 
0122         bool shouldGrayOut = index.data(KisNodeModel::ShouldGrayOutRole).toBool();
0123         if (shouldGrayOut) {
0124             option.state &= ~QStyle::State_Enabled;
0125         }
0126 
0127         drawFrame(p, option, index);
0128 
0129         if (index.column() == NodeView::SELECTED_COL) {
0130             drawSelectedButton(p, o, index, style);
0131         } else if (index.column() == NodeView::VISIBILITY_COL) {
0132             drawVisibilityIcon(p, option, index); // TODO hide when dragging
0133         } else {
0134             p->setFont(option.font);
0135             drawColorLabel(p, option, index);
0136             drawThumbnail(p, option, index);
0137             drawText(p, option, index); // BUG: Creating group moves things around (RTL-layout alignment)
0138             drawIcons(p, option, index);
0139             drawDecoration(p, option, index);
0140             drawExpandButton(p, option, index);
0141             drawAnimatedDecoration(p, option, index);
0142 
0143             drawProgressBar(p, option, index);
0144         }
0145     }
0146     p->restore();
0147 }
0148 
0149 void NodeDelegate::drawBranches(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
0150 {
0151     p->save();
0152     drawFrame(p, option, index);
0153     p->restore();
0154 
0155     QModelIndex tmp = index.parent();
0156 
0157     // there is no indentation if we have no parent group, so don't draw a branch
0158     if (!tmp.isValid()) return;
0159 
0160     const KisNodeViewColorScheme &scm = *KisNodeViewColorScheme::instance();
0161 
0162     int rtlNum = (option.direction == Qt::RightToLeft) ? 1 : -1;
0163     QPoint nodeCorner = (option.direction == Qt::RightToLeft) ? option.rect.topLeft() : option.rect.topRight();
0164     int branchSpacing = rtlNum * d->view->indentation();
0165 
0166     QPoint base = nodeCorner + 0.5 * QPoint(branchSpacing, option.rect.height()) + QPoint(0, scm.iconSize()/4);
0167 
0168     QColor color = scm.gridColor(option, d->view);
0169     QColor bgColor = option.state & QStyle::State_Selected ?
0170         qApp->palette().color(QPalette::Base) :
0171         qApp->palette().color(QPalette::Text);
0172     color = KisPaintingTweaks::blendColors(color, bgColor, 0.9);
0173 
0174     // TODO: if we are a mask type, use dotted lines for the branch style
0175     // p->setPen(QPen(p->pen().color(), 2, Qt::DashLine, Qt::RoundCap, Qt::RoundJoin));
0176     p->setPen(QPen(color, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
0177 
0178     QPoint p2 = base - QPoint(rtlNum*(qMin(d->view->indentation(), scm.iconSize())/2), 0);
0179     QPoint p3 = base - QPoint(0, scm.iconSize()/2);
0180     p->drawLine(base, p2);
0181     p->drawLine(base, p3);
0182 
0183     // draw parent lines (keep drawing until x position is less than 0
0184     QPoint parentBase1 = base + QPoint(branchSpacing, 0);
0185     QPoint parentBase2 = p3 + QPoint(branchSpacing, 0);
0186 
0187     // indent lines needs to be very subtle to avoid making the docker busy looking
0188     color = KisPaintingTweaks::blendColors(color, bgColor, 0.9); // makes it a little lighter than L lines
0189     p->setPen(QPen(color, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
0190 
0191 
0192     int levelRowIndex = tmp.row();
0193     tmp = tmp.parent(); // Ignore the first group as it was already painted
0194 
0195     while (tmp.isValid()) {
0196         bool moreSiblings = index.model()->rowCount(tmp) > (levelRowIndex + 1);
0197         if (moreSiblings) {
0198             p->drawLine(parentBase1, parentBase2);
0199         }
0200 
0201         parentBase1 += QPoint(branchSpacing, 0);
0202         parentBase2 += QPoint(branchSpacing, 0);
0203 
0204         levelRowIndex = tmp.row();
0205         tmp = tmp.parent();
0206     }
0207 }
0208 
0209 void NodeDelegate::drawColorLabel(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
0210 {
0211     KisNodeViewColorScheme scm;
0212     const int label = index.data(KisNodeModel::ColorLabelIndexRole).toInt();
0213     QColor color = scm.colorFromLabelIndex(label);
0214     if (color.alpha() <= 0) return;
0215 
0216     QColor bgColor = qApp->palette().color(QPalette::Base);
0217     color = KisPaintingTweaks::blendColors(color, bgColor, 0.3);
0218 
0219     QRect optionRect = (option.state & QStyle::State_Selected) ? iconsRect(option, index) : option.rect;
0220 
0221     p->fillRect(optionRect, color);
0222 }
0223 
0224 void NodeDelegate::drawFrame(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
0225 {
0226     KisNodeViewColorScheme scm;
0227 
0228     QPen oldPen = p->pen();
0229     p->setPen(scm.gridColor(option, d->view));
0230 
0231     const QRect visibilityRect = visibilityClickRect(option, index);
0232     const QRect thumbnailRect  = thumbnailClickRect(option, index);
0233     const QRect iconsRectR     = iconsRect(option, index);
0234 
0235     const float bottomY = thumbnailRect.bottomLeft().y();
0236 
0237     QPoint bottomLeftPoint;
0238     QPoint bottomRightPoint;
0239     if (option.direction == Qt::RightToLeft) {
0240         bottomLeftPoint = iconsRectR.bottomLeft();
0241         bottomRightPoint = visibilityRect.bottomRight();
0242     } else {
0243         bottomLeftPoint = visibilityRect.bottomLeft();
0244         bottomRightPoint = iconsRectR.bottomRight();
0245     }
0246 
0247     // bottom running horizontal line
0248     p->drawLine(bottomLeftPoint.x(), bottomY,
0249                 bottomRightPoint.x(), bottomY);
0250 
0251     // icons' lines are drawn by drawIcons
0252 
0253     //// For debugging purposes only
0254     p->setPen(Qt::blue);
0255     //KritaUtils::renderExactRect(p, iconsRectR);
0256     //KritaUtils::renderExactRect(p, textRect(option, index));
0257     //KritaUtils::renderExactRect(p, visibilityRect);
0258 
0259     p->setPen(oldPen);
0260 }
0261 
0262 QRect NodeDelegate::thumbnailClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
0263 {
0264     Q_UNUSED(index);
0265 
0266     QRect rc = d->thumbnailGeometry;
0267 
0268     // Move to current index
0269     rc.moveTop(option.rect.topLeft().y());
0270     // Move to correct location.
0271     if (option.direction == Qt::RightToLeft) {
0272         rc.moveRight(option.rect.right());
0273     } else {
0274         rc.moveLeft(option.rect.left());
0275     }
0276 
0277     return rc;
0278 }
0279 
0280 void NodeDelegate::drawThumbnail(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
0281 {
0282     KisNodeViewColorScheme scm;
0283 
0284     const qreal devicePixelRatio = p->device()->devicePixelRatioF();
0285     const int thumbSizeHighRes = d->thumbnailSize*devicePixelRatio;
0286 
0287     const qreal oldOpacity = p->opacity(); // remember previous opacity
0288 
0289     QImage img = index.data(int(KisNodeModel::BeginThumbnailRole) + thumbSizeHighRes).value<QImage>();
0290     img.setDevicePixelRatio(devicePixelRatio);
0291     if (!(option.state & QStyle::State_Enabled)) {
0292         p->setOpacity(0.35);
0293     }
0294 
0295     QRect fitRect = thumbnailClickRect(option, index);
0296     // Shrink to icon rect
0297     fitRect = kisGrowRect(fitRect, -(scm.thumbnailMargin()+scm.border()));
0298 
0299     QPoint offset;
0300     offset.setX((fitRect.width() - img.width()/devicePixelRatio) / 2);
0301     offset.setY((fitRect.height() - img.height()/devicePixelRatio) / 2);
0302     offset += fitRect.topLeft();
0303 
0304     QBrush brush(d->checkers);
0305     p->setBrushOrigin(offset);
0306     QRect imageRectLowRes = QRect(img.rect().topLeft(), img.rect().size()/devicePixelRatio);
0307     p->fillRect(imageRectLowRes.translated(offset), brush);
0308 
0309     p->drawImage(offset, img);
0310     p->setOpacity(oldOpacity); // restore old opacity
0311 
0312     QRect borderRect = kisGrowRect(imageRectLowRes, 1).translated(offset);
0313     KritaUtils::renderExactRect(p, borderRect, scm.gridColor(option, d->view));
0314 }
0315 
0316 QRect NodeDelegate::iconsRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
0317 {
0318     KisNodeViewColorScheme scm;
0319 
0320     int propCount = d->numProperties(index);
0321 
0322     const int iconsWidth =
0323         propCount * (scm.iconSize() + 2 * scm.iconMargin()) +
0324         (propCount + 1) * scm.border();
0325 
0326     QRect fitRect = QRect(0, 0, iconsWidth, d->rowHeight - scm.border());
0327     // Move to current index
0328     fitRect.moveTop(option.rect.topLeft().y());
0329     // Move to correct location.
0330     if (option.direction == Qt::RightToLeft) {
0331         fitRect.moveLeft(option.rect.topLeft().x());
0332     } else {
0333         fitRect.moveRight(option.rect.topRight().x());
0334     }
0335 
0336     return fitRect;
0337 }
0338 
0339 QRect NodeDelegate::textRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
0340 {
0341     KisNodeViewColorScheme scm;
0342 
0343     static QFont f;
0344     static int minbearing = 1337 + 666; //can be 0 or negative, 2003 is less likely
0345     if (minbearing == 2003 || f != option.font) {
0346         f = option.font; //getting your bearings can be expensive, so we cache them
0347         minbearing = option.fontMetrics.minLeftBearing() + option.fontMetrics.minRightBearing();
0348     }
0349 
0350     const QRect decoRect = decorationClickRect(option, index);
0351     const QRect iconRect = iconsRect(option, index);
0352 
0353     QRect rc = QRect((option.direction == Qt::RightToLeft) ? iconRect.topRight() : decoRect.topRight(),
0354                      (option.direction == Qt::RightToLeft) ? decoRect.bottomLeft() : iconRect.bottomLeft());
0355     rc.adjust(-(scm.border()+(minbearing/2)), 0,
0356                (scm.border()+(minbearing/2)), 0);
0357 
0358     return rc;
0359 }
0360 
0361 void NodeDelegate::drawText(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
0362 {
0363     KisNodeViewColorScheme scm;
0364     const QRect rc = textRect(option, index).adjusted(scm.textMargin(), 0,
0365                                                       -scm.textMargin(), 0);
0366 
0367     QPen oldPen = p->pen();
0368     const qreal oldOpacity = p->opacity(); // remember previous opacity
0369 
0370     p->setPen(option.palette.color(QPalette::Active,QPalette::Text ));
0371 
0372     if (!(option.state & QStyle::State_Enabled)) {
0373         p->setOpacity(0.55);
0374     }
0375 
0376     const QString text = index.data(Qt::DisplayRole).toString();
0377     const QString elided = p->fontMetrics().elidedText(text, Qt::ElideRight, rc.width());
0378     KisConfig cfg(true);
0379     if (cfg.layerInfoTextStyle() == KisConfig::LayerInfoTextStyle::INFOTEXT_NONE) {
0380         p->drawText(rc, Qt::AlignLeft | Qt::AlignVCenter, elided);
0381     }
0382     else {
0383         const QString infoText = index.data(KisNodeModel::InfoTextRole).toString();
0384         if (infoText.isEmpty()) {
0385             p->drawText(rc, Qt::AlignLeft | Qt::AlignVCenter, elided);
0386         } else {
0387             bool useOneLine = cfg.useInlineLayerInfoText();
0388             if (!useOneLine) {
0389                 // check whether there is enough space for two lines
0390                 const int textHeight = p->fontMetrics().height();
0391                 useOneLine = rc.height() < textHeight*2;
0392             }
0393 
0394             const int rectCenter = rc.height()/2;
0395             const int nameWidth = p->fontMetrics().horizontalAdvance(elided);
0396             // draw the layer name
0397             if (!useOneLine) {
0398                 // enforce Qt::TextSingleLine because we are adding a line below it
0399                 p->drawText(rc.adjusted(0, 0, 0, -rectCenter), Qt::AlignLeft | Qt::AlignBottom | Qt::TextSingleLine, elided);
0400             }
0401             else {
0402                 p->drawText(rc.adjusted(0, 0, 0, 0), Qt::AlignLeft | Qt::AlignVCenter, elided);
0403             }
0404             // draw the info-text
0405             p->save();
0406             QFont layerInfoTextFont = p->font();
0407             layerInfoTextFont.setBold(false);
0408             p->setFont(layerInfoTextFont);
0409             if (option.state & QStyle::State_Enabled) {
0410                 p->setOpacity(qreal(cfg.layerInfoTextOpacity())/100);
0411             }
0412             if (!useOneLine) {
0413                 const QString infoTextElided = p->fontMetrics().elidedText(infoText, Qt::ElideRight, rc.width());
0414                 p->drawText(rc.adjusted(0, rectCenter, 0, 0), Qt::AlignLeft | Qt::AlignTop, infoTextElided);
0415             }
0416             else {
0417                 const QString infoTextElided = p->fontMetrics().elidedText(" "+infoText, Qt::ElideRight, rc.width()-nameWidth);
0418                 p->drawText(rc.adjusted(nameWidth, 0, 0, 0), Qt::AlignLeft | Qt::AlignVCenter, infoTextElided);
0419             }
0420             p->restore();
0421         }
0422     }
0423 
0424     p->setPen(oldPen); // restore pen settings
0425     p->setOpacity(oldOpacity);
0426 }
0427 
0428 QList<OptionalProperty> NodeDelegate::Private::rightmostProperties(const KisBaseNode::PropertyList &props) const
0429 {
0430     QList<OptionalProperty> list;
0431     QList<OptionalProperty> prependList;
0432     list << OptionalProperty(0);
0433     list << OptionalProperty(0);
0434     list << OptionalProperty(0);
0435 
0436     KisBaseNode::PropertyList::const_iterator it = props.constBegin();
0437     KisBaseNode::PropertyList::const_iterator end = props.constEnd();
0438     for (; it != end; ++it) {
0439         if (!it->isMutable && it->id != KisLayerPropertiesIcons::layerError.id()) continue;
0440 
0441         if (it->id == KisLayerPropertiesIcons::visible.id()) {
0442             // noop...
0443         } else if (it->id == KisLayerPropertiesIcons::locked.id()) {
0444             list[0] = OptionalProperty(&(*it));
0445         } else if (it->id == KisLayerPropertiesIcons::inheritAlpha.id()) {
0446             list[1] = OptionalProperty(&(*it));
0447         } else if (it->id == KisLayerPropertiesIcons::alphaLocked.id()) {
0448             list[2] = OptionalProperty(&(*it));
0449         } else {
0450             prependList.prepend(OptionalProperty(&(*it)));
0451         }
0452     }
0453 
0454     {
0455         QMutableListIterator<OptionalProperty> i(prependList);
0456         i.toBack();
0457         while (i.hasPrevious()) {
0458             OptionalProperty val = i.previous();
0459 
0460             int emptyIndex = list.lastIndexOf(0);
0461             if (emptyIndex < 0) break;
0462 
0463             list[emptyIndex] = val;
0464             i.remove();
0465         }
0466     }
0467 
0468     return prependList + list;
0469 }
0470 
0471 int NodeDelegate::Private::numProperties(const QModelIndex &index) const
0472 {
0473     KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
0474     QList<OptionalProperty> realProps = rightmostProperties(props);
0475     return realProps.size();
0476 }
0477 
0478 OptionalProperty NodeDelegate::Private::findProperty(KisBaseNode::PropertyList &props, const OptionalProperty &refProp) const
0479 {
0480     KisBaseNode::PropertyList::iterator it = props.begin();
0481     KisBaseNode::PropertyList::iterator end = props.end();
0482     for (; it != end; ++it) {
0483         if (it->id == refProp->id) {
0484             return &(*it);
0485         }
0486     }
0487 
0488     return 0;
0489 }
0490 
0491 OptionalProperty NodeDelegate::Private::findVisibilityProperty(KisBaseNode::PropertyList &props) const
0492 {
0493     KisBaseNode::PropertyList::iterator it = props.begin();
0494     KisBaseNode::PropertyList::iterator end = props.end();
0495     for (; it != end; ++it) {
0496         if (it->id == KisLayerPropertiesIcons::visible.id()) {
0497             return &(*it);
0498         }
0499     }
0500 
0501     return 0;
0502 }
0503 
0504 void NodeDelegate::Private::toggleProperty(KisBaseNode::PropertyList &props, const OptionalProperty clickedProperty, const Qt::KeyboardModifiers modifier, const QModelIndex &index)
0505 {
0506     QModelIndex root(view->rootIndex());
0507 
0508     if ((modifier & Qt::ShiftModifier) == Qt::ShiftModifier && clickedProperty->canHaveStasis) {
0509         bool mode = true;
0510 
0511         OptionalProperty prop = findProperty(props, clickedProperty);
0512 
0513         // XXX: Change to use NodeProperty
0514         int position = shiftClickedIndexes.indexOf(index);
0515 
0516         StasisOperation record = (!prop->isInStasis)? StasisOperation::Record :
0517                       (position < 0) ? StasisOperation::Review : StasisOperation::Restore;
0518 
0519         shiftClickedIndexes.clear();
0520         shiftClickedIndexes.push_back(index);
0521 
0522         QList<QModelIndex> items;
0523         if (modifier == (Qt::ControlModifier | Qt::ShiftModifier)) {
0524             mode = false; // inverted mode
0525             items.insert(0, index); // important!
0526             getSiblingsIndex(items, index);
0527         } else {
0528             getParentsIndex(items, index);
0529             getChildrenIndex(items, index);
0530         }
0531         togglePropertyRecursive(root, clickedProperty, items, record, mode);
0532 
0533     } else {
0534         // If we have properties in stasis, we need to cancel stasis to avoid overriding
0535         // values in stasis.
0536         // IMPORTANT -- we also need to check the first row of nodes to determine
0537         // if a stasis is currently active in some cases.
0538         const bool hasPropInStasis = (shiftClickedIndexes.count() > 0 || checkImmediateStasis(root, clickedProperty));
0539         if (clickedProperty->canHaveStasis && hasPropInStasis) {
0540             shiftClickedIndexes.clear();
0541 
0542             restorePropertyInStasisRecursive(root, clickedProperty);
0543         } else {
0544             shiftClickedIndexes.clear();
0545 
0546             resetPropertyStateRecursive(root, clickedProperty);
0547 
0548             OptionalProperty prop = findProperty(props, clickedProperty);
0549             prop->state = !prop->state.toBool();
0550             prop->isInStasis = false;
0551             view->model()->setData(index, QVariant::fromValue(props), KisNodeModel::PropertiesRole);
0552         }
0553     }
0554 }
0555 
0556 void NodeDelegate::Private::togglePropertyRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty, const QList<QModelIndex> &items, StasisOperation record, bool mode)
0557 {
0558     int rowCount = view->model()->rowCount(root);
0559 
0560     for (int i = 0; i < rowCount; i++) {
0561         QModelIndex idx = view->model()->index(i, 0, root);
0562 
0563         // The entire property list has to be altered because model->setData cannot set individual properties
0564         KisBaseNode::PropertyList props = idx.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
0565         OptionalProperty prop = findProperty(props, clickedProperty);
0566 
0567         if (!prop) continue;
0568 
0569         if (record == StasisOperation::Record) {
0570              prop->stateInStasis = prop->state.toBool();
0571         }
0572         if (record == StasisOperation::Review || record ==  StasisOperation::Record) {
0573             prop->isInStasis = true;
0574             if(mode) { //include mode
0575                 prop->state = (items.contains(idx)) ? QVariant(true) : QVariant(false);
0576             } else { // exclude
0577                 prop->state = (!items.contains(idx))? prop->state :
0578                               (items.at(0) == idx)? QVariant(true) : QVariant(false);
0579             }
0580         } else { // restore
0581             prop->state = QVariant(prop->stateInStasis);
0582             prop->isInStasis = false;
0583         }
0584 
0585         view->model()->setData(idx, QVariant::fromValue(props), KisNodeModel::PropertiesRole);
0586 
0587         togglePropertyRecursive(idx,clickedProperty, items, record, mode);
0588     }
0589 }
0590 
0591 bool NodeDelegate::Private::stasisIsDirty(const QModelIndex &root, const OptionalProperty &clickedProperty, bool on, bool off)
0592 {
0593 
0594     int rowCount = view->model()->rowCount(root);
0595     bool result = false;
0596 
0597     for (int i = 0; i < rowCount; i++) {
0598         if (result) break; // return on first find
0599         QModelIndex idx = view->model()->index(i, 0, root);
0600         // The entire property list has to be altered because model->setData cannot set individual properties
0601         KisBaseNode::PropertyList props = idx.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
0602         OptionalProperty prop = findProperty(props, clickedProperty);
0603 
0604         if (!prop) continue;
0605         if (prop->isInStasis) {
0606             on = true;
0607         } else {
0608             off = true;
0609         }
0610         // stop if both states exist
0611         if (on && off) {
0612             return true;
0613         }
0614 
0615         result = stasisIsDirty(idx,clickedProperty, on, off);
0616     }
0617     return result;
0618 }
0619 
0620 void NodeDelegate::Private::resetPropertyStateRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty)
0621 {
0622     if (!clickedProperty->canHaveStasis) return;
0623     int rowCount = view->model()->rowCount(root);
0624 
0625     for (int i = 0; i < rowCount; i++) {
0626         QModelIndex idx = view->model()->index(i, 0, root);
0627         // The entire property list has to be altered because model->setData cannot set individual properties
0628         KisBaseNode::PropertyList props = idx.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
0629         OptionalProperty prop = findProperty(props, clickedProperty);
0630 
0631         if (!prop) continue;
0632         prop->isInStasis = false;
0633         view->model()->setData(idx, QVariant::fromValue(props), KisNodeModel::PropertiesRole);
0634 
0635         resetPropertyStateRecursive(idx,clickedProperty);
0636     }
0637 }
0638 
0639 void NodeDelegate::Private::restorePropertyInStasisRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty)
0640 {
0641     if (!clickedProperty->canHaveStasis) return;
0642     int rowCount = view->model()->rowCount(root);
0643 
0644     for (int i = 0; i < rowCount; i++) {
0645         QModelIndex idx = view->model()->index(i, 0, root);
0646         KisBaseNode::PropertyList props = idx.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
0647         OptionalProperty prop = findProperty(props, clickedProperty);
0648 
0649         if (prop->isInStasis) {
0650             prop->isInStasis = false;
0651             prop->state = QVariant(prop->stateInStasis);
0652         }
0653 
0654         view->model()->setData(idx, QVariant::fromValue(props), KisNodeModel::PropertiesRole);
0655 
0656         restorePropertyInStasisRecursive(idx, clickedProperty);
0657     }
0658 }
0659 
0660 bool NodeDelegate::Private::checkImmediateStasis(const QModelIndex &root, const OptionalProperty &clickedProperty)
0661 {
0662     if (!clickedProperty->canHaveStasis) return false;
0663 
0664     const int rowCount = view->model()->rowCount(root);
0665     for (int i = 0; i < rowCount; i++){
0666         QModelIndex idx = view->model()->index(i, 0, root);
0667         KisBaseNode::PropertyList props = idx.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
0668         OptionalProperty prop = findProperty(props, clickedProperty);
0669 
0670         if (prop->isInStasis) {
0671             return true;
0672         }
0673     }
0674 
0675     return false;
0676 }
0677 
0678 void NodeDelegate::Private::getParentsIndex(QList<QModelIndex> &items, const QModelIndex &index)
0679 {
0680     if (!index.isValid()) return;
0681     items.append(index);
0682     getParentsIndex(items, index.parent());
0683 }
0684 
0685 void NodeDelegate::Private::getChildrenIndex(QList<QModelIndex> &items, const QModelIndex &index)
0686 {
0687     qint32 childs = view->model()->rowCount(index);
0688     QModelIndex child;
0689     // STEP 1: Go.
0690     for (quint16 i = 0; i < childs; ++i) {
0691         child = view->model()->index(i, 0, index);
0692         items.append(child);
0693         getChildrenIndex(items, child);
0694     }
0695 }
0696 
0697 void NodeDelegate::Private::getSiblingsIndex(QList<QModelIndex> &items, const QModelIndex &index)
0698 {
0699     qint32 numberOfLeaves = view->model()->rowCount(index.parent());
0700     QModelIndex item;
0701     // STEP 1: Go.
0702     for (quint16 i = 0; i < numberOfLeaves; ++i) {
0703         item = view->model()->index(i, 0, index.parent());
0704         if (item != index) {
0705             items.append(item);
0706         }
0707     }
0708 }
0709 
0710 
0711 void NodeDelegate::drawIcons(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
0712 {
0713     KisNodeViewColorScheme scm;
0714     const QRect rc = iconsRect(option, index);
0715 
0716     QTransform oldTransform = p->transform();
0717     QPen oldPen = p->pen();
0718     p->setTransform(QTransform::fromTranslate(rc.x(), rc.y()));
0719     p->setPen(scm.gridColor(option, d->view));
0720 
0721     int x = 0;
0722     const int y = (d->rowHeight - scm.border() - scm.iconSize()) / 2;
0723     KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
0724     QList<OptionalProperty> realProps = d->rightmostProperties(props);
0725 
0726     if (option.direction == Qt::RightToLeft) {
0727         std::reverse(realProps.begin(), realProps.end());
0728     }
0729 
0730     Q_FOREACH (OptionalProperty prop, realProps) {
0731         x += scm.iconMargin();
0732         if (prop) {
0733             QIcon icon = prop->state.toBool() ? prop->onIcon : prop->offIcon;
0734             bool fullColor = prop->state.toBool() && option.state & QStyle::State_Enabled;
0735 
0736             const qreal oldOpacity = p->opacity(); // remember previous opacity
0737             if (fullColor) {
0738                  p->setOpacity(1.0);
0739             }
0740             else {
0741                 p->setOpacity(0.35);
0742             }
0743 
0744             p->drawPixmap(x, y, icon.pixmap(scm.iconSize(), QIcon::Normal));
0745             p->setOpacity(oldOpacity); // restore old opacity
0746         }
0747         x += scm.iconSize() + scm.iconMargin();
0748 
0749         x += scm.border();
0750     }
0751 
0752     p->setTransform(oldTransform);
0753     p->setPen(oldPen);
0754 }
0755 
0756 QRect NodeDelegate::visibilityClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
0757 {
0758     Q_UNUSED(index);
0759     KisNodeViewColorScheme scm;
0760 
0761     QRect rc = scm.relVisibilityRect();
0762     rc.setHeight(d->rowHeight);
0763 
0764     // Move to current index
0765     rc.moveCenter(option.rect.center());
0766     // Move to correct location.
0767     if (option.direction == Qt::RightToLeft) {
0768         rc.moveRight(option.rect.right());
0769     } else {
0770         rc.moveLeft(option.rect.left());
0771     }
0772 
0773     return rc;
0774 }
0775 
0776 QRect NodeDelegate::decorationClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
0777 {
0778     Q_UNUSED(index);
0779     KisNodeViewColorScheme scm;
0780 
0781     QRect rc = scm.relDecorationRect();
0782 
0783     // Move to current index
0784     rc.moveTop(option.rect.topLeft().y());
0785     rc.setHeight(d->rowHeight);
0786     // Move to correct location.
0787     if (option.direction == Qt::RightToLeft) {
0788         rc.moveRight(option.rect.right() - d->thumbnailGeometry.width());
0789     } else {
0790         rc.moveLeft(option.rect.left() + d->thumbnailGeometry.width());
0791     }
0792 
0793     return rc;
0794 }
0795 
0796 void NodeDelegate::drawVisibilityIcon(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
0797 {
0798     KisNodeViewColorScheme scm;
0799 
0800     KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
0801     OptionalProperty prop = d->findVisibilityProperty(props);
0802     if (!prop) return;
0803 
0804     QRect fitRect = visibilityClickRect(option, index);
0805     // Shrink to icon rect
0806     fitRect = kisGrowRect(fitRect, -(scm.visibilityMargin() + scm.border()));
0807 
0808     QIcon icon = prop->state.toBool() ? prop->onIcon : prop->offIcon;
0809 
0810     // if we are not showing the layer, make the icon slightly transparent like other inactive icons
0811     const qreal oldOpacity = p->opacity();
0812 
0813     if (!prop->state.toBool()) {
0814         p->setOpacity(0.35);
0815     }
0816 
0817     QPixmap pixmapIcon(icon.pixmap(scm.visibilitySize(), QIcon::Active));
0818     p->drawPixmap(fitRect.x(), fitRect.center().y() - scm.visibilitySize() / 2, pixmapIcon);
0819 
0820     if (prop->isInStasis) {
0821         QPainter::CompositionMode prevComposition = p->compositionMode();
0822         p->setCompositionMode(QPainter::CompositionMode_HardLight);
0823         pixmapIcon = icon.pixmap(scm.visibilitySize(), QIcon::Active);
0824         QBitmap mask = pixmapIcon.mask();
0825         pixmapIcon.fill(d->view->palette().color(QPalette::Highlight));
0826         pixmapIcon.setMask(mask);
0827         p->drawPixmap(fitRect.x(), fitRect.center().y() - scm.visibilitySize() / 2, pixmapIcon);
0828         p->setCompositionMode(prevComposition);
0829     }
0830 
0831     p->setOpacity(oldOpacity);
0832 
0833     //// For debugging purposes only
0834 // //     // p->save();
0835 // //     // p->setPen(Qt::blue);
0836 // //     // KritaUtils::renderExactRect(p, visibilityClickRect(option, index));
0837 // //     // p->restore();
0838 }
0839 
0840 void NodeDelegate::drawDecoration(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
0841 {
0842     KisNodeViewColorScheme scm;
0843     QIcon icon = index.data(Qt::DecorationRole).value<QIcon>();
0844 
0845     if (!icon.isNull()) {
0846         QPixmap pixmap = icon.pixmap(scm.decorationSize(),
0847                                      (option.state & QStyle::State_Enabled) ?
0848                                      QIcon::Normal : QIcon::Disabled);
0849 
0850         QRect rc = decorationClickRect(option, index);
0851 
0852         // Shrink to icon rect
0853         rc = kisGrowRect(rc, -(scm.decorationMargin()+scm.border()));
0854 
0855         const qreal oldOpacity = p->opacity(); // remember previous opacity
0856 
0857         if (!(option.state & QStyle::State_Enabled)) {
0858             p->setOpacity(0.35);
0859         }
0860 
0861         p->drawPixmap(rc.topLeft()-QPoint(0, 1), pixmap);
0862 
0863         p->setOpacity(oldOpacity); // restore old opacity
0864     }
0865 }
0866 
0867 void NodeDelegate::drawExpandButton(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
0868 {
0869     Q_UNUSED(index);
0870     KisNodeViewColorScheme scm;
0871 
0872     QRect rc = decorationClickRect(option, index);
0873 
0874     // Move to current index
0875 //     rc.moveTop(option.rect.topLeft().y());
0876     // Shrink to icon rect
0877     rc = kisGrowRect(rc, -(scm.decorationMargin()+scm.border()));
0878 
0879     if (!(option.state & QStyle::State_Children)) return;
0880 
0881     QString iconName = option.state & QStyle::State_Open ?
0882         "arrow-down" : ((option.direction == Qt::RightToLeft) ? "arrow-left" : "arrow-right");
0883     QIcon icon = KisIconUtils::loadIcon(iconName);
0884     QPixmap pixmap = icon.pixmap(rc.width(),
0885                                  (option.state & QStyle::State_Enabled) ?
0886                                  QIcon::Normal : QIcon::Disabled);
0887     p->drawPixmap(rc.bottomLeft()-QPoint(0, scm.decorationSize()-1), pixmap);
0888 }
0889 
0890 void NodeDelegate::drawAnimatedDecoration(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const {
0891 
0892     KisNodeViewColorScheme scm;
0893     QRect rc = decorationClickRect(option, index);
0894 
0895     QIcon animatedIndicatorIcon = KisIconUtils::loadIcon("layer-animated");
0896     const bool isAnimated = index.data(KisNodeModel::IsAnimatedRole).toBool();
0897 
0898     rc = kisGrowRect(rc, -(scm.decorationMargin()+scm.border()));
0899 
0900     if (!isAnimated) return;
0901 
0902     if ((option.state & QStyle::State_Children)) return;
0903 
0904     const qreal oldOpacity = p->opacity(); // remember previous opacity
0905 
0906     if (!(option.state & QStyle::State_Enabled)) {
0907         p->setOpacity(0.35);
0908     }
0909 
0910     int decorationSize = scm.decorationSize();
0911 
0912     QPixmap animPixmap = animatedIndicatorIcon.pixmap(decorationSize,
0913                                  (option.state & QStyle::State_Enabled) ?
0914                                  QIcon::Normal : QIcon::Disabled);
0915 
0916     p->drawPixmap(rc.bottomLeft()-QPoint(0, scm.decorationSize()-1), animPixmap);
0917 
0918     p->setOpacity(oldOpacity);
0919 }
0920 
0921 void NodeDelegate::drawSelectedButton(QPainter *p, const QStyleOptionViewItem &option,
0922                                       const QModelIndex &index, QStyle *style) const
0923 {
0924     QStyleOptionButton buttonOption;
0925 
0926     KisNodeViewColorScheme scm;
0927     QRect rect = option.rect;
0928 
0929     // adjust the icon to not touch the borders
0930     rect = kisGrowRect(rect, -(scm.thumbnailMargin() + scm.border()));
0931     // Make the rect a square so the check mark is not distorted. also center
0932     // it horizontally and vertically with respect to the whole area rect
0933     constexpr qint32 maximumAllowedSideLength = 48;
0934     const qint32 minimumSideLength = qMin(rect.width(), rect.height());
0935     const qint32 sideLength = qMin(minimumSideLength, maximumAllowedSideLength);
0936     rect =
0937         QRect(rect.left() + static_cast<qint32>(qRound(static_cast<qreal>(rect.width() - sideLength) / 2.0)),
0938               rect.top() + static_cast<qint32>(qRound(static_cast<qreal>(rect.height() - sideLength) / 2.0)),
0939               sideLength, sideLength);
0940 
0941     buttonOption.rect = rect;
0942 
0943     // Update palette colors to make the check box more readable over the base
0944     // color
0945     const QColor prevBaseColor = buttonOption.palette.base().color();
0946     const qint32 windowLightness = buttonOption.palette.window().color().lightness();
0947     const qint32 baseLightness = prevBaseColor.lightness();
0948     const QColor newBaseColor =
0949         baseLightness > windowLightness ? prevBaseColor.lighter(120) : prevBaseColor.darker(120);
0950     buttonOption.palette.setColor(QPalette::Window, prevBaseColor);
0951     buttonOption.palette.setColor(QPalette::Base, newBaseColor);
0952 
0953     // check if the current index exists in the selected rows.
0954     buttonOption.state.setFlag((d->view->selectionModel()->isRowSelected(index.row(), index.parent())
0955                                     ? QStyle::State_On
0956                                     : QStyle::State_Off));
0957     style->drawPrimitive(QStyle::PE_IndicatorCheckBox, &buttonOption, p);
0958 }
0959 
0960 boost::optional<KisBaseNode::Property>
0961 NodeDelegate::Private::propForMousePos(const QModelIndex &index, const QPoint &mousePos, const QStyleOptionViewItem &option)
0962 {
0963     KisNodeViewColorScheme scm;
0964 
0965     const QRect iconsRect = q->iconsRect(option, index);
0966 
0967     const bool iconsClicked = iconsRect.isValid() &&
0968         iconsRect.contains(mousePos);
0969 
0970     if (!iconsClicked) return boost::none;
0971 
0972     KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
0973     QList<OptionalProperty> realProps = this->rightmostProperties(props);
0974     if (option.direction == Qt::RightToLeft) {
0975         std::reverse(realProps.begin(), realProps.end());
0976     }
0977     const int numProps = realProps.size();
0978 
0979     const int iconWidth = scm.iconSize() + 2 * scm.iconMargin() + scm.border();
0980     const int xPos = mousePos.x() - iconsRect.left();
0981     const int clickedIcon = xPos / iconWidth;
0982     const int distToBorder = qMin(xPos % iconWidth, iconWidth - xPos % iconWidth);
0983 
0984 
0985     if (clickedIcon >= 0 &&
0986         clickedIcon < numProps &&
0987         realProps[clickedIcon] &&
0988         distToBorder > scm.iconMargin()) {
0989 
0990         return *realProps[clickedIcon];
0991     }
0992 
0993     return boost::none;
0994 }
0995 
0996 bool NodeDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
0997 {
0998     if ((event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick)
0999         && (index.flags() & Qt::ItemIsEnabled))
1000     {
1001         QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
1002 
1003         const bool leftButton = mouseEvent->buttons() & Qt::LeftButton;
1004         const bool altButton = mouseEvent->modifiers() & Qt::AltModifier;
1005 
1006         if (index.column() == NodeView::VISIBILITY_COL) {
1007 
1008             const QRect visibilityRect = visibilityClickRect(option, index);
1009             const bool visibilityClicked = visibilityRect.isValid() && visibilityRect.contains(mouseEvent->pos());
1010             if (leftButton && visibilityClicked) {
1011                 KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
1012                 OptionalProperty clickedProperty = d->findVisibilityProperty(props);
1013                 if (!clickedProperty) return false;
1014 
1015                 d->toggleProperty(props, clickedProperty, mouseEvent->modifiers(), index);
1016 
1017                 return true;
1018             }
1019             return false;
1020         } else if (index.column() == NodeView::SELECTED_COL) {
1021             if (leftButton && option.rect.contains(mouseEvent->pos())) {
1022                 changeSelectionAndCurrentIndex(index);
1023                 return true;
1024             }
1025         }
1026 
1027         const QRect thumbnailRect = thumbnailClickRect(option, index);
1028         const bool thumbnailClicked = thumbnailRect.isValid() &&
1029             thumbnailRect.contains(mouseEvent->pos());
1030 
1031         const QRect decorationRect = decorationClickRect(option, index);
1032         const bool decorationClicked = decorationRect.isValid() &&
1033             decorationRect.contains(mouseEvent->pos());
1034 
1035 
1036         if (leftButton) {
1037             if (decorationClicked) {
1038                 bool isExpandable = model->hasChildren(index);
1039                 if (isExpandable) {
1040                     bool isExpanded = d->view->isExpanded(index);
1041                     d->view->setExpanded(index, !isExpanded);
1042                 }
1043                 return true;
1044 
1045             } else if (thumbnailClicked) {
1046                 bool hasCorrectModifier = false;
1047                 SelectionAction action = SELECTION_REPLACE;
1048 
1049                 if (mouseEvent->modifiers() == Qt::ControlModifier) {
1050                     action = SELECTION_REPLACE;
1051                     hasCorrectModifier = true;
1052                 } else if (mouseEvent->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) {
1053                     action = SELECTION_ADD;
1054                     hasCorrectModifier = true;
1055                 } else if (mouseEvent->modifiers() == (Qt::ControlModifier | Qt::AltModifier)) {
1056                     action = SELECTION_SUBTRACT;
1057                     hasCorrectModifier = true;
1058                 } else if (mouseEvent->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier | Qt::AltModifier)) {
1059                     action = SELECTION_INTERSECT;
1060                     hasCorrectModifier = true;
1061                 }
1062 
1063                 if (hasCorrectModifier) {
1064                     model->setData(index, QVariant(int(action)), KisNodeModel::SelectOpaqueRole);
1065                 } else {
1066                     d->view->setCurrentIndex(index);
1067                 }
1068                 return hasCorrectModifier; //If not here then the item is !expanded when reaching return false;
1069 
1070             } else {
1071                 auto clickedProperty = d->propForMousePos(index, mouseEvent->pos(), option);
1072 
1073                 if (!clickedProperty) {
1074                     if (altButton) {
1075                         d->view->setCurrentIndex(index);
1076                         model->setData(index, true, KisNodeModel::AlternateActiveRole);
1077 
1078                         return true;
1079                     } else if (mouseEvent->modifiers() == Qt::ControlModifier) {
1080                         // the control modifier shifts the current index as well (even when deselected), so we
1081                         // handle it manually.
1082                         changeSelectionAndCurrentIndex(index);
1083                         return true;
1084                     }
1085                     return false;
1086                 }
1087 
1088                 KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
1089                 d->toggleProperty(props, &(*clickedProperty), mouseEvent->modifiers(), index);
1090                 return true;
1091             }
1092         }
1093     }
1094     else if (event->type() == QEvent::ToolTip) {
1095         if (!KisConfig(true).hidePopups()) {
1096             QHelpEvent *helpEvent = static_cast<QHelpEvent*>(event);
1097 
1098             auto hoveredProperty = d->propForMousePos(index, helpEvent->pos(), option);
1099             if (hoveredProperty && hoveredProperty->id == KisLayerPropertiesIcons::layerError.id()) {
1100                 QToolTip::showText(helpEvent->globalPos(), hoveredProperty->state.toString(), d->view);
1101             } else {
1102                 d->tip.showTip(d->view, helpEvent->pos(), option, index);
1103             }
1104         }
1105         return true;
1106     } else if (event->type() == QEvent::Leave) {
1107         d->tip.hide();
1108     }
1109 
1110     return false;
1111 }
1112 
1113 void NodeDelegate::changeSelectionAndCurrentIndex(const QModelIndex &index)
1114 {
1115     QItemSelectionModel *selectionModel = d->view->selectionModel();
1116     const bool wasSelected = selectionModel->isRowSelected(index.row(), index.parent());
1117 
1118     // if only one item is selected and that too is us then in that case we don't do anything to
1119     // the selection.
1120     if (selectionModel->selectedIndexes().size() == 1
1121         && selectionModel->isRowSelected(index.row(), index.parent())) {
1122         selectionModel->select(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1123     } else {
1124         selectionModel->select(index, QItemSelectionModel::Toggle | QItemSelectionModel::Rows);
1125     }
1126 
1127     const auto belongToSameRow = [](const QModelIndex &a, const QModelIndex &b) {
1128         return a.row() == b.row() && a.parent() == b.parent();
1129     };
1130 
1131     // in this condition we move the current index to the best guessed previous one.
1132     if (wasSelected && belongToSameRow(selectionModel->currentIndex(), index)) {
1133         selectionModel->setCurrentIndex(selectionModel->selectedRows().last(), QItemSelectionModel::NoUpdate);
1134     }
1135 }
1136 
1137 QWidget *NodeDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem&, const QModelIndex &index) const
1138 {
1139     // #400357 do not override QAbstractItemDelegate::setEditorData to update editor's text
1140     // because replacing the text while user type is confusing
1141     const QString &text = index.data(Qt::DisplayRole).toString();
1142     d->edit = new QLineEdit(text, parent);
1143     d->edit->setFocusPolicy(Qt::StrongFocus);
1144     d->edit->installEventFilter(const_cast<NodeDelegate*>(this)); //hack?
1145     return d->edit;
1146 }
1147 
1148 void NodeDelegate::setModelData(QWidget *widget, QAbstractItemModel *model, const QModelIndex &index) const
1149 {
1150     QLineEdit *edit = qobject_cast<QLineEdit*>(widget);
1151     Q_ASSERT(edit);
1152 
1153     model->setData(index, edit->text(), Qt::DisplayRole);
1154 }
1155 
1156 void NodeDelegate::updateEditorGeometry(QWidget *widget, const QStyleOptionViewItem &option, const QModelIndex &index) const
1157 {
1158     Q_UNUSED(index);
1159     widget->setGeometry(option.rect);
1160 }
1161 
1162 void NodeDelegate::toggleSolo(const QModelIndex &index) {
1163     KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
1164     OptionalProperty visibilityProperty = d->findVisibilityProperty(props);
1165     d->toggleProperty(props, visibilityProperty, Qt::ShiftModifier, index);
1166 }
1167 
1168 
1169 // PROTECTED
1170 
1171 
1172 bool NodeDelegate::eventFilter(QObject *object, QEvent *event)
1173 {
1174     switch (event->type()) {
1175     case QEvent::MouseButtonPress: {
1176         if (d->edit) {
1177             QMouseEvent *me = static_cast<QMouseEvent*>(event);
1178             if (!QRect(d->edit->mapToGlobal(QPoint()), d->edit->size()).contains(me->globalPos())) {
1179                 emit commitData(d->edit);
1180                 emit closeEditor(d->edit);
1181             }
1182         }
1183     } break;
1184     case QEvent::KeyPress: {
1185         QLineEdit *edit = qobject_cast<QLineEdit*>(object);
1186         if (edit && edit == d->edit) {
1187             QKeyEvent *ke = static_cast<QKeyEvent*>(event);
1188             switch (ke->key()) {
1189             case Qt::Key_Escape:
1190                 emit closeEditor(edit);
1191                 return true;
1192             case Qt::Key_Tab:
1193                 emit commitData(edit);
1194                 emit closeEditor(edit, EditNextItem);
1195                 return true;
1196             case Qt::Key_Backtab:
1197                 emit commitData(edit);
1198                 emit closeEditor(edit, EditPreviousItem);
1199                 return true;
1200             case Qt::Key_Return:
1201             case Qt::Key_Enter:
1202                 emit commitData(edit);
1203                 emit closeEditor(edit);
1204                 return true;
1205             default: break;
1206             }
1207         }
1208     } break;
1209     case QEvent::ShortcutOverride : {
1210         QLineEdit *edit = qobject_cast<QLineEdit*>(object);
1211         if (edit && edit == d->edit){
1212             auto* key = static_cast<QKeyEvent*>(event);
1213             if (key->modifiers() == Qt::NoModifier){
1214                 switch (key->key()){
1215                 case Qt::Key_Escape:
1216                 case Qt::Key_Tab:
1217                 case Qt::Key_Backtab:
1218                 case Qt::Key_Return:
1219                 case Qt::Key_Enter:
1220                     event->accept();
1221                     return true;
1222                 default: break;
1223                 }
1224             }
1225         }
1226 
1227     } break;
1228     case QEvent::FocusOut : {
1229         QLineEdit *edit = qobject_cast<QLineEdit*>(object);
1230         if (edit && edit == d->edit) {
1231             emit commitData(edit);
1232             emit closeEditor(edit);
1233         }
1234     }
1235     default: break;
1236     }
1237 
1238     return QAbstractItemDelegate::eventFilter(object, event);
1239 }
1240 
1241 
1242 // PRIVATE
1243 
1244 
1245 QStyleOptionViewItem NodeDelegate::getOptions(const QStyleOptionViewItem &o, const QModelIndex &index)
1246 {
1247     QStyleOptionViewItem option = o;
1248     QVariant v = index.data(Qt::FontRole);
1249     if (v.isValid()) {
1250         option.font = v.value<QFont>();
1251         option.fontMetrics = QFontMetrics(option.font);
1252     }
1253     v = index.data(Qt::TextAlignmentRole);
1254     if (v.isValid())
1255         option.displayAlignment = QFlag(v.toInt());
1256     v = index.data(Qt::ForegroundRole);
1257     if (v.isValid())
1258         option.palette.setColor(QPalette::Text, v.value<QColor>());
1259     v = index.data(Qt::BackgroundRole);
1260     if (v.isValid())
1261         option.palette.setColor(QPalette::Window, v.value<QColor>());
1262 
1263    return option;
1264 }
1265 
1266 void NodeDelegate::drawProgressBar(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
1267 {
1268     QVariant value = index.data(KisNodeModel::ProgressRole);
1269     if (!value.isNull() && (value.toInt() >= 0 && value.toInt() <= 100)) {
1270 
1271         /// The progress bar will display under the layer name area. The bars have accurate data, so we
1272         /// probably don't need to also show the actual number for % complete
1273 
1274         KisNodeViewColorScheme scm;
1275 
1276         const QRect thumbnailRect = thumbnailClickRect(option, index);
1277         const QRect iconsRectR    = iconsRect(option, index);
1278         const int height = 5;
1279         const QRect rc = QRect(
1280             ((option.direction == Qt::RightToLeft) ?
1281               iconsRectR.bottomRight() :
1282               thumbnailRect.bottomRight()) - QPoint(0, height),
1283             ((option.direction == Qt::RightToLeft) ?
1284               thumbnailRect.bottomLeft() :
1285               iconsRectR.bottomLeft()));
1286 
1287         p->save();
1288         {
1289             p->setClipRect(rc);
1290             QStyle* style = QApplication::style();
1291             QStyleOptionProgressBar opt;
1292 
1293             opt.rect = rc;
1294             opt.minimum = 0;
1295             opt.maximum = 100;
1296             opt.progress = value.toInt();
1297             opt.textVisible = false;
1298             opt.textAlignment = Qt::AlignHCenter;
1299             opt.text = i18n("%1 %", opt.progress);
1300             opt.orientation = Qt::Horizontal;
1301             opt.state = option.state;
1302             style->drawControl(QStyle::CE_ProgressBar, &opt, p, 0);
1303         }
1304         p->restore();
1305     }
1306 }
1307 
1308 void NodeDelegate::slotConfigChanged()
1309 {
1310     KisConfig cfg(true);
1311     const int oldHeight = d->rowHeight;
1312     // cache values that require a config lookup and get used frequently
1313     d->thumbnailSize = KisNodeViewColorScheme::instance()->thumbnailSize();
1314     d->thumbnailGeometry = KisNodeViewColorScheme::instance()->relThumbnailRect();
1315     d->rowHeight = KisNodeViewColorScheme::instance()->rowHeight();
1316 
1317     // generate the checker backdrop for thumbnails
1318     const int step = d->thumbnailSize / 6;
1319     if (d->checkers.width() != 2 * step) {
1320         d->checkers = QImage(2 * step, 2 * step, QImage::Format_ARGB32);
1321         QPainter gc(&d->checkers);
1322         gc.fillRect(QRect(0, 0, step, step), cfg.checkersColor1());
1323         gc.fillRect(QRect(step, 0, step, step), cfg.checkersColor2());
1324         gc.fillRect(QRect(step, step, step, step), cfg.checkersColor1());
1325         gc.fillRect(QRect(0, step, step, step), cfg.checkersColor2());
1326     }
1327 
1328     if (d->rowHeight != oldHeight) {
1329         // QAbstractItemView/QTreeView don't even look at the index and redo the whole layout...
1330         emit sizeHintChanged(QModelIndex());
1331     }
1332 }
1333 
1334 void NodeDelegate::slotUpdateIcon()
1335 {
1336    KisLayerPropertiesIcons::instance()->updateIcons();
1337 }
1338 
1339 void NodeDelegate::slotResetState(){
1340 
1341     NodeView *view = d->view;
1342     QModelIndex root = view->rootIndex();
1343     int childs = view->model()->rowCount(root);
1344     if (childs > 0){
1345         QModelIndex firstChild = view->model()->index(0, 0, root);
1346         KisBaseNode::PropertyList props = firstChild.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
1347 
1348         OptionalProperty visibilityProperty = d->findVisibilityProperty(props);
1349         if(d->stasisIsDirty(root, visibilityProperty)){ // clean inStasis if mixed!
1350             d->resetPropertyStateRecursive(root, visibilityProperty);
1351         }
1352     }
1353 }
1354