diff --git a/libs/ui/widgets/kis_color_filter_combo.cpp b/libs/ui/widgets/kis_color_filter_combo.cpp index a068b3f99f..17e617a670 100644 --- a/libs/ui/widgets/kis_color_filter_combo.cpp +++ b/libs/ui/widgets/kis_color_filter_combo.cpp @@ -1,446 +1,449 @@ /* * 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_color_filter_combo.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_node_view_color_scheme.h" #include "kis_debug.h" #include "kis_icon_utils.h" #include "krita_utils.h" #include "kis_node.h" enum AdditionalRoles { OriginalLabelIndex = Qt::UserRole + 1000 }; struct LabelFilteringModel : public QSortFilterProxyModel { LabelFilteringModel(QObject *parent) : QSortFilterProxyModel(parent) {} bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override { const QModelIndex index = sourceModel()->index(source_row, 0, source_parent); const int labelIndex = index.data(OriginalLabelIndex).toInt(); return labelIndex < 0 || m_acceptedLabels.contains(labelIndex); } void setAcceptedLabels(const QSet &value) { m_acceptedLabels = value; invalidateFilter(); } private: QSet m_acceptedLabels; }; class ComboEventFilter : public QObject { public: ComboEventFilter(KisColorFilterCombo *parent) : m_parent(parent), m_buttonPressed(false) {} protected: bool eventFilter(QObject *obj, QEvent *event) override { if (event->type() == QEvent::Leave) { m_buttonPressed = false; } else if (event->type() == QEvent::MouseButtonPress) { QMouseEvent *mevent = static_cast(event); m_buttonPressed = mevent->button() == Qt::LeftButton; } else if (event->type() == QEvent::MouseButtonRelease) { QMouseEvent *mevent = static_cast(event); QModelIndex index = m_parent->view()->indexAt(mevent->pos()); if (!index.isValid()) return false; /** * We should eat the first event that arrives exactly when * the drop down appears on screen. */ if (!m_buttonPressed) return true; const bool toUncheckedState = index.data(Qt::CheckStateRole) == Qt::Checked; if (toUncheckedState) { m_parent->model()->setData(index, Qt::Unchecked, Qt::CheckStateRole); } else { m_parent->model()->setData(index, Qt::Checked, Qt::CheckStateRole); } if (index.data(OriginalLabelIndex).toInt() == -1) { for (int i = 0; i < m_parent->model()->rowCount(); i++) { const QModelIndex &other = m_parent->model()->index(i, 0); if (other.data(OriginalLabelIndex) != -1) { m_parent->model()->setData(other, toUncheckedState ? Qt::Unchecked : Qt::Checked, Qt::CheckStateRole); } } } else { bool prevChecked = false; bool checkedVaries = false; QModelIndex allLabelsIndex; for (int i = 0; i < m_parent->model()->rowCount(); i++) { const QModelIndex &other = m_parent->model()->index(i, 0); if (other.data(OriginalLabelIndex) != -1) { const bool currentChecked = other.data(Qt::CheckStateRole) == Qt::Checked; if (i == 0) { prevChecked = currentChecked; } else { if (prevChecked != currentChecked) { checkedVaries = true; break; } } } else { allLabelsIndex = other; } } const bool allLabelsIndexShouldBeChecked = prevChecked && !checkedVaries; if (allLabelsIndexShouldBeChecked != (allLabelsIndex.data(Qt::CheckStateRole) == Qt::Checked)) { m_parent->model()->setData(allLabelsIndex, allLabelsIndexShouldBeChecked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); } } emit m_parent->selectedColorsChanged(); m_buttonPressed = false; return true; } return QObject::eventFilter(obj, event); } private: KisColorFilterCombo *m_parent; bool m_buttonPressed; }; class FullSizedListView : public QListView { public: QSize sizeHint() const override { return contentsSize(); } }; class PopupComboBoxStyle : public QProxyStyle { public: PopupComboBoxStyle(QStyle *baseStyle = nullptr) : QProxyStyle(baseStyle) {} int styleHint(QStyle::StyleHint hint, const QStyleOption *option, const QWidget *widget, QStyleHintReturn *returnData) const override { // This flag makes ComboBox popup float ontop of its parent ComboBox, like in Fusion style. // Only when this hint is set will Qt respect combobox popup size hints, otherwise the popup // can never exceed the width of its parent ComboBox, like in Breeze style. if (hint == QStyle::SH_ComboBox_Popup) { return true; } return QProxyStyle::styleHint(hint, option, widget, returnData); } }; struct KisColorFilterCombo::Private { LabelFilteringModel *filteringModel; /** * if the combobox is in the filter mode * (when no colors are selected) * it will show the filter icon ("view-filter") * otherwise it will show tag icon ("tag") */ bool filterMode {true}; /** * If the combobox is in the circle mode, * it will show the selected colors as circle * otherwise it will show it in a rectangle */ bool circleMode {true}; }; KisColorFilterCombo::KisColorFilterCombo(QWidget *parent, bool filterMode, bool circleMode) : QComboBox(parent), m_d(new Private) { m_d->filterMode = filterMode; m_d->circleMode = circleMode; QStandardItemModel *newModel = new QStandardItemModel(this); setModel(newModel); QStyle* newStyle = QStyleFactory::create(style()->objectName()); // proxy style steals the ownership of the style and deletes it later PopupComboBoxStyle *proxyStyle = new PopupComboBoxStyle(newStyle); proxyStyle->setParent(this); setStyle(proxyStyle); setView(new FullSizedListView); m_eventFilters.append(new ComboEventFilter(this)); m_eventFilters.append(new ComboEventFilter(this)); view()->installEventFilter(m_eventFilters[0]); view()->viewport()->installEventFilter(m_eventFilters[1]); KisNodeViewColorScheme scm; QStandardItem* item = new QStandardItem(i18nc("combo box: show all layers", "All")); item->setCheckable(true); item->setCheckState(Qt::Unchecked); item->setData(QColor(Qt::transparent), Qt::BackgroundColorRole); item->setData(int(-1), OriginalLabelIndex); item->setData(QSize(30, scm.rowHeight()), Qt::SizeHintRole); newModel->appendRow(item); int labelIndex = 0; foreach (const QColor &color, scm.allColorLabels()) { const QString title = color.alpha() > 0 ? "" : i18nc("combo box: select all layers without a label", "No Label"); QStandardItem* item = new QStandardItem(title); item->setCheckable(true); item->setCheckState(Qt::Unchecked); item->setData(color, Qt::BackgroundColorRole); item->setData(labelIndex, OriginalLabelIndex); item->setData(QSize(30, scm.rowHeight()), Qt::SizeHintRole); newModel->appendRow(item); labelIndex++; } m_d->filteringModel = new LabelFilteringModel(this); QAbstractItemModel *originalModel = model(); originalModel->setParent(m_d->filteringModel); m_d->filteringModel->setSourceModel(originalModel); setModel(m_d->filteringModel); } KisColorFilterCombo::~KisColorFilterCombo() { qDeleteAll(m_eventFilters); } void collectAvailableLabels(KisNodeSP root, QSet *labels) { labels->insert(root->colorLabelIndex()); KisNodeSP node = root->firstChild(); while (node) { collectAvailableLabels(node, labels); node = node->nextSibling(); } } void KisColorFilterCombo::updateAvailableLabels(KisNodeSP rootNode) { QSet labels; if (!rootNode.isNull()) { collectAvailableLabels(rootNode, &labels); } updateAvailableLabels(labels); } void KisColorFilterCombo::updateAvailableLabels(const QSet &labels) { m_d->filteringModel->setAcceptedLabels(labels); } void KisColorFilterCombo::setModes(bool filterMode, bool circleMode) { m_d->filterMode = filterMode; m_d->circleMode = circleMode; } QList KisColorFilterCombo::selectedColors() const { QList colors; for (int i = 0; i < model()->rowCount(); i++) { const QModelIndex &other = model()->index(i, 0); const int label = other.data(OriginalLabelIndex).toInt(); if (label != -1 && other.data(Qt::CheckStateRole) == Qt::Checked) { colors << label; } } return colors; } void KisColorFilterCombo::paintColorPie(QStylePainter &painter, const QPalette& palette, const QList &selectedColors, const QRect &rect, const int &baseSize) { KisNodeViewColorScheme scm; const QPen oldPen = painter.pen(); const QBrush oldBrush = painter.brush(); const int border = 0; + QColor shadowColor = palette.shadow().color(); + shadowColor.setAlpha(64); QRect pieRect(0, 0, baseSize - 2 * border, baseSize - 2 * border); pieRect.moveCenter(rect.center()); if (selectedColors.size() == 1) { const int currentLabel = selectedColors.first(); const QColor currentColor = scm.colorLabel(currentLabel); const QBrush brush = QBrush(currentColor); painter.setBrush(brush); - painter.setPen(QPen(palette.light().color(), 1)); + painter.setPen(QPen(shadowColor, 1)); if (currentColor.alpha() > 0) { painter.drawEllipse(rect); } else if (currentLabel == 0) { QColor white = QColorConstants::White; QColor grey = QColor(220,220,220); - painter.setBrush(QBrush(palette.light().color())); + painter.setBrush(QBrush(shadowColor)); painter.setRenderHint(QPainter::Antialiasing); painter.drawEllipse(rect); const int step = 16 * 360 / 4; const int checkerSteps = 4; for (int i = 0; i < checkerSteps; i++) { QBrush checkerBrush = QBrush((i % 2) ? grey : white); painter.setPen(Qt::NoPen); painter.setBrush(checkerBrush); painter.drawPie(pieRect, step * i, step); } } } else { const int numColors = selectedColors.size(); const int step = 16 * 360 / numColors; - painter.setPen(QPen(palette.light().color(), 1)); + painter.setPen(QPen(shadowColor, 1)); painter.setBrush(QColor(0,0,0,0)); painter.setRenderHint(QPainter::Antialiasing); painter.drawEllipse(rect); for (int i = 0; i < numColors; i++) { QColor color = scm.colorLabel(selectedColors[i]); QBrush brush = color.alpha() > 0 ? QBrush(color) : QBrush(Qt::black, Qt::Dense4Pattern); painter.setPen(Qt::NoPen); painter.setBrush(brush); painter.drawPie(pieRect, step * i, step); } } painter.setPen(oldPen); painter.setBrush(oldBrush); } void KisColorFilterCombo::paintEvent(QPaintEvent *event) { Q_UNUSED(event); QStylePainter painter(this); painter.setPen(palette().color(QPalette::Text)); // draw the combobox frame, focusrect and selected etc. QStyleOptionComboBox opt; initStyleOption(&opt); painter.drawComplexControl(QStyle::CC_ComboBox, opt); { const QRect editRect = style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxEditField, this); const int size = qMin(editRect.width(), editRect.height()); const QList selectedColors = this->selectedColors(); if (selectedColors.size() == 0 || selectedColors.size() == model()->rowCount() - 1) { QIcon icon = KisIconUtils::loadIcon(m_d->filterMode ? "view-filter" : "tag"); QPixmap pixmap = icon.pixmap(QSize(size, size), !isEnabled() ? QIcon::Disabled : QIcon::Normal); painter.drawPixmap(editRect.right() - size, editRect.top(), pixmap); } else { const int numColors = selectedColors.size(); if (m_d->circleMode) { KisColorFilterCombo::paintColorPie(painter, opt.palette, selectedColors, editRect, size ); } else { // show all colors in a rectangle + KisNodeViewColorScheme scm; int oneColorWidth = editRect.width()/numColors; int currentWidth = 0; for (int i = 0; i < numColors; i++) { QColor color = scm.colorLabel(selectedColors[i]); QBrush brush = color.alpha() > 0 ? QBrush(color) : QBrush(Qt::black, Qt::Dense4Pattern); painter.setPen(color); painter.setBrush(brush); if (i == numColors - 1) { // last color; let's fill up painter.fillRect(currentWidth, editRect.top(), editRect.width() - currentWidth, editRect.height(), brush); } else { painter.fillRect(currentWidth, editRect.top(), oneColorWidth, editRect.height(), brush); } currentWidth += oneColorWidth; } } } } // draw the icon and text //painter.drawControl(QStyle::CE_ComboBoxLabel, opt); } QSize KisColorFilterCombo::minimumSizeHint() const { return sizeHint(); } QSize KisColorFilterCombo::sizeHint() const { QStyleOptionComboBox opt; initStyleOption(&opt); const QStyleOption *baseOption = qstyleoption_cast(&opt); const int arrowSize = style()->pixelMetric(QStyle::PM_ScrollBarExtent, baseOption, this); const QSize originalHint = QComboBox::sizeHint(); QSize sh(3 * arrowSize, originalHint.height()); return sh; } diff --git a/libs/ui/widgets/kis_color_label_selector_widget.cpp b/libs/ui/widgets/kis_color_label_selector_widget.cpp index 0aca13262a..32faa23817 100644 --- a/libs/ui/widgets/kis_color_label_selector_widget.cpp +++ b/libs/ui/widgets/kis_color_label_selector_widget.cpp @@ -1,164 +1,174 @@ /* * 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_color_label_selector_widget.h" #include "kis_debug.h" #include "kis_global.h" #include #include #include #include #include #include #include #include #include "kis_color_label_button.h" #include "kis_node_view_color_scheme.h" struct Private { Private(KisColorLabelSelectorWidget *_q) : q(_q) , buttonSize(24) { } KisColorLabelSelectorWidget *q; QVector colors; QButtonGroup* colorButtonGroup; QSpacerItem* menuAlignmentOffset; const int buttonSize; }; KisColorLabelSelectorWidget::KisColorLabelSelectorWidget(QWidget *parent) : QWidget(parent) , m_d(new Private(this)) { KisNodeViewColorScheme scm; m_d->colors = scm.allColorLabels(); QHBoxLayout *layout = new QHBoxLayout(this); this->setLayout(layout); layout->setContentsMargins(0,0,0,0); layout->setAlignment(Qt::AlignLeft); m_d->menuAlignmentOffset = new QSpacerItem(0,0); layout->addItem(m_d->menuAlignmentOffset); { m_d->colorButtonGroup = new QButtonGroup(this); m_d->colorButtonGroup->setExclusive(true); for (int id = 0; id < m_d->colors.count(); id++) { KisColorLabelButton* btn = new KisColorLabelButton(m_d->colors[id], m_d->buttonSize, this); btn->setChecked(false); btn->setSelectionVisType(KisColorLabelButton::Outline); m_d->colorButtonGroup->addButton(btn, id); layout->addWidget(btn); } connect(m_d->colorButtonGroup, SIGNAL(buttonToggled(int,bool)), this, SLOT(groupButtonChecked(int,bool))); } } KisColorLabelSelectorWidget::~KisColorLabelSelectorWidget(){ } int KisColorLabelSelectorWidget::currentIndex() const { return m_d->colorButtonGroup->checkedId(); } QSize KisColorLabelSelectorWidget::sizeHint() const { return QSize(calculateMenuOffset() + m_d->buttonSize * m_d->colors.count(), m_d->buttonSize); } void KisColorLabelSelectorWidget::resizeEvent(QResizeEvent *e) { int menuOffset = calculateMenuOffset(); m_d->menuAlignmentOffset->changeSize(menuOffset, height()); layout()->invalidate(); QMenu *menu = qobject_cast(parent()); if(menu) { menu->resize(menu->width() + menuOffset, menu->height()); } QWidget::resizeEvent(e); } int KisColorLabelSelectorWidget::calculateMenuOffset() const { bool hasWideItems = false; QMenu *menu = qobject_cast(parent()); + bool hasCheckable = false; + bool hasIcon = false; + int menuOffset = 0; if (menu) { Q_FOREACH(QAction *action, menu->actions()) { - if (action->isCheckable() || - !action->icon().isNull()) { - - hasWideItems = true; + hasCheckable |= action->isCheckable(); + hasIcon |= action->icon().isNull(); + hasWideItems |= (hasCheckable || hasIcon); + + // Ideally, we would early terminate, + // but we don't know if we need both icons + // and checkbox space until we traverse + // the whole menu. Previous behavior caused + // a bug on KDE systems which provide icons + // and checkboxes. + if (hasCheckable && hasIcon) { break; } } } if (hasWideItems) { QStyleOption opt; opt.init(this); // some copy-pasted code from QFusionStyle style const int hmargin = style()->pixelMetric(QStyle::PM_MenuHMargin, &opt, this); const int icone = style()->pixelMetric(QStyle::PM_SmallIconSize, &opt, this); - menuOffset = hmargin + icone + 6; + menuOffset = (hmargin + icone + 6) * (hasCheckable + hasIcon); } return menuOffset; } void KisColorLabelSelectorWidget::groupButtonChecked(int index, bool state) { if (state == true) { emit currentIndexChanged(index); } } void KisColorLabelSelectorWidget::setCurrentIndex(int index) { if (index == -1) { QAbstractButton* btn = m_d->colorButtonGroup->checkedButton(); if (btn) { btn->group()->setExclusive(false); btn->setChecked(false); btn->group()->setExclusive(true); } } else if (index != m_d->colorButtonGroup->checkedId()) { QAbstractButton* btn = m_d->colorButtonGroup->button(index); if (btn) { btn->setChecked(true); } } emit currentIndexChanged(index); } diff --git a/libs/ui/widgets/kis_layer_filter_widget.cpp b/libs/ui/widgets/kis_layer_filter_widget.cpp index 1e5af25cba..eeb3cd0ffd 100644 --- a/libs/ui/widgets/kis_layer_filter_widget.cpp +++ b/libs/ui/widgets/kis_layer_filter_widget.cpp @@ -1,262 +1,293 @@ /* * Copyright (c) 2020 Eoin O'Neill * Copyright (c) 2020 Emmet O'Neill * * 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_layer_filter_widget.h" #include #include #include #include #include #include #include #include #include #include #include +#include #include "kis_debug.h" #include "kis_node.h" #include "kis_global.h" +#include "kis_icon_utils.h" #include "kis_color_filter_combo.h" #include "kis_color_label_button.h" #include "kis_color_label_selector_widget.h" #include "kis_node_view_color_scheme.h" #include "KisMouseClickEater.h" KisLayerFilterWidget::KisLayerFilterWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *layout = new QVBoxLayout(this); setLayout(layout); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); textFilter = new QLineEdit(this); textFilter->setPlaceholderText(i18n("Filter by name...")); textFilter->setMinimumWidth(192); textFilter->setMinimumHeight(28); textFilter->setClearButtonEnabled(true); connect(textFilter, SIGNAL(textChanged(QString)), this, SIGNAL(filteringOptionsChanged())); KisNodeViewColorScheme colorScheme; QWidget* buttonContainer = new QWidget(this); MouseClickIgnore* mouseEater = new MouseClickIgnore(this); buttonContainer->setToolTip(i18n("Filter by color label...")); buttonContainer->installEventFilter(mouseEater); buttonEventFilter = new KisColorLabelMouseDragFilter(buttonContainer); { QHBoxLayout *subLayout = new QHBoxLayout(buttonContainer); buttonContainer->setLayout(subLayout); subLayout->setContentsMargins(0,0,0,0); //subLayout->setAlignment(Qt::AlignLeft); ENTER_FUNCTION() << ppVar(subLayout->alignment()); buttonGroup = new KisColorLabelFilterGroup(buttonContainer); buttonGroup->setExclusive(false); QVector colors = colorScheme.allColorLabels(); for (int id = 0; id < colors.count(); id++) { KisColorLabelButton* btn = new KisColorLabelButton(colors[id], 28, buttonContainer); buttonGroup->addButton(btn, id); btn->installEventFilter(buttonEventFilter); subLayout->addWidget(btn); } connect(buttonGroup, SIGNAL(buttonToggled(int,bool)), this, SIGNAL(filteringOptionsChanged())); } resetButton = new QPushButton(i18n("Reset Filters"), this); resetButton->setMinimumHeight(28); connect(resetButton, &QPushButton::clicked, [this](){ this->reset(); }); layout->addWidget(textFilter); layout->addWidget(buttonContainer); layout->addWidget(resetButton); } void KisLayerFilterWidget::scanUsedColorLabels(KisNodeSP node, QSet &colorLabels) { if (node->parent()) { colorLabels.insert(node->colorLabelIndex()); } KisNodeSP child = node->firstChild(); while(child) { scanUsedColorLabels(child, colorLabels); child = child->nextSibling(); } } void KisLayerFilterWidget::updateColorLabels(KisNodeSP root) { QSet colorLabels; scanUsedColorLabels(root, colorLabels); buttonGroup->setViableLabels(colorLabels); } bool KisLayerFilterWidget::isCurrentlyFiltering() const { - const bool isFilteringText = !textFilter->text().isEmpty(); + const bool isFilteringText = hasTextFilter(); const bool isFilteringColors = buttonGroup->getActiveLabels().count() > 0; return isFilteringText || isFilteringColors; } +bool KisLayerFilterWidget::hasTextFilter() const +{ + return !textFilter->text().isEmpty(); +} + QSet KisLayerFilterWidget::getActiveColors() const { QSet activeColors = buttonGroup->getActiveLabels(); return activeColors; } QString KisLayerFilterWidget::getTextFilter() const { return textFilter->text(); } int KisLayerFilterWidget::getDesiredMinimumWidth() const { return qMax(textFilter->minimumWidth(), buttonGroup->countViableButtons() * 32); } int KisLayerFilterWidget::getDesiredMinimumHeight() const { QList viableButtons = buttonGroup->viableButtons(); if (viableButtons.count() > 1) { return viableButtons[0]->sizeHint().height() + textFilter->minimumHeight() + resetButton->minimumHeight(); } else { return textFilter->minimumHeight() + resetButton->minimumHeight(); } } void KisLayerFilterWidget::reset() { textFilter->clear(); buttonGroup->reset(); filteringOptionsChanged(); } QSize KisLayerFilterWidget::sizeHint() const { return QSize(getDesiredMinimumWidth(), getDesiredMinimumHeight()); } void KisLayerFilterWidget::showEvent(QShowEvent *show) { QMenu *parentMenu = dynamic_cast(parentWidget()); if (parentMenu) { const int widthBefore = parentMenu->width(); const int rightEdgeThreshold = 5; //Fake resize event needs to be made to register change in widget menu size. //Not doing this will cause QMenu to not resize properly! resize(sizeHint()); adjustSize(); QResizeEvent event = QResizeEvent(sizeHint(), parentMenu->size()); parentMenu->resize(sizeHint()); parentMenu->adjustSize(); qApp->sendEvent(parentMenu, &event); QScreen *screen = QGuiApplication::screenAt(parentMenu->mapToGlobal(parentMenu->pos())); QRect screenGeometry = screen ? screen->geometry() : parentMenu->parentWidget()->window()->geometry(); const bool onRightEdge = (parentMenu->pos().x() + widthBefore + rightEdgeThreshold) > screenGeometry.width(); const int widthAfter = parentMenu->width(); if (onRightEdge) { if (widthAfter > widthBefore) { const QRect newGeo = kisEnsureInRect( parentMenu->geometry(), screenGeometry ); const int xShift = newGeo.x() - parentMenu->pos().x(); parentMenu->move(parentMenu->pos().x() + xShift, parentMenu->pos().y() + 0); } else { const int xShift = widthBefore - widthAfter; parentMenu->move(parentMenu->pos().x() + xShift, parentMenu->pos().y() + 0); } } } QWidget::showEvent(show); } KisLayerFilterWidgetToolButton::KisLayerFilterWidgetToolButton(QWidget *parent) : QToolButton(parent) { m_textFilter = false; m_selectedColors = QList(); - ENTER_FUNCTION(); } KisLayerFilterWidgetToolButton::KisLayerFilterWidgetToolButton(const KisLayerFilterWidgetToolButton &rhs) : QToolButton(rhs.parentWidget()) , m_textFilter(rhs.m_textFilter) , m_selectedColors(rhs.m_selectedColors) { } void KisLayerFilterWidgetToolButton::setSelectedColors(QList colors) { m_selectedColors = colors; } +void KisLayerFilterWidgetToolButton::setTextFilter(bool isTextFiltering) +{ + m_textFilter = isTextFiltering; +} + void KisLayerFilterWidgetToolButton::paintEvent(QPaintEvent *paintEvent) { KisNodeViewColorScheme colorScheme; - if (m_textFilter == false && - ((m_selectedColors.count() == 0) || (m_selectedColors.count() == colorScheme.allColorLabels().count()))) + const bool validColorFilter = !(m_selectedColors.count() == 0 || m_selectedColors.count() == colorScheme.allColorLabels().count()); + + if (m_textFilter == false && !validColorFilter) { QToolButton::paintEvent(paintEvent); } else { QStylePainter paint(this); QStyleOptionToolButton opt; initStyleOption(&opt); + opt.icon = m_textFilter ? KisIconUtils::loadIcon("format-text-bold") : icon(); paint.drawComplexControl(QStyle::CC_ToolButton, opt); const QSize halfIconSize = this->iconSize() / 2; const QSize halfButtonSize = this->size() / 2; const QRect editRect = kisGrowRect(QRect(QPoint(halfButtonSize.width() - halfIconSize.width(), halfButtonSize.height() - halfIconSize.height()),this->iconSize()), -1); const int size = qMin(editRect.width(), editRect.height()); - KisColorFilterCombo::paintColorPie(paint, opt.palette, m_selectedColors, editRect, size ); + + if( validColorFilter ) + { + KisColorFilterCombo::paintColorPie(paint, opt.palette, m_selectedColors, editRect, size ); + if (m_textFilter) { + if (!opt.icon.isNull()) { + QRadialGradient radGradient = QRadialGradient(editRect.center(), size); + QColor shadowTransparent = palette().shadow().color(); + shadowTransparent.setAlpha(96); + radGradient.setColorAt(0.0f, shadowTransparent); + shadowTransparent.setAlpha(0); + radGradient.setColorAt(1.0f, shadowTransparent); + paint.setBrush(radGradient); + paint.setPen(Qt::NoPen); + paint.drawEllipse(editRect.center(), size, size); + opt.icon.paint(&paint, editRect); + } + } + } } } MouseClickIgnore::MouseClickIgnore(QWidget *parent) : QWidget(parent) { } bool MouseClickIgnore::eventFilter(QObject *obj, QEvent *event) { if (obj && (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick || event->type() == QEvent::MouseButtonRelease)) { event->setAccepted(true); return true; } else { return false; } } diff --git a/libs/ui/widgets/kis_layer_filter_widget.h b/libs/ui/widgets/kis_layer_filter_widget.h index a180a9340c..624054ab84 100644 --- a/libs/ui/widgets/kis_layer_filter_widget.h +++ b/libs/ui/widgets/kis_layer_filter_widget.h @@ -1,92 +1,94 @@ /* * Copyright (c) 2020 Eoin O'Neill * Copyright (c) 2020 Emmet O'Neill * * 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 KISLAYERFILTERWIDGET_H #define KISLAYERFILTERWIDGET_H #include #include #include "kis_types.h" #include "kritaui_export.h" class KRITAUI_EXPORT KisLayerFilterWidget : public QWidget { Q_OBJECT private: class KisColorLabelMouseDragFilter *buttonEventFilter; class QLineEdit *textFilter; class KisColorLabelFilterGroup *buttonGroup; class QPushButton *resetButton; public: KisLayerFilterWidget(QWidget *parent = nullptr); static void scanUsedColorLabels(KisNodeSP node, QSet &colorLabels); void updateColorLabels(KisNodeSP root); bool isCurrentlyFiltering() const; + bool hasTextFilter() const; QSet getActiveColors() const; QString getTextFilter() const; int getDesiredMinimumWidth() const; int getDesiredMinimumHeight() const; void reset(); QSize sizeHint() const override; /* Show Event has to be overridden to * correct for issues where QMenu isn't * correctly resizing. */ void showEvent(QShowEvent *show) override; Q_SIGNALS: void filteringOptionsChanged(); }; class KRITAUI_EXPORT KisLayerFilterWidgetToolButton : public QToolButton { Q_OBJECT public: explicit KisLayerFilterWidgetToolButton(QWidget *parent = nullptr); KisLayerFilterWidgetToolButton(const KisLayerFilterWidgetToolButton& rhs); ~KisLayerFilterWidgetToolButton(){} void setSelectedColors(QList colors); + void setTextFilter(bool isTextFiltering); private: void paintEvent(QPaintEvent *paintEvent) override; private: bool m_textFilter; QList m_selectedColors; }; class KRITAUI_EXPORT MouseClickIgnore : public QWidget { Q_OBJECT public: MouseClickIgnore(QWidget *parent = nullptr); bool eventFilter(QObject *obj, QEvent *event); }; #endif // KISLAYERFILTERWIDGET_H diff --git a/plugins/dockers/layerdocker/LayerBox.cpp b/plugins/dockers/layerdocker/LayerBox.cpp index f21e0bf74c..f1a93865af 100644 --- a/plugins/dockers/layerdocker/LayerBox.cpp +++ b/plugins/dockers/layerdocker/LayerBox.cpp @@ -1,1175 +1,1176 @@ /* * LayerBox.cc - part of Krita aka Krayon aka KimageShop * * Copyright (c) 2002 Patrick Julien * Copyright (C) 2006 Gábor Lehel * Copyright (C) 2007 Thomas Zander * Copyright (C) 2007 Boudewijn Rempt * Copyright (c) 2011 José Luis Vergara * * 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 "LayerBox.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_action_manager.h" #include "widgets/kis_cmb_composite.h" #include "kis_slider_spin_box.h" #include "KisViewManager.h" #include "kis_node_manager.h" #include "kis_node_model.h" #include "canvas/kis_canvas2.h" #include "kis_dummies_facade_base.h" #include "kis_shape_controller.h" #include "kis_selection_mask.h" #include "kis_config.h" #include "KisView.h" #include "krita_utils.h" #include "kis_color_label_selector_widget.h" #include "kis_signals_blocker.h" #include "kis_color_filter_combo.h" #include "kis_node_filter_proxy_model.h" #include "kis_selection.h" #include "kis_processing_applicator.h" #include "commands/kis_set_global_selection_command.h" #include "KisSelectionActionsAdapter.h" #include "kis_layer_utils.h" #include "ui_WdgLayerBox.h" #include "NodeView.h" #include "SyncButtonAndAction.h" class LayerBoxStyle : public QProxyStyle { public: LayerBoxStyle(QStyle *baseStyle = 0) : QProxyStyle(baseStyle) {} void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const { if (element == QStyle::PE_IndicatorItemViewItemDrop) { QColor color(widget->palette().color(QPalette::Highlight).lighter()); if (option->rect.height() == 0) { QBrush brush(color); QRect r(option->rect); r.setTop(r.top() - 2); r.setBottom(r.bottom() + 2); painter->fillRect(r, brush); } else { color.setAlpha(200); QBrush brush(color); painter->fillRect(option->rect, brush); } } else { QProxyStyle::drawPrimitive(element, option, painter, widget); } } }; inline void LayerBox::connectActionToButton(KisViewManager* viewManager, QAbstractButton *button, const QString &id) { if (!viewManager || !button) return; KisAction *action = viewManager->actionManager()->actionByName(id); if (!action) return; connect(button, SIGNAL(clicked()), action, SLOT(trigger())); connect(action, SIGNAL(sigEnableSlaves(bool)), button, SLOT(setEnabled(bool))); connect(viewManager->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateIcons())); } inline void LayerBox::addActionToMenu(QMenu *menu, const QString &id) { if (m_canvas) { menu->addAction(m_canvas->viewManager()->actionManager()->actionByName(id)); } } LayerBox::LayerBox() : QDockWidget(i18n("Layers")) , m_canvas(0) , m_wdgLayerBox(new Ui_WdgLayerBox) , m_thumbnailCompressor(500, KisSignalCompressor::FIRST_INACTIVE) , m_colorLabelCompressor(500, KisSignalCompressor::FIRST_INACTIVE) , m_thumbnailSizeCompressor(100, KisSignalCompressor::FIRST_INACTIVE) { KisConfig cfg(false); QWidget* mainWidget = new QWidget(this); setWidget(mainWidget); m_opacityDelayTimer.setSingleShot(true); m_wdgLayerBox->setupUi(mainWidget); m_wdgLayerBox->listLayers->setStyle(new LayerBoxStyle(m_wdgLayerBox->listLayers->style())); connect(m_wdgLayerBox->listLayers, SIGNAL(contextMenuRequested(QPoint,QModelIndex)), this, SLOT(slotContextMenuRequested(QPoint,QModelIndex))); connect(m_wdgLayerBox->listLayers, SIGNAL(collapsed(QModelIndex)), SLOT(slotCollapsed(QModelIndex))); connect(m_wdgLayerBox->listLayers, SIGNAL(expanded(QModelIndex)), SLOT(slotExpanded(QModelIndex))); connect(m_wdgLayerBox->listLayers, SIGNAL(selectionChanged(QModelIndexList)), SLOT(selectionChanged(QModelIndexList))); slotUpdateIcons(); m_wdgLayerBox->bnDelete->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnRaise->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnLower->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnProperties->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnDuplicate->setIconSize(QSize(22, 22)); m_wdgLayerBox->bnLower->setEnabled(false); m_wdgLayerBox->bnRaise->setEnabled(false); if (cfg.sliderLabels()) { m_wdgLayerBox->opacityLabel->hide(); m_wdgLayerBox->doubleOpacity->setPrefix(QString("%1: ").arg(i18n("Opacity"))); } m_wdgLayerBox->doubleOpacity->setRange(0, 100, 0); m_wdgLayerBox->doubleOpacity->setSuffix(i18n("%")); connect(m_wdgLayerBox->doubleOpacity, SIGNAL(valueChanged(qreal)), SLOT(slotOpacitySliderMoved(qreal))); connect(&m_opacityDelayTimer, SIGNAL(timeout()), SLOT(slotOpacityChanged())); connect(m_wdgLayerBox->cmbComposite, SIGNAL(activated(int)), SLOT(slotCompositeOpChanged(int))); m_newLayerMenu = new QMenu(this); m_wdgLayerBox->bnAdd->setMenu(m_newLayerMenu); m_wdgLayerBox->bnAdd->setPopupMode(QToolButton::MenuButtonPopup); m_nodeModel = new KisNodeModel(this); m_filteringModel = new KisNodeFilterProxyModel(this); m_filteringModel->setNodeModel(m_nodeModel); /** * Connect model updateUI() to enable/disable controls. * Note: nodeActivated() is connected separately in setImage(), because * it needs particular order of calls: first the connection to the * node manager should be called, then updateUI() */ connect(m_nodeModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(updateUI())); connect(m_nodeModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(updateUI())); connect(m_nodeModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), SLOT(updateUI())); connect(m_nodeModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(updateUI())); connect(m_nodeModel, SIGNAL(modelReset()), SLOT(slotModelReset())); connect(m_nodeModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode())); connect(m_nodeModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode())); connect(m_nodeModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode())); connect(m_nodeModel, SIGNAL(modelReset()), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode())); KisAction *showGlobalSelectionMask = new KisAction(i18n("&Show Global Selection Mask"), this); showGlobalSelectionMask->setObjectName("show-global-selection-mask"); showGlobalSelectionMask->setActivationFlags(KisAction::ACTIVE_IMAGE); showGlobalSelectionMask->setToolTip(i18nc("@info:tooltip", "Shows global selection as a usual selection mask in Layers docker")); showGlobalSelectionMask->setCheckable(true); connect(showGlobalSelectionMask, SIGNAL(triggered(bool)), SLOT(slotEditGlobalSelection(bool))); m_actions.append(showGlobalSelectionMask); showGlobalSelectionMask->setChecked(cfg.showGlobalSelection()); m_colorSelector = new KisColorLabelSelectorWidget(this); MouseClickIgnore* mouseEater = new MouseClickIgnore(this); m_colorSelector->installEventFilter(mouseEater); connect(m_colorSelector, SIGNAL(currentIndexChanged(int)), SLOT(slotColorLabelChanged(int))); m_colorSelectorAction = new QWidgetAction(this); m_colorSelectorAction->setDefaultWidget(m_colorSelector); connect(m_nodeModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), &m_colorLabelCompressor, SLOT(start())); m_wdgLayerBox->listLayers->setModel(m_filteringModel); // this connection should be done *after* the setModel() call to // happen later than the internal selection model connect(m_filteringModel.data(), &KisNodeFilterProxyModel::rowsAboutToBeRemoved, this, &LayerBox::slotAboutToRemoveRows); //LayerFilter Menu QMenu *layerFilterMenu = new QMenu(this); m_wdgLayerBox->bnLayerFilters->setMenu(layerFilterMenu); m_wdgLayerBox->bnLayerFilters->setPopupMode(QToolButton::InstantPopup); const QIcon filterIcon = KisIconUtils::loadIcon("view-filter"); m_wdgLayerBox->bnLayerFilters->setIcon(filterIcon); QPixmap filterEnabledPixmap = filterIcon.pixmap(64,64); const QBitmap filterEnabledBitmask = filterEnabledPixmap.mask(); filterEnabledPixmap.fill(palette().color(QPalette::Highlight)); filterEnabledPixmap.setMask(filterEnabledBitmask); const QIcon filterEnabledIcon = QIcon(filterEnabledPixmap); layerFilterWidget = new KisLayerFilterWidget(this); connect(layerFilterWidget, SIGNAL(filteringOptionsChanged()), this, SLOT(updateLayerFiltering())); connect(layerFilterWidget, &KisLayerFilterWidget::filteringOptionsChanged, [this, filterIcon, filterEnabledIcon](){ if(layerFilterWidget->isCurrentlyFiltering()) { m_wdgLayerBox->bnLayerFilters->setIcon(filterEnabledIcon); } else { m_wdgLayerBox->bnLayerFilters->setIcon(filterIcon); } m_wdgLayerBox->bnLayerFilters->setSelectedColors(QList::fromSet(layerFilterWidget->getActiveColors())); + m_wdgLayerBox->bnLayerFilters->setTextFilter(layerFilterWidget->hasTextFilter()); }); QWidgetAction *layerFilterMenuAction = new QWidgetAction(this); layerFilterMenuAction->setDefaultWidget(layerFilterWidget); layerFilterMenu->addAction(layerFilterMenuAction); setEnabled(false); connect(&m_thumbnailCompressor, SIGNAL(timeout()), SLOT(updateThumbnail())); connect(&m_colorLabelCompressor, SIGNAL(timeout()), SLOT(updateAvailableLabels())); // set up the configure menu for changing thumbnail size QMenu* configureMenu = new QMenu(this); configureMenu->setStyleSheet("margin: 6px"); configureMenu->addSection(i18n("Thumbnail Size")); m_wdgLayerBox->configureLayerDockerToolbar->setMenu(configureMenu); m_wdgLayerBox->configureLayerDockerToolbar->setIcon(KisIconUtils::loadIcon("configure")); m_wdgLayerBox->configureLayerDockerToolbar->setPopupMode(QToolButton::InstantPopup); // add horizontal slider thumbnailSizeSlider = new QSlider(this); thumbnailSizeSlider->setOrientation(Qt::Horizontal); thumbnailSizeSlider->setRange(20, 80); thumbnailSizeSlider->setValue(cfg.layerThumbnailSize(false)); // grab this from the kritarc thumbnailSizeSlider->setMinimumHeight(20); thumbnailSizeSlider->setMinimumWidth(40); thumbnailSizeSlider->setTickInterval(5); QWidgetAction *sliderAction= new QWidgetAction(this); sliderAction->setDefaultWidget(thumbnailSizeSlider); configureMenu->addAction(sliderAction); connect(thumbnailSizeSlider, SIGNAL(sliderMoved(int)), &m_thumbnailSizeCompressor, SLOT(start())); connect(&m_thumbnailSizeCompressor, SIGNAL(timeout()), SLOT(slotUpdateThumbnailIconSize())); } LayerBox::~LayerBox() { delete m_wdgLayerBox; } void expandNodesRecursively(KisNodeSP root, QPointer filteringModel, NodeView *nodeView) { if (!root) return; if (filteringModel.isNull()) return; if (!nodeView) return; nodeView->blockSignals(true); KisNodeSP node = root->firstChild(); while (node) { QModelIndex idx = filteringModel->indexFromNode(node); if (idx.isValid()) { nodeView->setExpanded(idx, !node->collapsed()); } if (!node->collapsed() && node->childCount() > 0) { expandNodesRecursively(node, filteringModel, nodeView); } node = node->nextSibling(); } nodeView->blockSignals(false); } void LayerBox::slotAddLayerBnClicked() { if (m_canvas) { KisNodeList nodes = m_nodeManager->selectedNodes(); if (nodes.size() == 1) { KisAction *action = m_canvas->viewManager()->actionManager()->actionByName("add_new_paint_layer"); action->trigger(); } else { KisAction *action = m_canvas->viewManager()->actionManager()->actionByName("create_quick_group"); action->trigger(); } } } void LayerBox::setViewManager(KisViewManager* kisview) { m_nodeManager = kisview->nodeManager(); if (m_nodeManager) { connect(m_nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotForgetAboutSavedNodeBeforeEditSelectionMode())); } Q_FOREACH (KisAction *action, m_actions) { kisview->actionManager()-> addAction(action->objectName(), action); } connect(m_wdgLayerBox->bnAdd, SIGNAL(clicked()), this, SLOT(slotAddLayerBnClicked())); connectActionToButton(kisview, m_wdgLayerBox->bnDuplicate, "duplicatelayer"); KisActionManager *actionManager = kisview->actionManager(); KisAction *action = actionManager->createAction("RenameCurrentLayer"); Q_ASSERT(action); connect(action, SIGNAL(triggered()), this, SLOT(slotRenameCurrentNode())); m_propertiesAction = actionManager->createAction("layer_properties"); Q_ASSERT(m_propertiesAction); new SyncButtonAndAction(m_propertiesAction, m_wdgLayerBox->bnProperties, this); connect(m_propertiesAction, SIGNAL(triggered()), this, SLOT(slotPropertiesClicked())); m_removeAction = actionManager->createAction("remove_layer"); Q_ASSERT(m_removeAction); new SyncButtonAndAction(m_removeAction, m_wdgLayerBox->bnDelete, this); connect(m_removeAction, SIGNAL(triggered()), this, SLOT(slotRmClicked())); action = actionManager->createAction("move_layer_up"); Q_ASSERT(action); new SyncButtonAndAction(action, m_wdgLayerBox->bnRaise, this); connect(action, SIGNAL(triggered()), this, SLOT(slotRaiseClicked())); action = actionManager->createAction("move_layer_down"); Q_ASSERT(action); new SyncButtonAndAction(action, m_wdgLayerBox->bnLower, this); connect(action, SIGNAL(triggered()), this, SLOT(slotLowerClicked())); m_changeCloneSourceAction = actionManager->createAction("set-copy-from"); Q_ASSERT(m_changeCloneSourceAction); connect(m_changeCloneSourceAction, &KisAction::triggered, this, &LayerBox::slotChangeCloneSourceClicked); } void LayerBox::setCanvas(KoCanvasBase *canvas) { if (m_canvas == canvas) return; setEnabled(canvas != 0); if (m_canvas) { m_canvas->disconnectCanvasObserver(this); m_nodeModel->setDummiesFacade(0, 0, 0, 0, 0); m_selectionActionsAdapter.reset(); if (m_image) { KisImageAnimationInterface *animation = m_image->animationInterface(); animation->disconnect(this); } disconnect(m_image, 0, this, 0); disconnect(m_nodeManager, 0, this, 0); disconnect(m_nodeModel, 0, m_nodeManager, 0); m_nodeManager->slotSetSelectedNodes(KisNodeList()); } m_canvas = dynamic_cast(canvas); if (m_canvas) { m_image = m_canvas->image(); emit imageChanged(); connect(m_image, SIGNAL(sigImageUpdated(QRect)), &m_thumbnailCompressor, SLOT(start())); KisDocument* doc = static_cast(m_canvas->imageView()->document()); KisShapeController *kritaShapeController = dynamic_cast(doc->shapeController()); KisDummiesFacadeBase *kritaDummiesFacade = static_cast(kritaShapeController); m_selectionActionsAdapter.reset(new KisSelectionActionsAdapter(m_canvas->viewManager()->selectionManager())); m_nodeModel->setDummiesFacade(kritaDummiesFacade, m_image, kritaShapeController, m_selectionActionsAdapter.data(), m_nodeManager); connect(m_image, SIGNAL(sigAboutToBeDeleted()), SLOT(notifyImageDeleted())); connect(m_image, SIGNAL(sigNodeCollapsedChanged()), SLOT(slotNodeCollapsedChanged())); // cold start if (m_nodeManager) { setCurrentNode(m_nodeManager->activeNode()); // Connection KisNodeManager -> LayerBox connect(m_nodeManager, SIGNAL(sigUiNeedChangeActiveNode(KisNodeSP)), this, SLOT(setCurrentNode(KisNodeSP))); connect(m_nodeManager, SIGNAL(sigUiNeedChangeSelectedNodes(QList)), SLOT(slotNodeManagerChangedSelection(QList))); } else { setCurrentNode(m_canvas->imageView()->currentNode()); } // Connection LayerBox -> KisNodeManager (isolate layer) connect(m_nodeModel, SIGNAL(toggleIsolateActiveNode()), m_nodeManager, SLOT(toggleIsolateActiveNode())); KisImageAnimationInterface *animation = m_image->animationInterface(); connect(animation, &KisImageAnimationInterface::sigUiTimeChanged, this, &LayerBox::slotImageTimeChanged); expandNodesRecursively(m_image->rootLayer(), m_filteringModel, m_wdgLayerBox->listLayers); m_wdgLayerBox->listLayers->scrollTo(m_wdgLayerBox->listLayers->currentIndex()); updateAvailableLabels(); addActionToMenu(m_newLayerMenu, "add_new_paint_layer"); addActionToMenu(m_newLayerMenu, "add_new_group_layer"); addActionToMenu(m_newLayerMenu, "add_new_clone_layer"); addActionToMenu(m_newLayerMenu, "add_new_shape_layer"); addActionToMenu(m_newLayerMenu, "add_new_adjustment_layer"); addActionToMenu(m_newLayerMenu, "add_new_fill_layer"); addActionToMenu(m_newLayerMenu, "add_new_file_layer"); m_newLayerMenu->addSeparator(); addActionToMenu(m_newLayerMenu, "add_new_transparency_mask"); addActionToMenu(m_newLayerMenu, "add_new_filter_mask"); addActionToMenu(m_newLayerMenu, "add_new_colorize_mask"); addActionToMenu(m_newLayerMenu, "add_new_transform_mask"); addActionToMenu(m_newLayerMenu, "add_new_selection_mask"); } } void LayerBox::unsetCanvas() { setEnabled(false); if (m_canvas) { m_newLayerMenu->clear(); } m_filteringModel->unsetDummiesFacade(); disconnect(m_image, 0, this, 0); disconnect(m_nodeManager, 0, this, 0); disconnect(m_nodeModel, 0, m_nodeManager, 0); m_nodeManager->slotSetSelectedNodes(KisNodeList()); m_canvas = 0; } void LayerBox::notifyImageDeleted() { setCanvas(0); } void LayerBox::updateUI() { if (!m_canvas) return; if (!m_nodeManager) return; KisNodeSP activeNode = m_nodeManager->activeNode(); if (activeNode != m_activeNode) { if( !m_activeNode.isNull() ) m_activeNode->disconnect(this); m_activeNode = activeNode; if (activeNode) { KisKeyframeChannel *opacityChannel = activeNode->getKeyframeChannel(KisKeyframeChannel::Opacity.id(), false); if (opacityChannel) { watchOpacityChannel(opacityChannel); } else { watchOpacityChannel(0); connect(activeNode.data(), &KisNode::keyframeChannelAdded, this, &LayerBox::slotKeyframeChannelAdded); } } } m_wdgLayerBox->bnRaise->setEnabled(activeNode && activeNode->isEditable(false) && (activeNode->nextSibling() || (activeNode->parent() && activeNode->parent() != m_image->root()))); m_wdgLayerBox->bnLower->setEnabled(activeNode && activeNode->isEditable(false) && (activeNode->prevSibling() || (activeNode->parent() && activeNode->parent() != m_image->root()))); m_wdgLayerBox->doubleOpacity->setEnabled(activeNode && activeNode->isEditable(false)); m_wdgLayerBox->cmbComposite->setEnabled(activeNode && activeNode->isEditable(false)); m_wdgLayerBox->cmbComposite->validate(m_image->colorSpace()); if (activeNode) { if (activeNode->inherits("KisColorizeMask") || activeNode->inherits("KisLayer")) { m_wdgLayerBox->doubleOpacity->setEnabled(true); if (!m_wdgLayerBox->doubleOpacity->isDragging()) { slotSetOpacity(activeNode->opacity() * 100.0 / 255); } const KoCompositeOp* compositeOp = activeNode->compositeOp(); if (compositeOp) { slotSetCompositeOp(compositeOp); } else { m_wdgLayerBox->cmbComposite->setEnabled(false); } const KisGroupLayer *group = qobject_cast(activeNode.data()); bool compositeSelectionActive = !(group && group->passThroughMode()); m_wdgLayerBox->cmbComposite->setEnabled(compositeSelectionActive); } else if (activeNode->inherits("KisMask")) { m_wdgLayerBox->cmbComposite->setEnabled(false); m_wdgLayerBox->doubleOpacity->setEnabled(false); } } } /** * This method is called *only* when non-GUI code requested the * change of the current node */ void LayerBox::setCurrentNode(KisNodeSP node) { m_filteringModel->setActiveNode(node); QModelIndex index = node ? m_filteringModel->indexFromNode(node) : QModelIndex(); m_filteringModel->setData(index, true, KisNodeModel::ActiveRole); updateUI(); } void LayerBox::slotModelReset() { if(m_nodeModel->hasDummiesFacade()) { QItemSelection selection; Q_FOREACH (const KisNodeSP node, m_nodeManager->selectedNodes()) { const QModelIndex &idx = m_filteringModel->indexFromNode(node); if(idx.isValid()){ QItemSelectionRange selectionRange(idx); selection << selectionRange; } } m_wdgLayerBox->listLayers->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); } updateUI(); } void LayerBox::slotSetCompositeOp(const KoCompositeOp* compositeOp) { KoID opId = KoCompositeOpRegistry::instance().getKoID(compositeOp->id()); m_wdgLayerBox->cmbComposite->blockSignals(true); m_wdgLayerBox->cmbComposite->selectCompositeOp(opId); m_wdgLayerBox->cmbComposite->blockSignals(false); } // range: 0-100 void LayerBox::slotSetOpacity(double opacity) { Q_ASSERT(opacity >= 0 && opacity <= 100); m_wdgLayerBox->doubleOpacity->blockSignals(true); m_wdgLayerBox->doubleOpacity->setValue(opacity); m_wdgLayerBox->doubleOpacity->blockSignals(false); } void LayerBox::slotContextMenuRequested(const QPoint &pos, const QModelIndex &index) { KisNodeList nodes = m_nodeManager->selectedNodes(); KisNodeSP activeNode = m_nodeManager->activeNode(); if (nodes.isEmpty() || !activeNode) return; if (m_canvas) { QMenu menu; const bool singleLayer = nodes.size() == 1; if (index.isValid()) { menu.addAction(m_propertiesAction); if (singleLayer) { addActionToMenu(&menu, "layer_style"); } Q_FOREACH(KisNodeSP node, nodes) { if (node && node->inherits("KisCloneLayer")) { menu.addAction(m_changeCloneSourceAction); break; } } { KisSignalsBlocker b(m_colorSelector); m_colorSelector->setCurrentIndex(singleLayer ? activeNode->colorLabelIndex() : -1); } menu.addAction(m_colorSelectorAction); menu.addSeparator(); addActionToMenu(&menu, "cut_layer_clipboard"); addActionToMenu(&menu, "copy_layer_clipboard"); addActionToMenu(&menu, "paste_layer_from_clipboard"); menu.addAction(m_removeAction); addActionToMenu(&menu, "duplicatelayer"); addActionToMenu(&menu, "merge_layer"); addActionToMenu(&menu, "new_from_visible"); if (singleLayer) { addActionToMenu(&menu, "flatten_image"); addActionToMenu(&menu, "flatten_layer"); } menu.addSeparator(); QMenu *selectMenu = menu.addMenu(i18n("&Select")); addActionToMenu(selectMenu, "select_all_layers"); addActionToMenu(selectMenu, "select_visible_layers"); addActionToMenu(selectMenu, "select_invisible_layers"); addActionToMenu(selectMenu, "select_locked_layers"); addActionToMenu(selectMenu, "select_unlocked_layers"); QMenu *groupMenu = menu.addMenu(i18n("&Group")); addActionToMenu(groupMenu, "create_quick_group"); addActionToMenu(groupMenu, "create_quick_clipping_group"); addActionToMenu(groupMenu, "quick_ungroup"); QMenu *locksMenu = menu.addMenu(i18n("&Toggle Locks && Visibility")); addActionToMenu(locksMenu, "toggle_layer_visibility"); addActionToMenu(locksMenu, "toggle_layer_lock"); addActionToMenu(locksMenu, "toggle_layer_inherit_alpha"); addActionToMenu(locksMenu, "toggle_layer_alpha_lock"); if (singleLayer) { QMenu *addLayerMenu = menu.addMenu(i18n("&Add")); addActionToMenu(addLayerMenu, "add_new_transparency_mask"); addActionToMenu(addLayerMenu, "add_new_filter_mask"); addActionToMenu(addLayerMenu, "add_new_colorize_mask"); addActionToMenu(addLayerMenu, "add_new_transform_mask"); addActionToMenu(addLayerMenu, "add_new_selection_mask"); addLayerMenu->addSeparator(); addActionToMenu(addLayerMenu, "add_new_clone_layer"); QMenu *convertToMenu = menu.addMenu(i18n("&Convert")); addActionToMenu(convertToMenu, "convert_to_paint_layer"); addActionToMenu(convertToMenu, "convert_to_transparency_mask"); addActionToMenu(convertToMenu, "convert_to_filter_mask"); addActionToMenu(convertToMenu, "convert_to_selection_mask"); addActionToMenu(convertToMenu, "convert_to_file_layer"); QMenu *splitAlphaMenu = menu.addMenu(i18n("S&plit Alpha")); addActionToMenu(splitAlphaMenu, "split_alpha_into_mask"); addActionToMenu(splitAlphaMenu, "split_alpha_write"); addActionToMenu(splitAlphaMenu, "split_alpha_save_merged"); } else { QMenu *addLayerMenu = menu.addMenu(i18n("&Add")); addActionToMenu(addLayerMenu, "add_new_clone_layer"); } menu.addSeparator(); addActionToMenu(&menu, "pin_to_timeline"); if (singleLayer) { KisNodeSP node = m_filteringModel->nodeFromIndex(index); if (node && !node->inherits("KisTransformMask")) { addActionToMenu(&menu, "isolate_active_layer"); addActionToMenu(&menu, "isolate_active_group"); } addActionToMenu(&menu, "selectopaque"); } } menu.exec(pos); } } void LayerBox::slotMinimalView() { m_wdgLayerBox->listLayers->setDisplayMode(NodeView::MinimalMode); } void LayerBox::slotDetailedView() { m_wdgLayerBox->listLayers->setDisplayMode(NodeView::DetailedMode); } void LayerBox::slotThumbnailView() { m_wdgLayerBox->listLayers->setDisplayMode(NodeView::ThumbnailMode); } void LayerBox::slotRmClicked() { if (!m_canvas) return; m_nodeManager->removeNode(); } void LayerBox::slotRaiseClicked() { if (!m_canvas) return; m_nodeManager->raiseNode(); } void LayerBox::slotLowerClicked() { if (!m_canvas) return; m_nodeManager->lowerNode(); } void LayerBox::slotPropertiesClicked() { if (!m_canvas) return; if (KisNodeSP active = m_nodeManager->activeNode()) { m_nodeManager->nodeProperties(active); } } void LayerBox::slotChangeCloneSourceClicked() { if (!m_canvas) return; m_nodeManager->changeCloneSource(); } void LayerBox::slotCompositeOpChanged(int index) { Q_UNUSED(index); if (!m_canvas) return; QString compositeOp = m_wdgLayerBox->cmbComposite->selectedCompositeOp().id(); m_nodeManager->nodeCompositeOpChanged(m_nodeManager->activeColorSpace()->compositeOp(compositeOp)); } void LayerBox::slotOpacityChanged() { if (!m_canvas) return; m_blockOpacityUpdate = true; m_nodeManager->nodeOpacityChanged(m_newOpacity); m_blockOpacityUpdate = false; } void LayerBox::slotOpacitySliderMoved(qreal opacity) { m_newOpacity = opacity; m_opacityDelayTimer.start(200); } void LayerBox::slotCollapsed(const QModelIndex &index) { KisNodeSP node = m_filteringModel->nodeFromIndex(index); if (node) { node->setCollapsed(true); } } void LayerBox::slotExpanded(const QModelIndex &index) { KisNodeSP node = m_filteringModel->nodeFromIndex(index); if (node) { node->setCollapsed(false); } } void LayerBox::slotSelectOpaque() { if (!m_canvas) return; QAction *action = m_canvas->viewManager()->actionManager()->actionByName("selectopaque"); if (action) { action->trigger(); } } void LayerBox::slotNodeCollapsedChanged() { expandNodesRecursively(m_image->rootLayer(), m_filteringModel, m_wdgLayerBox->listLayers); } inline bool isSelectionMask(KisNodeSP node) { return dynamic_cast(node.data()); } KisNodeSP LayerBox::findNonHidableNode(KisNodeSP startNode) { if (KisNodeManager::isNodeHidden(startNode, true) && startNode->parent() && !startNode->parent()->parent()) { KisNodeSP node = startNode->prevSibling(); while (node && KisNodeManager::isNodeHidden(node, true)) { node = node->prevSibling(); } if (!node) { node = startNode->nextSibling(); while (node && KisNodeManager::isNodeHidden(node, true)) { node = node->nextSibling(); } } if (!node) { node = m_image->root()->lastChild(); while (node && KisNodeManager::isNodeHidden(node, true)) { node = node->prevSibling(); } } KIS_ASSERT_RECOVER_NOOP(node && "cannot activate any node!"); startNode = node; } return startNode; } void LayerBox::slotEditGlobalSelection(bool showSelections) { KisNodeSP lastActiveNode = m_nodeManager->activeNode(); KisNodeSP activateNode = lastActiveNode; KisSelectionMaskSP globalSelectionMask; if (!showSelections) { activateNode = m_savedNodeBeforeEditSelectionMode ? KisNodeSP(m_savedNodeBeforeEditSelectionMode) : findNonHidableNode(activateNode); } m_nodeModel->setShowGlobalSelection(showSelections); globalSelectionMask = m_image->rootLayer()->selectionMask(); // try to find deactivated, but visible masks if (!globalSelectionMask) { KoProperties properties; properties.setProperty("visible", true); QList masks = m_image->rootLayer()->childNodes(QStringList("KisSelectionMask"), properties); if (!masks.isEmpty()) { globalSelectionMask = dynamic_cast(masks.first().data()); } } // try to find at least any selection mask if (!globalSelectionMask) { KoProperties properties; QList masks = m_image->rootLayer()->childNodes(QStringList("KisSelectionMask"), properties); if (!masks.isEmpty()) { globalSelectionMask = dynamic_cast(masks.first().data()); } } if (globalSelectionMask) { if (showSelections) { activateNode = globalSelectionMask; } } if (activateNode != lastActiveNode) { m_nodeManager->slotNonUiActivatedNode(activateNode); } else if (lastActiveNode) { setCurrentNode(lastActiveNode); } if (showSelections && !globalSelectionMask) { KisProcessingApplicator applicator(m_image, 0, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Quick Selection Mask")); applicator.applyCommand( new KisLayerUtils::KeepNodesSelectedCommand( m_nodeManager->selectedNodes(), KisNodeList(), lastActiveNode, 0, m_image, false), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KisSetEmptyGlobalSelectionCommand(m_image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.applyCommand(new KisLayerUtils::SelectGlobalSelectionMask(m_image), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.end(); } else if (!showSelections && globalSelectionMask && globalSelectionMask->selection()->selectedRect().isEmpty()) { KisProcessingApplicator applicator(m_image, 0, KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Cancel Quick Selection Mask")); applicator.applyCommand(new KisSetGlobalSelectionCommand(m_image, 0), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE); applicator.end(); } if (showSelections) { m_savedNodeBeforeEditSelectionMode = lastActiveNode; } } void LayerBox::selectionChanged(const QModelIndexList selection) { if (!m_nodeManager) return; /** * When the user clears the extended selection by clicking on the * empty area of the docker, the selection should be reset on to * the active layer, which might be even unselected(!). */ if (selection.isEmpty() && m_nodeManager->activeNode()) { QModelIndex selectedIndex = m_filteringModel->indexFromNode(m_nodeManager->activeNode()); m_wdgLayerBox->listLayers->selectionModel()-> setCurrentIndex(selectedIndex, QItemSelectionModel::ClearAndSelect); return; } QList selectedNodes; Q_FOREACH (const QModelIndex &idx, selection) { selectedNodes << m_filteringModel->nodeFromIndex(idx); } m_nodeManager->slotSetSelectedNodes(selectedNodes); updateUI(); } void LayerBox::slotAboutToRemoveRows(const QModelIndex &parent, int start, int end) { /** * Qt has changed its behavior when deleting an item. Previously * the selection priority was on the next item in the list, and * now it has shanged to the previous item. Here we just adjust * the selected item after the node removal. Please take care that * this method overrides what was done by the corresponding method * of QItemSelectionModel, which *has already done* its work. That * is why we use (start - 1) and (end + 1) in the activation * condition. * * See bug: https://bugs.kde.org/show_bug.cgi?id=345601 */ QModelIndex currentIndex = m_wdgLayerBox->listLayers->currentIndex(); QAbstractItemModel *model = m_filteringModel; if (currentIndex.isValid() && parent == currentIndex.parent() && currentIndex.row() >= start - 1 && currentIndex.row() <= end + 1) { QModelIndex old = currentIndex; if (model && end < model->rowCount(parent) - 1) // there are rows left below the change currentIndex = model->index(end + 1, old.column(), parent); else if (model && start > 0) // there are rows left above the change currentIndex = model->index(start - 1, old.column(), parent); else // there are no rows left in the table currentIndex = QModelIndex(); if (currentIndex.isValid() && currentIndex != old) { m_wdgLayerBox->listLayers->setCurrentIndex(currentIndex); } } } void LayerBox::slotNodeManagerChangedSelection(const KisNodeList &nodes) { if (!m_nodeManager) return; QModelIndexList newSelection; Q_FOREACH(KisNodeSP node, nodes) { newSelection << m_filteringModel->indexFromNode(node); } QItemSelectionModel *model = m_wdgLayerBox->listLayers->selectionModel(); if (KritaUtils::compareListsUnordered(newSelection, model->selectedIndexes())) { return; } QItemSelection selection; Q_FOREACH(const QModelIndex &idx, newSelection) { selection.select(idx, idx); } model->select(selection, QItemSelectionModel::ClearAndSelect); } void LayerBox::updateThumbnail() { m_wdgLayerBox->listLayers->updateNode(m_wdgLayerBox->listLayers->currentIndex()); } void LayerBox::slotRenameCurrentNode() { m_wdgLayerBox->listLayers->edit(m_wdgLayerBox->listLayers->currentIndex()); } void LayerBox::slotColorLabelChanged(int label) { KisNodeList selectedNodes = m_nodeManager->selectedNodes(); Q_FOREACH(KisNodeSP selectedNode, selectedNodes) { //Always apply label to selected nodes.. selectedNode->setColorLabelIndex(label); //Apply label only to unlabelled children.. KisNodeList children = selectedNode->childNodes(QStringList(), KoProperties()); auto applyLabelFunc = [label](KisNodeSP child) { if (child->colorLabelIndex() == 0) { child->setColorLabelIndex(label); } }; Q_FOREACH(KisNodeSP child, children) { KisLayerUtils::recursiveApplyNodes(child, applyLabelFunc); } } } void LayerBox::updateAvailableLabels() { if (!m_image) return; layerFilterWidget->updateColorLabels(m_image->root()); } void LayerBox::updateLayerFiltering() { m_filteringModel->setAcceptedLabels(layerFilterWidget->getActiveColors()); m_filteringModel->setTextFilter(layerFilterWidget->getTextFilter()); } void LayerBox::slotKeyframeChannelAdded(KisKeyframeChannel *channel) { if (channel->id() == KisKeyframeChannel::Opacity.id()) { watchOpacityChannel(channel); } } void LayerBox::watchOpacityChannel(KisKeyframeChannel *channel) { if (m_opacityChannel) { m_opacityChannel->disconnect(this); } m_opacityChannel = channel; if (m_opacityChannel) { connect(m_opacityChannel, SIGNAL(sigKeyframeAdded(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP))); connect(m_opacityChannel, SIGNAL(sigKeyframeRemoved(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP))); connect(m_opacityChannel, SIGNAL(sigKeyframeMoved(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeMoved(KisKeyframeSP))); connect(m_opacityChannel, SIGNAL(sigKeyframeChanged(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP))); } } void LayerBox::slotOpacityKeyframeChanged(KisKeyframeSP keyframe) { Q_UNUSED(keyframe); if (m_blockOpacityUpdate) return; updateUI(); } void LayerBox::slotOpacityKeyframeMoved(KisKeyframeSP keyframe, int fromTime) { Q_UNUSED(fromTime); slotOpacityKeyframeChanged(keyframe); } void LayerBox::slotImageTimeChanged(int time) { Q_UNUSED(time); updateUI(); } void LayerBox::slotForgetAboutSavedNodeBeforeEditSelectionMode() { m_savedNodeBeforeEditSelectionMode = 0; } void LayerBox::slotUpdateIcons() { m_wdgLayerBox->bnAdd->setIcon(KisIconUtils::loadIcon("addlayer")); m_wdgLayerBox->bnRaise->setIcon(KisIconUtils::loadIcon("arrowupblr")); m_wdgLayerBox->bnDelete->setIcon(KisIconUtils::loadIcon("deletelayer")); m_wdgLayerBox->bnLower->setIcon(KisIconUtils::loadIcon("arrowdown")); m_wdgLayerBox->bnProperties->setIcon(KisIconUtils::loadIcon("properties")); m_wdgLayerBox->bnDuplicate->setIcon(KisIconUtils::loadIcon("duplicatelayer")); // call child function about needing to update icons m_wdgLayerBox->listLayers->slotUpdateIcons(); } void LayerBox::slotUpdateThumbnailIconSize() { KisConfig cfg(false); cfg.setLayerThumbnailSize(thumbnailSizeSlider->value()); // this is a hack to force the layers list to update its display and // re-layout all the layers with the new thumbnail size resize(this->width()+1, this->height()+1); resize(this->width()-1, this->height()-1); } #include "moc_LayerBox.cpp"