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 }