File indexing completed on 2024-05-26 04:32:50
0001 /* 0002 * SPDX-FileCopyrightText: 2020 Mathias Wein <lynx.mw+kde@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-3.0-or-later 0005 */ 0006 0007 #include "WGSelectorConfigGrid.h" 0008 0009 #include "KisVisualColorSelector.h" 0010 0011 #include "kis_debug.h" 0012 0013 #include <QAction> 0014 #include <QActionGroup> 0015 #include <QEvent> 0016 #include <QGridLayout> 0017 #include <QIcon> 0018 #include <QPainter> 0019 #include <QString> 0020 #include <QTimer> 0021 #include <QToolButton> 0022 0023 0024 class SelectorConfigAction: public QAction 0025 { 0026 public: 0027 explicit SelectorConfigAction(const KisColorSelectorConfiguration &cfg, QObject *parent) 0028 : QAction(parent) 0029 , m_config(cfg) 0030 { 0031 setCheckable(true); 0032 } 0033 const KisColorSelectorConfiguration& configuration() { return m_config; } 0034 private: 0035 KisColorSelectorConfiguration m_config; 0036 }; 0037 0038 WGSelectorConfigGrid::WGSelectorConfigGrid(QWidget *parent, bool multiSelect) 0039 : QWidget(parent) 0040 , m_layout(new QGridLayout(this)) 0041 , m_actionGroup(new QActionGroup(this)) 0042 , m_selector(new KisVisualColorSelector(this)) 0043 { 0044 m_actionGroup->setExclusive(!multiSelect); 0045 connect(m_actionGroup, SIGNAL(triggered(QAction*)), SLOT(slotActionTriggered(QAction*))); 0046 0047 m_selector->setMinimumSliderWidth(10); 0048 m_selector->setGeometry(0, 0, m_iconSize, m_iconSize - 2); 0049 m_selector->setVisible(false); 0050 m_selector->setEnabled(false); 0051 m_selector->slotSetColorSpace(KoColorSpaceRegistry::instance()->rgb8()); 0052 m_selector->slotSetColor(KoColor(QColor(255, 0, 0), KoColorSpaceRegistry::instance()->rgb8())); 0053 } 0054 0055 void WGSelectorConfigGrid::clear() 0056 { 0057 while (QLayoutItem *child = m_layout->takeAt(0)) { 0058 delete child->widget(); 0059 delete child; 0060 } 0061 qDeleteAll(m_actionGroup->actions()); 0062 } 0063 0064 QIcon WGSelectorConfigGrid::currentIcon() const 0065 { 0066 return (m_currentAction && m_currentAction != m_dummyAction) ? m_currentAction->icon() : QIcon(); 0067 } 0068 0069 KisColorSelectorConfiguration WGSelectorConfigGrid::currentConfiguration() const 0070 { 0071 if (m_currentAction != m_dummyAction) { 0072 SelectorConfigAction *sa = dynamic_cast<SelectorConfigAction *>(m_currentAction); 0073 if (sa) { 0074 return sa->configuration(); 0075 } 0076 } 0077 return KisColorSelectorConfiguration(); 0078 } 0079 0080 QVector<KisColorSelectorConfiguration> WGSelectorConfigGrid::selectedConfigurations() const 0081 { 0082 QVector<KisColorSelectorConfiguration> configurations; 0083 const QList<QAction*> actions = m_actionGroup->actions(); 0084 for (QAction *action : actions) { 0085 SelectorConfigAction *sa = dynamic_cast<SelectorConfigAction *>(action); 0086 if (sa && sa->isChecked()) { 0087 configurations.append(sa->configuration()); 0088 } 0089 } 0090 return configurations; 0091 } 0092 0093 void WGSelectorConfigGrid::setColorModel(KisVisualColorModel::ColorModel model) 0094 { 0095 if (model != m_selector->selectorModel()->colorModel()) { 0096 m_selector->selectorModel()->setRGBColorModel(model); 0097 updateIcons(); 0098 } 0099 } 0100 0101 void WGSelectorConfigGrid::setConfigurations(const QVector<KisColorSelectorConfiguration> &configurations) 0102 { 0103 clear(); 0104 // exclusive action groups have no nice way to set none as checked, and 0105 // ExclusiveOptional is not what we want either, so use a dummy action 0106 m_dummyAction = new QAction("dummy", m_actionGroup); 0107 m_dummyAction->setCheckable(true); 0108 m_dummyAction->setChecked(true); 0109 m_currentAction = m_dummyAction; 0110 0111 for (int i = 0; i < configurations.size(); i++) { 0112 const KisColorSelectorConfiguration &config = configurations.at(i); 0113 SelectorConfigAction *action = new SelectorConfigAction(config, m_actionGroup); 0114 //action->setText(QString("TBD %1").arg(i)); 0115 action->setIcon(generateIcon(config, devicePixelRatioF(), true)); 0116 0117 QToolButton *button = new QToolButton(this); 0118 button->setAutoRaise(true); 0119 button->setDefaultAction(action); 0120 button->setIconSize(QSize(m_iconSize, m_iconSize)); 0121 m_layout->addWidget(button, i/m_columns, i%m_columns); 0122 } 0123 } 0124 0125 void WGSelectorConfigGrid::setChecked(const KisColorSelectorConfiguration &configuration) 0126 { 0127 const QList<QAction*> actions = m_actionGroup->actions(); 0128 for (QAction *action : actions) { 0129 SelectorConfigAction *sa = dynamic_cast<SelectorConfigAction *>(action); 0130 if (sa && sa->configuration() == configuration) { 0131 sa->setChecked(true); 0132 m_currentAction = action; 0133 return; 0134 } 0135 } 0136 m_dummyAction->setChecked(true); 0137 m_currentAction = m_dummyAction; 0138 } 0139 0140 QIcon WGSelectorConfigGrid::generateIcon(const KisColorSelectorConfiguration &configuration, qreal pixelRatio, bool dualState) const 0141 { 0142 QSize sz(m_selector->width() * pixelRatio, m_selector->height() * pixelRatio); 0143 QPixmap pixmap(sz); 0144 pixmap.setDevicePixelRatio(pixelRatio); 0145 pixmap.fill(Qt::transparent); 0146 m_selector->setConfiguration(&configuration); 0147 QPoint offset(0, dualState ? 2 : 1); 0148 m_selector->render(&pixmap, offset, QRegion(), RenderFlag::DrawChildren); 0149 QIcon icon(pixmap); 0150 0151 if (!dualState) { 0152 return icon; 0153 } 0154 0155 // highlight bar for on-icon 0156 QPainter painter(&pixmap); 0157 painter.setRenderHint(QPainter::Antialiasing); 0158 QBrush highlight(palette().brush(QPalette::Active, QPalette::Highlight)); 0159 painter.setPen(QPen(highlight, 2.0, Qt::SolidLine, Qt::RoundCap)); 0160 painter.drawLine(1, 1, m_iconSize-1, 1); 0161 painter.end(); 0162 icon.addPixmap(pixmap, QIcon::Normal, QIcon::On); 0163 return icon; 0164 } 0165 0166 QVector<KisColorSelectorConfiguration> WGSelectorConfigGrid::hueBasedConfigurations() 0167 { 0168 using KCSC = KisColorSelectorConfiguration; 0169 QVector<KCSC> configs; 0170 configs.push_back(KCSC(KCSC::Triangle, KCSC::Ring, KCSC::SV, KCSC::H)); 0171 configs.push_back(KCSC(KCSC::Square, KCSC::Ring, KCSC::SV, KCSC::H)); 0172 configs.push_back(KCSC(KCSC::Wheel, KCSC::Slider, KCSC::VH, KCSC::hsvS)); 0173 configs.push_back(KCSC(KCSC::Wheel, KCSC::Slider, KCSC::hsvSH, KCSC::V)); 0174 configs.push_back(KCSC(KCSC::Square, KCSC::Slider, KCSC::SV, KCSC::H)); 0175 configs.push_back(KCSC(KCSC::Square, KCSC::Slider, KCSC::VH, KCSC::hsvS)); 0176 configs.push_back(KCSC(KCSC::Square, KCSC::Slider, KCSC::hsvSH, KCSC::V)); 0177 return configs; 0178 } 0179 0180 bool WGSelectorConfigGrid::event(QEvent *event) 0181 { 0182 bool handled = QWidget::event(event); 0183 if (event->type() == QEvent::PaletteChange) { 0184 // For some reason, Qt doesn't like if we recreate the icons from this 0185 // even handler and randomly crashes somewhere after this function returns 0186 QTimer::singleShot(10, this, &WGSelectorConfigGrid::updateIcons); 0187 event->accept(); 0188 handled = true; 0189 } 0190 return handled; 0191 } 0192 0193 void WGSelectorConfigGrid::slotActionTriggered(QAction *action) 0194 { 0195 // Unfortunately, exclusive QActionGroups don't filter out attempts to toggle 0196 // an action that is already checked, so need to check ourselves if checked 0197 // action really changed. Checking the hidden dummy also is a silent operation. 0198 if (action == m_currentAction) { 0199 return; 0200 } 0201 m_currentAction = action; 0202 if (action != m_dummyAction) { 0203 SelectorConfigAction *sa = dynamic_cast<SelectorConfigAction *>(action); 0204 KIS_SAFE_ASSERT_RECOVER_RETURN(sa); 0205 0206 emit sigConfigSelected(sa->configuration()); 0207 } 0208 } 0209 0210 void WGSelectorConfigGrid::updateIcons() { 0211 const QList<QAction*> actions = m_actionGroup->actions(); 0212 for (QAction *action : actions) { 0213 SelectorConfigAction *sa = dynamic_cast<SelectorConfigAction *>(action); 0214 if (sa) { 0215 sa->setIcon(generateIcon(sa->configuration(), devicePixelRatioF(), true)); 0216 } 0217 } 0218 }