Warning, file /graphics/glaxnimate/src/gui/item_models/property_model_full.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_model_full.hpp"
0008 #include "property_model_private.hpp"
0009 
0010 #include <QPainter>
0011 
0012 #include "model/stretchable_time.hpp"
0013 #include "model/assets/assets.hpp"
0014 
0015 using namespace glaxnimate::gui;
0016 using namespace glaxnimate;
0017 
0018 class item_models::PropertyModelFull::Private : public PropertyModelBase::Private
0019 {
0020 public:
0021     using PropertyModelBase::Private::Private;
0022 
0023     PropertyModelFull* my_model()
0024     {
0025         return static_cast<PropertyModelFull*>(model);
0026     }
0027 
0028     void on_connect_object_list(Subtree* prop_node, model::DocumentNode* node, model::ObjectListPropertyBase* prop)
0029     {
0030         prop_node->merged_children_offset = prop_node->children.size();
0031 
0032         QVariantList prop_value = prop->value().toList();
0033         for ( auto it = prop_value.rbegin(); it != prop_value.rend(); ++it )
0034         {
0035             model::Object* subobj = it->value<model::Object*>();
0036             auto suboj_node = add_node(Subtree{subobj, prop_node->id});
0037             connect_recursive(suboj_node, false);
0038         }
0039 
0040         connect_docnode(node, prop_node);
0041     }
0042 
0043     void on_connect(model::Object* object, Subtree* tree, bool insert_row, ReferencedPropertiesMap* referenced) override
0044     {
0045         model::VisualNode* visual = object->cast<model::VisualNode>();
0046         model::DocumentNode* node = nullptr;
0047 
0048         // connect visual node signals
0049         if ( visual )
0050         {
0051             connect(visual, &model::VisualNode::docnode_visible_changed, model, [this, visual]() {
0052                 QModelIndex ind = node_index(visual);
0053                 QModelIndex par = node_index(visual->docnode_parent());
0054                 QModelIndex changed = model->index(ind.row(), ColumnVisible, par);
0055                 model->dataChanged(changed, changed, {Qt::DecorationRole, Qt::ToolTipRole});
0056             });
0057             connect(visual, &model::VisualNode::docnode_locked_changed, model, [this, visual]() {
0058                 QModelIndex ind = node_index(visual);
0059                 QModelIndex par = node_index(visual->docnode_parent());
0060                 QModelIndex changed = model->index(ind.row(), ColumnLocked, par);
0061                 model->dataChanged(changed, changed, {Qt::DecorationRole, Qt::ToolTipRole});
0062             });
0063             connect(visual, &model::VisualNode::docnode_group_color_changed, model, [this, visual]() {
0064                 QModelIndex ind = node_index(visual);
0065                 QModelIndex par = node_index(visual->docnode_parent());
0066                 QModelIndex changed = model->index(ind.row(), ColumnColor, par);
0067                 model->dataChanged(changed, changed, {Qt::BackgroundRole, Qt::EditRole, Qt::DisplayRole});
0068             });
0069 
0070             node = visual;
0071         }
0072         else
0073         {
0074             node = object->cast<model::DocumentNode>();
0075         }
0076 
0077         // connect document node signals
0078         if ( node )
0079         {
0080             connect(node, &model::DocumentNode::name_changed, model, [this, node]() {
0081                 QModelIndex ind = node_index(node);
0082                 QModelIndex par = node_index(node->docnode_parent());
0083                 QModelIndex changed = model->index(ind.row(), ColumnName, par);
0084                 model->dataChanged(changed, changed, {Qt::EditRole, Qt::DisplayRole});
0085             });
0086         }
0087 
0088         model::ObjectListPropertyBase* object_list = nullptr;
0089 
0090         for ( model::BaseProperty* prop : object->properties() )
0091         {
0092             if (
0093                 (prop->traits().flags & model::PropertyTraits::List) &&
0094                 prop->traits().type == model::PropertyTraits::Object
0095             )
0096             {
0097                 // found "shapes"
0098                 if ( node )
0099                     object_list = static_cast<model::ObjectListPropertyBase*>(prop);
0100             }
0101             // sub object
0102             else if ( prop->traits().type == model::PropertyTraits::Object )
0103             {
0104                 model::Object* subobj = prop->value().value<model::Object*>();
0105                 if ( subobj )
0106                 {
0107                     // For assets, avoid an intermediate node
0108                     if ( object == document->assets() )
0109                     {
0110                         model::DocumentNode* subobj = prop->value().value<model::DocumentNode*>();
0111                         model::ObjectListPropertyBase* asset_list = static_cast<model::ObjectListPropertyBase*>(subobj->get_property("values"));
0112                         Subtree* prop_node = add_property(prop, tree->id, insert_row, referenced);
0113 //                         Subtree* prop_node = add_node(Subtree{prop, tree->id});
0114 //                         properties[asset_list] = prop_node->id;
0115                         on_connect_object_list(prop_node, subobj, asset_list);
0116                     }
0117                     else if ( prop->name() == "transform" )
0118                     {
0119                         Subtree* prop_node = add_property(prop, tree->id, insert_row, referenced);
0120                         connect_subobject(subobj, prop_node, insert_row);
0121                     }
0122                     else
0123                     {
0124                         auto meta = subobj->metaObject();
0125                         if (
0126                             !meta->inherits(&model::AnimationContainer::staticMetaObject) &&
0127                             !meta->inherits(&model::StretchableTime::staticMetaObject) &&
0128                             !meta->inherits(&model::MaskSettings::staticMetaObject)
0129                         )
0130                             connect_subobject(subobj, tree, insert_row);
0131                     }
0132                 }
0133             }
0134             // sub object
0135             else if ( prop->traits().type == model::PropertyTraits::ObjectReference )
0136             {
0137                 if ( prop->name() == "parent" )
0138                     continue;
0139 
0140                 Subtree* prop_node = add_property(prop, tree->id, insert_row, referenced);
0141 
0142                 if ( prop->name() != "composition" )
0143                 {
0144                     model::Object* subobj = prop->value().value<model::Object*>();
0145                     if ( subobj && subobj->is_instance<model::Asset>() )
0146                         connect_subobject(subobj, prop_node, insert_row);
0147                 }
0148             }
0149             // scalar
0150             else if ( prop->traits().flags & model::PropertyTraits::Visual && !(prop->traits().flags & model::PropertyTraits::Hidden) )
0151             {
0152                 add_property(prop, tree->id, insert_row, referenced);
0153             }
0154         }
0155 
0156         // Show object lists at the end
0157         if ( object_list )
0158         {
0159             tree->prop = object_list;
0160             on_connect_object_list(tree, node, object_list);
0161         }
0162     }
0163 
0164     void connect_docnode(model::DocumentNode* node, Subtree* insert_into)
0165     {
0166         auto id = insert_into->id;
0167         connect(node, &model::DocumentNode::docnode_child_add_end, model,
0168         [this, id, node](model::DocumentNode* child, int row) {
0169             auto insert_into = this->node(id);
0170             int rows = node->docnode_child_count() - 1; // called at the end
0171             add_object(child, insert_into, true, rows -  row + insert_into->merged_children_offset);
0172         });
0173         connect(node, &model::DocumentNode::docnode_child_remove_end, model,
0174         [this](model::DocumentNode* child) {
0175             on_delete_object(child);
0176         });
0177         connect(node, &model::DocumentNode::docnode_child_move_begin, model, [this, id, node](int a, int b) {
0178             auto insert_into = this->node(id);
0179             int rows = node->docnode_child_count();
0180             int src = rows - a - 1 + insert_into->merged_children_offset;
0181             int dest = rows - b - 1 + insert_into->merged_children_offset;
0182             int dest_it = dest;
0183             if ( src < dest )
0184                 dest++;
0185 
0186             auto subtree = this->node(id);
0187             if ( !subtree )
0188                 return;
0189 
0190             QModelIndex parent = subtree_index(subtree);
0191             my_model()->beginMoveRows(parent, src, src, parent, dest);
0192 
0193             Subtree* moved = subtree->children[src];
0194             subtree->children.erase(subtree->children.begin() + src);
0195             subtree->children.insert(subtree->children.begin() + dest_it, moved);
0196 
0197             my_model()->endMoveRows();
0198         });
0199     }
0200 
0201     QVariant data_color(Subtree* tree, int role)
0202     {
0203         if ( tree->visual_node )
0204         {
0205             if ( role == Qt::DisplayRole || role == Qt::EditRole )
0206                 return tree->visual_node->docnode_group_color();
0207             if ( role == Qt::ToolTipRole )
0208                 return i18n("Group Color");
0209         }
0210         else if ( model::AnimatableBase* anprop = animatable(tree) )
0211         {
0212             if ( anprop->keyframe_count() > 1 )
0213             {
0214                 if ( role == Qt::ToolTipRole )
0215                     return i18n("Jump to previous keyframe");
0216                 else if ( role == Qt::DecorationRole )
0217                     return QIcon::fromTheme("go-previous");
0218             }
0219         }
0220 
0221         return {};
0222     }
0223 
0224     QIcon transparent_icon(const QIcon& icon, qreal alpha = 0.3)
0225     {
0226         QIcon out;
0227 
0228         for ( const auto& size : icon.availableSizes() )
0229         {
0230             QPixmap pixmap = icon.pixmap(size);
0231             QPixmap outpix(pixmap.size());
0232             outpix.fill(Qt::transparent);
0233 
0234             QPainter painter(&outpix);
0235             painter.setOpacity(alpha);
0236             painter.drawPixmap(0, 0, pixmap);
0237 
0238             out.addPixmap(outpix);
0239         }
0240 
0241         return out;
0242     }
0243 
0244     QVariant data_visible(Subtree* tree, int role)
0245     {
0246         if ( tree->visual_node )
0247         {
0248             if ( role == Qt::DecorationRole )
0249             {
0250                 if ( tree->visual_node->visible.get() )
0251                     return QIcon::fromTheme("view-visible");
0252                 return QIcon::fromTheme("view-hidden");
0253             }
0254             else if ( role == Qt::ToolTipRole )
0255             {
0256                 if ( tree->visual_node->visible.get() )
0257                     return i18n("Visible");
0258                 return i18n("Hidden");
0259             }
0260         }
0261         else if ( model::AnimatableBase* anprop = animatable(tree) )
0262         {
0263             if ( role == Qt::ToolTipRole )
0264             {
0265                 return i18n("Toggle Keyframe");
0266             }
0267             else if ( role == Qt::DecorationRole )
0268             {
0269                 if ( anprop->has_keyframe(document->current_time()) )
0270                     return QIcon::fromTheme("keyframe");
0271                 return transparent_icon(QIcon::fromTheme("keyframe-disable"));
0272             }
0273         }
0274 
0275         return {};
0276     }
0277 
0278     QVariant data_locked(Subtree* tree, int role)
0279     {
0280         if ( tree->visual_node )
0281         {
0282             if ( role == Qt::DecorationRole )
0283             {
0284                 if ( tree->visual_node->locked.get() )
0285                     return QIcon::fromTheme("object-locked");
0286                 return QIcon::fromTheme("object-unlocked");
0287             }
0288             else if ( role == Qt::ToolTipRole )
0289             {
0290                 if ( tree->visual_node->locked.get() )
0291                     return i18n("Locked");
0292                 return i18n("Unlocked");
0293             }
0294         }
0295         else if ( model::AnimatableBase* anprop = animatable(tree) )
0296         {
0297             if ( anprop->keyframe_count() > 1 )
0298             {
0299                 if ( role == Qt::ToolTipRole )
0300                     return i18n("Jump to next keyframe");
0301                 else if ( role == Qt::DecorationRole )
0302                     return QIcon::fromTheme("go-next");
0303             }
0304         }
0305 
0306         return {};
0307     }
0308 };
0309 
0310 
0311 item_models::PropertyModelFull::PropertyModelFull()
0312     : PropertyModelBase(std::make_unique<Private>(this))
0313 {}
0314 
0315 item_models::PropertyModelFull::Private* item_models::PropertyModelFull::dd() const
0316 {
0317     return static_cast<Private*>(d.get());
0318 }
0319 
0320 Qt::ItemFlags item_models::PropertyModelFull::flags(const QModelIndex& index) const
0321 {
0322     if ( d->roots.empty() || !index.isValid() )
0323         return {};
0324 
0325     Private::Subtree* tree = d->node_from_index(index);
0326     if ( !tree )
0327         return {};
0328 
0329     Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
0330 
0331     if ( tree->visual_node && !tree->visual_node->docnode_locked_recursive() )
0332     {
0333         flags |= Qt::ItemIsDragEnabled;
0334 
0335         if ( tree->visual_node->has("shapes") )
0336             flags |= Qt::ItemIsDropEnabled;
0337     }
0338 
0339     switch ( index.column() )
0340     {
0341         case ColumnColor:
0342         case ColumnName:
0343             if ( tree->visual_node )
0344                 flags |= Qt::ItemIsEditable;
0345             return flags;
0346         case ColumnLocked:
0347         case ColumnVisible:
0348             return flags;
0349         case ColumnValue:
0350 
0351             if ( auto lay = qobject_cast<model::Layer*>(tree->visual_node) )
0352                 if ( lay->is_top_level() )
0353                     return flags | Qt::ItemIsEditable;
0354 
0355             if ( tree->prop )
0356             {
0357                 model::PropertyTraits traits = tree->prop->traits();
0358 
0359                 if ( (traits.flags & (model::PropertyTraits::List|model::PropertyTraits::ReadOnly))
0360                     || traits.type == model::PropertyTraits::Object || traits.type == model::PropertyTraits::Unknown )
0361                     return flags;
0362 
0363                 if ( traits.type == model::PropertyTraits::Bool )
0364                     return flags | Qt::ItemIsUserCheckable;
0365 
0366                 return flags | Qt::ItemIsEditable;
0367             }
0368 
0369             return flags;
0370     }
0371 
0372     return {};
0373 }
0374 
0375 QVariant item_models::PropertyModelFull::data(const QModelIndex& index, int role) const
0376 {
0377     if ( d->roots.empty() || !index.isValid() )
0378         return {};
0379 
0380 
0381     Private::Subtree* tree = d->node_from_index(index);
0382     if ( !tree )
0383         return {};
0384 
0385     switch ( index.column() )
0386     {
0387         case ColumnName: return d->data_name(tree, role);
0388         case ColumnValue:
0389             if ( auto lay = qobject_cast<model::Layer*>(tree->visual_node) )
0390             {
0391                 if ( lay->is_top_level() )
0392                 {
0393                     if ( role == Qt::ToolTipRole )
0394                         return i18n("Parent Layer");
0395                     if ( !lay->parent.get() )
0396                     {
0397                         switch ( role )
0398                         {
0399                             case Qt::DisplayRole:
0400                                 return i18n("(No Parent)");
0401                             case Qt::ForegroundRole:
0402                                 return qApp->palette().brush(QPalette::Disabled, QPalette::Text);
0403                         }
0404                     }
0405                     return d->data_value(&lay->parent, nullptr, role);
0406                 }
0407             }
0408             return d->data_value(tree, role);
0409         case ColumnColor: return dd()->data_color(tree, role);
0410         case ColumnLocked: return dd()->data_locked(tree, role);
0411         case ColumnVisible: return dd()->data_visible(tree, role);
0412     }
0413     return {};
0414 }
0415 
0416 bool item_models::PropertyModelFull::setData(const QModelIndex& index, const QVariant& value, int role)
0417 {
0418     if ( d->roots.empty() || !index.isValid() )
0419         return false;
0420 
0421     Private::Subtree* tree = d->node_from_index(index);
0422     if ( !tree )
0423         return false;
0424 
0425     if ( index.column() == ColumnValue )
0426     {
0427         if ( auto lay = qobject_cast<model::Layer*>(tree->visual_node) )
0428             if ( lay->is_top_level() )
0429                 return d->set_prop_data(&lay->parent, value, role);
0430         return d->set_prop_data(tree, value, role);
0431     }
0432     else if ( tree->visual_node && role == Qt::EditRole )
0433     {
0434         switch ( index.column() )
0435         {
0436             case ColumnName:
0437                 return tree->visual_node->name.set_undoable(value);
0438             case ColumnColor:
0439                 return tree->visual_node->group_color.set_undoable(value);
0440         }
0441     }
0442 
0443     return false;
0444 }
0445 
0446 QVariant item_models::PropertyModelFull::headerData(int section, Qt::Orientation orientation, int role) const
0447 {
0448     if ( orientation == Qt::Horizontal )
0449     {
0450         switch ( section )
0451         {
0452             case ColumnName:
0453                 if ( role == Qt::DisplayRole )
0454                     return i18n("Name");
0455                 break;
0456             case ColumnValue:
0457                 if ( role == Qt::DisplayRole )
0458                     return i18n("Value");
0459                 break;
0460             case ColumnColor:
0461             case ColumnLocked:
0462             case ColumnVisible:
0463                 break;
0464         }
0465     }
0466     return {};
0467 }
0468 
0469 void item_models::PropertyModelFull::on_document_reset()
0470 {
0471     if ( d->document )
0472     {
0473         d->add_object(d->document->assets(), nullptr, false);
0474     }
0475 }
0476 
0477 int item_models::PropertyModelFull::columnCount(const QModelIndex &) const
0478 {
0479     return ColumnCount;
0480 }
0481 
0482 std::pair<model::VisualNode *, int> item_models::PropertyModelFull::drop_position(const QModelIndex& parent, int row, int column) const
0483 {
0484     Q_UNUSED(column);
0485 
0486     auto tree = d->node_from_index(parent);
0487     if ( !tree || !tree->visual_node )
0488         return {};
0489 
0490     if ( row != -1 )
0491     {
0492         row -= tree->merged_children_offset;
0493 
0494         if ( row < 0 )
0495             return {};
0496     }
0497     else
0498     {
0499         row = tree->merged_children_offset;
0500     }
0501 
0502     return {tree->visual_node, row};
0503 }