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 "document_swatch_widget.hpp"
0008 #include "ui_document_swatch_widget.h"
0009 
0010 #include <QMenu>
0011 #include <QInputDialog>
0012 #include <QVBoxLayout>
0013 #include <QComboBox>
0014 #include <QCheckBox>
0015 #include <QDialogButtonBox>
0016 #include <QMessageBox>
0017 
0018 #include <QtColorWidgets/color_palette_model.hpp>
0019 #include <QtColorWidgets/ColorDialog>
0020 
0021 #include "model/assets/assets.hpp"
0022 #include "model/document.hpp"
0023 #include "command/object_list_commands.hpp"
0024 #include "command/animation_commands.hpp"
0025 #include "utils/pseudo_mutex.hpp"
0026 #include "model/visitor.hpp"
0027 #include "model/shapes/styler.hpp"
0028 #include "model/assets/named_color.hpp"
0029 #include "command/undo_macro_guard.hpp"
0030 
0031 using namespace glaxnimate::gui;
0032 using namespace glaxnimate;
0033 
0034 class DocumentSwatchWidget::Private
0035 {
0036 public:
0037     Ui::DocumentSwatchWidget ui;
0038     model::Document* document = nullptr;
0039     utils::PseudoMutex updating_swatch;
0040     color_widgets::ColorPaletteModel* palette_model = nullptr;
0041     QPersistentModelIndex palette_index;
0042 
0043 
0044     class FetchColorVisitor : public model::Visitor
0045     {
0046     private:
0047         void on_visit_document(model::Document * doc, model::Composition*) override
0048         {
0049             macro = command::UndoMacroGuard(i18n("Gather Document Swatch"), doc);
0050 
0051             defs = doc->assets();
0052             for ( const auto& color : defs->colors->values )
0053             {
0054                 if ( !color->color.animated() )
0055                 {
0056                     QColor c = color->color.get();
0057                     colors[c.name(QColor::HexArgb)] = color.get();
0058                 }
0059             }
0060         }
0061 
0062         void on_visit(model::DocumentNode * node) override
0063         {
0064             if ( auto sty = qobject_cast<model::Styler*>(node) )
0065             {
0066                 if ( !sty->use.get() && !sty->color.animated() && !sty->docnode_locked_recursive() )
0067                 {
0068                     QString color_name = sty->color.get().name(QColor::HexArgb);
0069                     auto it = colors.find(color_name);
0070                     model::NamedColor* def = nullptr;
0071                     if ( it == colors.end() )
0072                     {
0073                         def = defs->add_color(sty->color.get());
0074                         colors[color_name] = def;
0075                     }
0076                     else
0077                     {
0078                         def = it->second;
0079                     }
0080 
0081                     sty->use.set_undoable(QVariant::fromValue(def));
0082                 }
0083             }
0084         }
0085 
0086         command::UndoMacroGuard macro;
0087         std::map<QString, model::NamedColor*> colors;
0088         model::Assets* defs;
0089     };
0090 
0091     class ApplyColorVisitor : public model::Visitor
0092     {
0093     public:
0094         ApplyColorVisitor(const std::vector<model::NamedColor*>& col)
0095         {
0096             for ( auto color : col )
0097             {
0098                 if ( !color->color.animated() )
0099                 {
0100                     QColor c = color->color.get();
0101                     colors[c.name(QColor::HexArgb)] = color;
0102                 }
0103             }
0104         }
0105 
0106         ApplyColorVisitor(model::Document * doc)
0107         {
0108             for ( const auto& color : doc->assets()->colors->values )
0109             {
0110                 if ( !color->color.animated() )
0111                 {
0112                     QColor c = color->color.get();
0113                     colors[c.name(QColor::HexArgb)] = color.get();
0114                 }
0115             }
0116         }
0117 
0118     private:
0119         void on_visit_document(model::Document * doc, model::Composition*) override
0120         {
0121             macro = command::UndoMacroGuard(i18n("Link Shapes to Swatch"), doc);
0122         }
0123 
0124         void on_visit(model::DocumentNode * node) override
0125         {
0126             if ( auto sty = qobject_cast<model::Styler*>(node) )
0127             {
0128                 if ( !sty->use.get() && !sty->color.animated() && !sty->docnode_locked_recursive() )
0129                 {
0130                     QString color_name = sty->color.get().name(QColor::HexArgb);
0131                     auto it = colors.find(color_name);
0132                     if ( it != colors.end() )
0133                         sty->use.set_undoable(QVariant::fromValue(it->second));
0134                 }
0135             }
0136         }
0137 
0138 
0139         command::UndoMacroGuard macro;
0140         std::map<QString, model::NamedColor*> colors;
0141     };
0142 };
0143 
0144 DocumentSwatchWidget::DocumentSwatchWidget(QWidget* parent)
0145     : QWidget(parent), d(std::make_unique<Private>())
0146 {
0147     d->ui.setupUi(this);
0148 
0149     auto palette = &d->ui.swatch->palette();
0150     connect(palette, &color_widgets::ColorPalette::colorAdded, this, &DocumentSwatchWidget::swatch_palette_color_added);
0151     connect(palette, &color_widgets::ColorPalette::colorRemoved, this, &DocumentSwatchWidget::swatch_palette_color_removed);
0152     connect(palette, &color_widgets::ColorPalette::colorChanged, this, &DocumentSwatchWidget::swatch_palette_color_changed);
0153 
0154     QMenu* menu = new QMenu(this);
0155     menu->addAction(d->ui.action_generate);
0156     menu->addAction(d->ui.action_open);
0157     menu->addAction(d->ui.action_save);
0158     d->ui.button_extra->setMenu(menu);
0159     connect(d->ui.action_generate, &QAction::triggered, this, &DocumentSwatchWidget::generate);
0160     connect(d->ui.action_open, &QAction::triggered, this, &DocumentSwatchWidget::open);
0161     connect(d->ui.action_save, &QAction::triggered, this, &DocumentSwatchWidget::save);
0162 }
0163 
0164 DocumentSwatchWidget::~DocumentSwatchWidget() = default;
0165 
0166 void DocumentSwatchWidget::changeEvent ( QEvent* e )
0167 {
0168     QWidget::changeEvent(e);
0169     if ( e->type() == QEvent::LanguageChange)
0170     {
0171         d->ui.retranslateUi(this);
0172     }
0173 }
0174 
0175 void DocumentSwatchWidget::set_document(model::Document* document)
0176 {
0177     auto palette = &d->ui.swatch->palette();
0178 
0179     if ( d->document )
0180     {
0181         disconnect(d->document->assets(), nullptr, this, nullptr);
0182     }
0183 
0184     d->document = document;
0185 
0186     if ( d->document )
0187     {
0188         auto l = d->updating_swatch.get_lock();
0189 
0190         palette->setColors(QVector<QColor>{});
0191         for ( const auto& col : d->document->assets()->colors->values )
0192             palette->appendColor(col->color.get(), col->name.get());
0193 
0194         palette->setName("");
0195 
0196         connect(d->document->assets()->colors.get(), &model::NamedColorList::color_added, this, &DocumentSwatchWidget::swatch_doc_color_added);
0197         connect(d->document->assets()->colors.get(), &model::NamedColorList::color_removed, this, &DocumentSwatchWidget::swatch_doc_color_removed);
0198         connect(d->document->assets()->colors.get(), &model::NamedColorList::color_changed, this, &DocumentSwatchWidget::swatch_doc_color_changed);
0199     }
0200 }
0201 
0202 
0203 void DocumentSwatchWidget::swatch_palette_color_added(int index)
0204 {
0205     if ( !d->document )
0206         return;
0207 
0208     if ( auto l = d->updating_swatch.get_lock() )
0209     {
0210         auto defs = d->document->assets();
0211         auto palette = &d->ui.swatch->palette();
0212 
0213         defs->add_color(palette->colorAt(index), palette->nameAt(index));
0214     }
0215 }
0216 
0217 void DocumentSwatchWidget::swatch_palette_color_removed(int index)
0218 {
0219     if ( !d->document )
0220         return;
0221 
0222     if ( auto l = d->updating_swatch.get_lock() )
0223     {
0224         auto defs = d->document->assets();
0225 
0226         if ( defs->colors->values.valid_index(index) )
0227             defs->push_command(new command::RemoveObject<model::NamedColor>(index, &defs->colors->values));
0228     }
0229 }
0230 
0231 void DocumentSwatchWidget::swatch_palette_color_changed(int index)
0232 {
0233     if ( !d->document )
0234         return;
0235 
0236     if ( auto l = d->updating_swatch.get_lock() )
0237     {
0238 
0239         auto defs = d->document->assets();
0240         auto palette = &d->ui.swatch->palette();
0241 
0242         if ( !defs->colors->values.valid_index(index) )
0243             return;
0244 
0245         command::UndoMacroGuard macro(i18n("Modify Palette Color"), d->document);
0246         auto color = defs->colors->values[index];
0247         color->name.set_undoable(palette->nameAt(index));
0248         color->color.set_undoable(palette->colorAt(index));
0249     }
0250 }
0251 
0252 void DocumentSwatchWidget::swatch_add()
0253 {
0254     Q_EMIT needs_new_color();
0255 }
0256 
0257 void DocumentSwatchWidget::add_new_color(const QColor& color)
0258 {
0259     d->ui.swatch->palette().appendColor(color);
0260     swatch_link(d->ui.swatch->palette().count() - 1, {});
0261 }
0262 
0263 
0264 void DocumentSwatchWidget::swatch_link(int index, Qt::KeyboardModifiers mod)
0265 {
0266     if ( d->document )
0267     {
0268         auto def = index != -1 ? d->document->assets()->colors->values[index] : nullptr;
0269 
0270         if ( mod & Qt::ShiftModifier )
0271             Q_EMIT secondary_color_def(def);
0272         else
0273             Q_EMIT current_color_def(def);
0274     }
0275 }
0276 
0277 void DocumentSwatchWidget::swatch_doc_color_added(int position, model::NamedColor* color)
0278 {
0279     if ( auto l = d->updating_swatch.get_lock() )
0280     {
0281         d->ui.swatch->palette().insertColor(position, color->color.get(), color->name.get());
0282     }
0283 }
0284 
0285 void DocumentSwatchWidget::swatch_doc_color_removed(int pos)
0286 {
0287     if ( auto l = d->updating_swatch.get_lock() )
0288     {
0289         d->ui.swatch->palette().eraseColor(pos);
0290     }
0291 }
0292 
0293 void DocumentSwatchWidget::swatch_doc_color_changed(int position, model::NamedColor* color)
0294 {
0295     if ( auto l = d->updating_swatch.get_lock() )
0296     {
0297         d->ui.swatch->palette().setColorAt(position, color->color.get(), color->name.get());
0298     }
0299 }
0300 
0301 model::NamedColor * DocumentSwatchWidget::current_color() const
0302 {
0303     if ( !d->document )
0304         return nullptr;
0305 
0306     int index = d->ui.swatch->selected();
0307     if ( index == -1 )
0308         return nullptr;
0309 
0310     return d->document->assets()->colors->values[index];
0311 }
0312 
0313 void DocumentSwatchWidget::generate()
0314 {
0315     Private::FetchColorVisitor().visit(d->document, nullptr);
0316 }
0317 
0318 void DocumentSwatchWidget::open()
0319 {
0320     if ( d->palette_model->count() == 0 )
0321     {
0322         QMessageBox::information(
0323             this,
0324             i18n("Load from Palette"),
0325             i18n("No palettes are installed")
0326         );
0327         return;
0328     }
0329 
0330     QDialog dialog(this);
0331     dialog.setWindowTitle(i18n("Swatch from Palette"));
0332     QVBoxLayout* lay = new QVBoxLayout(&dialog);
0333     dialog.setLayout(lay);
0334 
0335     QComboBox combo;
0336     combo.setModel(d->palette_model);
0337     lay->addWidget(&combo);
0338 
0339     QCheckBox check_overwrite;
0340     check_overwrite.setText(i18n("Overwrite on save"));
0341     lay->addWidget(&check_overwrite);
0342 
0343     QCheckBox check_link;
0344     check_link.setText(i18n("Link shapes with matching colors"));
0345     lay->addWidget(&check_link);
0346 
0347     QCheckBox check_clear;
0348     check_clear.setChecked(true);
0349     check_clear.setText(i18n("Remove existing colors"));
0350     lay->addWidget(&check_clear);
0351 
0352     QDialogButtonBox buttons(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
0353     lay->addWidget(&buttons);
0354     connect(&buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
0355     connect(&buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
0356 
0357     if ( dialog.exec() == QDialog::Rejected )
0358         return;
0359 
0360     if ( combo.currentIndex() < 0 || combo.currentIndex() >= d->palette_model->count() )
0361         return;
0362 
0363     if ( check_overwrite.isChecked() )
0364         d->palette_index = d->palette_model->index(combo.currentIndex(), 0);
0365     else
0366         d->palette_index = QPersistentModelIndex();
0367 
0368     command::UndoMacroGuard macro(i18n("Load Palette"), d->document);
0369 
0370     if ( check_clear.isChecked() )
0371     {
0372         while ( d->document->assets()->colors->values.size() )
0373             d->document->push_command(new command::RemoveObject(
0374                 d->document->assets()->colors->values.back(),
0375                 &d->document->assets()->colors->values
0376             ));
0377     }
0378 
0379     for ( const auto& p : d->palette_model->palette(combo.currentIndex()).colors() )
0380         d->document->assets()->add_color(p.first, p.second);
0381 
0382     if ( check_link.isChecked() )
0383         Private::ApplyColorVisitor(d->document).visit(d->document, nullptr);
0384 }
0385 
0386 void DocumentSwatchWidget::save()
0387 {
0388     if ( d->ui.swatch->palette().name().isEmpty() )
0389     {
0390         QString default_name = i18n("Palette");
0391         for ( const auto& comp :  d->document->assets()->compositions->values )
0392         {
0393             if ( comp->name.get() != "" )
0394             {
0395                 default_name = comp->name.get();
0396                 break;
0397             }
0398         }
0399 
0400         QString name = QInputDialog::getText(this, i18n("Save Palette"), i18n("Name"), QLineEdit::Normal, default_name);
0401         if ( name.isEmpty() )
0402             return;
0403 
0404         d->ui.swatch->palette().setName(name);
0405     }
0406 
0407     if ( !d->palette_index.isValid() )
0408     {
0409         d->palette_model->addPalette(d->ui.swatch->palette(), true);
0410         d->palette_index = d->palette_model->index(d->palette_model->count() - 1, 0);
0411     }
0412     else
0413     {
0414         d->palette_model->updatePalette(d->palette_index.row(), d->ui.swatch->palette(), true);
0415     }
0416 }
0417 
0418 void DocumentSwatchWidget::set_palette_model(color_widgets::ColorPaletteModel* palette_model)
0419 {
0420     d->palette_model = palette_model;
0421 }
0422 
0423 void DocumentSwatchWidget::swatch_menu ( int index )
0424 {
0425     QMenu menu;
0426 
0427     if ( index == -1 )
0428     {
0429         menu.addSection(i18n("Unlink Color"));
0430 
0431         menu.addAction(
0432             QIcon::fromTheme("format-fill-color"),
0433             i18n("Unlink fill"),
0434             this,
0435             [this]{
0436                 Q_EMIT current_color_def(nullptr);
0437             }
0438         );
0439 
0440         menu.addAction(
0441             QIcon::fromTheme("format-stroke-color"),
0442             i18n("Unlink stroke"),
0443             this,
0444             [this]{
0445                 Q_EMIT secondary_color_def(nullptr);
0446             }
0447         );
0448     }
0449     else
0450     {
0451         model::NamedColor* item = d->document->assets()->colors->values[index];
0452         menu.addSection(item->object_name());
0453 
0454         menu.addAction(
0455             QIcon::fromTheme("edit-rename"),
0456             i18n("Rename..."),
0457             this,
0458             [item, this]{
0459                 bool ok = false;
0460                 QString name = QInputDialog::getText(this, i18n("Rename Swatch Color"), i18n("Name"), QLineEdit::Normal, item->type_name_human(), &ok);
0461                 if ( ok )
0462                     item->name.set_undoable(name);
0463             }
0464         );
0465 
0466         menu.addAction(
0467             QIcon::fromTheme("color-management"),
0468             i18n("Edit Color..."),
0469             this,
0470             [item, this]{
0471                 color_widgets::ColorDialog dialog(this);
0472                 QColor old = item->color.get();
0473                 dialog.setColor(old);
0474                 connect(&dialog, &color_widgets::ColorDialog::colorChanged, &item->color, &model::AnimatedProperty<QColor>::set);
0475                 auto result = dialog.exec();
0476                 item->color.set(old);
0477                 if ( result == QDialog::Accepted )
0478                     item->color.set_undoable(dialog.color());
0479             }
0480         );
0481 
0482         menu.addSeparator();
0483 
0484         menu.addAction(
0485             QIcon::fromTheme("list-remove"),
0486             i18n("Remove"),
0487             this,
0488             [index, this]{
0489                 d->ui.swatch->palette().eraseColor(index);
0490             }
0491         );
0492 
0493         menu.addAction(
0494             QIcon::fromTheme("edit-duplicate"),
0495             i18n("Duplicate"),
0496             this,
0497             [item, index, this]{
0498                 auto clone = item->clone_covariant();
0499                 item->push_command(new command::AddObject(
0500                     &d->document->assets()->colors->values,
0501                     std::move(clone),
0502                     index+1
0503                 ));
0504             }
0505         );
0506 
0507         menu.addSeparator();
0508 
0509         menu.addAction(
0510             QIcon::fromTheme("format-fill-color"),
0511             i18n("Set as fill"),
0512             this,
0513             [item, this]{
0514                 Q_EMIT current_color_def(item);
0515             }
0516         );
0517 
0518         menu.addAction(
0519             QIcon::fromTheme("format-stroke-color"),
0520             i18n("Set as stroke"),
0521             this,
0522             [item, this]{
0523                 Q_EMIT secondary_color_def(item);
0524             }
0525         );
0526 
0527         if ( !item->color.animated() )
0528         {
0529             menu.addAction(
0530                 QIcon::fromTheme("insert-link"),
0531                 i18n("Link shapes with matching colors"),
0532                 this,
0533                 [item, this]{
0534                     Private::ApplyColorVisitor({item}).visit(d->document, nullptr);
0535                 }
0536             );
0537         }
0538 
0539     }
0540 
0541     menu.exec(QCursor::pos());
0542 
0543 }