diff --git a/libs/ui/KisNodeDelegate.cpp b/libs/ui/KisNodeDelegate.cpp index c17e0771b5..06796f19a9 100644 --- a/libs/ui/KisNodeDelegate.cpp +++ b/libs/ui/KisNodeDelegate.cpp @@ -1,962 +1,1015 @@ /* Copyright (c) 2006 Gábor Lehel Copyright (c) 2008 Cyrille Berger Copyright (c) 2011 José Luis Vergara This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_config.h" #include "KisNodeDelegate.h" #include "kis_node_model.h" #include "KisNodeToolTip.h" #include "KisNodeView.h" #include "KisPart.h" #include "input/kis_input_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_node_view_color_scheme.h" #include "kis_icon_utils.h" #include "kis_layer_properties_icons.h" #include "krita_utils.h" #include "kis_config_notifier.h" typedef KisBaseNode::Property* OptionalProperty; #include class KisNodeDelegate::Private { public: Private() : view(0), edit(0) { } KisNodeView *view; QPointer edit; KisNodeToolTip tip; QColor checkersColor1; QColor checkersColor2; QList rightmostProperties(const KisBaseNode::PropertyList &props) const; int numProperties(const QModelIndex &index) const; OptionalProperty findProperty(KisBaseNode::PropertyList &props, const OptionalProperty &refProp) const; OptionalProperty findVisibilityProperty(KisBaseNode::PropertyList &props) const; void toggleProperty(KisBaseNode::PropertyList &props, OptionalProperty prop, bool controlPressed, const QModelIndex &index); }; KisNodeDelegate::KisNodeDelegate(KisNodeView *view, QObject *parent) : QAbstractItemDelegate(parent) , d(new Private) { d->view = view; QApplication::instance()->installEventFilter(this); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); slotConfigChanged(); } KisNodeDelegate::~KisNodeDelegate() { delete d; } QSize KisNodeDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index); KisNodeViewColorScheme scm; return QSize(option.rect.width(), scm.rowHeight()); } void KisNodeDelegate::paint(QPainter *p, const QStyleOptionViewItem &o, const QModelIndex &index) const { p->save(); { QStyleOptionViewItem option = getOptions(o, index); QStyle *style = option.widget ? option.widget->style() : QApplication::style(); style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, p, option.widget); bool shouldGrayOut = index.data(KisNodeModel::ShouldGrayOutRole).toBool(); if (shouldGrayOut) { option.state &= ~QStyle::State_Enabled; } p->setFont(option.font); - drawColorLabel(p, option, index); drawFrame(p, option, index); drawThumbnail(p, option, index); - drawText(p, option, index); + drawText(p, option, index); // BUG: Creating group moves things around (RTL-layout alignment) drawIcons(p, option, index); - drawVisibilityIconHijack(p, option, index); + drawVisibilityIconHijack(p, option, index); // TODO hide when dragging drawDecoration(p, option, index); drawExpandButton(p, option, index); drawBranch(p, option, index); drawProgressBar(p, option, index); } p->restore(); } -void KisNodeDelegate::drawBranch(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { - Q_UNUSED(index); +void KisNodeDelegate::drawBranch(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QModelIndex tmp = index.parent(); + + // there is no indention if we have no parent group, so don't draw a branch + if (!tmp.isValid()) return; KisNodeViewColorScheme scm; - const QPoint base = scm.relThumbnailRect().translated(option.rect.topLeft()).topLeft() - QPoint( scm.indentation(), 0); - // there is no indention if we are starting negative, so don't draw a branch - if (base.x() < 0) { - return; + int rtlNum = (option.direction == Qt::RightToLeft) ? 1 : -1; + + QRect baseRect = scm.relThumbnailRect(); + + // Move to current index + baseRect.moveTop(option.rect.topLeft().y()); + // Move to correct location. + if (option.direction == Qt::RightToLeft) { + baseRect.moveLeft(option.rect.topRight().x()); + } else { + baseRect.moveRight(option.rect.topLeft().x()); } + QPoint base = baseRect.adjusted(rtlNum*scm.indentation(), 0, + rtlNum*scm.indentation(), 0).center() + QPoint(0, scm.iconSize()/4); QPen oldPen = p->pen(); const qreal oldOpacity = p->opacity(); // remember previous opacity p->setOpacity(1.0); QColor color = scm.gridColor(option, d->view); QColor bgColor = option.state & QStyle::State_Selected ? qApp->palette().color(QPalette::Base) : qApp->palette().color(QPalette::Text); color = KritaUtils::blendColors(color, bgColor, 0.9); - // TODO: if we are a mask type, use dotted lines for the branch style // p->setPen(QPen(p->pen().color(), 2, Qt::DashLine, Qt::RoundCap, Qt::RoundJoin)); p->setPen(QPen(color, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); - QPoint p2 = base + QPoint(scm.iconSize() - scm.decorationMargin()*2, scm.iconSize()*0.45); - QPoint p3 = base + QPoint(scm.iconSize() - scm.decorationMargin()*2, scm.iconSize()); - QPoint p4 = base + QPoint(scm.iconSize()*1.4, scm.iconSize()); - p->drawLine(p2, p3); - p->drawLine(p3, p4); - - + QPoint p2 = base - QPoint(rtlNum*(scm.iconSize()/2), 0); + QPoint p3 = base - QPoint(0, scm.iconSize()/2); + p->drawLine(base, p2); + p->drawLine(base, p3); // draw parent lines (keep drawing until x position is less than 0 - QPoint p5 = p2 - QPoint(scm.indentation(), 0); - QPoint p6 = p3 - QPoint(scm.indentation(), 0); - - QPoint parentBase1 = p5; - QPoint parentBase2 = p6; + QPoint parentBase1 = base + QPoint(rtlNum*scm.indentation(), 0); + QPoint parentBase2 = p3 + QPoint(rtlNum*scm.indentation(), 0); // indent lines needs to be very subtle to avoid making the docker busy looking color = KritaUtils::blendColors(color, bgColor, 0.9); // makes it a little lighter than L lines p->setPen(QPen(color, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); - while (parentBase1.x() > scm.visibilityColumnWidth()) { - p->drawLine(parentBase1, parentBase2); - - parentBase1 = parentBase1 - QPoint(scm.indentation(), 0); - parentBase2 = parentBase2 - QPoint(scm.indentation(), 0); + if (tmp.isValid()) { + tmp = tmp.parent(); // Ignore the first group as it was already painted } + while (tmp.isValid()) { + p->drawLine(parentBase1, parentBase2); + parentBase1 += QPoint(rtlNum*scm.indentation(), 0); + parentBase2 += QPoint(rtlNum*scm.indentation(), 0); - + tmp = tmp.parent(); + } p->setPen(oldPen); p->setOpacity(oldOpacity); } void KisNodeDelegate::drawColorLabel(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { KisNodeViewColorScheme scm; const int label = index.data(KisNodeModel::ColorLabelIndexRole).toInt(); QColor color = scm.colorLabel(label); if (color.alpha() <= 0) return; QColor bgColor = qApp->palette().color(QPalette::Base); - color = KritaUtils::blendColors(color, bgColor, 0.3); + if ((option.state & QStyle::State_MouseOver) && !(option.state & QStyle::State_Selected)) { + color = KritaUtils::blendColors(color, bgColor, 0.6); + } else { + color = KritaUtils::blendColors(color, bgColor, 0.3); + } + + QRect optionRect = option.rect.adjusted(0, 0, scm.indentation(), 0); + if (option.state & QStyle::State_Selected) { + optionRect = iconsRect(option, index); + } - const QRect rect = option.state & QStyle::State_Selected ? - iconsRect(option, index) : - option.rect.adjusted(-scm.indentation(), 0, 0, 0); + if (option.direction == Qt::RightToLeft) { + optionRect.moveLeft(option.rect.topLeft().x()); + } else { + optionRect.moveRight(option.rect.topRight().x()); + } - p->fillRect(rect, color); + p->fillRect(optionRect, color); } void KisNodeDelegate::drawFrame(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { KisNodeViewColorScheme scm; QPen oldPen = p->pen(); p->setPen(scm.gridColor(option, d->view)); - const QPoint base = option.rect.topLeft(); + const QRect visibilityRect = visibilityClickRect(option, index); + const QRect thumbnailRect = thumbnailClickRect(option, index); + const QRect decorationRect = decorationClickRect(option, index); + const QRect iconsRectR = iconsRect(option, index); - QPoint p2 = base + QPoint(-scm.indentation() - 1, 0); - QPoint p3 = base + QPoint(2 * scm.decorationMargin() + scm.decorationSize(), 0); - QPoint p4 = base + QPoint(-1, 0); - QPoint p5(iconsRect(option, - index).left() - 1, base.y()); - QPoint p6(option.rect.right(), base.y()); - - QPoint v(0, option.rect.height()); + const float topY = thumbnailRect.topLeft().y(); + const float bottomY = thumbnailRect.bottomLeft().y(); + QPoint bottomLeftPoint; + QPoint bottomRightPoint; + if (option.direction == Qt::RightToLeft) { + bottomLeftPoint = iconsRectR.bottomLeft(); + bottomRightPoint = visibilityRect.bottomRight(); + } else { + bottomLeftPoint = visibilityRect.bottomLeft(); + bottomRightPoint = iconsRectR.bottomRight(); + } - // draw a line that goes the length of the entire frame. one for the - // top, and one for the bottom - QPoint pTopLeft(0, option.rect.topLeft().y()); - QPoint pTopRight(option.rect.bottomRight().x(),option.rect.topLeft().y() ); - p->drawLine(pTopLeft, pTopRight); + // bottom running horizontal line + p->drawLine(bottomLeftPoint.x(), bottomY, + bottomRightPoint.x(), bottomY); - QPoint pBottomLeft(0, option.rect.topLeft().y() + scm.rowHeight()); - QPoint pBottomRight(option.rect.bottomRight().x(),option.rect.topLeft().y() + scm.rowHeight() ); - p->drawLine(pBottomLeft, pBottomRight); + // visiblity icon vertical line - left + p->drawLine(visibilityRect.topLeft().x()-1, topY, + visibilityRect.bottomLeft().x()-1, bottomY); + // visiblity icon vertical line - right + p->drawLine(visibilityRect.topRight().x()+1, topY, + visibilityRect.bottomRight().x()+1, bottomY); - const bool paintForParent = - index.parent().isValid() && - !index.row(); + // thumbnail vertical line - left + p->drawLine(thumbnailRect.topLeft().x(), topY, + thumbnailRect.bottomLeft().x(), bottomY); - if (paintForParent) { - QPoint p1(-2 * scm.indentation() - 1, 0); - p1 += base; - p->drawLine(p1, p2); - } + // thumbnail vertical line - right + p->drawLine(thumbnailRect.topRight().x(), topY, + thumbnailRect.bottomRight().x(), bottomY); + // decoration vertical line - left + p->drawLine(decorationRect.topLeft().x(), topY, + decorationRect.bottomLeft().x(), bottomY); - QPoint k0(0, base.y()); - QPoint k1(1 * scm.border() + 2 * scm.visibilityMargin() + scm.visibilitySize(), base.y()); - p->drawLine(k0, k1); - p->drawLine(k0 + v, k1 + v); - p->drawLine(k0, k0 + v); - p->drawLine(k1, k1 + v); + // decoration vertical line - right + p->drawLine(decorationRect.topRight().x(), topY, + decorationRect.bottomRight().x(), bottomY); - p->drawLine(p2, p6); - p->drawLine(p2 + v, p6 + v); - p->drawLine(p2, p2 + v); - p->drawLine(p3, p3 + v); - p->drawLine(p4, p4 + v); - p->drawLine(p5, p5 + v); - p->drawLine(p6, p6 + v); + // icons' lines are drawn by drawIcons //// For debugging purposes only - //p->setPen(Qt::blue); - //KritaUtils::renderExactRect(p, iconsRect(option, index)); + p->setPen(Qt::blue); + //KritaUtils::renderExactRect(p, iconsRectR); //KritaUtils::renderExactRect(p, textRect(option, index)); - //KritaUtils::renderExactRect(p, scm.relThumbnailRect().translated(option.rect.topLeft())); + //KritaUtils::renderExactRect(p, visibilityRect); p->setPen(oldPen); } QRect KisNodeDelegate::thumbnailClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index); + KisNodeViewColorScheme scm; - int steps = 0; - QModelIndex tmp = index.parent(); - while (tmp.isValid()) { - steps++; - tmp = tmp.parent(); + QRect rc = scm.relThumbnailRect(); + + // Move to current index + rc.moveTop(option.rect.topLeft().y()); + // Move to correct location. + if (option.direction == Qt::RightToLeft) { + rc.moveLeft(option.rect.topRight().x()); + } else { + rc.moveRight(option.rect.topLeft().x()); } - KisNodeViewColorScheme scm; - return QRect(scm.border() + - 2 * scm.visibilityMargin() + scm.visibilitySize() + - scm.border() + steps * scm.indentation(), - scm.border() + option.rect.top(), - 2 * scm.thumbnailMargin() + scm.thumbnailSize(), - scm.rowHeight() - scm.border()); + return rc; } void KisNodeDelegate::drawThumbnail(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { KisNodeViewColorScheme scm; const int thumbSize = scm.thumbnailSize(); const qreal oldOpacity = p->opacity(); // remember previous opacity QImage img = index.data(int(KisNodeModel::BeginThumbnailRole) + thumbSize).value(); if (!(option.state & QStyle::State_Enabled)) { p->setOpacity(0.35); } - QRect fitRect = scm.relThumbnailRect().translated(option.rect.topLeft()); - - QPoint offset; - offset.setX((fitRect.width() - img.width()) / 2); - offset.setY((fitRect.height() - img.height()) / 2); - offset += fitRect.topLeft(); + QRect fitRect = thumbnailClickRect(option, index); + // Shrink to icon rect + fitRect = kisGrowRect(fitRect, -(scm.thumbnailMargin()+scm.border())); // paint in a checkerboard pattern behind the layer contents to represent transparent const int step = scm.thumbnailSize() / 6; QImage checkers(2 * step, 2 * step, QImage::Format_ARGB32); QPainter gc(&checkers); gc.fillRect(QRect(0, 0, step, step), d->checkersColor1); gc.fillRect(QRect(step, 0, step, step), d->checkersColor2); gc.fillRect(QRect(step, step, step, step), d->checkersColor1); gc.fillRect(QRect(0, step, step, step), d->checkersColor2); QBrush brush(checkers); - p->setBrushOrigin(offset); - p->fillRect(img.rect().translated(offset), brush); + p->fillRect(fitRect, brush); - p->drawImage(offset, img); + p->drawImage(fitRect, img); p->setOpacity(oldOpacity); // restore old opacity - QRect borderRect = kisGrowRect(img.rect(), 1).translated(offset); + QRect borderRect = kisGrowRect(fitRect, 1); KritaUtils::renderExactRect(p, borderRect, scm.gridColor(option, d->view)); } QRect KisNodeDelegate::iconsRect(const QStyleOptionViewItem &option, const QModelIndex &index) const { KisNodeViewColorScheme scm; int propCount = d->numProperties(index); const int iconsWidth = propCount * (scm.iconSize() + 2 * scm.iconMargin()) + - (propCount - 1) * scm.border(); - - const int x = option.rect.x() + option.rect.width() - - (iconsWidth + scm.border()); - const int y = option.rect.y() + scm.border(); + (propCount + 1) * scm.border(); + + QRect fitRect = QRect(0, 0, + iconsWidth, scm.rowHeight() - scm.border()); + // Move to current index + fitRect.moveTop(option.rect.topLeft().y()); + // Move to correct location. + if (option.direction == Qt::RightToLeft) { + fitRect.moveLeft(option.rect.topLeft().x()); + } else { + fitRect.moveRight(option.rect.topRight().x()); + } - return QRect(x, y, - iconsWidth, - scm.rowHeight() - scm.border()); + return fitRect; } QRect KisNodeDelegate::textRect(const QStyleOptionViewItem &option, const QModelIndex &index) const { KisNodeViewColorScheme scm; static QFont f; static int minbearing = 1337 + 666; //can be 0 or negative, 2003 is less likely if (minbearing == 2003 || f != option.font) { f = option.font; //getting your bearings can be expensive, so we cache them minbearing = option.fontMetrics.minLeftBearing() + option.fontMetrics.minRightBearing(); } - const int decorationOffset = - 2 * scm.border() + - 2 * scm.decorationMargin() + - scm.decorationSize(); + const QRect decoRect = decorationClickRect(option, index); + const QRect iconRect = iconsRect(option, index); - const int width = - iconsRect(option, index).left() - option.rect.x() - - scm.border() + minbearing - decorationOffset; + QRect rc = QRect((option.direction == Qt::RightToLeft) ? iconRect.topRight() : decoRect.topRight(), + (option.direction == Qt::RightToLeft) ? decoRect.bottomLeft() : iconRect.bottomLeft()); + rc.adjust(-(scm.border()+minbearing), 0, + (scm.border()+minbearing), 0); - return QRect(option.rect.x() - minbearing + decorationOffset, - option.rect.y() + scm.border(), - width, - scm.rowHeight() - scm.border()); + return rc; } void KisNodeDelegate::drawText(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { KisNodeViewColorScheme scm; - const QRect rc = textRect(option, index) - .adjusted(scm.textMargin(), 0, -scm.textMargin(), 0); + const QRect rc = textRect(option, index).adjusted(scm.textMargin(), 0, + -scm.textMargin(), 0); QPen oldPen = p->pen(); const qreal oldOpacity = p->opacity(); // remember previous opacity p->setPen(option.palette.color(QPalette::Active,QPalette::Text )); - - if (!(option.state & QStyle::State_Enabled)) { p->setOpacity(0.55); } - const QString text = index.data(Qt::DisplayRole).toString(); - const QString elided = elidedText(p->fontMetrics(), rc.width(), Qt::ElideRight, text); + const QString elided = p->fontMetrics().elidedText(text, Qt::ElideRight, rc.width()); p->drawText(rc, Qt::AlignLeft | Qt::AlignVCenter, elided); p->setPen(oldPen); // restore pen settings p->setOpacity(oldOpacity); } QList KisNodeDelegate::Private::rightmostProperties(const KisBaseNode::PropertyList &props) const { QList list; QList prependList; list << OptionalProperty(0); list << OptionalProperty(0); list << OptionalProperty(0); KisBaseNode::PropertyList::const_iterator it = props.constBegin(); KisBaseNode::PropertyList::const_iterator end = props.constEnd(); for (; it != end; ++it) { if (!it->isMutable) continue; if (it->id == KisLayerPropertiesIcons::visible.id()) { // noop... } else if (it->id == KisLayerPropertiesIcons::locked.id()) { list[0] = OptionalProperty(&(*it)); } else if (it->id == KisLayerPropertiesIcons::inheritAlpha.id()) { list[1] = OptionalProperty(&(*it)); } else if (it->id == KisLayerPropertiesIcons::alphaLocked.id()) { list[2] = OptionalProperty(&(*it)); } else { prependList.prepend(OptionalProperty(&(*it))); } } { QMutableListIterator i(prependList); i.toBack(); while (i.hasPrevious()) { OptionalProperty val = i.previous(); int emptyIndex = list.lastIndexOf(0); if (emptyIndex < 0) break; list[emptyIndex] = val; i.remove(); } } return prependList + list; } int KisNodeDelegate::Private::numProperties(const QModelIndex &index) const { KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value(); QList realProps = rightmostProperties(props); return realProps.size(); } OptionalProperty KisNodeDelegate::Private::findProperty(KisBaseNode::PropertyList &props, const OptionalProperty &refProp) const { KisBaseNode::PropertyList::iterator it = props.begin(); KisBaseNode::PropertyList::iterator end = props.end(); for (; it != end; ++it) { if (it->id == refProp->id) { return &(*it); } } return 0; } OptionalProperty KisNodeDelegate::Private::findVisibilityProperty(KisBaseNode::PropertyList &props) const { KisBaseNode::PropertyList::iterator it = props.begin(); KisBaseNode::PropertyList::iterator end = props.end(); for (; it != end; ++it) { if (it->id == KisLayerPropertiesIcons::visible.id()) { return &(*it); } } return 0; } void KisNodeDelegate::drawIcons(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { KisNodeViewColorScheme scm; - const QRect r = iconsRect(option, index); + const QRect rc = iconsRect(option, index); QTransform oldTransform = p->transform(); QPen oldPen = p->pen(); - p->setTransform(QTransform::fromTranslate(r.x(), r.y())); + p->setTransform(QTransform::fromTranslate(rc.x(), rc.y())); p->setPen(scm.gridColor(option, d->view)); int x = 0; const int y = (scm.rowHeight() - scm.border() - scm.iconSize()) / 2; KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value(); QList realProps = d->rightmostProperties(props); + if (option.direction == Qt::RightToLeft) { + std::reverse(realProps.begin(), realProps.end()); + } + Q_FOREACH (OptionalProperty prop, realProps) { + if (option.direction == Qt::LeftToRight) + p->drawLine(x, 0, x, scm.rowHeight() - scm.border()); + x += scm.iconMargin(); if (prop) { QIcon icon = prop->state.toBool() ? prop->onIcon : prop->offIcon; bool fullColor = prop->state.toBool() && option.state & QStyle::State_Enabled; - const qreal oldOpacity = p->opacity(); // remember previous opacity - + const qreal oldOpacity = p->opacity(); // remember previous opacity if (fullColor) { p->setOpacity(1.0); } else { p->setOpacity(0.35); } p->drawPixmap(x, y, icon.pixmap(scm.iconSize(), QIcon::Normal)); p->setOpacity(oldOpacity); // restore old opacity - - } x += scm.iconSize() + scm.iconMargin(); - p->drawLine(x, 0, x, scm.rowHeight() - scm.border()); + + if (!(option.direction == Qt::LeftToRight)) + p->drawLine(x, 0, x, scm.rowHeight() - scm.border()); x += scm.border(); } p->setTransform(oldTransform); p->setPen(oldPen); } QRect KisNodeDelegate::visibilityClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index); KisNodeViewColorScheme scm; - return QRect(scm.border(), scm.border() + option.rect.top(), - 2 * scm.visibilityMargin() + scm.visibilitySize(), - scm.rowHeight() - scm.border()); + + QRect rc = scm.relVisibilityRect(); + rc.setHeight(scm.rowHeight()); + + // Move to current index + rc.moveCenter(option.rect.center()); + // Move to correct location. + if (option.direction == Qt::RightToLeft) { + // HACK: Without the -5, the right edge is outside the view + rc.moveRight(d->view->width()-5); + } else { + rc.moveLeft(0); + } + + return rc; } QRect KisNodeDelegate::decorationClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const { - Q_UNUSED(option); Q_UNUSED(index); KisNodeViewColorScheme scm; - QRect realVisualRect = d->view->originalVisualRect(index); + QRect rc = scm.relDecorationRect(); + + // Move to current index + rc.moveTop(option.rect.topLeft().y()); + rc.setHeight(scm.rowHeight()); + // Move to correct location. + if (option.direction == Qt::RightToLeft) { + rc.moveRight(option.rect.topRight().x()); + } else { + rc.moveLeft(option.rect.topLeft().x()); + } - return QRect(realVisualRect.left(), scm.border() + realVisualRect.top(), - 2 * scm.decorationMargin() + scm.decorationSize(), - scm.rowHeight() - scm.border()); + return rc; } void KisNodeDelegate::drawVisibilityIconHijack(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { /** * Small hack Alert: * * Here wepaint over the area that sits basically outside our layer's * row. Anyway, just update it later... */ KisNodeViewColorScheme scm; KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value(); OptionalProperty prop = d->findVisibilityProperty(props); if (!prop) return; - const int x = scm.border() + scm.visibilityMargin(); - const int y = option.rect.top() + (scm.rowHeight() - scm.border() - scm.visibilitySize()) / 2; + QRect fitRect = visibilityClickRect(option, index); + // Shrink to icon rect + fitRect = kisGrowRect(fitRect, -(scm.visibilityMargin()+scm.border())); QIcon icon = prop->state.toBool() ? prop->onIcon : prop->offIcon; // if we are not showing the layer, make the icon slightly transparent like other inactive icons const qreal oldOpacity = p->opacity(); - if (prop->state.toBool() == true) { - p->setOpacity(1.0); - } - else { + + if (!prop->state.toBool()) { p->setOpacity(0.35); } - p->drawPixmap(x, y, icon.pixmap(scm.visibilitySize(), QIcon::Normal)); + p->drawPixmap(fitRect.x(), fitRect.center().y() - scm.visibilitySize() / 2, + icon.pixmap(scm.visibilitySize(), QIcon::Normal)); p->setOpacity(oldOpacity); //// For debugging purposes only - // p->save(); - // p->setPen(Qt::blue); - // KritaUtils::renderExactRect(p, visibilityClickRect(option, index)); - // p->restore(); +// // // p->save(); +// // // p->setPen(Qt::blue); +// // // KritaUtils::renderExactRect(p, visibilityClickRect(option, index)); +// // // p->restore(); } void KisNodeDelegate::drawDecoration(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { KisNodeViewColorScheme scm; QIcon icon = index.data(Qt::DecorationRole).value(); if (!icon.isNull()) { - QPixmap pixmap = - icon.pixmap(scm.decorationSize(), - (option.state & QStyle::State_Enabled) ? - QIcon::Normal : QIcon::Disabled); + QPixmap pixmap = icon.pixmap(scm.decorationSize(), + (option.state & QStyle::State_Enabled) ? + QIcon::Normal : QIcon::Disabled); + + QRect rc = decorationClickRect(option, index); + + // Shrink to icon rect + rc = kisGrowRect(rc, -(scm.decorationMargin()+scm.border())); - const QRect rc = scm.relDecorationRect().translated(option.rect.topLeft()); const qreal oldOpacity = p->opacity(); // remember previous opacity if (!(option.state & QStyle::State_Enabled)) { p->setOpacity(0.35); } - - p->drawPixmap(rc.topLeft(), pixmap); + p->drawPixmap(rc.topLeft()-QPoint(0, 1), pixmap); p->setOpacity(oldOpacity); // restore old opacity } } void KisNodeDelegate::drawExpandButton(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index); KisNodeViewColorScheme scm; - QRect rc = scm.relExpandButtonRect().translated(option.rect.topLeft()); - rc = kisGrowRect(rc, 0); - if (!(option.state & QStyle::State_Children)) { - return; - } + QRect rc = decorationClickRect(option, index); + + // Move to current index +// rc.moveTop(option.rect.topLeft().y()); + // Shrink to icon rect + rc = kisGrowRect(rc, -(scm.decorationMargin()+scm.border())); + + if (!(option.state & QStyle::State_Children)) return; + - QString iconName = option.state & QStyle::State_Open ? "arrow-down" : "arrow-right"; + QString iconName = option.state & QStyle::State_Open ? + "arrow-down" : ((option.direction == Qt::RightToLeft) ? "arrow-left" : "arrow-right"); QIcon icon = KisIconUtils::loadIcon(iconName); QPixmap pixmap = icon.pixmap(rc.width(), (option.state & QStyle::State_Enabled) ? QIcon::Normal : QIcon::Disabled); - p->drawPixmap(rc.topLeft(), pixmap); + p->drawPixmap(rc.bottomLeft()-QPoint(0, scm.decorationSize()-1), pixmap); } void KisNodeDelegate::Private::toggleProperty(KisBaseNode::PropertyList &props, OptionalProperty clickedProperty, bool controlPressed, const QModelIndex &index) { QAbstractItemModel *model = view->model(); // Using Ctrl+click to enter stasis if (controlPressed && clickedProperty->canHaveStasis) { // STEP 0: Prepare to Enter or Leave control key stasis quint16 numberOfLeaves = model->rowCount(index.parent()); QModelIndex eachItem; // STEP 1: Go. if (clickedProperty->isInStasis == false) { // Enter /* Make every leaf of this node go State = False, saving the old property value to stateInStasis */ for (quint16 i = 0; i < numberOfLeaves; ++i) { // Foreach leaf in the node (index.parent()) eachItem = model->index(i, 1, index.parent()); // The entire property list has to be altered because model->setData cannot set individual properties KisBaseNode::PropertyList eachPropertyList = eachItem.data(KisNodeModel::PropertiesRole).value(); OptionalProperty prop = findProperty(eachPropertyList, clickedProperty); if (!prop) continue; prop->stateInStasis = prop->state.toBool(); prop->state = eachItem == index; prop->isInStasis = true; model->setData(eachItem, QVariant::fromValue(eachPropertyList), KisNodeModel::PropertiesRole); } for (quint16 i = 0; i < numberOfLeaves; ++i) { // Foreach leaf in the node (index.parent()) eachItem = model->index(i, 1, index.parent()); KisBaseNode::PropertyList eachPropertyList = eachItem.data(KisNodeModel::PropertiesRole).value(); OptionalProperty prop = findProperty(eachPropertyList, clickedProperty); if (!prop) continue; } } else { // Leave /* Make every leaf of this node go State = stateInStasis */ for (quint16 i = 0; i < numberOfLeaves; ++i) { eachItem = model->index(i, 1, index.parent()); // The entire property list has to be altered because model->setData cannot set individual properties KisBaseNode::PropertyList eachPropertyList = eachItem.data(KisNodeModel::PropertiesRole).value(); OptionalProperty prop = findProperty(eachPropertyList, clickedProperty); if (!prop) continue; prop->state = prop->stateInStasis; prop->isInStasis = false; model->setData(eachItem, QVariant::fromValue(eachPropertyList), KisNodeModel::PropertiesRole); } } } else { clickedProperty->state = !clickedProperty->state.toBool(); model->setData(index, QVariant::fromValue(props), KisNodeModel::PropertiesRole); } } bool KisNodeDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) { KisNodeViewColorScheme scm; + QStyleOptionViewItem newOption = option; + newOption.rect = d->view->originalVisualRect(index); + if ((event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick) && (index.flags() & Qt::ItemIsEnabled)) { QMouseEvent *mouseEvent = static_cast(event); /** * Small hack Alert: * * Here we handle clicking even when it happened outside * the rectangle of the current index. The point is, we * use some virtual scroling offset to move the tree to the * right of the visibility icon. So the icon itself is placed * in an empty area that doesn't belong to any index. But we still * handle it. */ - const QRect iconsRect = this->iconsRect(option, index); - const bool iconsClicked = iconsRect.isValid() && - iconsRect.contains(mouseEvent->pos()); - - const QRect visibilityRect = visibilityClickRect(option, index); + const QRect visibilityRect = visibilityClickRect(newOption, index); const bool visibilityClicked = visibilityRect.isValid() && visibilityRect.contains(mouseEvent->pos()); - const QRect decorationRect = decorationClickRect(option, index); + const QRect thumbnailRect = thumbnailClickRect(newOption, index); + const bool thumbnailClicked = thumbnailRect.isValid() && + thumbnailRect.contains(mouseEvent->pos()); + + const QRect decorationRect = decorationClickRect(newOption, index); const bool decorationClicked = decorationRect.isValid() && decorationRect.contains(mouseEvent->pos()); - const bool leftButton = mouseEvent->buttons() & Qt::LeftButton; + const QRect iconsRect = this->iconsRect(newOption, index); + const bool iconsClicked = iconsRect.isValid() && + iconsRect.contains(mouseEvent->pos()); - const QRect thumbnailRect = thumbnailClickRect(option, index); - const bool thumbnailClicked = thumbnailRect.contains(mouseEvent->pos()); + const bool leftButton = mouseEvent->buttons() & Qt::LeftButton; if (leftButton && iconsClicked) { KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value(); QList realProps = d->rightmostProperties(props); + if (newOption.direction == Qt::RightToLeft) { + std::reverse(realProps.begin(), realProps.end()); + } const int numProps = realProps.size(); const int iconWidth = scm.iconSize() + 2 * scm.iconMargin() + scm.border(); const int xPos = mouseEvent->pos().x() - iconsRect.left(); const int clickedIcon = xPos / iconWidth; const int distToBorder = qMin(xPos % iconWidth, iconWidth - xPos % iconWidth); if (iconsClicked && clickedIcon >= 0 && clickedIcon < numProps && distToBorder > scm.iconMargin()) { OptionalProperty clickedProperty = realProps[clickedIcon]; if (!clickedProperty) return false; d->toggleProperty(props, clickedProperty, mouseEvent->modifiers() == Qt::ControlModifier, index); return true; } } else if (leftButton && visibilityClicked) { KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value(); OptionalProperty clickedProperty = d->findVisibilityProperty(props); if (!clickedProperty) return false; d->toggleProperty(props, clickedProperty, mouseEvent->modifiers() == Qt::ControlModifier, index); return true; } else if (leftButton && decorationClicked) { bool isExpandable = model->hasChildren(index); if (isExpandable) { bool isExpanded = d->view->isExpanded(index); d->view->setExpanded(index, !isExpanded); } return true; } else if (leftButton && thumbnailClicked) { bool hasCorrectModifier = false; SelectionAction action = SELECTION_REPLACE; if (mouseEvent->modifiers() == Qt::ControlModifier) { action = SELECTION_REPLACE; hasCorrectModifier = true; } else if (mouseEvent->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) { action = SELECTION_ADD; hasCorrectModifier = true; } else if (mouseEvent->modifiers() == (Qt::ControlModifier | Qt::AltModifier)) { action = SELECTION_SUBTRACT; hasCorrectModifier = true; } else if (mouseEvent->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier | Qt::AltModifier)) { action = SELECTION_INTERSECT; hasCorrectModifier = true; } if (hasCorrectModifier) { model->setData(index, QVariant(int(action)), KisNodeModel::SelectOpaqueRole); - return true; } + return true; //If not here then the item is !expanded when reaching return false; } if (mouseEvent->button() == Qt::LeftButton && mouseEvent->modifiers() == Qt::AltModifier) { d->view->setCurrentIndex(index); model->setData(index, true, KisNodeModel::AlternateActiveRole); return true; } } else if (event->type() == QEvent::ToolTip) { if (!KisConfig(true).hidePopups()) { QHelpEvent *helpEvent = static_cast(event); - d->tip.showTip(d->view, helpEvent->pos(), option, index); + d->tip.showTip(d->view, helpEvent->pos(), newOption, index); } return true; } else if (event->type() == QEvent::Leave) { d->tip.hide(); } return false; } QWidget *KisNodeDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem&, const QModelIndex&) const { d->edit = new QLineEdit(parent); d->edit->setFocusPolicy(Qt::StrongFocus); d->edit->installEventFilter(const_cast(this)); //hack? return d->edit; } void KisNodeDelegate::setEditorData(QWidget *widget, const QModelIndex &index) const { QLineEdit *edit = qobject_cast(widget); Q_ASSERT(edit); edit->setText(index.data(Qt::DisplayRole).toString()); } void KisNodeDelegate::setModelData(QWidget *widget, QAbstractItemModel *model, const QModelIndex &index) const { QLineEdit *edit = qobject_cast(widget); Q_ASSERT(edit); model->setData(index, edit->text(), Qt::DisplayRole); } void KisNodeDelegate::updateEditorGeometry(QWidget *widget, const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index); widget->setGeometry(option.rect); } // PROTECTED bool KisNodeDelegate::eventFilter(QObject *object, QEvent *event) { switch (event->type()) { case QEvent::MouseButtonPress: { if (d->edit) { QMouseEvent *me = static_cast(event); if (!QRect(d->edit->mapToGlobal(QPoint()), d->edit->size()).contains(me->globalPos())) { emit commitData(d->edit); emit closeEditor(d->edit); } } } break; case QEvent::KeyPress: { QLineEdit *edit = qobject_cast(object); if (edit && edit == d->edit) { QKeyEvent *ke = static_cast(event); switch (ke->key()) { case Qt::Key_Escape: emit closeEditor(edit); return true; case Qt::Key_Tab: emit commitData(edit); emit closeEditor(edit, EditNextItem); return true; case Qt::Key_Backtab: emit commitData(edit); emit closeEditor(edit, EditPreviousItem); return true; case Qt::Key_Return: case Qt::Key_Enter: emit commitData(edit); emit closeEditor(edit); return true; default: break; } } } break; case QEvent::ShortcutOverride : { QLineEdit *edit = qobject_cast(object); if (edit && edit == d->edit){ auto* key = static_cast(event); if (key->modifiers() == Qt::NoModifier){ switch (key->key()){ case Qt::Key_Escape: case Qt::Key_Tab: case Qt::Key_Backtab: case Qt::Key_Return: case Qt::Key_Enter: event->accept(); return true; default: break; } } } } break; case QEvent::FocusOut : { QLineEdit *edit = qobject_cast(object); if (edit && edit == d->edit) { emit commitData(edit); emit closeEditor(edit); } } default: break; } return QAbstractItemDelegate::eventFilter(object, event); } // PRIVATE QStyleOptionViewItem KisNodeDelegate::getOptions(const QStyleOptionViewItem &o, const QModelIndex &index) { QStyleOptionViewItem option = o; QVariant v = index.data(Qt::FontRole); if (v.isValid()) { option.font = v.value(); option.fontMetrics = QFontMetrics(option.font); } v = index.data(Qt::TextAlignmentRole); if (v.isValid()) option.displayAlignment = QFlag(v.toInt()); v = index.data(Qt::TextColorRole); if (v.isValid()) option.palette.setColor(QPalette::Text, v.value()); v = index.data(Qt::BackgroundColorRole); if (v.isValid()) option.palette.setColor(QPalette::Window, v.value()); return option; } void KisNodeDelegate::drawProgressBar(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const { QVariant value = index.data(KisNodeModel::ProgressRole); if (!value.isNull() && (value.toInt() >= 0 && value.toInt() <= 100)) { /// The progress bar will display under the layer name area. The bars have accurate data, so we /// probably don't need to also show the actual number for % complete KisNodeViewColorScheme scm; - const int width = textRect(option, index).width() + scm.iconSize()*2; + + const QRect thumbnailRect = thumbnailClickRect(option, index); + const QRect iconsRectR = iconsRect(option, index); const int height = 5; - const QPoint base = option.rect.bottomLeft() - QPoint(0, height ); - const QRect r = QRect(base.x(), base.y(), width, height); + const QRect rc = QRect( + ((option.direction == Qt::RightToLeft) ? iconsRectR.bottomRight() + : thumbnailRect.bottomRight()) - QPoint(0, height), + ((option.direction == Qt::RightToLeft) ? thumbnailRect.bottomLeft() + : iconsRectR.bottomLeft())); p->save(); { - p->setClipRect(r); + p->setClipRect(rc); QStyle* style = QApplication::style(); QStyleOptionProgressBar opt; opt.minimum = 0; opt.maximum = 100; opt.progress = value.toInt(); opt.textVisible = false; opt.textAlignment = Qt::AlignHCenter; opt.text = i18n("%1 %", opt.progress); - opt.rect = r; opt.orientation = Qt::Horizontal; opt.state = option.state; style->drawControl(QStyle::CE_ProgressBar, &opt, p, 0); } p->restore(); } } void KisNodeDelegate::slotConfigChanged() { KisConfig cfg(true); d->checkersColor1 = cfg.checkersColor1(); d->checkersColor2 = cfg.checkersColor2(); } void KisNodeDelegate::slotUpdateIcon() { KisLayerPropertiesIcons::instance()->updateIcons(); } diff --git a/libs/ui/KisNodeDelegate.h b/libs/ui/KisNodeDelegate.h index ad53f443e1..048eee631e 100644 --- a/libs/ui/KisNodeDelegate.h +++ b/libs/ui/KisNodeDelegate.h @@ -1,85 +1,89 @@ /* Copyright (c) 2006 Gábor Lehel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_DOCUMENT_SECTION_DELEGATE_H #define KIS_DOCUMENT_SECTION_DELEGATE_H #include class KisNodeView; class KisNodeModel; /** * See KisNodeModel and KisNodeView. * * A delegate provides the gui machinery, using Qt's model/view terminology. * This class is owned by KisNodeView to do the work of generating the * graphical representation of each item. */ class KisNodeDelegate: public QAbstractItemDelegate { Q_OBJECT public: explicit KisNodeDelegate(KisNodeView *view, QObject *parent = 0); ~KisNodeDelegate() override; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override; QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void setEditorData(QWidget *editor, const QModelIndex &index) const override; void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex& index) const override; void slotUpdateIcon(); protected: bool eventFilter(QObject *object, QEvent *event) override; private: typedef KisNodeModel Model; typedef KisNodeView View; class Private; Private* const d; static QStyleOptionViewItem getOptions(const QStyleOptionViewItem &option, const QModelIndex &index); void drawProgressBar(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const; void drawBranch(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const; void drawColorLabel(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const; void drawFrame(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const; + + QRect thumbnailClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const; void drawThumbnail(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const; + QRect iconsRect(const QStyleOptionViewItem &option, const QModelIndex &index) const; QRect textRect(const QStyleOptionViewItem &option, const QModelIndex &index) const; void drawText(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const; void drawIcons(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const; + QRect visibilityClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const; - QRect decorationClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const; - QRect thumbnailClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const; void drawVisibilityIconHijack(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const; + + QRect decorationClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const; void drawDecoration(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const; void drawExpandButton(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const; private Q_SLOTS: void slotConfigChanged(); }; #endif diff --git a/libs/ui/KisNodeView.cpp b/libs/ui/KisNodeView.cpp index af0dcb65d6..26a1ad4aaa 100644 --- a/libs/ui/KisNodeView.cpp +++ b/libs/ui/KisNodeView.cpp @@ -1,586 +1,592 @@ /* Copyright (c) 2006 Gábor Lehel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisNodeView.h" #include "KisNodePropertyAction_p.h" #include "KisNodeDelegate.h" #include "kis_node_view_visibility_delegate.h" #include "kis_node_model.h" #include "kis_signals_blocker.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_node_view_color_scheme.h" #ifdef HAVE_X11 #define DRAG_WHILE_DRAG_WORKAROUND #endif #ifdef DRAG_WHILE_DRAG_WORKAROUND #define DRAG_WHILE_DRAG_WORKAROUND_START() d->isDragging = true #define DRAG_WHILE_DRAG_WORKAROUND_STOP() d->isDragging = false #else #define DRAG_WHILE_DRAG_WORKAROUND_START() #define DRAG_WHILE_DRAG_WORKAROUND_STOP() #endif class Q_DECL_HIDDEN KisNodeView::Private { public: Private(KisNodeView* _q) : delegate(_q, _q) , mode(DetailedMode) #ifdef DRAG_WHILE_DRAG_WORKAROUND , isDragging(false) #endif { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group = config->group("NodeView"); mode = (DisplayMode) group.readEntry("NodeViewMode", (int)MinimalMode); } KisNodeDelegate delegate; DisplayMode mode; QPersistentModelIndex hovered; QPoint lastPos; #ifdef DRAG_WHILE_DRAG_WORKAROUND bool isDragging; #endif }; KisNodeView::KisNodeView(QWidget *parent) : QTreeView(parent) , m_draggingFlag(false) , d(new Private(this)) { setItemDelegateForColumn(0, &d->delegate); setMouseTracking(true); setSelectionBehavior(SelectRows); setDefaultDropAction(Qt::MoveAction); setVerticalScrollMode(QAbstractItemView::ScrollPerItem); setSelectionMode(QAbstractItemView::ExtendedSelection); header()->hide(); setDragEnabled(true); setDragDropMode(QAbstractItemView::DragDrop); setAcceptDrops(true); setDropIndicatorShown(true); { QScroller *scroller = KisKineticScroller::createPreconfiguredScroller(this); if (scroller) { connect(scroller, SIGNAL(stateChanged(QScroller::State)), this, SLOT(slotScrollerStateChanged(QScroller::State))); } } } KisNodeView::~KisNodeView() { delete d; } void KisNodeView::setDisplayMode(DisplayMode mode) { if (d->mode != mode) { d->mode = mode; KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group = config->group("NodeView"); group.writeEntry("NodeViewMode", (int)mode); scheduleDelayedItemsLayout(); } } KisNodeView::DisplayMode KisNodeView::displayMode() const { return d->mode; } void KisNodeView::addPropertyActions(QMenu *menu, const QModelIndex &index) { KisBaseNode::PropertyList list = index.data(KisNodeModel::PropertiesRole).value(); for (int i = 0, n = list.count(); i < n; ++i) { if (list.at(i).isMutable) { PropertyAction *a = new PropertyAction(i, list.at(i), index, menu); connect(a, SIGNAL(toggled(bool,QPersistentModelIndex,int)), this, SLOT(slotActionToggled(bool,QPersistentModelIndex,int))); menu->addAction(a); } } } void KisNodeView::updateNode(const QModelIndex &index) { dataChanged(index, index); } QItemSelectionModel::SelectionFlags KisNodeView::selectionCommand(const QModelIndex &index, const QEvent *event) const { /** * Qt has a bug: when we Ctrl+click on an item, the item's * selections gets toggled on mouse *press*, whereas usually it is * done on mouse *release*. Therefore the user cannot do a * Ctrl+D&D with the default configuration. This code fixes the * problem by manually returning QItemSelectionModel::NoUpdate * flag when the user clicks on an item and returning * QItemSelectionModel::Toggle on release. */ if (event && (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) && index.isValid()) { const QMouseEvent *mevent = static_cast(event); if (mevent->button() == Qt::RightButton && selectionModel()->selectedIndexes().contains(index)) { // Allow calling context menu for multiple layers return QItemSelectionModel::NoUpdate; } if (event->type() == QEvent::MouseButtonPress && (mevent->modifiers() & Qt::ControlModifier)) { return QItemSelectionModel::NoUpdate; } if (event->type() == QEvent::MouseButtonRelease && (mevent->modifiers() & Qt::ControlModifier)) { return QItemSelectionModel::Toggle; } } /** * Qt 5.6 has a bug: it reads global modifiers, not the ones * passed from event. So if you paste an item using Ctrl+V it'll * select multiple layers for you */ Qt::KeyboardModifiers globalModifiers = QApplication::keyboardModifiers(); if (!event && globalModifiers != Qt::NoModifier) { return QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows; } return QAbstractItemView::selectionCommand(index, event); } + QRect KisNodeView::visualRect(const QModelIndex &index) const { QRect rc = QTreeView::visualRect(index); - rc.setLeft(0); + if (layoutDirection() == Qt::RightToLeft) + rc.setRight(width()); + else + rc.setLeft(0); return rc; } QRect KisNodeView::originalVisualRect(const QModelIndex &index) const { return QTreeView::visualRect(index); } QModelIndex KisNodeView::indexAt(const QPoint &point) const { KisNodeViewColorScheme scm; QModelIndex index = QTreeView::indexAt(point); - if (!index.isValid() && point.x() < scm.visibilityColumnWidth()) { - index = QTreeView::indexAt(point + QPoint(scm.visibilityColumnWidth(), 0)); + if (!index.isValid()) { + // Middle is a good position for both LTR and RTL layouts + // First reset x, then get the x in the middle + index = QTreeView::indexAt(point - QPoint(point.x(), 0) + QPoint(width() / 2, 0)); } return index; } bool KisNodeView::viewportEvent(QEvent *e) { if (model()) { switch(e->type()) { case QEvent::MouseButtonPress: { DRAG_WHILE_DRAG_WORKAROUND_STOP(); const QPoint pos = static_cast(e)->pos(); d->lastPos = pos; if (!indexAt(pos).isValid()) { return QTreeView::viewportEvent(e); } QModelIndex index = model()->buddy(indexAt(pos)); if (d->delegate.editorEvent(e, model(), optionForIndex(index), index)) { return true; } } break; case QEvent::Leave: { QEvent e(QEvent::Leave); d->delegate.editorEvent(&e, model(), optionForIndex(d->hovered), d->hovered); d->hovered = QModelIndex(); } break; case QEvent::MouseMove: { #ifdef DRAG_WHILE_DRAG_WORKAROUND if (d->isDragging) { return false; } #endif const QPoint pos = static_cast(e)->pos(); QModelIndex hovered = indexAt(pos); if (hovered != d->hovered) { if (d->hovered.isValid()) { QEvent e(QEvent::Leave); d->delegate.editorEvent(&e, model(), optionForIndex(d->hovered), d->hovered); } if (hovered.isValid()) { QEvent e(QEvent::Enter); d->delegate.editorEvent(&e, model(), optionForIndex(hovered), hovered); } d->hovered = hovered; } /* This is a workaround for a bug in QTreeView that immediately begins a dragging action when the mouse lands on the decoration/icon of a different index and moves 1 pixel or more */ Qt::MouseButtons buttons = static_cast(e)->buttons(); if ((Qt::LeftButton | Qt::MidButton) & buttons) { if ((pos - d->lastPos).manhattanLength() > qApp->startDragDistance()) { return QTreeView::viewportEvent(e); } return true; } } break; case QEvent::ToolTip: { const QPoint pos = static_cast(e)->pos(); if (!indexAt(pos).isValid()) { return QTreeView::viewportEvent(e); } QModelIndex index = model()->buddy(indexAt(pos)); return d->delegate.editorEvent(e, model(), optionForIndex(index), index); } break; case QEvent::Resize: { scheduleDelayedItemsLayout(); break; } default: break; } } return QTreeView::viewportEvent(e); } void KisNodeView::contextMenuEvent(QContextMenuEvent *e) { QTreeView::contextMenuEvent(e); QModelIndex i = indexAt(e->pos()); if (model()) i = model()->buddy(i); showContextMenu(e->globalPos(), i); } void KisNodeView::showContextMenu(const QPoint &globalPos, const QModelIndex &index) { emit contextMenuRequested(globalPos, index); } void KisNodeView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) { QTreeView::currentChanged(current, previous); if (current != previous) { Q_ASSERT(!current.isValid() || current.model() == model()); model()->setData(current, true, KisNodeModel::ActiveRole); } } void KisNodeView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &/*roles*/) { QTreeView::dataChanged(topLeft, bottomRight); for (int x = topLeft.row(); x <= bottomRight.row(); ++x) { for (int y = topLeft.column(); y <= bottomRight.column(); ++y) { QModelIndex index = topLeft.sibling(x, y); if (index.data(KisNodeModel::ActiveRole).toBool()) { if (currentIndex() != index) { setCurrentIndex(index); } return; } } } } void KisNodeView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { QTreeView::selectionChanged(selected, deselected); emit selectionChanged(selectedIndexes()); } void KisNodeView::slotActionToggled(bool on, const QPersistentModelIndex &index, int num) { KisBaseNode::PropertyList list = index.data(KisNodeModel::PropertiesRole).value(); list[num].state = on; const_cast(index.model())->setData(index, QVariant::fromValue(list), KisNodeModel::PropertiesRole); } QStyleOptionViewItem KisNodeView::optionForIndex(const QModelIndex &index) const { QStyleOptionViewItem option = viewOptions(); option.rect = visualRect(index); if (index == currentIndex()) option.state |= QStyle::State_HasFocus; return option; } void KisNodeView::startDrag(Qt::DropActions supportedActions) { DRAG_WHILE_DRAG_WORKAROUND_START(); if (displayMode() == KisNodeView::ThumbnailMode) { const QModelIndexList indexes = selectionModel()->selectedIndexes(); if (!indexes.isEmpty()) { QMimeData *data = model()->mimeData(indexes); if (!data) { return; } QDrag *drag = new QDrag(this); drag->setPixmap(createDragPixmap()); drag->setMimeData(data); //m_dragSource = this; drag->exec(supportedActions); } } else { QTreeView::startDrag(supportedActions); } } QPixmap KisNodeView::createDragPixmap() const { const QModelIndexList selectedIndexes = selectionModel()->selectedIndexes(); Q_ASSERT(!selectedIndexes.isEmpty()); const int itemCount = selectedIndexes.count(); // If more than one item is dragged, align the items inside a // rectangular grid. The maximum grid size is limited to 4 x 4 items. int xCount = 2; int size = 96; if (itemCount > 9) { xCount = 4; size = KisIconUtils::SizeLarge; } else if (itemCount > 4) { xCount = 3; size = KisIconUtils::SizeHuge; } else if (itemCount < xCount) { xCount = itemCount; } int yCount = itemCount / xCount; if (itemCount % xCount != 0) { ++yCount; } if (yCount > xCount) { yCount = xCount; } // Draw the selected items into the grid cells QPixmap dragPixmap(xCount * size + xCount - 1, yCount * size + yCount - 1); dragPixmap.fill(Qt::transparent); QPainter painter(&dragPixmap); int x = 0; int y = 0; Q_FOREACH (const QModelIndex &selectedIndex, selectedIndexes) { const QImage img = selectedIndex.data(int(KisNodeModel::BeginThumbnailRole) + size).value(); painter.drawPixmap(x, y, QPixmap().fromImage(img.scaled(QSize(size, size), Qt::KeepAspectRatio, Qt::SmoothTransformation))); x += size + 1; if (x >= dragPixmap.width()) { x = 0; y += size + 1; } if (y >= dragPixmap.height()) { break; } } return dragPixmap; } void KisNodeView::resizeEvent(QResizeEvent * event) { KisNodeViewColorScheme scm; header()->setStretchLastSection(false); header()->setOffset(-scm.visibilityColumnWidth()); header()->resizeSection(0, event->size().width() - scm.visibilityColumnWidth()); setIndentation(scm.indentation()); QTreeView::resizeEvent(event); } void KisNodeView::paintEvent(QPaintEvent *event) { event->accept(); QTreeView::paintEvent(event); // Paint the line where the slide should go if (isDragging() && (displayMode() == KisNodeView::ThumbnailMode)) { QSize size(visualRect(model()->index(0, 0, QModelIndex())).width(), visualRect(model()->index(0, 0, QModelIndex())).height()); int numberRow = cursorPageIndex(); int scrollBarValue = verticalScrollBar()->value(); QPoint point1(0, numberRow * size.height() - scrollBarValue); QPoint point2(size.width(), numberRow * size.height() - scrollBarValue); QLineF line(point1, point2); QPainter painter(this->viewport()); QPen pen = QPen(palette().brush(QPalette::Highlight), 8); pen.setCapStyle(Qt::RoundCap); painter.setPen(pen); painter.setOpacity(0.8); painter.drawLine(line); } } void KisNodeView::drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const { Q_UNUSED(painter); Q_UNUSED(rect); Q_UNUSED(index); /** * Noop... Everything is going to be painted by KisNodeDelegate. * So this override basically disables painting of Qt's branch-lines. */ } void KisNodeView::dropEvent(QDropEvent *ev) { if (displayMode() == KisNodeView::ThumbnailMode) { setDraggingFlag(false); ev->accept(); clearSelection(); if (!model()) { return; } int newIndex = cursorPageIndex(); model()->dropMimeData(ev->mimeData(), ev->dropAction(), newIndex, -1, QModelIndex()); return; } QTreeView::dropEvent(ev); DRAG_WHILE_DRAG_WORKAROUND_STOP(); } int KisNodeView::cursorPageIndex() const { QSize size(visualRect(model()->index(0, 0, QModelIndex())).width(), visualRect(model()->index(0, 0, QModelIndex())).height()); int scrollBarValue = verticalScrollBar()->value(); QPoint cursorPosition = QWidget::mapFromGlobal(QCursor::pos()); int numberRow = (cursorPosition.y() + scrollBarValue) / size.height(); //If cursor is at the half button of the page then the move action is performed after the slide, otherwise it is //performed before the page if (abs((cursorPosition.y() + scrollBarValue) - size.height()*numberRow) > (size.height()/2)) { numberRow++; } if (numberRow > model()->rowCount(QModelIndex())) { numberRow = model()->rowCount(QModelIndex()); } return numberRow; } void KisNodeView::dragEnterEvent(QDragEnterEvent *ev) { DRAG_WHILE_DRAG_WORKAROUND_START(); QVariant data = qVariantFromValue( static_cast(const_cast(ev->mimeData()))); model()->setData(QModelIndex(), data, KisNodeModel::DropEnabled); QTreeView::dragEnterEvent(ev); } void KisNodeView::dragMoveEvent(QDragMoveEvent *ev) { DRAG_WHILE_DRAG_WORKAROUND_START(); if (displayMode() == KisNodeView::ThumbnailMode) { ev->accept(); if (!model()) { return; } QTreeView::dragMoveEvent(ev); setDraggingFlag(); viewport()->update(); return; } QTreeView::dragMoveEvent(ev); } void KisNodeView::dragLeaveEvent(QDragLeaveEvent *e) { if (displayMode() == KisNodeView::ThumbnailMode) { setDraggingFlag(false); } else { QTreeView::dragLeaveEvent(e); } DRAG_WHILE_DRAG_WORKAROUND_STOP(); } bool KisNodeView::isDragging() const { return m_draggingFlag; } void KisNodeView::setDraggingFlag(bool flag) { m_draggingFlag = flag; } void KisNodeView::slotUpdateIcons() { d->delegate.slotUpdateIcon(); } void KisNodeView::slotScrollerStateChanged(QScroller::State state){ KisKineticScroller::updateCursor(this, state); } diff --git a/libs/ui/kis_node_view_color_scheme.cpp b/libs/ui/kis_node_view_color_scheme.cpp index 384a8d23fa..f875165465 100644 --- a/libs/ui/kis_node_view_color_scheme.cpp +++ b/libs/ui/kis_node_view_color_scheme.cpp @@ -1,194 +1,199 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_node_view_color_scheme.h" #include #include #include "krita_utils.h" #include #include #include Q_GLOBAL_STATIC(KisNodeViewColorScheme, s_instance) struct KisNodeViewColorScheme::Private { Private() { if (colorLabels.isEmpty()) { colorLabels << Qt::transparent; colorLabels << QColor(91,173,220); colorLabels << QColor(151,202,63); colorLabels << QColor(247,229,61); colorLabels << QColor(255,170,63); colorLabels << QColor(177,102,63); colorLabels << QColor(238,50,51); colorLabels << QColor(191,106,209); colorLabels << QColor(118,119,114); const QColor noLabelSetColor = qApp->palette().color(QPalette::Highlight); for (auto it = colorLabels.begin(); it != colorLabels.end(); ++it) { KritaUtils::dragColor(&(*it), noLabelSetColor, 0.35); } } } static QVector colorLabels; }; QVector KisNodeViewColorScheme::Private::colorLabels; KisNodeViewColorScheme::KisNodeViewColorScheme() : m_d(new Private) { } KisNodeViewColorScheme::~KisNodeViewColorScheme() { } KisNodeViewColorScheme* KisNodeViewColorScheme::instance() { return s_instance; } QColor KisNodeViewColorScheme::gridColor(const QStyleOptionViewItem &option, QTreeView *view) { const int gridHint = view->style()->styleHint(QStyle::SH_Table_GridLineColor, &option, view); const QColor gridColor = static_cast(gridHint); return gridColor; } int KisNodeViewColorScheme::visibilitySize() const { return 16; } int KisNodeViewColorScheme::visibilityMargin() const { return 2; } int KisNodeViewColorScheme::thumbnailSize() const { KisConfig cfg(true); return cfg.layerThumbnailSize(false); } int KisNodeViewColorScheme::thumbnailMargin() const { return 3; } int KisNodeViewColorScheme::decorationSize() const { return 12; } int KisNodeViewColorScheme::decorationMargin() const { return 1; } int KisNodeViewColorScheme::textMargin() const { return 2; } int KisNodeViewColorScheme::iconSize() const { return 16; } int KisNodeViewColorScheme::iconMargin() const { return 1; } int KisNodeViewColorScheme::border() const { return 1; } int KisNodeViewColorScheme::rowHeight() const { return border() + 2 * thumbnailMargin() + thumbnailSize(); } int KisNodeViewColorScheme::visibilityColumnWidth() const { return border() + 2 * visibilityMargin() + visibilitySize() + border(); } int KisNodeViewColorScheme::indentation() const { return 2 * thumbnailMargin() + thumbnailSize() + border(); } +QRect KisNodeViewColorScheme::relVisibilityRect() const +{ + return QRect(0, 0, + visibilitySize() + 2 * visibilityMargin() + 2 * border(), + visibilitySize() + 2 * visibilityMargin() + 1 * border()); +} + QRect KisNodeViewColorScheme::relThumbnailRect() const { - return QRect(-indentation(), - border(), - thumbnailSize() + 2 * thumbnailMargin(), - thumbnailSize() + 2 * thumbnailMargin()); + return QRect(0, 0, + thumbnailSize() + 2 * thumbnailMargin() + 2 * border(), + thumbnailSize() + 2 * thumbnailMargin() + 1 * border()); } QRect KisNodeViewColorScheme::relDecorationRect() const { - return QRect(border() + decorationMargin(), - border() + decorationMargin(), - decorationSize(), - decorationSize()); + return QRect(0, 0, + decorationSize() + 2 * decorationMargin() + 2 * border(), + decorationSize() + 2 * decorationMargin() + 1 * border()); } QRect KisNodeViewColorScheme::relExpandButtonRect() const { const int newY = rowHeight() - decorationMargin() - decorationSize(); QRect rc = relDecorationRect(); rc.moveTop(newY); return rc; } QColor KisNodeViewColorScheme::colorLabel(int index) const { /** * We should ensure that the index of the overflowing range * will never be zero again. */ if (index >= m_d->colorLabels.size()) { index = 1 + index % (m_d->colorLabels.size() - 1); } else { index = index % m_d->colorLabels.size(); } return m_d->colorLabels[index]; } QVector KisNodeViewColorScheme::allColorLabels() const { return m_d->colorLabels; } diff --git a/libs/ui/kis_node_view_color_scheme.h b/libs/ui/kis_node_view_color_scheme.h index ef96293a47..afb1de6945 100644 --- a/libs/ui/kis_node_view_color_scheme.h +++ b/libs/ui/kis_node_view_color_scheme.h @@ -1,73 +1,74 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_NODE_VIEW_COLOR_SCHEME_H #define __KIS_NODE_VIEW_COLOR_SCHEME_H #include #include #include "kritaui_export.h" class QTreeView; class QStyleOptionViewItem; class QRect; class KRITAUI_EXPORT KisNodeViewColorScheme { public: KisNodeViewColorScheme(); ~KisNodeViewColorScheme(); static KisNodeViewColorScheme* instance(); QColor gridColor(const QStyleOptionViewItem &option, QTreeView *view); int visibilitySize() const; int visibilityMargin() const; int thumbnailSize() const; int thumbnailMargin() const; int decorationSize() const; int decorationMargin() const; int textMargin() const; int iconSize() const; int iconMargin() const; int border() const; int rowHeight() const; int visibilityColumnWidth() const; int indentation() const; + QRect relVisibilityRect() const; QRect relThumbnailRect() const; QRect relDecorationRect() const; QRect relExpandButtonRect() const; QColor colorLabel(int index) const; QVector allColorLabels() const; private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_NODE_VIEW_COLOR_SCHEME_H */