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