File indexing completed on 2025-02-02 04:11:31

0001 /*
0002  * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best>
0003  *
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  */
0006 
0007 #include "gradient_list_widget.hpp"
0008 #include "ui_gradient_list_widget.h"
0009 
0010 #include <QEvent>
0011 #include <QMetaEnum>
0012 #include <QRegularExpression>
0013 #include <QDialog>
0014 #include <QDialogButtonBox>
0015 #include <QComboBox>
0016 #include <QVBoxLayout>
0017 
0018 #include <QtColorWidgets/gradient_delegate.hpp>
0019 #include <QtColorWidgets/gradient_list_model.hpp>
0020 
0021 #include "model/document.hpp"
0022 #include "model/assets/assets.hpp"
0023 #include "model/shapes/fill.hpp"
0024 #include "model/shapes/stroke.hpp"
0025 
0026 #include "command/undo_macro_guard.hpp"
0027 #include "command/object_list_commands.hpp"
0028 #include "command/property_commands.hpp"
0029 
0030 #include "item_models/gradient_list_model.hpp"
0031 
0032 using namespace glaxnimate::gui;
0033 using namespace glaxnimate;
0034 
0035 class GradientListWidget::Private
0036 {
0037 public:
0038     Ui::GradientListWidget ui;
0039     item_models::GradientListModel model;
0040     model::Document* document = nullptr;
0041     glaxnimate::gui::SelectionManager* window = nullptr;
0042     std::vector<model::Styler*> fills;
0043     std::vector<model::Styler*> strokes;
0044     model::Fill* current_fill = nullptr;
0045     model::Stroke* current_stroke = nullptr;
0046     color_widgets::GradientDelegate delegate;
0047     color_widgets::GradientListModel presets;
0048     GradientListWidget* parent;
0049 
0050     model::GradientColors* current()
0051     {
0052         if ( !document )
0053             return nullptr;
0054         auto index = ui.list_view->currentIndex();
0055         if ( !index.isValid() )
0056             return nullptr;
0057         return document->assets()->gradient_colors->values[index.row()];
0058     }
0059 
0060     struct TypeButtonSlot
0061     {
0062         std::pair<QToolButton*, QToolButton*> btn_others;
0063         GradientListWidget* widget;
0064         model::Gradient::GradientType gradient_type;
0065         bool secondary;
0066 
0067         void operator() (bool checked)
0068         {
0069             if ( !checked )
0070             {
0071                 widget->d->clear_gradient(secondary);
0072             }
0073             else
0074             {
0075                 btn_others.first->setChecked(false);
0076                 btn_others.second->setChecked(false);
0077                 widget->d->set_gradient(secondary, gradient_type);
0078             }
0079         }
0080     };
0081 
0082     TypeButtonSlot slot_conical(GradientListWidget* widget, bool secondary)
0083     {
0084         return {
0085             {secondary ? ui.btn_stroke_linear : ui.btn_fill_linear, secondary ? ui.btn_stroke_radial : ui.btn_fill_radial},
0086             widget,
0087             model::Gradient::Conical,
0088             secondary
0089         };
0090     }
0091 
0092     TypeButtonSlot slot_radial(GradientListWidget* widget, bool secondary)
0093     {
0094         return {
0095             {secondary ? ui.btn_stroke_linear : ui.btn_fill_linear, secondary ? ui.btn_stroke_conical : ui.btn_fill_conical},
0096             widget,
0097             model::Gradient::Radial,
0098             secondary
0099         };
0100     }
0101 
0102     TypeButtonSlot slot_linear(GradientListWidget* widget, bool secondary)
0103     {
0104         return {
0105             {secondary ? ui.btn_stroke_radial : ui.btn_fill_radial, secondary ? ui.btn_stroke_conical : ui.btn_fill_conical},
0106             widget,
0107             model::Gradient::Linear,
0108             secondary
0109         };
0110     }
0111 
0112     void set_gradient(bool secondary, model::Gradient::GradientType gradient_type)
0113     {
0114         auto& targets = secondary ? strokes : fills;
0115 
0116         // no valid selection
0117         if ( targets.empty() )
0118             return;
0119 
0120         // gather colors
0121         model::GradientColors* colors = current();
0122         if ( !colors )
0123         {
0124             if ( document->assets()->gradient_colors->values.empty() )
0125                 add_gradient();
0126             else
0127                 ui.list_view->setCurrentIndex(model.gradient_to_index(document->assets()->gradient_colors->values.back()));
0128 
0129             colors = current();
0130         }
0131 
0132         // Undo macro
0133         command::UndoMacroGuard macro(i18n("Set %1 Gradient", model::Gradient::gradient_type_name(gradient_type)), document);
0134 
0135         // Gather bounding box
0136         auto shape_element = window->current_shape();
0137         QRectF bounds;
0138 
0139         if ( shape_element )
0140         {
0141             bounds = shape_element->local_bounding_rect(shape_element->time());
0142             if ( bounds.isNull() )
0143             {
0144                 if ( auto parent = window->current_shape_container() )
0145                     bounds = parent->bounding_rect(shape_element->time());
0146             }
0147         }
0148 
0149         if ( bounds.isNull() )
0150             bounds = QRectF(QPointF(0, 0), window->current_composition()->size());
0151 
0152 
0153         // Insert Gradient
0154         auto grad = std::make_unique<model::Gradient>(document);
0155         grad->colors.set(colors);
0156         grad->type.set(gradient_type);
0157 
0158         if ( gradient_type == model::Gradient::Linear )
0159             grad->start_point.set(QPointF(bounds.left(), bounds.center().y()));
0160         else
0161             grad->start_point.set(bounds.center());
0162 
0163         grad->highlight.set(grad->start_point.get());
0164         grad->end_point.set(QPointF(bounds.right(), bounds.center().y()));
0165 
0166         model::Gradient* gradient = grad.get();
0167         document->push_command(new command::AddObject<model::Gradient>(
0168             &document->assets()->gradients->values,
0169             std::move(grad)
0170         ));
0171 
0172         // Apply changes
0173         for ( auto styler : targets )
0174         {
0175             if ( styler->docnode_locked_recursive() )
0176                 continue;
0177 
0178             // update existing Gradient object
0179             model::Gradient* old = styler->use.get() ? styler->use->cast<model::Gradient>() : nullptr;
0180             if ( old )
0181             {
0182                 document->push_command(new command::SetPropertyValue(
0183                     &old->type,
0184                     QVariant::fromValue(gradient_type)
0185                 ));
0186 
0187                 document->push_command(new command::SetPropertyValue(
0188                     &old->colors,
0189                     QVariant::fromValue(colors)
0190                 ));
0191 
0192                 Q_EMIT parent->selected(old, secondary);
0193             }
0194             else
0195             {
0196                 styler->use.set_undoable(QVariant::fromValue(gradient));
0197             }
0198         }
0199 
0200         gradient->remove_if_unused(false);
0201 
0202         Q_EMIT parent->selected(gradient, secondary);
0203 
0204     }
0205 
0206     void clear_gradient(bool secondary)
0207     {
0208         auto& targets = secondary ? strokes : fills;
0209         if ( targets.empty() )
0210             return;
0211 
0212         command::UndoMacroGuard macro(i18n("Remove Gradient"), document);
0213 
0214         for ( auto styler : targets )
0215         {
0216             if ( styler->docnode_locked_recursive() )
0217                 continue;
0218 
0219             model::Gradient* old = styler->use.get() ? styler->use->cast<model::Gradient>() : nullptr;
0220 
0221             styler->use.set_undoable(QVariant::fromValue((model::BrushStyle*)nullptr));
0222 
0223             if ( old )
0224                 old->remove_if_unused(false);
0225         }
0226 
0227         Q_EMIT parent->selected(nullptr, secondary);
0228     }
0229 
0230     void add_gradient()
0231     {
0232         add_gradient({{0, window->current_color()}, {1, window->secondary_color()}}, i18n("Gradient"));
0233     }
0234 
0235     void add_gradient(const QGradientStops& stops, const QString& name)
0236     {
0237         if ( !document )
0238             return;
0239 
0240         auto ptr = std::make_unique<model::GradientColors>(document);
0241         auto raw = ptr.get();
0242         ptr->colors.set(stops);
0243         ptr->name.set(name);
0244         document->push_command(new command::AddObject(
0245             &document->assets()->gradient_colors->values,
0246             std::move(ptr)
0247         ));
0248 
0249         clear_buttons();
0250         ui.list_view->setCurrentIndex(model.gradient_to_index(raw));
0251     }
0252 
0253     void clear_buttons()
0254     {
0255         ui.btn_fill_linear->setChecked(false);
0256         ui.btn_fill_radial->setChecked(false);
0257         ui.btn_fill_conical->setChecked(false);
0258         ui.btn_stroke_linear->setChecked(false);
0259         ui.btn_stroke_radial->setChecked(false);
0260         ui.btn_stroke_conical->setChecked(false);
0261     }
0262 
0263     void set_targets(const std::vector<model::Fill*>& fills, const std::vector<model::Stroke*>& strokes)
0264     {
0265         this->fills.assign(fills.begin(), fills.end());
0266         this->strokes.assign(strokes.begin(), strokes.end());
0267 
0268         ui.btn_fill_linear->setEnabled(fills.size());
0269         ui.btn_fill_radial->setEnabled(fills.size());
0270         ui.btn_fill_conical->setEnabled(fills.size());
0271         ui.btn_stroke_linear->setEnabled(strokes.size());
0272         ui.btn_stroke_radial->setEnabled(strokes.size());
0273         ui.btn_stroke_conical->setEnabled(strokes.size());
0274     }
0275 
0276     void buttons_from_targets(bool set_current)
0277     {
0278         auto gradient_fill = current_fill ? qobject_cast<model::Gradient*>(current_fill->use.get()) : nullptr;
0279         auto gradient_stroke = current_stroke ? qobject_cast<model::Gradient*>(current_stroke->use.get()) : nullptr;
0280 
0281         clear_buttons();
0282 
0283         model::GradientColors* colors_fill = gradient_fill ? gradient_fill->colors.get() : nullptr;
0284         model::GradientColors* colors_stroke = gradient_stroke ? gradient_stroke->colors.get() : nullptr;
0285 
0286         if ( !colors_fill && !colors_stroke )
0287             return;
0288 
0289         model::GradientColors* colors;
0290 
0291         if ( set_current )
0292         {
0293             colors = colors_fill ? colors_fill : colors_stroke;
0294             ui.list_view->setCurrentIndex(model.gradient_to_index(colors));
0295         }
0296         else
0297         {
0298             colors = current();
0299         }
0300 
0301 
0302 
0303         if ( colors_fill == colors )
0304         {
0305             switch ( gradient_fill->type.get() )
0306             {
0307                 case model::Gradient::Radial:
0308                     ui.btn_fill_radial->setChecked(true);
0309                     break;
0310                 case model::Gradient::Linear:
0311                     ui.btn_fill_linear->setChecked(true);
0312                     break;
0313                 case model::Gradient::Conical:
0314                     ui.btn_fill_conical->setChecked(true);
0315                     break;
0316             }
0317         }
0318 
0319         if ( colors_stroke == colors )
0320         {
0321             switch ( gradient_stroke->type.get() )
0322             {
0323                 case model::Gradient::Radial:
0324                     ui.btn_stroke_radial->setChecked(true);
0325                     break;
0326                 case model::Gradient::Linear:
0327                     ui.btn_stroke_linear->setChecked(true);
0328                     break;
0329                 case model::Gradient::Conical:
0330                     ui.btn_stroke_conical->setChecked(true);
0331                     break;
0332             }
0333         }
0334     }
0335 
0336     void current_gradient_changed()
0337     {
0338         model::GradientColors* colors = current();
0339         if ( !colors )
0340         {
0341             clear_buttons();
0342             return;
0343         }
0344 
0345         buttons_from_targets(false);
0346     }
0347 
0348     void delete_gradient()
0349     {
0350         model::GradientColors* colors = current();
0351         if ( !colors )
0352         {
0353             if ( document->assets()->gradient_colors->values.empty() )
0354                 return;
0355 
0356             colors = document->assets()->gradient_colors->values.back();
0357         }
0358 
0359         document->push_command(new command::RemoveObject(
0360             colors,
0361             &document->assets()->gradient_colors->values
0362         ));
0363     }
0364 
0365     void build_presets()
0366     {
0367         QMetaEnum meta = QMetaEnum::fromType<QGradient::Preset>();
0368         static QRegularExpression nocamel {"(\\w)([A-Z])"};
0369 
0370         for ( int i = 0; i < meta.keyCount(); i++ )
0371         {
0372             auto value = QGradient::Preset(meta.value(i));
0373             if ( value == QGradient::NumPresets )
0374                 continue;
0375 
0376             QString name = meta.key(i);
0377             name.replace(nocamel, "\\1 \\2");
0378             QGradient grad(value);
0379             presets.setGradient(name, grad.stops());
0380         }
0381         presets.setEditMode(color_widgets::GradientListModel::EditName);
0382         connect(ui.btn_preset, &QAbstractButton::clicked, ui.btn_preset, [this]{ from_preset(); });
0383     }
0384 
0385     void from_preset()
0386     {
0387         QDialog dialog(window->as_widget());
0388         dialog.setWindowTitle(i18n("Gradient Presets"));
0389         dialog.setWindowIcon(QIcon::fromTheme("color-gradient"));
0390         QVBoxLayout lay;
0391         dialog.setLayout(&lay);
0392         QComboBox combo;
0393         combo.setModel(&presets);
0394         combo.setEditable(true);
0395         combo.setInsertPolicy(QComboBox::NoInsert);
0396         lay.addWidget(&combo);
0397         QDialogButtonBox buttons(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
0398         lay.addWidget(&buttons);
0399         connect(&buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
0400         connect(&buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
0401 
0402         if ( dialog.exec() != QDialog::Rejected )
0403         {
0404             int index = combo.currentIndex();
0405             if ( index != -1 )
0406                 add_gradient(presets.gradientStops(index), presets.nameFromIndex(index));
0407         }
0408     }
0409 };
0410 
0411 GradientListWidget::GradientListWidget(QWidget* parent)
0412     : QWidget(parent), d(std::make_unique<Private>())
0413 {
0414     d->parent = this;
0415     d->ui.setupUi(this);
0416     d->ui.list_view->setModel(&d->model);
0417     d->ui.list_view->horizontalHeader()->setSectionResizeMode(item_models::GradientListModel::Users, QHeaderView::ResizeToContents);
0418     d->ui.list_view->setItemDelegateForColumn(item_models::GradientListModel::Gradient, &d->delegate);
0419 
0420     d->build_presets();
0421 
0422     connect(d->ui.btn_new, &QAbstractButton::clicked, this, [this]{ d->add_gradient(); });
0423     connect(d->ui.btn_remove, &QAbstractButton::clicked, this, [this]{ d->delete_gradient(); });
0424     connect(d->ui.btn_fill_linear,   &QAbstractButton::clicked, this, d->slot_linear(this, false));
0425     connect(d->ui.btn_fill_radial,   &QAbstractButton::clicked, this, d->slot_radial(this, false));
0426     connect(d->ui.btn_fill_conical,   &QAbstractButton::clicked, this, d->slot_conical(this, false));
0427     connect(d->ui.btn_stroke_linear, &QAbstractButton::clicked, this, d->slot_linear(this, true));
0428     connect(d->ui.btn_stroke_radial, &QAbstractButton::clicked, this, d->slot_radial(this, true));
0429     connect(d->ui.btn_stroke_conical, &QAbstractButton::clicked, this, d->slot_conical(this, true));
0430 }
0431 
0432 GradientListWidget::~GradientListWidget() = default;
0433 
0434 void GradientListWidget::changeEvent ( QEvent* e )
0435 {
0436     QWidget::changeEvent(e);
0437 
0438     if ( e->type() == QEvent::LanguageChange)
0439     {
0440         d->ui.retranslateUi(this);
0441     }
0442 }
0443 
0444 void GradientListWidget::set_document(model::Document* document)
0445 {
0446     d->document = document;
0447     d->fills = {};
0448     d->strokes = {};
0449     d->current_fill = nullptr;
0450     d->current_stroke = nullptr;
0451     d->clear_buttons();
0452 
0453     if ( !document )
0454         d->model.set_defs(nullptr);
0455     else
0456         d->model.set_defs(document->assets());
0457 }
0458 
0459 void GradientListWidget::set_window(glaxnimate::gui::SelectionManager* window)
0460 {
0461     d->window = window;
0462 }
0463 
0464 
0465 void GradientListWidget::set_targets(const std::vector<model::Fill*>& fills, const std::vector<model::Stroke*>& strokes)
0466 {
0467     d->set_targets(fills, strokes);
0468 }
0469 
0470 void GradientListWidget::change_current_gradient()
0471 {
0472     d->current_gradient_changed();
0473 }
0474 
0475 void glaxnimate::gui::GradientListWidget::set_current(model::Fill* fill, model::Stroke* stroke)
0476 {
0477     d->current_fill = fill;
0478     d->current_stroke = stroke;
0479     d->buttons_from_targets(true);
0480 }