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 }