File indexing completed on 2024-05-26 04:30:45
0001 /* 0002 * SPDX-FileCopyrightText: 2015 Dmitry Kazakov <dimula73@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "kis_color_filter_combo.h" 0008 0009 #include <klocalizedstring.h> 0010 #include <QStylePainter> 0011 #include <QtCore/qmath.h> 0012 #include <QApplication> 0013 #include <QProxyStyle> 0014 #include <QStyleOption> 0015 #include <QSortFilterProxyModel> 0016 #include <QStandardItemModel> 0017 #include <QListView> 0018 #include <QMouseEvent> 0019 0020 #include <QStyleFactory> 0021 0022 0023 0024 #include "kis_node_view_color_scheme.h" 0025 #include "kis_debug.h" 0026 #include "kis_icon_utils.h" 0027 #include "krita_utils.h" 0028 #include "kis_node.h" 0029 0030 enum AdditionalRoles { 0031 OriginalLabelIndex = Qt::UserRole + 1000 0032 }; 0033 0034 0035 struct LabelFilteringModel : public QSortFilterProxyModel 0036 { 0037 LabelFilteringModel(QObject *parent) : QSortFilterProxyModel(parent) {} 0038 0039 bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override { 0040 const QModelIndex index = sourceModel()->index(source_row, 0, source_parent); 0041 const int labelIndex = index.data(OriginalLabelIndex).toInt(); 0042 0043 0044 return labelIndex < 0 || m_acceptedLabels.contains(labelIndex); 0045 } 0046 0047 void setAcceptedLabels(const QSet<int> &value) { 0048 m_acceptedLabels = value; 0049 invalidateFilter(); 0050 } 0051 0052 private: 0053 QSet<int> m_acceptedLabels; 0054 }; 0055 0056 0057 class ComboEventFilter : public QObject 0058 { 0059 public: 0060 ComboEventFilter(KisColorFilterCombo *parent) : m_parent(parent), m_buttonPressed(false) {} 0061 0062 protected: 0063 bool eventFilter(QObject *obj, QEvent *event) override { 0064 if (event->type() == QEvent::Leave) { 0065 m_buttonPressed = false; 0066 0067 } else if (event->type() == QEvent::MouseButtonPress) { 0068 QMouseEvent *mevent = static_cast<QMouseEvent*>(event); 0069 m_buttonPressed = mevent->button() == Qt::LeftButton; 0070 0071 } else if (event->type() == QEvent::MouseButtonRelease) { 0072 QMouseEvent *mevent = static_cast<QMouseEvent*>(event); 0073 QModelIndex index = m_parent->view()->indexAt(mevent->pos()); 0074 if (!index.isValid()) return false; 0075 0076 /** 0077 * We should eat the first event that arrives exactly when 0078 * the drop down appears on screen. 0079 */ 0080 if (!m_buttonPressed) return true; 0081 0082 const bool toUncheckedState = index.data(Qt::CheckStateRole) == Qt::Checked; 0083 0084 if (toUncheckedState) { 0085 m_parent->model()->setData(index, Qt::Unchecked, Qt::CheckStateRole); 0086 } else { 0087 m_parent->model()->setData(index, Qt::Checked, Qt::CheckStateRole); 0088 } 0089 0090 if (index.data(OriginalLabelIndex).toInt() == -1) { 0091 for (int i = 0; i < m_parent->model()->rowCount(); i++) { 0092 const QModelIndex &other = m_parent->model()->index(i, 0); 0093 if (other.data(OriginalLabelIndex) != -1) { 0094 m_parent->model()->setData(other, toUncheckedState ? Qt::Unchecked : Qt::Checked, Qt::CheckStateRole); 0095 } 0096 } 0097 } else { 0098 bool prevChecked = false; 0099 bool checkedVaries = false; 0100 QModelIndex allLabelsIndex; 0101 0102 for (int i = 0; i < m_parent->model()->rowCount(); i++) { 0103 const QModelIndex &other = m_parent->model()->index(i, 0); 0104 if (other.data(OriginalLabelIndex) != -1) { 0105 const bool currentChecked = other.data(Qt::CheckStateRole) == Qt::Checked; 0106 0107 if (i == 0) { 0108 prevChecked = currentChecked; 0109 } else { 0110 if (prevChecked != currentChecked) { 0111 checkedVaries = true; 0112 break; 0113 } 0114 } 0115 } else { 0116 allLabelsIndex = other; 0117 } 0118 } 0119 0120 const bool allLabelsIndexShouldBeChecked = 0121 prevChecked && !checkedVaries; 0122 0123 if (allLabelsIndexShouldBeChecked != 0124 (allLabelsIndex.data(Qt::CheckStateRole) == Qt::Checked)) { 0125 0126 m_parent->model()->setData(allLabelsIndex, allLabelsIndexShouldBeChecked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); 0127 } 0128 } 0129 0130 emit m_parent->selectedColorsChanged(); 0131 0132 m_buttonPressed = false; 0133 return true; 0134 } 0135 0136 return QObject::eventFilter(obj, event); 0137 } 0138 0139 private: 0140 KisColorFilterCombo *m_parent; 0141 bool m_buttonPressed; 0142 }; 0143 0144 class FullSizedListView : public QListView 0145 { 0146 public: 0147 QSize sizeHint() const override { 0148 return contentsSize(); 0149 } 0150 }; 0151 0152 class PopupComboBoxStyle : public QProxyStyle 0153 { 0154 public: 0155 PopupComboBoxStyle(QStyle *baseStyle = nullptr) : QProxyStyle(baseStyle) {} 0156 0157 int styleHint(QStyle::StyleHint hint, const QStyleOption *option, const QWidget *widget, QStyleHintReturn *returnData) const override 0158 { 0159 // This flag makes ComboBox popup float on top of its parent ComboBox, like in Fusion style. 0160 // Only when this hint is set will Qt respect combobox popup size hints, otherwise the popup 0161 // can never exceed the width of its parent ComboBox, like in Breeze style. 0162 if (hint == QStyle::SH_ComboBox_Popup) { 0163 return true; 0164 } 0165 0166 return QProxyStyle::styleHint(hint, option, widget, returnData); 0167 } 0168 }; 0169 0170 struct KisColorFilterCombo::Private 0171 { 0172 LabelFilteringModel *filteringModel; 0173 /** 0174 * if the combobox is in the filter mode 0175 * (when no colors are selected) 0176 * it will show the filter icon ("view-filter") 0177 * otherwise it will show tag icon ("tag") 0178 */ 0179 bool filterMode {true}; 0180 /** 0181 * If the combobox is in the circle mode, 0182 * it will show the selected colors as circle 0183 * otherwise it will show it in a rectangle 0184 */ 0185 bool circleMode {true}; 0186 }; 0187 0188 KisColorFilterCombo::KisColorFilterCombo(QWidget *parent, bool filterMode, bool circleMode) 0189 : QComboBox(parent), 0190 m_d(new Private) 0191 { 0192 m_d->filterMode = filterMode; 0193 m_d->circleMode = circleMode; 0194 0195 0196 QStandardItemModel *newModel = new QStandardItemModel(this); 0197 setModel(newModel); 0198 0199 QStyle* newStyle = QStyleFactory::create(style()->objectName()); 0200 // proxy style steals the ownership of the style and deletes it later 0201 PopupComboBoxStyle *proxyStyle = new PopupComboBoxStyle(newStyle); 0202 0203 proxyStyle->setParent(this); 0204 setStyle(proxyStyle); 0205 0206 setView(new FullSizedListView); 0207 m_eventFilters.append(new ComboEventFilter(this)); 0208 m_eventFilters.append(new ComboEventFilter(this)); 0209 0210 view()->installEventFilter(m_eventFilters[0]); 0211 view()->viewport()->installEventFilter(m_eventFilters[1]); 0212 0213 KisNodeViewColorScheme scm; 0214 0215 QStandardItem* item = new QStandardItem(i18nc("combo box: show all layers", "All")); 0216 item->setCheckable(true); 0217 item->setCheckState(Qt::Unchecked); 0218 item->setData(QColor(Qt::transparent), Qt::BackgroundRole); 0219 item->setData(int(-1), OriginalLabelIndex); 0220 item->setData(QSize(30, scm.rowHeight()), Qt::SizeHintRole); 0221 newModel->appendRow(item); 0222 0223 int labelIndex = 0; 0224 foreach (const QColor &color, scm.allColorLabels()) { 0225 const QString title = color.alpha() > 0 ? "" : i18nc("combo box: select all layers without a label", "No Label"); 0226 0227 QStandardItem* item = new QStandardItem(title); 0228 item->setCheckable(true); 0229 item->setCheckState(Qt::Unchecked); 0230 item->setData(color, Qt::BackgroundRole); 0231 item->setData(labelIndex, OriginalLabelIndex); 0232 item->setData(QSize(30, scm.rowHeight()), Qt::SizeHintRole); 0233 newModel->appendRow(item); 0234 0235 labelIndex++; 0236 } 0237 0238 m_d->filteringModel = new LabelFilteringModel(this); 0239 QAbstractItemModel *originalModel = model(); 0240 originalModel->setParent(m_d->filteringModel); 0241 0242 m_d->filteringModel->setSourceModel(originalModel); 0243 setModel(m_d->filteringModel); 0244 } 0245 0246 KisColorFilterCombo::~KisColorFilterCombo() 0247 { 0248 qDeleteAll(m_eventFilters); 0249 } 0250 0251 void collectAvailableLabels(KisNodeSP root, QSet<int> *labels) 0252 { 0253 labels->insert(root->colorLabelIndex()); 0254 0255 KisNodeSP node = root->firstChild(); 0256 while (node) { 0257 collectAvailableLabels(node, labels); 0258 node = node->nextSibling(); 0259 } 0260 } 0261 0262 void KisColorFilterCombo::updateAvailableLabels(KisNodeSP rootNode) 0263 { 0264 QSet<int> labels; 0265 if (!rootNode.isNull()) { 0266 collectAvailableLabels(rootNode, &labels); 0267 } 0268 0269 updateAvailableLabels(labels); 0270 } 0271 0272 void KisColorFilterCombo::updateAvailableLabels(const QSet<int> &labels) 0273 { 0274 m_d->filteringModel->setAcceptedLabels(labels); 0275 } 0276 0277 void KisColorFilterCombo::setModes(bool filterMode, bool circleMode) 0278 { 0279 m_d->filterMode = filterMode; 0280 m_d->circleMode = circleMode; 0281 } 0282 0283 QList<int> KisColorFilterCombo::selectedColors() const 0284 { 0285 QList<int> colors; 0286 for (int i = 0; i < model()->rowCount(); i++) { 0287 const QModelIndex &other = model()->index(i, 0); 0288 const int label = other.data(OriginalLabelIndex).toInt(); 0289 0290 if (label != -1 && 0291 other.data(Qt::CheckStateRole) == Qt::Checked) { 0292 0293 colors << label; 0294 } 0295 } 0296 return colors; 0297 } 0298 0299 void KisColorFilterCombo::paintColorPie(QStylePainter &painter, const QPalette& palette, const QList<int> &selectedColors, const QRect &rect, const int &baseSize) 0300 { 0301 KisNodeViewColorScheme scm; 0302 const QPen oldPen = painter.pen(); 0303 const QBrush oldBrush = painter.brush(); 0304 const int border = 0; 0305 QColor shadowColor = palette.shadow().color(); 0306 shadowColor.setAlpha(64); 0307 0308 QRect pieRect(0, 0, baseSize - 2 * border, baseSize - 2 * border); 0309 pieRect.moveCenter(rect.center()); 0310 0311 if (selectedColors.size() == 1) { 0312 const int currentLabel = selectedColors.first(); 0313 const QColor currentColor = scm.colorFromLabelIndex(currentLabel); 0314 const QBrush brush = QBrush(currentColor); 0315 painter.setBrush(brush); 0316 painter.setPen(QPen(shadowColor, 1)); 0317 0318 if (currentColor.alpha() > 0) { 0319 painter.drawEllipse(rect); 0320 } else if (currentLabel == 0) { 0321 QColor white = Qt::white; 0322 QColor grey = QColor(220,220,220); 0323 painter.setBrush(QBrush(shadowColor)); 0324 painter.setRenderHint(QPainter::Antialiasing); 0325 painter.drawEllipse(rect); 0326 const int step = 16 * 360 / 4; 0327 const int checkerSteps = 4; 0328 0329 for (int i = 0; i < checkerSteps; i++) { 0330 QBrush checkerBrush = QBrush((i % 2) ? grey : white); 0331 painter.setPen(Qt::NoPen); 0332 painter.setBrush(checkerBrush); 0333 painter.drawPie(pieRect, step * i, step); 0334 } 0335 0336 } 0337 } else { 0338 const int numColors = selectedColors.size(); 0339 const int step = 16 * 360 / numColors; 0340 0341 painter.setPen(QPen(shadowColor, 1)); 0342 painter.setBrush(QColor(0,0,0,0)); 0343 painter.setRenderHint(QPainter::Antialiasing); 0344 painter.drawEllipse(rect); 0345 for (int i = 0; i < numColors; i++) { 0346 QColor color = scm.colorFromLabelIndex(selectedColors[i]); 0347 QBrush brush = color.alpha() > 0 ? QBrush(color) : QBrush(Qt::black, Qt::Dense4Pattern); 0348 painter.setPen(Qt::NoPen); 0349 painter.setBrush(brush); 0350 0351 painter.drawPie(pieRect, step * i, step); 0352 } 0353 } 0354 0355 painter.setPen(oldPen); 0356 painter.setBrush(oldBrush); 0357 } 0358 0359 0360 void KisColorFilterCombo::paintEvent(QPaintEvent *event) 0361 { 0362 Q_UNUSED(event); 0363 0364 QStylePainter painter(this); 0365 painter.setPen(palette().color(QPalette::Text)); 0366 0367 // draw the combobox frame, focusrect and selected etc. 0368 QStyleOptionComboBox opt; 0369 initStyleOption(&opt); 0370 painter.drawComplexControl(QStyle::CC_ComboBox, opt); 0371 0372 0373 { 0374 const QRect editRect = style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxEditField, this); 0375 const int size = qMin(editRect.width(), editRect.height()); 0376 0377 const QList<int> selectedColors = this->selectedColors(); 0378 0379 if (selectedColors.size() == 0 || selectedColors.size() == model()->rowCount() - 1) { 0380 QIcon icon = KisIconUtils::loadIcon(m_d->filterMode ? "view-filter" : "tag"); 0381 QPixmap pixmap = icon.pixmap(QSize(size, size), !isEnabled() ? QIcon::Disabled : QIcon::Normal); 0382 painter.drawPixmap(editRect.right() - size, editRect.top(), pixmap); 0383 0384 } else { 0385 const int numColors = selectedColors.size(); 0386 if (m_d->circleMode) { 0387 KisColorFilterCombo::paintColorPie(painter, opt.palette, selectedColors, editRect, size ); 0388 } else { 0389 // show all colors in a rectangle 0390 KisNodeViewColorScheme scm; 0391 0392 int oneColorWidth = editRect.width()/numColors; 0393 int currentWidth = 0; 0394 for (int i = 0; i < numColors; i++) { 0395 QColor color = scm.colorFromLabelIndex(selectedColors[i]); 0396 QBrush brush = color.alpha() > 0 ? QBrush(color) : QBrush(Qt::black, Qt::Dense4Pattern); 0397 painter.setPen(color); 0398 painter.setBrush(brush); 0399 if (i == numColors - 1) { 0400 // last color; let's fill up 0401 painter.fillRect(currentWidth, editRect.top(), editRect.width() - currentWidth, editRect.height(), brush); 0402 } else { 0403 painter.fillRect(currentWidth, editRect.top(), oneColorWidth, editRect.height(), brush); 0404 } 0405 0406 currentWidth += oneColorWidth; 0407 } 0408 0409 0410 0411 0412 } 0413 } 0414 } 0415 0416 // draw the icon and text 0417 //painter.drawControl(QStyle::CE_ComboBoxLabel, opt); 0418 } 0419 0420 QSize KisColorFilterCombo::minimumSizeHint() const 0421 { 0422 return sizeHint(); 0423 } 0424 0425 QSize KisColorFilterCombo::sizeHint() const 0426 { 0427 QStyleOptionComboBox opt; 0428 initStyleOption(&opt); 0429 0430 const QStyleOption *baseOption = qstyleoption_cast<const QStyleOption *>(&opt); 0431 const int arrowSize = style()->pixelMetric(QStyle::PM_ScrollBarExtent, baseOption, this); 0432 0433 const QSize originalHint = QComboBox::sizeHint(); 0434 QSize sh(3 * arrowSize, originalHint.height()); 0435 0436 return sh; 0437 }