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 }