diff --git a/libs/ui/kis_node_filter_proxy_model.cpp b/libs/ui/kis_node_filter_proxy_model.cpp index 95ae9f3468..689bf8cffb 100644 --- a/libs/ui/kis_node_filter_proxy_model.cpp +++ b/libs/ui/kis_node_filter_proxy_model.cpp @@ -1,183 +1,188 @@ /* * 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_filter_proxy_model.h" #include #include #include "kis_node.h" #include "kis_node_model.h" #include "kis_node_manager.h" #include "kis_signal_compressor.h" #include "kis_image.h" struct KisNodeFilterProxyModel::Private { Private() : nodeModel(0), activeNodeCompressor(1000, KisSignalCompressor::FIRST_INACTIVE) {} KisNodeModel *nodeModel; KisNodeSP pendingActiveNode; KisNodeSP activeNode; QSet acceptedColorLabels; boost::optional activeTextFilter; KisSignalCompressor activeNodeCompressor; bool isUpdatingFilter = false; bool checkIndexAllowedRecursively(QModelIndex srcIndex); }; KisNodeFilterProxyModel::KisNodeFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent), m_d(new Private) { connect(&m_d->activeNodeCompressor, SIGNAL(timeout()), SLOT(slotUpdateCurrentNodeFilter()), Qt::QueuedConnection); } KisNodeFilterProxyModel::~KisNodeFilterProxyModel() { } void KisNodeFilterProxyModel::setNodeModel(KisNodeModel *model) { m_d->nodeModel = model; setSourceModel(model); } bool KisNodeFilterProxyModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (m_d->isUpdatingFilter && role == KisNodeModel::ActiveRole) { return false; } return QSortFilterProxyModel::setData(index, value, role); } bool KisNodeFilterProxyModel::Private::checkIndexAllowedRecursively(QModelIndex srcIndex) { if (!srcIndex.isValid()) return false; KisNodeSP node = nodeModel->nodeFromIndex(srcIndex); const bool nodeTextFilterMatch = (!activeTextFilter || node->name().contains(activeTextFilter.get(), Qt::CaseInsensitive)); + + // directParentTextFilterMatch -- There's an argument to be made that searching for a parent name should show + // all of the direct children of said text-search. For now, it will remain unused. const bool directParentTextFilterMatch = (!activeTextFilter || (node->parent() && node->parent()->name().contains(activeTextFilter.get(), Qt::CaseInsensitive))); + Q_UNUSED(directParentTextFilterMatch); + const bool nodeColorMatch = (acceptedColorLabels.count() == 0 || acceptedColorLabels.contains(node->colorLabelIndex())); if ( node == activeNode || - ( nodeColorMatch && (nodeTextFilterMatch || directParentTextFilterMatch) )) { + ( nodeColorMatch && nodeTextFilterMatch )) { return true; } bool result = false; const int numChildren = srcIndex.model()->rowCount(srcIndex); for (int i = 0; i < numChildren; i++) { QModelIndex child = nodeModel->index(i, 0, srcIndex); if (checkIndexAllowedRecursively(child)) { result = true; break; } } return result; } bool KisNodeFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { KIS_ASSERT_RECOVER(m_d->nodeModel) { return true; } const QModelIndex index = sourceModel()->index(source_row, 0, source_parent); if (!index.isValid()) return false; KisNodeSP node = m_d->nodeModel->nodeFromIndex(index); return !node || (m_d->acceptedColorLabels.isEmpty() && !m_d->activeTextFilter) || m_d->checkIndexAllowedRecursively(index); } KisNodeSP KisNodeFilterProxyModel::nodeFromIndex(const QModelIndex &index) const { KIS_ASSERT_RECOVER(m_d->nodeModel) { return 0; } QModelIndex srcIndex = mapToSource(index); return m_d->nodeModel->nodeFromIndex(srcIndex); } QModelIndex KisNodeFilterProxyModel::indexFromNode(KisNodeSP node) const { KIS_ASSERT_RECOVER(m_d->nodeModel) { return QModelIndex(); } QModelIndex srcIndex = m_d->nodeModel->indexFromNode(node); return mapFromSource(srcIndex); } void KisNodeFilterProxyModel::setAcceptedLabels(const QSet &value) { //#if QT_VERSION >= QT_VERSION_CHECK(5,14,0) // m_d->acceptedColorLabels = QSet(value.begin(), value.end()); //#else // m_d->acceptedColorLabels = QSet::fromList(value); //#endif m_d->acceptedColorLabels = value; invalidateFilter(); } void KisNodeFilterProxyModel::setTextFilter(const QString &text) { m_d->activeTextFilter = !text.isEmpty() ? boost::make_optional(text) : boost::none; invalidateFilter(); } void KisNodeFilterProxyModel::setActiveNode(KisNodeSP node) { // NOTE: the current node might change due to beginRemoveRows, in such case // we must ensure we don't trigger recursive model invalidation. // the new node may temporary become null when the last layer // of the document in removed m_d->pendingActiveNode = node; m_d->activeNodeCompressor.start(); } void KisNodeFilterProxyModel::slotUpdateCurrentNodeFilter() { m_d->activeNode = m_d->pendingActiveNode; /** * During the filter update the model might emit "current changed" signals, * which (in their turn) will issue setData(..., KisNodeModel::ActiveRole) * call, leading to a double recursion. Which, obviously, crashes Krita. * * Right now, just blocking the KisNodeModel::ActiveRole call is the * most obvious solution for the problem. */ m_d->isUpdatingFilter = true; invalidateFilter(); m_d->isUpdatingFilter = false; } void KisNodeFilterProxyModel::unsetDummiesFacade() { m_d->nodeModel->setDummiesFacade(0, 0, 0, 0, 0); m_d->pendingActiveNode = 0; m_d->activeNode = 0; } diff --git a/libs/ui/widgets/kis_layer_filter_widget.cpp b/libs/ui/widgets/kis_layer_filter_widget.cpp index eaf6ebf6b7..74abe3e29e 100644 --- a/libs/ui/widgets/kis_layer_filter_widget.cpp +++ b/libs/ui/widgets/kis_layer_filter_widget.cpp @@ -1,293 +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(255); 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->setSpacing(0); subLayout->setAlignment(Qt::AlignLeft); 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 = 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(); } 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; 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()); 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) +MouseClickIgnore::MouseClickIgnore(QObject *parent) + : QObject(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 624054ab84..7dbe94e317 100644 --- a/libs/ui/widgets/kis_layer_filter_widget.h +++ b/libs/ui/widgets/kis_layer_filter_widget.h @@ -1,94 +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. */ + * correct for parent QMenu to properly + * resize. */ 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 { +class KRITAUI_EXPORT MouseClickIgnore : public QObject { Q_OBJECT public: - MouseClickIgnore(QWidget *parent = nullptr); + MouseClickIgnore(QObject *parent = nullptr); bool eventFilter(QObject *obj, QEvent *event); }; #endif // KISLAYERFILTERWIDGET_H