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 }