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 }