Krita Source Code Documentation
Loading...
Searching...
No Matches
NodeDelegate.cpp
Go to the documentation of this file.
1/*
2 SPDX-FileCopyrightText: 2006 Gábor Lehel <illissius@gmail.com>
3 SPDX-FileCopyrightText: 2008 Cyrille Berger <cberger@cberger.net>
4 SPDX-FileCopyrightText: 2011 José Luis Vergara <pentalis@gmail.com>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8#include "kis_config.h"
9#include "NodeDelegate.h"
10#include "kis_node_model.h"
11#include "NodeToolTip.h"
12#include "NodeView.h"
13#include "KisPart.h"
15
16#include <QtDebug>
17#include <QApplication>
18#include <QKeyEvent>
19#include <QLineEdit>
20#include <QModelIndex>
21#include <QMouseEvent>
22#include <QPainter>
23#include <QPointer>
24#include <QStyle>
25#include <QStyleOptionViewItem>
26#include <QBitmap>
27#include <QToolTip>
28
29#include <klocalizedstring.h>
31#include "kis_icon_utils.h"
33#include "krita_utils.h"
34#include "kis_config_notifier.h"
35#include <kis_painting_tweaks.h>
36
38
39#include <kis_base_node.h>
40
42{
43public:
44 Private(NodeDelegate *_q) : q(_q), view(0), edit(0) { }
45
47
51
52 QImage checkers;
55
57 int thumbnailSize {-1};
58 int rowHeight {-1};
59
61
67
69 int numProperties(const QModelIndex &index) const;
72
73 void toggleProperty(KisBaseNode::PropertyList &props, const OptionalProperty clickedProperty, const Qt::KeyboardModifiers modifier, const QModelIndex &index);
74 void togglePropertyRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty, const QList<QModelIndex> &items, StasisOperation record, bool mode);
75
76 bool stasisIsDirty(const QModelIndex &root, const OptionalProperty &clickedProperty, bool on = false, bool off = false);
77 void resetPropertyStateRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty);
78 void restorePropertyInStasisRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty);
79
80 bool checkImmediateStasis(const QModelIndex &root, const OptionalProperty &clickedProperty);
81
82 void getParentsIndex(QList<QModelIndex> &items, const QModelIndex &index);
83 void getChildrenIndex(QList<QModelIndex> &items, const QModelIndex &index);
84 void getSiblingsIndex(QList<QModelIndex> &items, const QModelIndex &index);
85 boost::optional<KisBaseNode::Property>
86 propForMousePos(const QModelIndex &index, const QPoint &mousePos, const QStyleOptionViewItem &option);
87};
88
89NodeDelegate::NodeDelegate(NodeView *view, QObject *parent)
90 : QAbstractItemDelegate(parent)
91 , d(new Private(this))
92{
93 d->view = view;
94
95 QApplication::instance()->installEventFilter(this);
96 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
97 connect(this, SIGNAL(resetVisibilityStasis()), SLOT(slotResetState()));
99}
100
102{
103 delete d;
104}
105
106QSize NodeDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
107{
109 if (index.column() == NodeView::VISIBILITY_COL) {
110 return QSize(scm.visibilityColumnWidth(), d->rowHeight);
111 }
112 return QSize(option.rect.width(), d->rowHeight);
113}
114
115void NodeDelegate::paint(QPainter *p, const QStyleOptionViewItem &o, const QModelIndex &index) const
116{
117 p->save();
118
119 {
120 QStyleOptionViewItem option = getOptions(o, index);
121 QStyle *style = option.widget ? option.widget->style() : QApplication::style();
122 style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, p, option.widget);
123
124 bool shouldGrayOut = index.data(KisNodeModel::ShouldGrayOutRole).toBool();
125 if (shouldGrayOut) {
126 option.state &= ~QStyle::State_Enabled;
127 }
128
129 drawFrame(p, option, index);
130
131 if (index.column() == NodeView::SELECTED_COL) {
132 drawSelectedButton(p, o, index, style);
133 } else if (index.column() == NodeView::VISIBILITY_COL) {
134 drawVisibilityIcon(p, option, index); // TODO hide when dragging
135 } else {
136 p->setFont(option.font);
137 drawColorLabel(p, option, index);
138 drawThumbnail(p, option, index);
139 drawText(p, option, index); // BUG: Creating group moves things around (RTL-layout alignment)
140 drawIcons(p, option, index);
141 drawDecoration(p, option, index);
142 drawExpandButton(p, option, index);
143 drawAnimatedDecoration(p, option, index);
144
145 drawProgressBar(p, option, index);
146 }
147 }
148 p->restore();
149}
150
151void NodeDelegate::drawBranches(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
152{
153 p->save();
154 drawFrame(p, option, index);
155 p->restore();
156
157 QModelIndex tmp = index.parent();
158
159 // there is no indentation if we have no parent group, so don't draw a branch
160 if (!tmp.isValid()) return;
161
163
164 int rtlNum = (option.direction == Qt::RightToLeft) ? 1 : -1;
165 QPoint nodeCorner = (option.direction == Qt::RightToLeft) ? option.rect.topLeft() : option.rect.topRight();
166 int branchSpacing = rtlNum * d->view->indentation();
167
168 QPoint base = nodeCorner + 0.5 * QPoint(branchSpacing, option.rect.height()) + QPoint(0, scm.iconSize()/4);
169
170 QColor color = scm.gridColor(option, d->view);
171 QColor bgColor = option.state & QStyle::State_Selected ?
172 qApp->palette().color(QPalette::Base) :
173 qApp->palette().color(QPalette::Text);
174 color = KisPaintingTweaks::blendColors(color, bgColor, 0.9);
175
176 // TODO: if we are a mask type, use dotted lines for the branch style
177 // p->setPen(QPen(p->pen().color(), 2, Qt::DashLine, Qt::RoundCap, Qt::RoundJoin));
178 p->setPen(QPen(color, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
179
180 QPoint p2 = base - QPoint(rtlNum*(qMin(d->view->indentation(), scm.iconSize())/2), 0);
181 QPoint p3 = base - QPoint(0, scm.iconSize()/2);
182 p->drawLine(base, p2);
183 p->drawLine(base, p3);
184
185 // draw parent lines (keep drawing until x position is less than 0
186 QPoint parentBase1 = base + QPoint(branchSpacing, 0);
187 QPoint parentBase2 = p3 + QPoint(branchSpacing, 0);
188
189 // indent lines needs to be very subtle to avoid making the docker busy looking
190 color = KisPaintingTweaks::blendColors(color, bgColor, 0.9); // makes it a little lighter than L lines
191 p->setPen(QPen(color, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
192
193
194 int levelRowIndex = tmp.row();
195 tmp = tmp.parent(); // Ignore the first group as it was already painted
196
197 while (tmp.isValid()) {
198 bool moreSiblings = index.model()->rowCount(tmp) > (levelRowIndex + 1);
199 if (moreSiblings) {
200 p->drawLine(parentBase1, parentBase2);
201 }
202
203 parentBase1 += QPoint(branchSpacing, 0);
204 parentBase2 += QPoint(branchSpacing, 0);
205
206 levelRowIndex = tmp.row();
207 tmp = tmp.parent();
208 }
209}
210
211void NodeDelegate::drawColorLabel(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
212{
214 const int label = index.data(KisNodeModel::ColorLabelIndexRole).toInt();
215 QColor color = scm.colorFromLabelIndex(label);
216 if (color.alpha() <= 0) return;
217
218 QColor bgColor = qApp->palette().color(QPalette::Base);
219 color = KisPaintingTweaks::blendColors(color, bgColor, 0.3);
220
221 QRect optionRect = (option.state & QStyle::State_Selected) ? iconsRect(option, index) : option.rect;
222
223 p->fillRect(optionRect, color);
224}
225
226void NodeDelegate::drawFrame(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
227{
229
230 QPen oldPen = p->pen();
231 p->setPen(scm.gridColor(option, d->view));
232
233 const QRect visibilityRect = visibilityClickRect(option, index);
234 const QRect thumbnailRect = thumbnailClickRect(option, index);
235 const QRect iconsRectR = iconsRect(option, index);
236
237 const float bottomY = thumbnailRect.bottomLeft().y();
238
239 QPoint bottomLeftPoint;
240 QPoint bottomRightPoint;
241 if (option.direction == Qt::RightToLeft) {
242 bottomLeftPoint = iconsRectR.bottomLeft();
243 bottomRightPoint = visibilityRect.bottomRight();
244 } else {
245 bottomLeftPoint = visibilityRect.bottomLeft();
246 bottomRightPoint = iconsRectR.bottomRight();
247 }
248
249 // bottom running horizontal line
250 p->drawLine(bottomLeftPoint.x(), bottomY,
251 bottomRightPoint.x(), bottomY);
252
253 // icons' lines are drawn by drawIcons
254
256 p->setPen(Qt::blue);
257 //KritaUtils::renderExactRect(p, iconsRectR);
258 //KritaUtils::renderExactRect(p, textRect(option, index));
259 //KritaUtils::renderExactRect(p, visibilityRect);
260
261 p->setPen(oldPen);
262}
263
264QRect NodeDelegate::thumbnailClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
265{
266 Q_UNUSED(index);
267
268 QRect rc = d->thumbnailGeometry;
269
270 // Move to current index
271 rc.moveTop(option.rect.topLeft().y());
272 // Move to correct location.
273 if (option.direction == Qt::RightToLeft) {
274 rc.moveRight(option.rect.right());
275 } else {
276 rc.moveLeft(option.rect.left());
277 }
278
279 return rc;
280}
281
282void NodeDelegate::drawThumbnail(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
283{
285
286 const qreal devicePixelRatio = p->device()->devicePixelRatioF();
287 const int thumbSizeHighRes = d->thumbnailSize*devicePixelRatio;
288
289 const qreal oldOpacity = p->opacity(); // remember previous opacity
290
291 QImage img = index.data(int(KisNodeModel::BeginThumbnailRole) + thumbSizeHighRes).value<QImage>();
292 img.setDevicePixelRatio(devicePixelRatio);
293 if (!(option.state & QStyle::State_Enabled)) {
294 p->setOpacity(0.35);
295 }
296
297 QRect fitRect = thumbnailClickRect(option, index);
298 // Shrink to icon rect
299 fitRect = kisGrowRect(fitRect, -(scm.thumbnailMargin()+scm.border()));
300
301 QPoint offset;
302 offset.setX((fitRect.width() - img.width()/devicePixelRatio) / 2);
303 offset.setY((fitRect.height() - img.height()/devicePixelRatio) / 2);
304 offset += fitRect.topLeft();
305
306 QBrush brush(d->checkers);
307 p->setBrushOrigin(offset);
308 QRect imageRectLowRes = QRect(img.rect().topLeft(), img.rect().size()/devicePixelRatio);
309 p->fillRect(imageRectLowRes.translated(offset), brush);
310
311 p->drawImage(offset, img);
312 p->setOpacity(oldOpacity); // restore old opacity
313
314 QRect borderRect = kisGrowRect(imageRectLowRes, 1).translated(offset);
315 KritaUtils::renderExactRect(p, borderRect, scm.gridColor(option, d->view));
316}
317
318QRect NodeDelegate::iconsRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
319{
321
322 int propCount = d->numProperties(index);
323
324 const int iconsWidth =
325 propCount * (scm.iconSize() + 2 * scm.iconMargin()) +
326 (propCount + 1) * scm.border();
327
328 QRect fitRect = QRect(0, 0, iconsWidth, d->rowHeight - scm.border());
329 // Move to current index
330 fitRect.moveTop(option.rect.topLeft().y());
331 // Move to correct location.
332 if (option.direction == Qt::RightToLeft) {
333 fitRect.moveLeft(option.rect.topLeft().x());
334 } else {
335 fitRect.moveRight(option.rect.topRight().x());
336 }
337
338 return fitRect;
339}
340
341QRect NodeDelegate::textRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
342{
344
345 static QFont f;
346 static int minbearing = 1337 + 666; //can be 0 or negative, 2003 is less likely
347 if (minbearing == 2003 || f != option.font) {
348 f = option.font; //getting your bearings can be expensive, so we cache them
349 minbearing = option.fontMetrics.minLeftBearing() + option.fontMetrics.minRightBearing();
350 }
351
352 const QRect decoRect = decorationClickRect(option, index);
353 const QRect iconRect = iconsRect(option, index);
354
355 QRect rc = QRect((option.direction == Qt::RightToLeft) ? iconRect.topRight() : decoRect.topRight(),
356 (option.direction == Qt::RightToLeft) ? decoRect.bottomLeft() : iconRect.bottomLeft());
357 rc.adjust(-(scm.border()+(minbearing/2)), 0,
358 (scm.border()+(minbearing/2)), 0);
359
360 return rc;
361}
362
363void NodeDelegate::drawText(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
364{
366 const QRect rc = textRect(option, index).adjusted(scm.textMargin(), 0,
367 -scm.textMargin(), 0);
368
369 QPen oldPen = p->pen();
370 const qreal oldOpacity = p->opacity(); // remember previous opacity
371
372 p->setPen(option.palette.color(QPalette::Active,QPalette::Text ));
373
374 if (!(option.state & QStyle::State_Enabled)) {
375 p->setOpacity(0.55);
376 }
377
378 const QString text = index.data(Qt::DisplayRole).toString();
379 const QString elided = p->fontMetrics().elidedText(text, Qt::ElideRight, rc.width());
380 KisConfig cfg(true);
382 p->drawText(rc, Qt::AlignLeft | Qt::AlignVCenter, elided);
383 }
384 else {
385 const QString infoText = index.data(KisNodeModel::InfoTextRole).toString();
386 if (infoText.isEmpty()) {
387 p->drawText(rc, Qt::AlignLeft | Qt::AlignVCenter, elided);
388 } else {
389 bool useOneLine = cfg.useInlineLayerInfoText();
390 if (!useOneLine) {
391 // check whether there is enough space for two lines
392 const int textHeight = p->fontMetrics().height();
393 useOneLine = rc.height() < textHeight*2;
394 }
395
396 const int rectCenter = rc.height()/2;
397 const int nameWidth = p->fontMetrics().horizontalAdvance(elided);
398 // draw the layer name
399 if (!useOneLine) {
400 // enforce Qt::TextSingleLine because we are adding a line below it
401 p->drawText(rc.adjusted(0, 0, 0, -rectCenter), Qt::AlignLeft | Qt::AlignBottom | Qt::TextSingleLine, elided);
402 }
403 else {
404 p->drawText(rc.adjusted(0, 0, 0, 0), Qt::AlignLeft | Qt::AlignVCenter, elided);
405 }
406 // draw the info-text
407 p->save();
408 QFont layerInfoTextFont = p->font();
409 layerInfoTextFont.setBold(false);
410 p->setFont(layerInfoTextFont);
411 if (option.state & QStyle::State_Enabled) {
412 p->setOpacity(qreal(cfg.layerInfoTextOpacity())/100);
413 }
414 if (!useOneLine) {
415 const QString infoTextElided = p->fontMetrics().elidedText(infoText, Qt::ElideRight, rc.width());
416 p->drawText(rc.adjusted(0, rectCenter, 0, 0), Qt::AlignLeft | Qt::AlignTop, infoTextElided);
417 }
418 else {
419 const QString infoTextElided = p->fontMetrics().elidedText(" "+infoText, Qt::ElideRight, rc.width()-nameWidth);
420 p->drawText(rc.adjusted(nameWidth, 0, 0, 0), Qt::AlignLeft | Qt::AlignVCenter, infoTextElided);
421 }
422 p->restore();
423 }
424 }
425
426 p->setPen(oldPen); // restore pen settings
427 p->setOpacity(oldOpacity);
428}
429
431{
433 QList<OptionalProperty> prependList;
434 list << OptionalProperty(0);
435 list << OptionalProperty(0);
436 list << OptionalProperty(0);
437
438 KisBaseNode::PropertyList::const_iterator it = props.constBegin();
439 KisBaseNode::PropertyList::const_iterator end = props.constEnd();
440 for (; it != end; ++it) {
441 if (!it->isMutable &&
444 it->id != KisLayerPropertiesIcons::colorOverlay.id()) continue;
445
446 if (it->id == KisLayerPropertiesIcons::visible.id()) {
447 // noop...
448 } else if (it->id == KisLayerPropertiesIcons::locked.id()) {
449 list[0] = OptionalProperty(&(*it));
450 } else if (it->id == KisLayerPropertiesIcons::inheritAlpha.id()) {
451 list[1] = OptionalProperty(&(*it));
452 } else if (it->id == KisLayerPropertiesIcons::alphaLocked.id()) {
453 list[2] = OptionalProperty(&(*it));
454 } else {
455 prependList.prepend(OptionalProperty(&(*it)));
456 }
457 }
458
459 auto putToTheLeft = [] (QList<OptionalProperty> &list, const QString &id) {
460 auto it = std::find_if(list.begin(), list.end(), kismpl::mem_equal_to(&KisBaseNode::Property::id, id));
461 if (it != list.end()) {
462 std::rotate(list.begin(), it, std::next(it));
463 }
464 };
465
466 putToTheLeft(prependList, KisLayerPropertiesIcons::colorOverlay.id());
467 putToTheLeft(prependList, KisLayerPropertiesIcons::layerColorSpaceMismatch.id());
468 putToTheLeft(prependList, KisLayerPropertiesIcons::layerError.id());
469
470 {
471 QMutableListIterator<OptionalProperty> i(prependList);
472 i.toBack();
473 while (i.hasPrevious()) {
474 OptionalProperty val = i.previous();
475
476 int emptyIndex = list.lastIndexOf(nullptr);
477 if (emptyIndex < 0) break;
478
479 list[emptyIndex] = val;
480 i.remove();
481 }
482 }
483
484 return prependList + list;
485}
486
487int NodeDelegate::Private::numProperties(const QModelIndex &index) const
488{
490 QList<OptionalProperty> realProps = rightmostProperties(props);
491 return realProps.size();
492}
493
495{
496 KisBaseNode::PropertyList::iterator it = props.begin();
497 KisBaseNode::PropertyList::iterator end = props.end();
498 for (; it != end; ++it) {
499 if (it->id == refProp->id) {
500 return &(*it);
501 }
502 }
503
504 return 0;
505}
506
508{
509 KisBaseNode::PropertyList::iterator it = props.begin();
510 KisBaseNode::PropertyList::iterator end = props.end();
511 for (; it != end; ++it) {
512 if (it->id == KisLayerPropertiesIcons::visible.id()) {
513 return &(*it);
514 }
515 }
516
517 return 0;
518}
519
520void NodeDelegate::Private::toggleProperty(KisBaseNode::PropertyList &props, const OptionalProperty clickedProperty, const Qt::KeyboardModifiers modifier, const QModelIndex &index)
521{
522 QModelIndex root(view->rootIndex());
523
524 if (clickedProperty->id == KisLayerPropertiesIcons::colorOverlay.id()) {
525 // Open the properties dialog for the layer's fast color overlay mask.
526 view->model()->setData(index, QVariant() /* unused */, KisNodeModel::LayerColorOverlayPropertiesRole);
527
528 } else if ((modifier & Qt::ShiftModifier) == Qt::ShiftModifier && clickedProperty->canHaveStasis) {
529 bool mode = true;
530
531 OptionalProperty prop = findProperty(props, clickedProperty);
532
533 // XXX: Change to use NodeProperty
534 int position = shiftClickedIndexes.indexOf(index);
535
536 StasisOperation record = (!prop->isInStasis)? StasisOperation::Record :
537 (position < 0) ? StasisOperation::Review : StasisOperation::Restore;
538
539 shiftClickedIndexes.clear();
540 shiftClickedIndexes.push_back(index);
541
542 QList<QModelIndex> items;
543 if (modifier == (Qt::ControlModifier | Qt::ShiftModifier)) {
544 mode = false; // inverted mode
545 items.insert(0, index); // important!
546 getSiblingsIndex(items, index);
547 } else {
548 getParentsIndex(items, index);
549 getChildrenIndex(items, index);
550 }
551 togglePropertyRecursive(root, clickedProperty, items, record, mode);
552
553 } else {
554 // If we have properties in stasis, we need to cancel stasis to avoid overriding
555 // values in stasis.
556 // IMPORTANT -- we also need to check the first row of nodes to determine
557 // if a stasis is currently active in some cases.
558 const bool hasPropInStasis = (shiftClickedIndexes.count() > 0 || checkImmediateStasis(root, clickedProperty));
559 if (clickedProperty->canHaveStasis && hasPropInStasis) {
560 shiftClickedIndexes.clear();
561
562 restorePropertyInStasisRecursive(root, clickedProperty);
563 } else {
564 shiftClickedIndexes.clear();
565
566 resetPropertyStateRecursive(root, clickedProperty);
567
568 OptionalProperty prop = findProperty(props, clickedProperty);
569 prop->state = !prop->state.toBool();
570 prop->isInStasis = false;
571 view->model()->setData(index, QVariant::fromValue(props), KisNodeModel::PropertiesRole);
572 }
573 }
574}
575
576void NodeDelegate::Private::togglePropertyRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty, const QList<QModelIndex> &items, StasisOperation record, bool mode)
577{
578 int rowCount = view->model()->rowCount(root);
579
580 for (int i = 0; i < rowCount; i++) {
581 QModelIndex idx = view->model()->index(i, 0, root);
582
583 // The entire property list has to be altered because model->setData cannot set individual properties
585 OptionalProperty prop = findProperty(props, clickedProperty);
586
587 if (!prop) continue;
588
589 if (record == StasisOperation::Record) {
590 prop->stateInStasis = prop->state.toBool();
591 }
592 if (record == StasisOperation::Review || record == StasisOperation::Record) {
593 prop->isInStasis = true;
594 if(mode) { //include mode
595 prop->state = (items.contains(idx)) ? QVariant(true) : QVariant(false);
596 } else { // exclude
597 prop->state = (!items.contains(idx))? prop->state :
598 (items.at(0) == idx)? QVariant(true) : QVariant(false);
599 }
600 } else { // restore
601 prop->state = QVariant(prop->stateInStasis);
602 prop->isInStasis = false;
603 }
604
605 view->model()->setData(idx, QVariant::fromValue(props), KisNodeModel::PropertiesRole);
606
607 togglePropertyRecursive(idx,clickedProperty, items, record, mode);
608 }
609}
610
611bool NodeDelegate::Private::stasisIsDirty(const QModelIndex &root, const OptionalProperty &clickedProperty, bool on, bool off)
612{
613
614 int rowCount = view->model()->rowCount(root);
615 bool result = false;
616
617 for (int i = 0; i < rowCount; i++) {
618 if (result) break; // return on first find
619 QModelIndex idx = view->model()->index(i, 0, root);
620 // The entire property list has to be altered because model->setData cannot set individual properties
622 OptionalProperty prop = findProperty(props, clickedProperty);
623
624 if (!prop) continue;
625 if (prop->isInStasis) {
626 on = true;
627 } else {
628 off = true;
629 }
630 // stop if both states exist
631 if (on && off) {
632 return true;
633 }
634
635 result = stasisIsDirty(idx,clickedProperty, on, off);
636 }
637 return result;
638}
639
640void NodeDelegate::Private::resetPropertyStateRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty)
641{
642 if (!clickedProperty->canHaveStasis) return;
643 int rowCount = view->model()->rowCount(root);
644
645 for (int i = 0; i < rowCount; i++) {
646 QModelIndex idx = view->model()->index(i, 0, root);
647 // The entire property list has to be altered because model->setData cannot set individual properties
649 OptionalProperty prop = findProperty(props, clickedProperty);
650
651 if (!prop) continue;
652 prop->isInStasis = false;
653 view->model()->setData(idx, QVariant::fromValue(props), KisNodeModel::PropertiesRole);
654
655 resetPropertyStateRecursive(idx,clickedProperty);
656 }
657}
658
659void NodeDelegate::Private::restorePropertyInStasisRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty)
660{
661 if (!clickedProperty->canHaveStasis) return;
662 int rowCount = view->model()->rowCount(root);
663
664 for (int i = 0; i < rowCount; i++) {
665 QModelIndex idx = view->model()->index(i, 0, root);
667 OptionalProperty prop = findProperty(props, clickedProperty);
668
669 if (prop->isInStasis) {
670 prop->isInStasis = false;
671 prop->state = QVariant(prop->stateInStasis);
672 }
673
674 view->model()->setData(idx, QVariant::fromValue(props), KisNodeModel::PropertiesRole);
675
676 restorePropertyInStasisRecursive(idx, clickedProperty);
677 }
678}
679
680bool NodeDelegate::Private::checkImmediateStasis(const QModelIndex &root, const OptionalProperty &clickedProperty)
681{
682 if (!clickedProperty->canHaveStasis) return false;
683
684 const int rowCount = view->model()->rowCount(root);
685 for (int i = 0; i < rowCount; i++){
686 QModelIndex idx = view->model()->index(i, 0, root);
688 OptionalProperty prop = findProperty(props, clickedProperty);
689
690 if (prop->isInStasis) {
691 return true;
692 }
693 }
694
695 return false;
696}
697
698void NodeDelegate::Private::getParentsIndex(QList<QModelIndex> &items, const QModelIndex &index)
699{
700 if (!index.isValid()) return;
701 items.append(index);
702 getParentsIndex(items, index.parent());
703}
704
706{
707 qint32 childs = view->model()->rowCount(index);
708 QModelIndex child;
709 // STEP 1: Go.
710 for (quint16 i = 0; i < childs; ++i) {
711 child = view->model()->index(i, 0, index);
712 items.append(child);
713 getChildrenIndex(items, child);
714 }
715}
716
718{
719 qint32 numberOfLeaves = view->model()->rowCount(index.parent());
720 QModelIndex item;
721 // STEP 1: Go.
722 for (quint16 i = 0; i < numberOfLeaves; ++i) {
723 item = view->model()->index(i, 0, index.parent());
724 if (item != index) {
725 items.append(item);
726 }
727 }
728}
729
730
731void NodeDelegate::drawIcons(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
732{
734 const QRect rc = iconsRect(option, index);
735
736 QTransform oldTransform = p->transform();
737 QPen oldPen = p->pen();
738 p->setTransform(QTransform::fromTranslate(rc.x(), rc.y()));
739 p->setPen(scm.gridColor(option, d->view));
740
741 int x = 0;
742 const int y = (d->rowHeight - scm.border() - scm.iconSize()) / 2;
745
746 if (option.direction == Qt::RightToLeft) {
747 std::reverse(realProps.begin(), realProps.end());
748 }
749
750 Q_FOREACH (OptionalProperty prop, realProps) {
751 x += scm.iconMargin();
752 if (prop) {
753 QIcon icon = prop->state.toBool() ? prop->onIcon : prop->offIcon;
754 bool fullColor = prop->state.toBool() && option.state & QStyle::State_Enabled;
755
756 const qreal oldOpacity = p->opacity(); // remember previous opacity
757 if (fullColor) {
758 p->setOpacity(1.0);
759 } else {
760 p->setOpacity(0.35);
761 }
762
764 // Parent layer can show its color overlay mask color here.
765 QRect colorRect(x, y, scm.iconSize(), scm.iconSize());
766 colorRect = colorRect.marginsRemoved(QMargins(2, 1, 2, 1));
767 p->fillRect(colorRect, index.data(KisNodeModel::LayerColorOverlayColorRole).value<QColor>());
768 p->drawRect(colorRect);
769 } else {
770 p->drawPixmap(x, y, icon.pixmap(scm.iconSize(), QIcon::Normal));
771 }
772 p->setOpacity(oldOpacity); // restore old opacity
773 }
774 x += scm.iconSize() + scm.iconMargin();
775
776 x += scm.border();
777 }
778
779 // Draw a color preview "icon" for some types of filter masks,
780 // but especially for KisFilterFastColorOverlay.
781
782 if (index.data(KisNodeModel::FilterMaskColorRole).isNull() == false) {
783 if (!(option.state & QStyle::State_Enabled)) {
784 p->setOpacity(0.35);
785 } else {
786 p->setOpacity(1.0);
787 }
788
789 const QRect colorRect = filterColorClickRect(option, index).translated(-rc.x(), -rc.y());
790 p->fillRect(colorRect, index.data(KisNodeModel::FilterMaskColorRole).value<QColor>());
791 p->drawRect(colorRect);
792 }
793
794 p->setTransform(oldTransform);
795 p->setPen(oldPen);
796}
797
798QRect NodeDelegate::visibilityClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
799{
800 Q_UNUSED(index);
802
803 QRect rc = scm.relVisibilityRect();
804 rc.setHeight(d->rowHeight);
805
806 // Move to current index
807 rc.moveCenter(option.rect.center());
808 // Move to correct location.
809 if (option.direction == Qt::RightToLeft) {
810 rc.moveRight(option.rect.right());
811 } else {
812 rc.moveLeft(option.rect.left());
813 }
814
815 return rc;
816}
817
818QRect NodeDelegate::decorationClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
819{
820 Q_UNUSED(index);
822
823 QRect rc = scm.relDecorationRect();
824
825 // Move to current index
826 rc.moveTop(option.rect.topLeft().y());
827 rc.setHeight(d->rowHeight);
828 // Move to correct location.
829 if (option.direction == Qt::RightToLeft) {
830 rc.moveRight(option.rect.right() - d->thumbnailGeometry.width());
831 } else {
832 rc.moveLeft(option.rect.left() + d->thumbnailGeometry.width());
833 }
834
835 return rc;
836}
837
838void NodeDelegate::drawVisibilityIcon(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
839{
841
844 if (!prop) return;
845
846 QRect fitRect = visibilityClickRect(option, index);
847 // Shrink to icon rect
848 fitRect = kisGrowRect(fitRect, -(scm.visibilityMargin() + scm.border()));
849
850 QIcon icon = prop->state.toBool() ? prop->onIcon : prop->offIcon;
851
852 // if we are not showing the layer, make the icon slightly transparent like other inactive icons
853 const qreal oldOpacity = p->opacity();
854
855 if (!prop->state.toBool()) {
856 p->setOpacity(0.35);
857 }
858
859 QPixmap pixmapIcon(icon.pixmap(scm.visibilitySize(), QIcon::Active));
860 p->drawPixmap(fitRect.x(), fitRect.center().y() - scm.visibilitySize() / 2, pixmapIcon);
861
862 if (prop->isInStasis) {
863 QPainter::CompositionMode prevComposition = p->compositionMode();
864 p->setCompositionMode(QPainter::CompositionMode_HardLight);
865 pixmapIcon = icon.pixmap(scm.visibilitySize(), QIcon::Active);
866 QBitmap mask = pixmapIcon.mask();
867 pixmapIcon.fill(d->view->palette().color(QPalette::Highlight));
868 pixmapIcon.setMask(mask);
869 p->drawPixmap(fitRect.x(), fitRect.center().y() - scm.visibilitySize() / 2, pixmapIcon);
870 p->setCompositionMode(prevComposition);
871 }
872
873 p->setOpacity(oldOpacity);
874
876// // // p->save();
877// // // p->setPen(Qt::blue);
878// // // KritaUtils::renderExactRect(p, visibilityClickRect(option, index));
879// // // p->restore();
880}
881
882void NodeDelegate::drawDecoration(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
883{
885 QIcon icon = index.data(Qt::DecorationRole).value<QIcon>();
886
887 if (!icon.isNull()) {
888 QPixmap pixmap = icon.pixmap(scm.decorationSize(),
889 (option.state & QStyle::State_Enabled) ?
890 QIcon::Normal : QIcon::Disabled);
891
892 QRect rc = decorationClickRect(option, index);
893
894 // Shrink to icon rect
895 rc = kisGrowRect(rc, -(scm.decorationMargin()+scm.border()));
896
897 const qreal oldOpacity = p->opacity(); // remember previous opacity
898
899 if (!(option.state & QStyle::State_Enabled)) {
900 p->setOpacity(0.35);
901 }
902
903 p->drawPixmap(rc.topLeft()-QPoint(0, 1), pixmap);
904
905 p->setOpacity(oldOpacity); // restore old opacity
906 }
907}
908
909void NodeDelegate::drawExpandButton(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
910{
911 Q_UNUSED(index);
913
914 QRect rc = decorationClickRect(option, index);
915
916 // Move to current index
917// rc.moveTop(option.rect.topLeft().y());
918 // Shrink to icon rect
919 rc = kisGrowRect(rc, -(scm.decorationMargin()+scm.border()));
920
921 if (!(option.state & QStyle::State_Children)) return;
922
923 QString iconName = option.state & QStyle::State_Open ?
924 "arrow-down" : ((option.direction == Qt::RightToLeft) ? "arrow-left" : "arrow-right");
925 QIcon icon = KisIconUtils::loadIcon(iconName);
926 QPixmap pixmap = icon.pixmap(rc.width(),
927 (option.state & QStyle::State_Enabled) ?
928 QIcon::Normal : QIcon::Disabled);
929 p->drawPixmap(rc.bottomLeft()-QPoint(0, scm.decorationSize()-1), pixmap);
930}
931
932void NodeDelegate::drawAnimatedDecoration(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const {
933
935 QRect rc = decorationClickRect(option, index);
936
937 QIcon animatedIndicatorIcon = KisIconUtils::loadIcon("layer-animated");
938 const bool isAnimated = index.data(KisNodeModel::IsAnimatedRole).toBool();
939
940 rc = kisGrowRect(rc, -(scm.decorationMargin()+scm.border()));
941
942 if (!isAnimated) return;
943
944 if ((option.state & QStyle::State_Children)) return;
945
946 const qreal oldOpacity = p->opacity(); // remember previous opacity
947
948 if (!(option.state & QStyle::State_Enabled)) {
949 p->setOpacity(0.35);
950 }
951
952 int decorationSize = scm.decorationSize();
953
954 QPixmap animPixmap = animatedIndicatorIcon.pixmap(decorationSize,
955 (option.state & QStyle::State_Enabled) ?
956 QIcon::Normal : QIcon::Disabled);
957
958 p->drawPixmap(rc.bottomLeft()-QPoint(0, scm.decorationSize()-1), animPixmap);
959
960 p->setOpacity(oldOpacity);
961}
962
963void NodeDelegate::drawSelectedButton(QPainter *p, const QStyleOptionViewItem &option,
964 const QModelIndex &index, QStyle *style) const
965{
966 QStyleOptionButton buttonOption;
967
969 QRect rect = option.rect;
970
971 // adjust the icon to not touch the borders
972 rect = kisGrowRect(rect, -(scm.thumbnailMargin() + scm.border()));
973 // Make the rect a square so the check mark is not distorted. also center
974 // it horizontally and vertically with respect to the whole area rect
975 constexpr qint32 maximumAllowedSideLength = 48;
976 const qint32 minimumSideLength = qMin(rect.width(), rect.height());
977 const qint32 sideLength = qMin(minimumSideLength, maximumAllowedSideLength);
978 rect =
979 QRect(rect.left() + static_cast<qint32>(qRound(static_cast<qreal>(rect.width() - sideLength) / 2.0)),
980 rect.top() + static_cast<qint32>(qRound(static_cast<qreal>(rect.height() - sideLength) / 2.0)),
981 sideLength, sideLength);
982
983 buttonOption.rect = rect;
984
985 // Update palette colors to make the check box more readable over the base
986 // color
987 const QColor prevBaseColor = buttonOption.palette.base().color();
988 const qint32 windowLightness = buttonOption.palette.window().color().lightness();
989 const qint32 baseLightness = prevBaseColor.lightness();
990 const QColor newBaseColor =
991 baseLightness > windowLightness ? prevBaseColor.lighter(120) : prevBaseColor.darker(120);
992 buttonOption.palette.setColor(QPalette::Window, prevBaseColor);
993 buttonOption.palette.setColor(QPalette::Base, newBaseColor);
994
995 // check if the current index exists in the selected rows.
996 buttonOption.state.setFlag((d->view->selectionModel()->isRowSelected(index.row(), index.parent())
997 ? QStyle::State_On
998 : QStyle::State_Off));
999 style->drawPrimitive(QStyle::PE_IndicatorCheckBox, &buttonOption, p);
1000}
1001
1002boost::optional<KisBaseNode::Property>
1003NodeDelegate::Private::propForMousePos(const QModelIndex &index, const QPoint &mousePos, const QStyleOptionViewItem &option)
1004{
1006
1007 const QRect iconsRect = q->iconsRect(option, index);
1008
1009 const bool iconsClicked = iconsRect.isValid() &&
1010 iconsRect.contains(mousePos);
1011
1012 if (!iconsClicked) return boost::none;
1013
1015 QList<OptionalProperty> realProps = this->rightmostProperties(props);
1016 if (option.direction == Qt::RightToLeft) {
1017 std::reverse(realProps.begin(), realProps.end());
1018 }
1019 const int numProps = realProps.size();
1020
1021 const int iconWidth = scm.iconSize() + 2 * scm.iconMargin() + scm.border();
1022 const int xPos = mousePos.x() - iconsRect.left();
1023 const int clickedIcon = xPos / iconWidth;
1024 const int distToBorder = qMin(xPos % iconWidth, iconWidth - xPos % iconWidth);
1025
1026
1027 if (clickedIcon >= 0 &&
1028 clickedIcon < numProps &&
1029 realProps[clickedIcon] &&
1030 distToBorder > scm.iconMargin()) {
1031
1032 return *realProps[clickedIcon];
1033 }
1034
1035 return boost::none;
1036}
1037
1038bool NodeDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
1039{
1040 if ((event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick)
1041 && (index.flags() & Qt::ItemIsEnabled))
1042 {
1043 QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
1044
1045 const bool leftButton = mouseEvent->buttons() & Qt::LeftButton;
1046 const bool altButton = mouseEvent->modifiers() & Qt::AltModifier;
1047
1048 if (index.column() == NodeView::VISIBILITY_COL) {
1049
1050 const QRect visibilityRect = visibilityClickRect(option, index);
1051 const bool visibilityClicked = visibilityRect.isValid() && visibilityRect.contains(mouseEvent->pos());
1052 if (leftButton && visibilityClicked) {
1054 OptionalProperty clickedProperty = d->findVisibilityProperty(props);
1055 if (!clickedProperty) return false;
1056
1057 d->toggleProperty(props, clickedProperty, mouseEvent->modifiers(), index);
1058
1059 return true;
1060 }
1061 return false;
1062 } else if (index.column() == NodeView::SELECTED_COL) {
1063 if (leftButton && option.rect.contains(mouseEvent->pos())) {
1065 return true;
1066 }
1067 }
1068
1069 const QRect thumbnailRect = thumbnailClickRect(option, index);
1070 const bool thumbnailClicked = thumbnailRect.isValid() &&
1071 thumbnailRect.contains(mouseEvent->pos());
1072
1073 const QRect decorationRect = decorationClickRect(option, index);
1074 const bool decorationClicked = decorationRect.isValid() &&
1075 decorationRect.contains(mouseEvent->pos());
1076
1077 const QRect filterRect = filterColorClickRect(option, index);
1078 const bool filterColorClicked =
1079 (index.data(KisNodeModel::FilterMaskColorRole).isNull() == false) &&
1080 filterRect.isValid() &&
1081 filterRect.contains(mouseEvent->pos());
1082
1083 if (leftButton) {
1084 if (decorationClicked) {
1085 bool isExpandable = model->hasChildren(index);
1086 if (isExpandable) {
1087 bool isExpanded = d->view->isExpanded(index);
1088 d->view->setExpanded(index, !isExpanded);
1089 }
1090 return true;
1091
1092 } else if (thumbnailClicked) {
1093 bool hasCorrectModifier = false;
1095
1096 if (mouseEvent->modifiers() == Qt::ControlModifier) {
1097 action = SELECTION_REPLACE;
1098 hasCorrectModifier = true;
1099 } else if (mouseEvent->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) {
1100 action = SELECTION_ADD;
1101 hasCorrectModifier = true;
1102 } else if (mouseEvent->modifiers() == (Qt::ControlModifier | Qt::AltModifier)) {
1103 action = SELECTION_SUBTRACT;
1104 hasCorrectModifier = true;
1105 } else if (mouseEvent->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier | Qt::AltModifier)) {
1106 action = SELECTION_INTERSECT;
1107 hasCorrectModifier = true;
1108 }
1109
1110 if (hasCorrectModifier) {
1111 model->setData(index, QVariant(int(action)), KisNodeModel::SelectOpaqueRole);
1112 } else {
1113 d->view->setCurrentIndex(index);
1114 }
1115 return hasCorrectModifier; //If not here then the item is !expanded when reaching return false;
1116
1117 } else if (filterColorClicked) {
1118 model->setData(index, QVariant() /* unused */, KisNodeModel::FilterMaskPropertiesRole);
1119 return true;
1120
1121 } else {
1122 auto clickedProperty = d->propForMousePos(index, mouseEvent->pos(), option);
1123
1124 if (!clickedProperty) {
1125 if (altButton) {
1126 d->view->setCurrentIndex(index);
1127 model->setData(index, true, KisNodeModel::AlternateActiveRole);
1128
1129 return true;
1130 } else if (mouseEvent->modifiers() == Qt::ControlModifier) {
1131 // the control modifier shifts the current index as well (even when deselected), so we
1132 // handle it manually.
1134 return true;
1135 }
1136 return false;
1137 }
1138
1140 d->toggleProperty(props, &(*clickedProperty), mouseEvent->modifiers(), index);
1141 return true;
1142 }
1143 }
1144 }
1145 else if (event->type() == QEvent::ToolTip) {
1146 if (!KisConfig(true).hidePopups()) {
1147 QHelpEvent *helpEvent = static_cast<QHelpEvent*>(event);
1148
1149 auto hoveredProperty = d->propForMousePos(index, helpEvent->pos(), option);
1150 if (hoveredProperty &&
1151 (hoveredProperty->id == KisLayerPropertiesIcons::layerError.id() ||
1152 hoveredProperty->id == KisLayerPropertiesIcons::layerColorSpaceMismatch.id())) {
1153 QToolTip::showText(helpEvent->globalPos(), hoveredProperty->state.toString(), d->view);
1154 } else {
1155 d->tip.showTip(d->view, helpEvent->pos(), option, index);
1156 }
1157 }
1158 return true;
1159 } else if (event->type() == QEvent::Leave) {
1160 d->tip.hide();
1161 }
1162
1163 return false;
1164}
1165
1167{
1168 QItemSelectionModel *selectionModel = d->view->selectionModel();
1169 const bool wasSelected = selectionModel->isRowSelected(index.row(), index.parent());
1170
1171 // if only one item is selected and that too is us then in that case we don't do anything to
1172 // the selection.
1173 if (selectionModel->selectedIndexes().size() == 1
1174 && selectionModel->isRowSelected(index.row(), index.parent())) {
1175 selectionModel->select(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1176 } else {
1177 selectionModel->select(index, QItemSelectionModel::Toggle | QItemSelectionModel::Rows);
1178 }
1179
1180 const auto belongToSameRow = [](const QModelIndex &a, const QModelIndex &b) {
1181 return a.row() == b.row() && a.parent() == b.parent();
1182 };
1183
1184 // in this condition we move the current index to the best guessed previous one.
1185 if (wasSelected && belongToSameRow(selectionModel->currentIndex(), index)) {
1186 selectionModel->setCurrentIndex(selectionModel->selectedRows().last(), QItemSelectionModel::NoUpdate);
1187 }
1188}
1189
1190QWidget *NodeDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem&, const QModelIndex &index) const
1191{
1192 // #400357 do not override QAbstractItemDelegate::setEditorData to update editor's text
1193 // because replacing the text while user type is confusing
1194 const QString &text = index.data(Qt::DisplayRole).toString();
1195 d->edit = new QLineEdit(text, parent);
1196 d->edit->setFocusPolicy(Qt::StrongFocus);
1197 d->edit->installEventFilter(const_cast<NodeDelegate*>(this)); //hack?
1198 return d->edit;
1199}
1200
1201void NodeDelegate::setModelData(QWidget *widget, QAbstractItemModel *model, const QModelIndex &index) const
1202{
1203 QLineEdit *edit = qobject_cast<QLineEdit*>(widget);
1204 Q_ASSERT(edit);
1205
1206 model->setData(index, edit->text(), Qt::DisplayRole);
1207}
1208
1209void NodeDelegate::updateEditorGeometry(QWidget *widget, const QStyleOptionViewItem &option, const QModelIndex &index) const
1210{
1211 Q_UNUSED(index);
1212 widget->setGeometry(option.rect);
1213}
1214
1215void NodeDelegate::toggleSolo(const QModelIndex &index) {
1217 OptionalProperty visibilityProperty = d->findVisibilityProperty(props);
1218 d->toggleProperty(props, visibilityProperty, Qt::ShiftModifier, index);
1219}
1220
1221
1222// PROTECTED
1223
1224
1225bool NodeDelegate::eventFilter(QObject *object, QEvent *event)
1226{
1227 switch (event->type()) {
1228 case QEvent::MouseButtonPress: {
1229 if (d->edit) {
1230 QMouseEvent *me = static_cast<QMouseEvent*>(event);
1231 if (!QRect(d->edit->mapToGlobal(QPoint()), d->edit->size()).contains(me->globalPos())) {
1232 Q_EMIT commitData(d->edit);
1233 Q_EMIT closeEditor(d->edit);
1234 }
1235 }
1236 } break;
1237 case QEvent::KeyPress: {
1238 QLineEdit *edit = qobject_cast<QLineEdit*>(object);
1239 if (edit && edit == d->edit) {
1240 QKeyEvent *ke = static_cast<QKeyEvent*>(event);
1241 switch (ke->key()) {
1242 case Qt::Key_Escape:
1243 Q_EMIT closeEditor(edit);
1244 return true;
1245 case Qt::Key_Tab:
1246 Q_EMIT commitData(edit);
1247 Q_EMIT closeEditor(edit, EditNextItem);
1248 return true;
1249 case Qt::Key_Backtab:
1250 Q_EMIT commitData(edit);
1251 Q_EMIT closeEditor(edit, EditPreviousItem);
1252 return true;
1253 case Qt::Key_Return:
1254 case Qt::Key_Enter:
1255 Q_EMIT commitData(edit);
1256 Q_EMIT closeEditor(edit);
1257 return true;
1258 default: break;
1259 }
1260 }
1261 } break;
1262 case QEvent::ShortcutOverride : {
1263 QLineEdit *edit = qobject_cast<QLineEdit*>(object);
1264 if (edit && edit == d->edit){
1265 auto* key = static_cast<QKeyEvent*>(event);
1266 if (key->modifiers() == Qt::NoModifier){
1267 switch (key->key()){
1268 case Qt::Key_Escape:
1269 case Qt::Key_Tab:
1270 case Qt::Key_Backtab:
1271 case Qt::Key_Return:
1272 case Qt::Key_Enter:
1273 event->accept();
1274 return true;
1275 default: break;
1276 }
1277 }
1278 }
1279
1280 } break;
1281 case QEvent::FocusOut : {
1282 QLineEdit *edit = qobject_cast<QLineEdit*>(object);
1283 if (edit && edit == d->edit) {
1284 Q_EMIT commitData(edit);
1285 Q_EMIT closeEditor(edit);
1286 }
1287 }
1288 default: break;
1289 }
1290
1291 return QAbstractItemDelegate::eventFilter(object, event);
1292}
1293
1294
1295// PRIVATE
1296
1297
1298QStyleOptionViewItem NodeDelegate::getOptions(const QStyleOptionViewItem &o, const QModelIndex &index)
1299{
1300 QStyleOptionViewItem option = o;
1301 QVariant v = index.data(Qt::FontRole);
1302 if (v.isValid()) {
1303 option.font = v.value<QFont>();
1304 option.fontMetrics = QFontMetrics(option.font);
1305 }
1306 v = index.data(Qt::TextAlignmentRole);
1307 if (v.isValid())
1308 option.displayAlignment = QFlag(v.toInt());
1309 v = index.data(Qt::ForegroundRole);
1310 if (v.isValid())
1311 option.palette.setColor(QPalette::Text, v.value<QColor>());
1312 v = index.data(Qt::BackgroundRole);
1313 if (v.isValid())
1314 option.palette.setColor(QPalette::Window, v.value<QColor>());
1315
1316 return option;
1317}
1318
1319void NodeDelegate::drawProgressBar(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
1320{
1321 QVariant value = index.data(KisNodeModel::ProgressRole);
1322 if (!value.isNull() && (value.toInt() >= 0 && value.toInt() <= 100)) {
1323
1326
1328
1329 const QRect thumbnailRect = thumbnailClickRect(option, index);
1330 const QRect iconsRectR = iconsRect(option, index);
1331 const int height = 5;
1332 const QRect rc = QRect(
1333 ((option.direction == Qt::RightToLeft) ?
1334 iconsRectR.bottomRight() :
1335 thumbnailRect.bottomRight()) - QPoint(0, height),
1336 ((option.direction == Qt::RightToLeft) ?
1337 thumbnailRect.bottomLeft() :
1338 iconsRectR.bottomLeft()));
1339
1340 p->save();
1341 {
1342 p->setClipRect(rc);
1343 QStyle* style = QApplication::style();
1344 QStyleOptionProgressBar opt;
1345
1346 opt.rect = rc;
1347 opt.minimum = 0;
1348 opt.maximum = 100;
1349 opt.progress = value.toInt();
1350 opt.textVisible = false;
1351 opt.textAlignment = Qt::AlignHCenter;
1352 opt.text = i18n("%1 %", opt.progress);
1353 opt.state = option.state;
1354 style->drawControl(QStyle::CE_ProgressBar, &opt, p, 0);
1355 }
1356 p->restore();
1357 }
1358}
1359
1361{
1362 KisConfig cfg(true);
1363 const int oldHeight = d->rowHeight;
1364 // cache values that require a config lookup and get used frequently
1368
1369 const QColor newCheckersColor1 = cfg.checkersColor1();
1370 const QColor newCheckersColor2 = cfg.checkersColor2();
1371
1372 // generate the checker backdrop for thumbnails
1373 const int step = d->thumbnailSize / 6;
1374 if ((d->checkers.width() != 2 * step) ||
1375 (d->checkersColor1 != newCheckersColor1) ||
1376 (d->checkersColor2 != newCheckersColor2)) {
1377
1378 d->checkersColor1 = newCheckersColor1;
1379 d->checkersColor2 = newCheckersColor2;
1380 d->checkers = QImage(2 * step, 2 * step, QImage::Format_ARGB32);
1381
1382 QPainter gc(&d->checkers);
1383 gc.fillRect(QRect(0, 0, step, step), newCheckersColor1);
1384 gc.fillRect(QRect(step, 0, step, step), newCheckersColor2);
1385 gc.fillRect(QRect(step, step, step, step), newCheckersColor1);
1386 gc.fillRect(QRect(0, step, step, step), newCheckersColor2);
1387 }
1388
1389 if (d->rowHeight != oldHeight) {
1390 // QAbstractItemView/QTreeView don't even look at the index and redo the whole layout...
1391 Q_EMIT sizeHintChanged(QModelIndex());
1392 }
1393}
1394
1399
1401
1402 NodeView *view = d->view;
1403 QModelIndex root = view->rootIndex();
1404 int childs = view->model()->rowCount(root);
1405 if (childs > 0){
1406 QModelIndex firstChild = view->model()->index(0, 0, root);
1408
1409 OptionalProperty visibilityProperty = d->findVisibilityProperty(props);
1410 if(d->stasisIsDirty(root, visibilityProperty)){ // clean inStasis if mixed!
1411 d->resetPropertyStateRecursive(root, visibilityProperty);
1412 }
1413 }
1414}
1415
1416QRect NodeDelegate::filterColorClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
1417{
1419 const QRect icons = iconsRect(option, index);
1420
1421 // Filter masks have two unused properties/icons,
1422 // and we can use that space to draw the filter's selected color.
1423
1424 QRect rc;
1425 const int dx = scm.iconSize() + scm.iconMargin();
1426 if (option.direction == Qt::RightToLeft) {
1427 // The free space is at the beginning on the left.
1428 rc.setRect(0, 0, icons.width() - dx, icons.height());
1429 } else {
1430 // The free space is at the end on the right.
1431 rc.setRect(dx, 0, icons.width() - dx, icons.height());
1432 }
1433 rc = rc.marginsRemoved(QMargins(8, 10, 8, 10));
1434
1435 rc.translate(icons.x(), icons.y());
1436
1437 return rc;
1438}
float value(const T *src, size_t ch)
const Params2D p
qreal v
QPointF p2
QPointF p3
SelectionAction
@ SELECTION_REPLACE
@ SELECTION_INTERSECT
@ SELECTION_SUBTRACT
@ SELECTION_ADD
KisBaseNode::Property * OptionalProperty
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
static KisConfigNotifier * instance()
LayerInfoTextStyle layerInfoTextStyle(bool defaultValue=false) const
QColor checkersColor2(bool defaultValue=false) const
bool useInlineLayerInfoText(bool defaultValue=false) const
QColor checkersColor1(bool defaultValue=false) const
bool hidePopups(bool defaultValue=false) const
int layerInfoTextOpacity(bool defaultValue=false) const
static KisLayerPropertiesIcons * instance()
@ LayerColorOverlayPropertiesRole
@ ProgressRole
Use to communicate a progress report to the section delegate on an action (a value of -1 or a QVarian...
@ PropertiesRole
A list of properties the part has.
QColor colorFromLabelIndex(int index) const
QColor gridColor(const QStyleOptionViewItem &option, QTreeView *view) const
static KisNodeViewColorScheme * instance()
QString id() const
Definition KoID.cpp:63
void showTip(QWidget *widget, const QPoint &pos, const QStyleOptionViewItem &option, const QModelIndex &index)
QList< OptionalProperty > rightmostProperties(const KisBaseNode::PropertyList &props) const
void restorePropertyInStasisRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty)
bool stasisIsDirty(const QModelIndex &root, const OptionalProperty &clickedProperty, bool on=false, bool off=false)
Private(NodeDelegate *_q)
int numProperties(const QModelIndex &index) const
QPointer< QWidget > edit
void getChildrenIndex(QList< QModelIndex > &items, const QModelIndex &index)
OptionalProperty findProperty(KisBaseNode::PropertyList &props, const OptionalProperty &refProp) const
boost::optional< KisBaseNode::Property > propForMousePos(const QModelIndex &index, const QPoint &mousePos, const QStyleOptionViewItem &option)
void getSiblingsIndex(QList< QModelIndex > &items, const QModelIndex &index)
bool checkImmediateStasis(const QModelIndex &root, const OptionalProperty &clickedProperty)
void getParentsIndex(QList< QModelIndex > &items, const QModelIndex &index)
OptionalProperty findVisibilityProperty(KisBaseNode::PropertyList &props) const
void resetPropertyStateRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty)
QList< QModelIndex > shiftClickedIndexes
void togglePropertyRecursive(const QModelIndex &root, const OptionalProperty &clickedProperty, const QList< QModelIndex > &items, StasisOperation record, bool mode)
void toggleProperty(KisBaseNode::PropertyList &props, const OptionalProperty clickedProperty, const Qt::KeyboardModifiers modifier, const QModelIndex &index)
void drawBranches(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
Private *const d
void drawFrame(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
void drawProgressBar(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
~NodeDelegate() override
void drawText(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
QRect textRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
void drawIcons(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
void resetVisibilityStasis()
void changeSelectionAndCurrentIndex(const QModelIndex &index)
QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override
QRect thumbnailClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
static QStyleOptionViewItem getOptions(const QStyleOptionViewItem &option, const QModelIndex &index)
bool eventFilter(QObject *object, QEvent *event) override
QRect decorationClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
QRect filterColorClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
void toggleSolo(const QModelIndex &index)
QRect iconsRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
QRect visibilityClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
void drawThumbnail(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override
NodeDelegate(NodeView *view, QObject *parent=0)
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
void drawAnimatedDecoration(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
void drawVisibilityIcon(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
void drawSelectedButton(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index, QStyle *style) const
void drawColorLabel(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
void slotConfigChanged()
void drawDecoration(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
void drawExpandButton(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override
@ SELECTED_COL
Definition NodeView.h:44
@ VISIBILITY_COL
Definition NodeView.h:43
T kisGrowRect(const T &rect, U offset)
Definition kis_global.h:186
QIcon loadIcon(const QString &name)
QColor blendColors(const QColor &c1, const QColor &c2, qreal r1)
void renderExactRect(QPainter *p, const QRect &rc)
auto mem_equal_to(MemTypeNoRef Class::*ptr, MemType &&value)
mem_equal_to is an unary functor that compares a member of the object to a given value
Definition KisMpl.h:233