File indexing completed on 2024-12-22 04:13:07

0001 /*
0002  *  SPDX-FileCopyrightText: 2004 Cyrille Berger <cberger@cberger.net>
0003  *  SPDX-FileCopyrightText: 2016 Sven Langkamp <sven.langkamp@gmail.com>
0004  *  SPDX-FileCopyrightText: 2021 Deif Lou <ginoba@gmail.com>
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include <QPainter>
0010 #include <QSpinBox>
0011 #include <QDoubleSpinBox>
0012 #include <QPoint>
0013 #include <QMenu>
0014 #include <QAction>
0015 #include <QDialog>
0016 
0017 #include <KoColorSpace.h>
0018 #include <resources/KoStopGradient.h>
0019 
0020 #include "kis_debug.h"
0021 #include <kis_signals_blocker.h>
0022 
0023 #include <kis_icon_utils.h>
0024 
0025 #include <KoCanvasResourcesIds.h>
0026 #include <KoCanvasResourcesInterface.h>
0027 
0028 #include "KisStopGradientEditor.h"
0029 
0030 KisStopGradientEditor::KisStopGradientEditor(QWidget *parent)
0031     : QWidget(parent),
0032       m_gradient(0)
0033 {
0034     setupUi(this);
0035 
0036     QAction *selectPreviousStopAction = new QAction(KisIconUtils::loadIcon("arrow-left"), i18nc("Button to select previous stop in the stop gradient editor", "Select previous stop"), this);
0037     selectPreviousStopAction->setToolTip(selectPreviousStopAction->text());
0038     connect(selectPreviousStopAction, SIGNAL(triggered()), gradientSlider, SLOT(selectPreviousStop()));
0039 
0040     QAction *selectNextStopAction = new QAction(KisIconUtils::loadIcon("arrow-right"), i18nc("Button to select next stop in the stop gradient editor", "Select next stop"), this);
0041     selectNextStopAction->setToolTip(selectNextStopAction->text());
0042     connect(selectNextStopAction, SIGNAL(triggered()), gradientSlider, SLOT(selectNextStop()));
0043 
0044     m_editStopAction = new QAction(KisIconUtils::loadIcon("document-edit"), i18nc("Button to edit the selected stop color in the stop gradient editor", "Edit stop"), this);
0045     m_editStopAction->setToolTip(m_editStopAction->text());
0046     connect(m_editStopAction, SIGNAL(triggered()), this, SLOT(editSelectedStop()));
0047 
0048     m_deleteStopAction = new QAction(KisIconUtils::loadIcon("edit-delete"), i18nc("Button to delete the selected stop in the stop gradient editor", "Delete stop"), this);
0049     m_deleteStopAction->setToolTip(m_deleteStopAction->text());
0050     connect(m_deleteStopAction, SIGNAL(triggered()), gradientSlider, SLOT(deleteSelectedStop()));
0051 
0052     QAction *flipStopsAction = new QAction(KisIconUtils::loadIcon("transform_icons_mirror_x"), i18nc("Button to flip the stops in the stop gradient editor", "Flip gradient"), this);
0053     flipStopsAction->setToolTip(flipStopsAction->text());
0054     connect(flipStopsAction, SIGNAL(triggered()), this, SLOT(reverse()));
0055 
0056     QAction *sortByValueAction = new QAction(KisIconUtils::loadIcon("sort-by-value"), i18nc("Button to sort the stops by value in the stop gradient editor", "Sort stops by value"), this);
0057     sortByValueAction->setToolTip(sortByValueAction->text());
0058     connect(sortByValueAction, &QAction::triggered, this, [this]{ this->sortByValue(SORT_ASCENDING); } );
0059     
0060     QAction *sortByHueAction = new QAction(KisIconUtils::loadIcon("sort-by-hue"), i18nc("Button to sort the stops by hue in the stop gradient editor", "Sort stops by hue"), this);
0061     sortByHueAction->setToolTip(sortByHueAction->text());
0062     connect(sortByHueAction, &QAction::triggered, this, [this]{ this->sortByHue(SORT_ASCENDING); } );
0063 
0064     QAction *distributeEvenlyAction = new QAction(KisIconUtils::loadIcon("distribute-horizontal"), i18nc("Button to evenly distribute the stops in the stop gradient editor", "Distribute stops evenly"), this);
0065     distributeEvenlyAction->setToolTip(distributeEvenlyAction->text());
0066     connect(distributeEvenlyAction, SIGNAL(triggered()), this, SLOT(distributeStopsEvenly()));
0067 
0068     selectPreviousStopButton->setAutoRaise(true);
0069     selectPreviousStopButton->setDefaultAction(selectPreviousStopAction);
0070     
0071     selectNextStopButton->setAutoRaise(true);
0072     selectNextStopButton->setDefaultAction(selectNextStopAction);
0073 
0074     deleteStopButton->setAutoRaise(true);
0075     deleteStopButton->setDefaultAction(m_deleteStopAction);
0076 
0077     flipStopsButton->setAutoRaise(true);
0078     flipStopsButton->setDefaultAction(flipStopsAction);
0079 
0080     sortByValueButton->setAutoRaise(true);
0081     sortByValueButton->setDefaultAction(sortByValueAction);
0082 
0083     sortByHueButton->setAutoRaise(true);
0084     sortByHueButton->setDefaultAction(sortByHueAction);
0085 
0086     distributeEvenlyButton->setAutoRaise(true);
0087     distributeEvenlyButton->setDefaultAction(distributeEvenlyAction);
0088 
0089     compactModeSelectPreviousStopButton->setAutoRaise(true);
0090     compactModeSelectPreviousStopButton->setDefaultAction(selectPreviousStopAction);
0091     
0092     compactModeSelectNextStopButton->setAutoRaise(true);
0093     compactModeSelectNextStopButton->setDefaultAction(selectNextStopAction);
0094     
0095     compactModeMiscOptionsButton->setPopupMode(QToolButton::InstantPopup);
0096     compactModeMiscOptionsButton->setArrowVisible(false);
0097     compactModeMiscOptionsButton->setAutoRaise(true);
0098     compactModeMiscOptionsButton->setIcon(KisIconUtils::loadIcon("view-choose"));
0099     QMenu *compactModeMiscOptionsButtonMenu = new QMenu(this);
0100     QAction *separator = new QAction(this);
0101     separator->setSeparator(true);
0102     compactModeMiscOptionsButtonMenu->addAction(m_editStopAction);
0103     compactModeMiscOptionsButtonMenu->addAction(m_deleteStopAction);
0104     compactModeMiscOptionsButtonMenu->addAction(separator);
0105     compactModeMiscOptionsButtonMenu->addAction(flipStopsAction);
0106     compactModeMiscOptionsButtonMenu->addAction(sortByValueAction);
0107     compactModeMiscOptionsButtonMenu->addAction(sortByHueAction);
0108     compactModeMiscOptionsButtonMenu->addAction(distributeEvenlyAction);
0109     compactModeMiscOptionsButton->setPopupWidget(compactModeMiscOptionsButtonMenu);
0110 
0111     stopEditor->setUseTransParentCheckBox(false);
0112 
0113     connect(gradientSlider, SIGNAL(sigSelectedStop(int)), this, SLOT(stopChanged(int)));
0114     connect(nameedit, SIGNAL(editingFinished()), this, SLOT(nameChanged()));
0115     connect(stopEditor, SIGNAL(colorChanged(KoColor)), SLOT(colorChanged(KoColor)));
0116     connect(stopEditor, SIGNAL(colorTypeChanged(KisGradientWidgetsUtils::ColorType)), this, SLOT(stopTypeChanged(KisGradientWidgetsUtils::ColorType)));
0117     connect(stopEditor, SIGNAL(opacityChanged(qreal)), this, SLOT(opacityChanged(qreal)));
0118     connect(stopEditor, SIGNAL(positionChanged(qreal)), this, SLOT(positionChanged(qreal)));
0119 
0120     setCompactMode(false);
0121 
0122     setGradient(0);
0123     stopChanged(-1);
0124 }
0125 
0126 KisStopGradientEditor::KisStopGradientEditor(KoStopGradientSP gradient, QWidget *parent, const char* name, const QString& caption,
0127       KoCanvasResourcesInterfaceSP canvasResourcesInterface)
0128     : KisStopGradientEditor(parent)
0129 {
0130     m_canvasResourcesInterface = canvasResourcesInterface;
0131     setObjectName(name);
0132     setWindowTitle(caption);
0133     setGradient(gradient);
0134 }
0135 
0136 void KisStopGradientEditor::setCompactMode(bool value)
0137 {
0138     lblName->setVisible(!value);
0139     nameedit->setVisible(!value);
0140     buttonsContainer->setVisible(!value);
0141     stopEditorContainer->setVisible(!value);
0142     compactModeButtonsContainer->setVisible(value);
0143 }
0144 
0145 void KisStopGradientEditor::setGradient(KoStopGradientSP gradient)
0146 {
0147     m_gradient = gradient;
0148     setEnabled(m_gradient);
0149 
0150     if (m_gradient) {
0151         nameedit->setText(gradient->name());
0152         gradientSlider->setGradientResource(m_gradient);
0153         // stopChanged(gradientSlider->selectedStop());
0154     }
0155 
0156     emit sigGradientChanged();
0157 }
0158 
0159 void KisStopGradientEditor::setCanvasResourcesInterface(KoCanvasResourcesInterfaceSP canvasResourcesInterface)
0160 {
0161     m_canvasResourcesInterface = canvasResourcesInterface;
0162 }
0163 
0164 KoCanvasResourcesInterfaceSP KisStopGradientEditor::canvasResourcesInterface() const
0165 {
0166     return m_canvasResourcesInterface;
0167 }
0168 
0169 void KisStopGradientEditor::notifyGlobalColorChanged(const KoColor &color)
0170 {
0171     if (stopEditor->colorType() == KisGradientWidgetsUtils::Custom) {
0172 
0173         stopEditor->setColor(color);
0174     }
0175 }
0176 
0177 boost::optional<KoColor> KisStopGradientEditor::currentActiveStopColor() const
0178 {
0179     if (stopEditor->colorType() != KisGradientWidgetsUtils::Custom) return boost::none;
0180     return stopEditor->color();
0181 }
0182 
0183 void KisStopGradientEditor::stopChanged(int stop)
0184 {
0185     if (!m_gradient) return;
0186 
0187     const bool hasStopSelected = stop >= 0;
0188 
0189     m_editStopAction->setEnabled(hasStopSelected);
0190     m_deleteStopAction->setEnabled(hasStopSelected && m_gradient->stops().size() > 2);
0191     stopEditorContainer->setCurrentIndex(hasStopSelected ? 0 : 1);
0192 
0193     if (hasStopSelected) {
0194         selectedStopLabel->setText(i18nc("Text that indicates the selected stop in the stop gradient editor", "Stop #%1", stop + 1));
0195 
0196         KoGradientStop gradientStop = m_gradient->stops()[stop];
0197         KisSignalsBlocker blocker(stopEditor);
0198         stopEditor->setPosition(gradientStop.position * 100.0);
0199 
0200         KoColor color;
0201         qreal opacity;
0202         KoGradientStopType type = gradientStop.type;
0203 
0204         if (type == FOREGROUNDSTOP) {
0205             stopEditor->setColorType(KisGradientWidgetsUtils::Foreground);
0206             if (m_canvasResourcesInterface) {
0207                 color = m_canvasResourcesInterface->resource(KoCanvasResource::ForegroundColor).value<KoColor>();
0208             } else {
0209                 color = gradientStop.color;
0210             }
0211             opacity = 100.0;
0212         }
0213         else if (type == BACKGROUNDSTOP) {
0214             stopEditor->setColorType(KisGradientWidgetsUtils::Background);
0215             if (m_canvasResourcesInterface) {
0216                 color = m_canvasResourcesInterface->resource(KoCanvasResource::BackgroundColor).value<KoColor>();
0217             } else {
0218                 color = gradientStop.color;
0219             }
0220             opacity = 100.0;
0221         }
0222         else {
0223             stopEditor->setColorType(KisGradientWidgetsUtils::Custom);
0224             color = gradientStop.color;
0225             opacity = color.opacityF() * 100.0;
0226         }
0227 
0228         stopEditor->setColor(color);
0229         stopEditor->setOpacity(opacity);
0230 
0231     } else {
0232         selectedStopLabel->setText(i18nc("Text that indicates no stop is selected in the stop gradient editor", "No stop selected"));
0233     }
0234 
0235     emit sigGradientChanged();
0236 }
0237 
0238 void KisStopGradientEditor::stopTypeChanged(KisGradientWidgetsUtils::ColorType type) {
0239     QList<KoGradientStop> stops = m_gradient->stops();
0240     int currentStop = gradientSlider->selectedStop();
0241     KoGradientStop stop = stops[currentStop];
0242  
0243     if (type == KisGradientWidgetsUtils::Foreground) {
0244         stop.type = FOREGROUNDSTOP;
0245         if (m_canvasResourcesInterface) {
0246             stop.color = m_canvasResourcesInterface->resource(KoCanvasResource::ForegroundColor).value<KoColor>();
0247         }
0248     } else if (type == KisGradientWidgetsUtils::Background) {
0249         stop.type = BACKGROUNDSTOP;
0250         if (m_canvasResourcesInterface) {
0251             stop.color = m_canvasResourcesInterface->resource(KoCanvasResource::BackgroundColor).value<KoColor>();
0252         }
0253     } else {
0254         stop.type = COLORSTOP;
0255     }
0256 
0257     stop.color.setOpacity(1.0);
0258 
0259     stops.removeAt(currentStop);
0260     stops.insert(currentStop, stop);
0261     m_gradient->setStops(stops);
0262     stopEditor->setColor(stop.color);
0263     stopEditor->setOpacity(100.0);
0264     emit gradientSlider->updateRequested(); //setSelectedStopType(type);
0265     emit sigGradientChanged();
0266 }
0267 
0268 void KisStopGradientEditor::colorChanged(const KoColor& color)
0269 {
0270     if (!m_gradient) return;
0271 
0272     QList<KoGradientStop> stops = m_gradient->stops();
0273     int currentStop = gradientSlider->selectedStop();
0274     KoGradientStop stop = stops[currentStop];
0275         
0276     KoColor c(color);
0277     c.setOpacity(stop.color.opacityU8());
0278     stop.color = c;
0279     
0280     stops.removeAt(currentStop);
0281     stops.insert(currentStop, stop);
0282     m_gradient->setStops(stops);
0283 
0284     emit gradientSlider->updateRequested();
0285     emit sigGradientChanged();
0286 }
0287 
0288 void KisStopGradientEditor::opacityChanged(qreal value)
0289 {
0290     if (!m_gradient) return;
0291 
0292     QList<KoGradientStop> stops = m_gradient->stops();
0293     int currentStop = gradientSlider->selectedStop();
0294     KoGradientStop stop = stops[currentStop];
0295     
0296     stop.color.setOpacity(value / 100.0);
0297 
0298     stops.removeAt(currentStop);
0299     stops.insert(currentStop, stop);
0300     m_gradient->setStops(stops);
0301 
0302     emit gradientSlider->updateRequested();
0303     emit sigGradientChanged();
0304 }
0305 
0306 void KisStopGradientEditor::positionChanged(qreal value)
0307 {
0308     if (!m_gradient) return;
0309 
0310     QList<KoGradientStop> stops = m_gradient->stops();
0311     int currentStop = gradientSlider->selectedStop();
0312     KoGradientStop stop = stops[currentStop];
0313     stop.position = value / 100.0;
0314     
0315     stops.removeAt(currentStop);
0316     {
0317         currentStop = 0;
0318         for (int i = 0; i < stops.size(); i++) {
0319             if (stop.position <= stops[i].position) break;
0320 
0321             currentStop = i + 1;
0322         }
0323     }
0324     stops.insert(currentStop, stop);
0325     m_gradient->setStops(stops);
0326     gradientSlider->setSelectedStop(currentStop);
0327 
0328     emit gradientSlider->updateRequested();
0329     emit sigGradientChanged();
0330 }
0331 
0332 void KisStopGradientEditor::nameChanged()
0333 {
0334     if (!m_gradient) return;
0335 
0336     m_gradient->setName(nameedit->text());
0337     m_gradient->setFilename(nameedit->text() + m_gradient->defaultFileExtension());
0338     emit sigGradientChanged();
0339 }
0340 
0341 void KisStopGradientEditor::reverse()
0342 {
0343     if (!m_gradient) return;
0344 
0345     QList<KoGradientStop> stops = m_gradient->stops();
0346     QList<KoGradientStop> reversedStops;
0347     for(const KoGradientStop& stop : stops) {
0348         reversedStops.push_front(KoGradientStop(1 - stop.position, stop.color, stop.type));
0349     }
0350     m_gradient->setStops(reversedStops);
0351     if (gradientSlider->selectedStop() >= 0) {
0352         gradientSlider->setSelectedStop(stops.size() - 1 - gradientSlider->selectedStop());
0353     } else {
0354         emit gradientSlider->updateRequested();
0355     }
0356 
0357     emit sigGradientChanged();
0358 }
0359 
0360 void KisStopGradientEditor::distributeStopsEvenly()
0361 {
0362     if (!m_gradient) return;
0363 
0364     QList<KoGradientStop> stops = m_gradient->stops();
0365     qreal spacing = 1.0 / static_cast<qreal>(stops.size() - 1);
0366     for (int i = 0; i < stops.size(); ++i) {
0367         stops[i].position = qBound(0.0, static_cast<qreal>(i) * spacing, 1.0);
0368     }
0369     m_gradient->setStops(stops);
0370     if (gradientSlider->selectedStop() >= 0) {
0371         stopEditor->setPosition(stops[gradientSlider->selectedStop()].position * 100.0);
0372     }
0373     emit gradientSlider->updateRequested();
0374     emit sigGradientChanged();
0375 }
0376 
0377 void KisStopGradientEditor::sortByValue( SortFlags flags = SORT_ASCENDING )
0378 {
0379     if (!m_gradient) return;
0380 
0381     bool ascending = (flags & SORT_ASCENDING) > 0;
0382     bool evenDistribution = (flags & EVEN_DISTRIBUTION) > 0;
0383 
0384     QList<KoGradientStop> stops = m_gradient->stops();
0385     const int stopCount = stops.size();
0386 
0387     QList<KoGradientStop> sortedStops;
0388     std::sort(stops.begin(), stops.end(), KoGradientStopValueSort());
0389 
0390     int stopIndex = 0;
0391     for (const KoGradientStop& stop : stops) {
0392         const float value = evenDistribution ? (float)stopIndex / (float)(stopCount - 1) : stop.color.toQColor().valueF();
0393         const float position = ascending ? value : 1.f - value;
0394 
0395         if (ascending) {
0396             sortedStops.push_back(KoGradientStop(position, stop.color, stop.type));
0397         } else {
0398             sortedStops.push_front(KoGradientStop(position, stop.color, stop.type));
0399         }
0400 
0401         stopIndex++;
0402     }
0403 
0404     m_gradient->setStops(sortedStops);
0405     gradientSlider->setSelectedStop(stopCount - 1);
0406 
0407     emit gradientSlider->updateRequested();
0408     emit sigGradientChanged();
0409 }
0410 
0411 void KisStopGradientEditor::sortByHue( SortFlags flags = SORT_ASCENDING )
0412 {
0413     if (!m_gradient) return;
0414 
0415     bool ascending = (flags & SORT_ASCENDING) > 0;
0416     bool evenDistribution = (flags & EVEN_DISTRIBUTION) > 0;
0417 
0418     QList<KoGradientStop> stops = m_gradient->stops();
0419     const int stopCount = stops.size();
0420 
0421     QList<KoGradientStop> sortedStops;
0422     std::sort(stops.begin(), stops.end(), KoGradientStopHueSort());
0423 
0424     int stopIndex = 0;
0425     for (const KoGradientStop& stop : stops) {
0426         const float value = evenDistribution ? (float)stopIndex / (float)(stopCount - 1) : qMax(0.0, stop.color.toQColor().hueF());
0427         const float position = ascending ? value : 1.f - value;
0428 
0429         if (ascending) {
0430             sortedStops.push_back(KoGradientStop(position, stop.color, stop.type));
0431         } else {
0432             sortedStops.push_front(KoGradientStop(position, stop.color, stop.type));
0433         }
0434 
0435         stopIndex++;
0436     }
0437 
0438     m_gradient->setStops(sortedStops);
0439     gradientSlider->setSelectedStop(stopCount - 1);
0440 
0441     emit gradientSlider->updateRequested();
0442     emit sigGradientChanged();
0443 }
0444 
0445 void KisStopGradientEditor::editSelectedStop()
0446 {
0447     if (gradientSlider->selectedStop() < 0) {
0448         return;
0449     }
0450 
0451     QDialog *dialog = new QDialog(this);
0452     dialog->setModal(true);
0453     dialog->setWindowTitle(i18nc("Title for the gradient stop editor", "Edit Stop"));
0454     dialog->setAttribute(Qt::WA_DeleteOnClose);
0455 
0456     QWidget *editor = stopEditorContainer->currentWidget();
0457     int index = stopEditorContainer->indexOf(editor);
0458     stopEditorContainer->removeWidget(editor);
0459 
0460     QVBoxLayout *dialogLayout = new QVBoxLayout;
0461     dialogLayout->setMargin(10);
0462     dialogLayout->addWidget(editor);
0463 
0464     dialog->setLayout(dialogLayout);
0465     editor->show();
0466     dialog->resize(0, 0);
0467 
0468     connect(dialog, &QDialog::finished, [this, editor, index](int)
0469                                         {
0470                                             stopEditorContainer->insertWidget(index, editor);
0471                                             stopEditorContainer->setCurrentIndex(index);
0472                                         });
0473 
0474     dialog->show();
0475     dialog->raise();
0476     dialog->activateWindow();
0477 }