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