Warning, file /graphics/glaxnimate/src/gui/style/property_delegate.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
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 "property_delegate.hpp" 0008 0009 #include <QComboBox> 0010 #include <QFontComboBox> 0011 #include <QTimer> 0012 #include <QApplication> 0013 #include <QMetaEnum> 0014 #include <QLineEdit> 0015 #include <QCheckBox> 0016 0017 #include <QtColorWidgets/GradientEditor> 0018 #include <QtColorWidgets/ColorSelector> 0019 0020 #include "item_models/property_model_base.hpp" 0021 #include "widgets/spin2d.hpp" 0022 #include "widgets/enum_combo.hpp" 0023 #include "model/property/option_list_property.hpp" 0024 #include "model/property/reference_property.hpp" 0025 0026 using namespace glaxnimate::gui::style; 0027 using namespace glaxnimate::gui; 0028 0029 0030 void PropertyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const 0031 { 0032 QVariant data = index.data(); 0033 0034 switch ( data.userType() ) 0035 { 0036 case QMetaType::QPointF: 0037 return paint_xy<QPointF>(painter, option, index); 0038 case QMetaType::QVector2D: 0039 { 0040 auto value = index.data().value<QVector2D>(); 0041 return paint_plaintext( 0042 QString("%1% x %2%") 0043 .arg(math::get(value, 0) * 100) 0044 .arg(math::get(value, 1) * 100), 0045 painter, 0046 option, 0047 index 0048 ); 0049 } 0050 case QMetaType::QSizeF: 0051 return paint_xy<QSizeF>(painter, option, index); 0052 } 0053 0054 0055 if ( data.userType() == qMetaTypeId<QGradientStops>() ) 0056 { 0057 QLinearGradient g(0, 0, 1, 0); 0058 g.setStops(data.value<QGradientStops>()); 0059 g.setCoordinateMode(QGradient::ObjectMode); 0060 paintItem(painter, option, index, g); 0061 return; 0062 } 0063 0064 return color_widgets::ColorDelegate::paint(painter, option, index); 0065 } 0066 0067 QWidget* PropertyDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const 0068 { 0069 QVariant data = index.data(Qt::EditRole); 0070 auto refprop = index.data(item_models::PropertyModelBase::ReferenceProperty); 0071 int prop_flags = index.data(item_models::PropertyModelBase::Flags).toInt(); 0072 auto min = index.data(item_models::PropertyModelBase::MinValue); 0073 auto max = index.data(item_models::PropertyModelBase::MaxValue); 0074 0075 if ( auto wid = create_editor_from_variant(data, prop_flags, parent, refprop, min, max) ) 0076 return wid; 0077 0078 return color_widgets::ColorDelegate::createEditor(parent, option, index); 0079 } 0080 0081 static void combo_setup(QComboBox* combo) 0082 { 0083 QTimer* cheeky = new QTimer(combo); 0084 QObject::connect(cheeky, &QTimer::timeout, combo, [combo]{ 0085 combo->setFocus(); 0086 combo->showPopup(); 0087 }); 0088 cheeky->setSingleShot(true); 0089 cheeky->start(qApp->doubleClickInterval() / 2); 0090 } 0091 0092 void PropertyDelegate::setEditorData ( QWidget * editor, const QModelIndex & index ) const 0093 { 0094 QVariant data = index.data(Qt::EditRole); 0095 auto refprop = index.data(item_models::PropertyModelBase::ReferenceProperty); 0096 int prop_flags = index.data(item_models::PropertyModelBase::Flags).toInt(); 0097 0098 if ( !set_editor_data(editor, data, prop_flags, refprop) ) 0099 color_widgets::ColorDelegate::setEditorData(editor, index); 0100 } 0101 0102 void PropertyDelegate::setModelData ( QWidget * editor, QAbstractItemModel * model, const QModelIndex & index ) const 0103 { 0104 QVariant data = index.data(Qt::EditRole); 0105 auto refprop = index.data(item_models::PropertyModelBase::ReferenceProperty); 0106 int prop_flags = index.data(item_models::PropertyModelBase::Flags).toInt(); 0107 0108 int status = -1; 0109 QVariant value = get_editor_data(editor, data, prop_flags, refprop, status); 0110 if ( status == 1 ) 0111 model->setData(index, value); 0112 else if ( status == 0 ) 0113 return color_widgets::ColorDelegate::setModelData(editor, model, index); 0114 } 0115 0116 void PropertyDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const 0117 { 0118 0119 QStyleOptionViewItem opt = option; 0120 initStyleOption(&opt, index); 0121 const QWidget* widget = option.widget; 0122 0123 // Disable decoration 0124 auto data = index.data(Qt::EditRole); 0125 if ( (data.userType() >= QMetaType::User && data.canConvert<int>()) || 0126 index.data(item_models::PropertyModelBase::ReferenceProperty).canConvert<model::ReferencePropertyBase*>() ) 0127 { 0128 opt.icon = QIcon(); 0129 opt.features &= ~QStyleOptionViewItem::HasDecoration; 0130 } 0131 opt.showDecorationSelected = true; 0132 0133 QStyle *style = widget ? widget->style() : QApplication::style(); 0134 QRect geom = style->subElementRect(QStyle::SE_ItemViewItemText, &opt, widget); 0135 const int delta = editor->minimumWidth() - geom.width(); 0136 if (delta > 0) 0137 { 0138 // we need to widen the geometry 0139 if (editor->layoutDirection() == Qt::RightToLeft) 0140 geom.adjust(-delta, 0, 0, 0); 0141 else 0142 geom.adjust(0, 0, delta, 0); 0143 } 0144 editor->setGeometry(geom); 0145 0146 return; 0147 // color_widgets::ColorDelegate::updateEditorGeometry(editor, option, index); 0148 } 0149 0150 0151 void PropertyDelegate::paint_plaintext(const QString& text, QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const 0152 { 0153 QStyleOptionViewItem opt = option; 0154 initStyleOption(&opt, index); 0155 opt.text = text; 0156 const QWidget* widget = option.widget; 0157 QStyle *style = widget ? widget->style() : QApplication::style(); 0158 style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget); 0159 } 0160 0161 QSize style::PropertyDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const 0162 { 0163 auto sh = color_widgets::ColorDelegate::sizeHint(option, index); 0164 if ( force_height ) 0165 sh.setHeight(force_height); 0166 return sh; 0167 } 0168 0169 QVariant PropertyDelegate::refprop(model::BaseProperty *prop, const model::PropertyTraits &traits) const 0170 { 0171 if ( traits.flags & model::PropertyTraits::OptionList ) 0172 return QVariant::fromValue(static_cast<model::OptionListPropertyBase*>(prop)); 0173 else if ( traits.type == model::PropertyTraits::ObjectReference ) 0174 return QVariant::fromValue(static_cast<model::ReferencePropertyBase*>(prop)); 0175 return {}; 0176 } 0177 0178 QWidget *PropertyDelegate::editor_from_property(model::BaseProperty *prop, QWidget *parent) const 0179 { 0180 auto slot = [prop, this]{ set_property_data(static_cast<QWidget*>(sender()), prop); }; 0181 auto traits = prop->traits(); 0182 if ( traits.type == model::PropertyTraits::Color ) 0183 { 0184 auto wid = new color_widgets::ColorSelector(parent); 0185 connect(wid, &color_widgets::ColorSelector::colorSelected, this, slot); 0186 wid->setMinimumSize(32, 32); 0187 return wid; 0188 } 0189 else if ( traits.type == model::PropertyTraits::Bool ) 0190 { 0191 auto wid = new QCheckBox(parent); 0192 connect(wid, &QCheckBox::clicked, this, slot); 0193 return wid; 0194 } 0195 0196 QVariant min; 0197 QVariant max; 0198 if ( traits.type == model::PropertyTraits::Float && (traits.flags & model::PropertyTraits::Animated) ) 0199 { 0200 auto float_prop = static_cast<model::AnimatedProperty<float>*>(prop); 0201 min = float_prop->min(); 0202 max = float_prop->max(); 0203 } 0204 0205 QWidget *editor = create_editor_from_variant(prop->value(), traits.flags, parent, refprop(prop, traits), min, max); 0206 if ( !editor ) 0207 { 0208 auto wid = new QLineEdit(parent); 0209 connect(wid, &QLineEdit::textEdited, this, slot); 0210 return wid; 0211 } 0212 0213 if ( auto combo = qobject_cast<QComboBox*>(editor) ) 0214 connect(combo, QOverload<int>::of(&QComboBox::activated), this, slot); 0215 else if ( auto wid = qobject_cast<Spin2D*>(editor) ) 0216 connect(wid, &Spin2D::value_changed, this, slot); 0217 else if ( auto wid = qobject_cast<QSpinBox*>(editor) ) 0218 connect(wid, QOverload<int>::of(&QSpinBox::valueChanged), this, slot); 0219 else if ( auto wid = qobject_cast<QDoubleSpinBox*>(editor) ) 0220 connect(wid, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, slot); 0221 0222 return editor; 0223 0224 } 0225 0226 void PropertyDelegate::set_editor_data(QWidget *editor, model::BaseProperty *prop) const 0227 { 0228 QSignalBlocker block(editor); 0229 0230 auto traits = prop->traits(); 0231 if ( traits.type == model::PropertyTraits::Color ) 0232 return static_cast<color_widgets::ColorSelector*>(editor)->setColor(prop->value().value<QColor>()); 0233 0234 if ( traits.type == model::PropertyTraits::Bool ) 0235 return static_cast<QCheckBox*>(editor)->setChecked(prop->value().toBool()); 0236 0237 if ( !set_editor_data(editor, prop->value(), traits.flags, refprop(prop, traits)) ) 0238 static_cast<QLineEdit*>(editor)->setText(prop->value().toString()); 0239 } 0240 0241 bool PropertyDelegate::set_property_data(QWidget *editor, model::BaseProperty *prop) const 0242 { 0243 auto traits = prop->traits(); 0244 if ( traits.type == model::PropertyTraits::Color ) 0245 return prop->set_undoable(static_cast<color_widgets::ColorSelector*>(editor)->color()); 0246 if ( traits.type == model::PropertyTraits::Bool ) 0247 return prop->set_undoable(static_cast<QCheckBox*>(editor)->isChecked()); 0248 0249 int status = -1; 0250 QVariant value = get_editor_data(editor, prop->value(), traits.flags, refprop(prop, traits), status); 0251 if ( status == 1 ) 0252 return prop->set_undoable(value); 0253 if ( status == -1 ) 0254 return false; 0255 return prop->set_undoable(static_cast<QLineEdit*>(editor)->text()); 0256 } 0257 0258 QWidget *PropertyDelegate::create_editor_from_variant(const QVariant &data, int prop_flags, QWidget *parent, const QVariant& refprop, const QVariant& min, const QVariant& max) const 0259 { 0260 if ( refprop.canConvert<model::ReferencePropertyBase*>() ) 0261 return new QComboBox(parent); 0262 0263 if ( prop_flags & model::PropertyTraits::OptionList ) 0264 { 0265 if ( auto prop = refprop.value<model::OptionListPropertyBase*>() ) 0266 { 0267 if ( prop->option_list_flags() & model::OptionListPropertyBase::FontCombo ) 0268 return new QFontComboBox(parent); 0269 } 0270 0271 return new QComboBox(parent); 0272 } 0273 0274 if ( prop_flags & model::PropertyTraits::OptionList ) 0275 { 0276 if ( auto prop = refprop.value<model::OptionListPropertyBase*>() ) 0277 { 0278 if ( prop->option_list_flags() & model::OptionListPropertyBase::FontCombo ) 0279 return new QFontComboBox(parent); 0280 } 0281 0282 return new QComboBox(parent); 0283 } 0284 0285 if ( data.userType() >= QMetaType::User && data.canConvert<int>() ) 0286 return new EnumCombo(parent); 0287 0288 switch ( data.userType() ) 0289 { 0290 case QMetaType::QPointF: 0291 return new Spin2D(false, parent); 0292 case QMetaType::QVector2D: 0293 return new Spin2D(true, parent); 0294 case QMetaType::QSizeF: 0295 return new Spin2D(true, parent); 0296 case QMetaType::Float: 0297 case QMetaType::Double: 0298 { 0299 auto box = new SmallerSpinBox(false, parent); 0300 qreal mult = 1; 0301 if ( prop_flags & model::PropertyTraits::Percent ) 0302 { 0303 mult = 100; 0304 box->setSuffix(i18n("%")); 0305 box->setDecimals(0); 0306 } 0307 0308 if ( min.isValid() ) 0309 { 0310 double mind = min.toDouble(); 0311 if ( mind != std::numeric_limits<double>::lowest() ) 0312 mind *= mult; 0313 box->setMinimum(mind); 0314 } 0315 0316 if ( max.isValid() ) 0317 { 0318 qreal maxf = max.toDouble(); 0319 if ( maxf != std::numeric_limits<double>::max() ) 0320 maxf *= mult; 0321 box->setMaximum(maxf); 0322 if ( max == 1 ) 0323 box->setSingleStep(0.1); 0324 } 0325 return box; 0326 } 0327 case QMetaType::Int: 0328 return new SmallerSpinBoxInt(parent); 0329 } 0330 0331 if ( data.userType() == qMetaTypeId<QGradientStops>() ) 0332 return new color_widgets::GradientEditor(Qt::Horizontal, parent); 0333 0334 return nullptr; 0335 } 0336 0337 bool PropertyDelegate::set_editor_data(QWidget *editor, const QVariant &data, int prop_flags, const QVariant &refprop) const 0338 { 0339 // Object reference combo 0340 if ( refprop.canConvert<model::ReferencePropertyBase*>() ) 0341 { 0342 if ( auto rpb = refprop.value<model::ReferencePropertyBase*>() ) 0343 { 0344 if ( QComboBox* combo = qobject_cast<QComboBox*>(editor) ) 0345 { 0346 model::DocumentNode* current = rpb->value().value<model::DocumentNode*>(); 0347 combo->clear(); 0348 for ( model::DocumentNode* ptr : rpb->valid_options() ) 0349 { 0350 if ( ptr ) 0351 combo->addItem(QIcon(ptr->instance_icon()), ptr->object_name(), QVariant::fromValue(ptr)); 0352 else 0353 combo->addItem("", QVariant::fromValue(ptr)); 0354 0355 if ( ptr == current ) 0356 combo->setCurrentIndex(combo->count() - 1); 0357 } 0358 combo_setup(combo); 0359 } 0360 } 0361 return true; 0362 } 0363 // Option list reference combo 0364 else if ( prop_flags & model::PropertyTraits::OptionList ) 0365 { 0366 if ( auto prop = refprop.value<model::OptionListPropertyBase*>() ) 0367 { 0368 // Font combo 0369 if ( prop->option_list_flags() & model::OptionListPropertyBase::FontCombo ) 0370 { 0371 QFontComboBox* fcombo = static_cast<QFontComboBox*>(editor); 0372 fcombo->setCurrentText(prop->value().toString()); 0373 return true; 0374 }; 0375 0376 QComboBox* combo = static_cast<QComboBox*>(editor); 0377 combo->setEditable(true); 0378 0379 if ( !(prop->option_list_flags() & model::OptionListPropertyBase::LaxValues) ) 0380 combo->setInsertPolicy(QComboBox::NoInsert); 0381 0382 auto current = prop->value(); 0383 bool found = false; 0384 for ( const auto& item : prop->value_options() ) 0385 { 0386 combo->addItem(item.toString(), item); 0387 if ( item == current ) 0388 { 0389 found = true; 0390 combo->setCurrentIndex(combo->count() - 1); 0391 } 0392 } 0393 0394 if ( !found && (prop->option_list_flags() & model::OptionListPropertyBase::LaxValues) ) 0395 { 0396 combo->addItem(current.toString(), current); 0397 combo->setCurrentIndex(combo->count() - 1); 0398 } 0399 } 0400 0401 return true; 0402 } 0403 // Enum combo 0404 else if ( data.userType() >= QMetaType::User && data.canConvert<int>() ) 0405 { 0406 EnumCombo* combo = static_cast<EnumCombo*>(editor); 0407 combo->set_data_from_qvariant(data); 0408 return true; 0409 } 0410 // Cradient 0411 else if ( data.userType() == qMetaTypeId<QGradientStops>() ) 0412 { 0413 static_cast<color_widgets::GradientEditor*>(editor)->setStops(data.value<QGradientStops>()); 0414 return true; 0415 } 0416 0417 // Spin boxes 0418 switch ( data.userType() ) 0419 { 0420 case QMetaType::QPointF: 0421 static_cast<Spin2D*>(editor)->set_value(data.value<QPointF>()); 0422 return true; 0423 case QMetaType::QVector2D: 0424 static_cast<Spin2D*>(editor)->set_value(data.value<QVector2D>()); 0425 return true; 0426 case QMetaType::QSizeF: 0427 static_cast<Spin2D*>(editor)->set_value(data.value<QSizeF>()); 0428 return true; 0429 case QMetaType::Float: 0430 case QMetaType::Double: 0431 static_cast<QDoubleSpinBox*>(editor)->setValue(data.toDouble() * ((prop_flags & model::PropertyTraits::Percent) ? 100 : 1)); 0432 return true; 0433 case QMetaType::Int: 0434 static_cast<QSpinBox*>(editor)->setValue(data.toInt()); 0435 return true; 0436 } 0437 0438 return false; 0439 } 0440 0441 QVariant PropertyDelegate::get_editor_data(QWidget *editor, const QVariant& data, int prop_flags, const QVariant &refprop, int& status) const 0442 { 0443 status = 1; 0444 0445 // Object reference combo 0446 if ( 0447 (data.userType() >= QMetaType::User && data.canConvert<int>()) || 0448 refprop.canConvert<model::ReferencePropertyBase*>() 0449 ) 0450 { 0451 QComboBox* combo = static_cast<QComboBox*>(editor); 0452 return combo->itemData(combo->currentIndex()); 0453 } 0454 // Option list combo 0455 else if ( prop_flags & model::PropertyTraits::OptionList ) 0456 { 0457 if ( auto prop = refprop.value<model::OptionListPropertyBase*>() ) 0458 { 0459 // Font list 0460 if ( prop->option_list_flags() & model::OptionListPropertyBase::FontCombo ) 0461 { 0462 QFontComboBox* combo = static_cast<QFontComboBox*>(editor); 0463 return combo->currentFont().family(); 0464 } 0465 else 0466 { 0467 QComboBox* combo = static_cast<QComboBox*>(editor); 0468 if ( prop->option_list_flags() & model::OptionListPropertyBase::LaxValues ) 0469 return combo->currentText(); 0470 else 0471 return combo->currentData(); 0472 } 0473 } 0474 status = -1; 0475 return {}; 0476 } 0477 // Gradient 0478 else if ( data.userType() == qMetaTypeId<QGradientStops>() ) 0479 { 0480 return QVariant::fromValue(static_cast<color_widgets::GradientEditor*>(editor)->stops()); 0481 } 0482 0483 // Spin boxes 0484 switch ( data.userType() ) 0485 { 0486 case QMetaType::QPointF: 0487 return static_cast<Spin2D*>(editor)->value_point(); 0488 case QMetaType::QVector2D: 0489 return static_cast<Spin2D*>(editor)->value_vector(); 0490 case QMetaType::QSizeF: 0491 return static_cast<Spin2D*>(editor)->value_size(); 0492 case QMetaType::Float: 0493 case QMetaType::Double: 0494 return static_cast<QDoubleSpinBox*>(editor)->value() / ((prop_flags & model::PropertyTraits::Percent) ? 100 : 1); 0495 case QMetaType::Int: 0496 return static_cast<QSpinBox*>(editor)->value(); 0497 } 0498 0499 status = 0; 0500 return {}; 0501 }