Warning, file /graphics/glaxnimate/src/gui/item_models/property_model_base.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_private.hpp"
0008 #include "model/assets/assets.hpp"
0009 
0010 using namespace glaxnimate::gui;
0011 using namespace glaxnimate;
0012 
0013 
0014 void item_models::PropertyModelBase::Private::begin_insert_row(item_models::PropertyModelBase::Private::Subtree* row_tree, int index)
0015 {
0016     model->beginInsertRows(subtree_index(row_tree), index, index);
0017 }
0018 
0019 void item_models::PropertyModelBase::Private::end_insert_row()
0020 {
0021     model->endInsertRows();
0022 }
0023 
0024 void item_models::PropertyModelBase::Private::add_object(model::Object* object, Subtree* parent, bool insert_row, int index)
0025 {
0026     auto& container = parent ? parent->children : roots;
0027     if ( std::find_if(container.begin(), container.end(), [object](Subtree* st){ return st->object == object; }) != container.end() )
0028         return;
0029 
0030     if ( index == -1 )
0031         index = container.size();
0032 
0033     if ( insert_row )
0034         begin_insert_row(parent, index);
0035 
0036     auto node = do_add_node(Subtree{object, parent ? parent->id : 0}, parent, index);
0037     connect_recursive(node, false);
0038 
0039     if ( insert_row )
0040         end_insert_row();
0041 }
0042 
0043 item_models::PropertyModelBase::Private::Subtree* item_models::PropertyModelBase::Private::do_add_node(Subtree st, Subtree* parent, int index)
0044 {
0045     auto it = nodes.insert({next_id, st}).first;
0046 
0047 
0048     auto& container = parent ? parent->children : roots;
0049     if ( index == -1 )
0050         container.push_back(&it->second);
0051     else
0052         container.insert(container.begin() + index, &it->second);
0053 
0054     it->second.id = next_id;
0055     next_id++;
0056     return &it->second;
0057 }
0058 
0059 item_models::PropertyModelBase::Private::Subtree* item_models::PropertyModelBase::Private::add_node(Subtree st)
0060 {
0061     auto parent = node(st.parent);
0062     return do_add_node(std::move(st), parent, -1);
0063 }
0064 
0065 void item_models::PropertyModelBase::Private::clear()
0066 {
0067     for ( const auto& p : roots )
0068         disconnect_recursive(p);
0069 
0070     roots.clear();
0071     next_id = 1;
0072     nodes.clear();
0073     objects.clear();
0074 }
0075 
0076 item_models::PropertyModelBase::Private::Subtree* item_models::PropertyModelBase::Private::node_from_index(const QModelIndex& index)
0077 {
0078     if ( !index.isValid() )
0079         return nullptr;
0080 
0081     auto it = nodes.find(index.internalId());
0082     return it == nodes.end() ? nullptr : &it->second;
0083 }
0084 
0085 item_models::PropertyModelBase::Private::Subtree* item_models::PropertyModelBase::Private::node(id_type id)
0086 {
0087     auto it = nodes.find(id);
0088     return it != nodes.end() ? &it->second : nullptr;
0089 }
0090 
0091 QVariant item_models::PropertyModelBase::Private::data_name(Subtree* tree, int role)
0092 {
0093     if ( role == Qt::DisplayRole )
0094     {
0095         if ( tree->object )
0096             return tree->object->object_name();
0097         else if ( tree->prop )
0098             return tree->prop->localized_name();
0099     }
0100     else if ( role == Qt::FontRole )
0101     {
0102         QFont font;
0103         font.setBold(true);
0104         return font;
0105     }
0106     else if ( role == Qt::EditRole && tree->visual_node )
0107     {
0108         return tree->visual_node->object_name();
0109     }
0110     else if ( role == Qt::DecorationRole && tree->visual_node )
0111     {
0112         return tree->visual_node->tree_icon();
0113     }
0114 
0115 
0116     return {};
0117 }
0118 
0119 QVariant item_models::PropertyModelBase::Private::data_value(Subtree* tree, int role)
0120 {
0121     return data_value(tree->prop, tree->object, role);
0122 }
0123 
0124 QVariant item_models::PropertyModelBase::Private::data_value(model::BaseProperty* prop, model::Object* object, int role)
0125 {
0126     if ( !prop )
0127     {
0128         return {};
0129     }
0130 
0131     model::PropertyTraits traits = prop->traits();
0132 
0133     if ( role == Qt::ForegroundRole )
0134     {
0135         if ( (traits.flags & (model::PropertyTraits::List|model::PropertyTraits::ReadOnly))
0136             || traits.type == model::PropertyTraits::Object || traits.type == model::PropertyTraits::Unknown
0137         )
0138             return QApplication::palette().color(QPalette::Disabled, QPalette::Text);
0139     }
0140 
0141     if ( role == Flags )
0142         return prop->traits().flags;
0143 
0144     if ( role == ReferenceProperty && prop->traits().flags & model::PropertyTraits::OptionList )
0145         return QVariant::fromValue(static_cast<model::OptionListPropertyBase*>(prop));
0146 
0147     if ( (traits.flags & model::PropertyTraits::Animated) )
0148     {
0149         model::AnimatableBase* anprop = static_cast<model::AnimatableBase*>(prop);
0150         auto frame_status = anprop->keyframe_status(document->current_time());
0151 
0152         if ( role == Qt::DecorationRole )
0153         {
0154             switch ( frame_status )
0155             {
0156                 case model::AnimatableBase::Tween:
0157                     return QIcon(app::Application::instance()->data_file("images/keyframe/status/tween.svg"));
0158                 case model::AnimatableBase::IsKeyframe:
0159                     return QIcon(app::Application::instance()->data_file("images/keyframe/status/key.svg"));
0160                 case model::AnimatableBase::Mismatch:
0161                     return QIcon(app::Application::instance()->data_file("images/keyframe/status/mismatch.svg"));
0162                 case model::AnimatableBase::NotAnimated:
0163                     return QIcon(app::Application::instance()->data_file("images/keyframe/status/not-animated.svg"));
0164             }
0165 
0166         }
0167         else if ( role == Qt::BackgroundRole )
0168         {
0169             switch ( frame_status )
0170             {
0171                 case model::AnimatableBase::Tween:
0172                     return QColor::fromHsv(100, 167, 127);
0173                 case model::AnimatableBase::IsKeyframe:
0174                     return QColor::fromHsv(51, 171, 133);
0175                 case model::AnimatableBase::Mismatch:
0176                     return QColor::fromHsv(29, 180, 149);
0177                 case model::AnimatableBase::NotAnimated:
0178                     return QColor::fromHsv(0, 0, 120);
0179             }
0180         }
0181         else if ( role == Qt::ForegroundRole )
0182         {
0183             return QColor(Qt::white);
0184         }
0185         else if ( role == MinValue && traits.type == model::PropertyTraits::Float )
0186         {
0187             return static_cast<model::AnimatedProperty<float>*>(anprop)->min();
0188         }
0189         else if ( role == MaxValue && traits.type == model::PropertyTraits::Float )
0190         {
0191             return static_cast<model::AnimatedProperty<float>*>(anprop)->max();
0192         }
0193     }
0194 
0195     if ( (traits.flags & model::PropertyTraits::List) || traits.type == model::PropertyTraits::Unknown )
0196     {
0197         return {};
0198     }
0199     else if ( traits.type == model::PropertyTraits::Object )
0200     {
0201         if ( object && role == Qt::DisplayRole )
0202             return object->object_name();
0203         return {};
0204     }
0205     else if ( traits.type == model::PropertyTraits::Bool )
0206     {
0207         if ( role == Qt::CheckStateRole )
0208             return QVariant::fromValue(prop->value().toBool() ? Qt::Checked : Qt::Unchecked);
0209         return {};
0210     }
0211     else if ( traits.type == model::PropertyTraits::ObjectReference )
0212     {
0213         if ( role == Qt::DisplayRole )
0214         {
0215             QVariant value = prop->value();
0216             if ( value.isNull() )
0217                 return "";
0218             return value.value<model::DocumentNode*>()->object_name();
0219         }
0220 
0221         if ( role == Qt::DecorationRole )
0222         {
0223             QVariant value = prop->value();
0224             if ( value.isNull() )
0225                 return {};
0226             return QIcon(value.value<model::DocumentNode*>()->instance_icon());
0227         }
0228 
0229         if ( role == ReferenceProperty )
0230             return QVariant::fromValue(static_cast<model::ReferencePropertyBase*>(prop));
0231 
0232         return {};
0233     }
0234     else if ( traits.type == model::PropertyTraits::Enum )
0235     {
0236         if ( role == Qt::DisplayRole )
0237             return EnumCombo::data_for(prop->value()).first;
0238         if ( role == Qt::EditRole )
0239             return prop->value();
0240         if ( role == Qt::DecorationRole )
0241             return QIcon::fromTheme(EnumCombo::data_for(prop->value()).second);
0242         return {};
0243     }
0244     else
0245     {
0246         if ( role == Qt::DisplayRole && (prop->traits().flags & model::PropertyTraits::Percent) )
0247             return QString(i18n("%1%", prop->value().toDouble() * 100));
0248         if ( role == Qt::DisplayRole || role == Qt::EditRole )
0249             return prop->value();
0250         return {};
0251     }
0252 }
0253 
0254 void item_models::PropertyModelBase::Private::connect_recursive(Subtree* this_node, bool insert_row)
0255 {
0256     auto object = this_node->object;
0257     if ( !object )
0258         return;
0259 
0260     objects[object] = this_node->id;
0261     QObject::connect(object, &model::Object::destroyed, model, &PropertyModelBase::on_delete_object);
0262     QObject::connect(object, &model::Object::removed, model, &PropertyModelBase::on_delete_object);
0263     QObject::connect(object, &model::Object::property_changed, model, &PropertyModelBase::property_changed);
0264 
0265     on_connect(object, this_node, insert_row, nullptr);
0266 }
0267 
0268 void item_models::PropertyModelBase::Private::connect_subobject(model::Object* object, Subtree* this_node, bool insert_row)
0269 {
0270     this_node->expand_referenced = true;
0271 
0272     if ( !object )
0273         return;
0274 
0275     QObject::connect(object, &model::Object::property_changed, model, &PropertyModelBase::property_changed);
0276 
0277     ReferencedPropertiesMap* referenced = nullptr;
0278     if ( this_node->prop && this_node->prop->traits().type == model::PropertyTraits::ObjectReference )
0279         referenced = &referenced_properties[object];
0280 
0281     on_connect(object, this_node, insert_row, referenced);
0282 }
0283 
0284 void item_models::PropertyModelBase::Private::disconnect_recursive(Subtree* node)
0285 {
0286     if ( node->object )
0287     {
0288         QObject::disconnect(node->object, nullptr, model, nullptr);
0289         objects.erase(node->object);
0290     }
0291 
0292     if ( node->prop )
0293     {
0294         auto it = properties.find(node->prop);
0295         if ( it != properties.end() && it->second == node->id )
0296             properties.erase(it);
0297     }
0298 
0299     for ( Subtree* child : node->children )
0300     {
0301         disconnect_recursive(child);
0302         nodes.erase(child->id);
0303     }
0304 
0305     node->children.clear();
0306 }
0307 
0308 item_models::PropertyModelBase::Private::Subtree* item_models::PropertyModelBase::Private::object_tree(model::Object* obj)
0309 {
0310     auto it1 = objects.find(obj);
0311     if ( it1 == objects.end() )
0312         return nullptr;
0313 
0314     auto it2 = nodes.find(it1->second);
0315     if ( it2 == nodes.end() )
0316         return nullptr;
0317 
0318     return &it2->second;
0319 }
0320 
0321 void item_models::PropertyModelBase::Private::on_delete_object(model::Object* obj)
0322 {
0323     auto it = objects.find(obj);
0324     if ( it == objects.end() )
0325         return;
0326 
0327     auto it2 = nodes.find(it->second);
0328     if ( it2 == nodes.end() )
0329         return;
0330 
0331     Subtree* node = &it2->second;
0332 
0333     auto index = model->object_index(obj);
0334     model->beginRemoveRows(index.parent(), index.row(), index.row());
0335 
0336     disconnect_recursive(node);
0337 
0338     if ( node->parent )
0339     {
0340         auto& siblings = this->node(node->parent)->children;
0341         for ( auto itc = siblings.begin(); itc != siblings.end(); ++itc )
0342         {
0343             if ( *itc == node )
0344             {
0345                 siblings.erase(itc);
0346                 break;
0347             }
0348         }
0349     }
0350 
0351     auto it_roots = std::find(roots.begin(), roots.end(), node);
0352     if ( it_roots != roots.end() )
0353         roots.erase(it_roots);
0354 
0355     nodes.erase(it2);
0356 
0357     model->endRemoveRows();
0358 }
0359 
0360 item_models::PropertyModelBase::Private::Subtree* item_models::PropertyModelBase::Private::visual_node_parent(Subtree* tree)
0361 {
0362     while ( tree->parent )
0363     {
0364         tree = node(tree->parent);
0365         if ( !tree )
0366             return nullptr;
0367 
0368         if ( tree->visual_node )
0369             return tree;
0370     }
0371 
0372     return nullptr;
0373 }
0374 
0375 QModelIndex item_models::PropertyModelBase::Private::node_index(model::DocumentNode* node)
0376 {
0377     auto it = objects.find(node);
0378     if ( it == objects.end() )
0379         return {};
0380 
0381     Subtree* tree = this->node(it->second);
0382     if ( !tree )
0383         return {};
0384 
0385     int row = 0;
0386     if ( Subtree* parent = visual_node_parent(tree) )
0387     {
0388         row = parent->visual_node->docnode_child_index(node);
0389         if ( row == -1 )
0390             return {};
0391     }
0392     else
0393     {
0394         for ( ; row < int(roots.size()); row++ )
0395             if ( roots[row]->visual_node == node )
0396                 break;
0397 
0398         if ( row == int(roots.size()) )
0399             return {};
0400     }
0401 
0402     return model->createIndex(row, 0, tree->id);
0403 }
0404 
0405 QModelIndex item_models::PropertyModelBase::Private::subtree_index(id_type id)
0406 {
0407     auto it = nodes.find(id);
0408     if ( it == nodes.end() )
0409         return {};
0410     return subtree_index(&it->second);
0411 }
0412 
0413 QModelIndex item_models::PropertyModelBase::Private::subtree_index(Subtree* tree)
0414 {
0415     if ( !tree )
0416         return {};
0417 
0418     int row = 0;
0419     if ( tree->parent )
0420     {
0421         auto it = nodes.find(tree->parent);
0422         if ( it == nodes.end() )
0423             return {};
0424 
0425         auto parent = &*it;
0426         if ( !parent )
0427             return {};
0428 
0429         row = parent->second.child_index(tree);
0430         if ( row == -1 )
0431             return {};
0432     }
0433     else
0434     {
0435         for ( ; row < int(roots.size()); row++ )
0436             if ( roots[row] == tree )
0437                 break;
0438 
0439         if ( row == int(roots.size()) )
0440             return {};
0441     }
0442 
0443     return model->createIndex(row, 0, tree->id);
0444 }
0445 
0446 bool item_models::PropertyModelBase::Private::set_prop_data(Subtree* tree, const QVariant& value, int role)
0447 {
0448     return set_prop_data(tree->prop, value, role);
0449 }
0450 
0451 bool item_models::PropertyModelBase::Private::set_prop_data(model::BaseProperty* prop, const QVariant& value, int role)
0452 {
0453     if ( !prop )
0454         return false;
0455 
0456     model::PropertyTraits traits = prop->traits();
0457 
0458 
0459     if ( (traits.flags & (model::PropertyTraits::List|model::PropertyTraits::ReadOnly)) ||
0460         traits.type == model::PropertyTraits::Object ||
0461         traits.type == model::PropertyTraits::Unknown )
0462     {
0463         return false;
0464     }
0465     else if ( traits.type == model::PropertyTraits::Bool )
0466     {
0467         if ( role == Qt::CheckStateRole )
0468         {
0469             return prop->set_undoable(value.value<Qt::CheckState>() == Qt::Checked);
0470         }
0471         return false;
0472     }
0473     else
0474     {
0475         if ( role == Qt::EditRole )
0476             return prop->set_undoable(value);
0477         return false;
0478     }
0479 }
0480 
0481 
0482 
0483 item_models::PropertyModelBase::PropertyModelBase(std::unique_ptr<Private> d)
0484     : d(std::move(d))
0485 {
0486 }
0487 
0488 item_models::PropertyModelBase::~PropertyModelBase()
0489 {
0490 }
0491 
0492 QModelIndex item_models::PropertyModelBase::index(int row, int column, const QModelIndex& parent) const
0493 {
0494     Private::Subtree* tree = d->node_from_index(parent);
0495     if ( !tree )
0496     {
0497         if ( row >= 0 && row < int(d->roots.size()) )
0498             return createIndex(row, column, d->roots[row]->id);
0499         return {};
0500     }
0501 
0502 
0503     if ( row >= 0 && row < int(tree->children.size()) )
0504         return createIndex(row, column, tree->children[row]->id);
0505 
0506     return {};
0507 }
0508 
0509 QModelIndex item_models::PropertyModelBase::parent(const QModelIndex& child) const
0510 {
0511     // tree_child is the subtree for the child index
0512     Private::Subtree* tree_child = d->node_from_index(child);
0513 
0514     // child is a root node, so parent is invalid
0515     if ( !tree_child || tree_child->parent == 0 )
0516         return {};
0517 
0518     // no parent? => error
0519     auto it = d->nodes.find(tree_child->parent);
0520     if ( it == d->nodes.end() )
0521         return {};
0522 
0523     // tree is the subtree we want to return
0524     Private::Subtree* tree = &it->second;
0525 
0526     // We have a root node
0527     if ( !tree->parent )
0528     {
0529         // failsafe
0530         if ( !d->document )
0531             return {};
0532 
0533         // main is 0
0534         int index = 0;
0535         // assets is 1
0536         if ( tree->object == d->document->assets() )
0537             index = 1;
0538 
0539         return createIndex(index, 0, tree->id);
0540     }
0541 
0542     // no parent? => error
0543     it = d->nodes.find(tree->parent);
0544     if ( it == d->nodes.end() )
0545         return {};
0546 
0547     // tree_parent is the parent of tree, grandparent of the model index
0548     Private::Subtree* tree_parent = &it->second;
0549 
0550     // We look for the row index for the parent index
0551     for ( int i = 0; i < int(tree_parent->children.size()); i++ )
0552         if ( tree_parent->children[i] == tree )
0553             return createIndex(i, 0, tree->id);
0554 
0555     return {};
0556 }
0557 
0558 void item_models::PropertyModelBase::set_document(model::Document* document)
0559 {
0560     beginResetModel();
0561     d->document = document;
0562     d->clear();
0563     on_document_reset();
0564     endResetModel();
0565 }
0566 
0567 void item_models::PropertyModelBase::clear_document()
0568 {
0569     set_document(nullptr);
0570 }
0571 
0572 item_models::PropertyModelBase::Item item_models::PropertyModelBase::item(const QModelIndex& index) const
0573 {
0574     if ( Private::Subtree* st = d->node_from_index(index) )
0575     {
0576         Item item = st->object;
0577         if ( st->prop )
0578             item.property = st->prop;
0579         return item;
0580     }
0581 
0582     return {};
0583 }
0584 
0585 
0586 QModelIndex item_models::PropertyModelBase::property_index(model::BaseProperty* prop) const
0587 {
0588     auto it = d->properties.find(prop);
0589     if ( it == d->properties.end() )
0590         return {};
0591 
0592     return index_by_id(it->second, 1);
0593 }
0594 
0595 QModelIndex item_models::PropertyModelBase::object_index(model::Object* obj) const
0596 {
0597     auto it = d->objects.find(obj);
0598     if ( it == d->objects.end() )
0599         return {};
0600 
0601     return index_by_id(it->second);
0602 }
0603 
0604 QModelIndex item_models::PropertyModelBase::index_by_id(quintptr id, int column) const
0605 {
0606     Private::Subtree* prop_node = d->node(id);
0607     if ( !prop_node )
0608         return {};
0609 
0610     Private::Subtree* parent = d->node(prop_node->parent);
0611 
0612     int i = 0;
0613     if ( !parent )
0614         i = std::find(d->roots.begin(), d->roots.end(), prop_node) - d->roots.begin();
0615     else
0616         i = std::find(parent->children.begin(), parent->children.end(), prop_node) - parent->children.begin();
0617 
0618     return createIndex(i, column, prop_node->id);
0619 }
0620 
0621 
0622 model::VisualNode* item_models::PropertyModelBase::visual_node(const QModelIndex& index) const
0623 {
0624     Private::Subtree* tree = d->node_from_index(index);
0625     if ( !tree )
0626         return nullptr;
0627 
0628     return tree->visual_node;
0629 }
0630 
0631 void item_models::PropertyModelBase::on_delete_object()
0632 {
0633     d->on_delete_object(static_cast<model::Object*>(sender()));
0634 }
0635 
0636 void item_models::PropertyModelBase::property_changed(const model::BaseProperty* prop, const QVariant& value)
0637 {
0638     d->property_changed(prop, value);
0639 }
0640 
0641 void item_models::PropertyModelBase::Private::property_changed(const model::BaseProperty* prop, const QVariant& value)
0642 {
0643     auto it = properties.find(const_cast<model::BaseProperty*>(prop));
0644     if ( it != properties.end() )
0645         on_property_changed(it->second, prop, value);
0646 
0647     auto itref = referenced_properties.find(prop->object());
0648     if ( itref != referenced_properties.end() )
0649     {
0650         auto itprop = itref->second.find(const_cast<model::BaseProperty*>(prop));
0651         if ( itprop != itref->second.end() )
0652         {
0653             for ( id_type id : itprop->second )
0654                 on_property_changed(id, prop, value);
0655         }
0656     }
0657 }
0658 
0659 void item_models::PropertyModelBase::Private::clean_object_references(const QModelIndex& index, Private::Subtree* prop_node)
0660 {
0661     if ( prop_node->children.empty() )
0662         return;
0663 
0664     model->beginRemoveRows(index, 0, prop_node->children.size());
0665 
0666     clean_subtree(prop_node);
0667 
0668     model->endRemoveRows();
0669 }
0670 
0671 void item_models::PropertyModelBase::Private::on_property_changed(id_type prop_node_id, const model::BaseProperty*, const QVariant& value)
0672 {
0673     Private::Subtree* prop_node = node(prop_node_id);
0674     if ( !prop_node )
0675         return;
0676 
0677     Private::Subtree* parent = node(prop_node->parent);
0678 
0679     if ( !parent )
0680         return;
0681 
0682     int i = std::find(parent->children.begin(), parent->children.end(), prop_node) - parent->children.begin();
0683     QModelIndex index = model->createIndex(i, 1, prop_node->id);
0684 
0685     if ( !(prop_node->prop->traits().flags & model::PropertyTraits::List) )
0686     {
0687         if ( prop_node->prop->traits().type == model::PropertyTraits::ObjectReference )
0688         {
0689             model::Object* obj_value = value.value<model::Object*>();
0690 
0691             if ( !prop_node->children.empty() )
0692                 clean_object_references(index, prop_node);
0693 
0694             if ( prop_node->expand_referenced && obj_value )
0695                 connect_subobject(obj_value, prop_node, true);
0696         }
0697 
0698         Q_EMIT model->dataChanged(index, index, {});
0699     }
0700 }
0701 
0702 void item_models::PropertyModelBase::Private::clean_subtree(item_models::PropertyModelBase::Private::Subtree* node)
0703 {
0704     for ( auto& child : node->children )
0705     {
0706         clean_subtree(child);
0707         nodes.erase(child->id);
0708     }
0709 
0710     node->children.clear();
0711 
0712     auto itref = referenced_properties.find(node->object);
0713     if ( itref != referenced_properties.end() )
0714     {
0715         auto itprop = itref->second.find(node->prop);
0716         if ( itprop != itref->second.end() )
0717         {
0718             itprop->second.erase(
0719                 std::remove_if(itprop->second.begin(), itprop->second.end(), [node, this](id_type id){
0720                     auto n = this->node(id);
0721                     return !n || n->parent == node->id;
0722                 }),
0723                 itprop->second.end()
0724             );
0725         }
0726     }
0727 }
0728 
0729 int item_models::PropertyModelBase::rowCount(const QModelIndex& parent) const
0730 {
0731     if ( d->roots.empty() )
0732         return 0;
0733 
0734     Private::Subtree* tree = d->node_from_index(parent);
0735     if ( !tree )
0736         return d->roots.size();
0737 
0738     return tree->children.size();
0739 }
0740 
0741 QModelIndex item_models::PropertyModelBase::node_index(model::DocumentNode* node) const
0742 {
0743     return object_index(node);
0744 }
0745 
0746 model::Document * item_models::PropertyModelBase::document() const
0747 {
0748     return d->document;
0749 }
0750 
0751 model::DocumentNode * item_models::PropertyModelBase::node(const QModelIndex& index) const
0752 {
0753     Private::Subtree* tree = d->node_from_index(index);
0754     if ( !tree )
0755         return nullptr;
0756 
0757     if ( tree->visual_node )
0758         return tree->visual_node;
0759 
0760     return qobject_cast<model::DocumentNode*>(tree->object);
0761 }
0762 
0763 model::AnimatableBase* item_models::PropertyModelBase::Private::animatable(Subtree* tree)
0764 {
0765     if ( !tree || !tree->prop )
0766         return nullptr;
0767 
0768     model::PropertyTraits traits = tree->prop->traits();
0769     if ( traits.flags & model::PropertyTraits::Animated )
0770         return static_cast<model::AnimatableBase*>(tree->prop);
0771 
0772     return nullptr;
0773 }
0774 
0775 model::AnimatableBase* item_models::PropertyModelBase::animatable(const QModelIndex& index) const
0776 {
0777     return d->animatable(d->node_from_index(index));
0778 }
0779 
0780 
0781 item_models::PropertyModelBase::Private::Subtree*
0782 item_models::PropertyModelBase::Private::add_property(
0783     model::BaseProperty* prop, id_type parent, bool insert_row, ReferencedPropertiesMap* referenced)
0784 {
0785     if ( insert_row )
0786     {
0787         Subtree* parent_node = node(parent);
0788         begin_insert_row(parent_node, parent_node->children.size());
0789     }
0790 
0791     Subtree* prop_node = add_node(Subtree{prop, parent});
0792 
0793     if ( referenced )
0794         (*referenced)[prop].push_back(prop_node->id);
0795     else
0796         properties[prop] = prop_node->id;
0797 
0798     if ( insert_row )
0799         end_insert_row();
0800 
0801     return prop_node;
0802 }