File indexing completed on 2024-05-12 03:47:25

0001 /*
0002     File                 : AbstractAspect.cpp
0003     Project              : LabPlot
0004     Description          : Base class for all objects in a Project.
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2007-2009 Tilman Benkert <thzs@gmx.net>
0007     SPDX-FileCopyrightText: 2007-2010 Knut Franke <knut.franke@gmx.de>
0008     SPDX-FileCopyrightText: 2011-2022 Alexander Semke <alexander.semke@web.de>
0009     SPDX-FileCopyrightText: 2023 Stefan Gerlach <stefan.gerlach@uni.kn>
0010 
0011     SPDX-License-Identifier: GPL-2.0-or-later
0012 */
0013 
0014 #include "backend/core/AbstractAspect.h"
0015 #include "backend/core/AspectFactory.h"
0016 #include "backend/core/AspectPrivate.h"
0017 #include "backend/core/Project.h"
0018 #include "backend/core/aspectcommands.h"
0019 #include "backend/lib/PropertyChangeCommand.h"
0020 #include "backend/lib/SignallingUndoCommand.h"
0021 #include "backend/lib/XmlStreamReader.h"
0022 #include "backend/lib/trace.h"
0023 
0024 #include <KStandardAction>
0025 #include <QClipboard>
0026 #include <QMenu>
0027 #include <QMimeData>
0028 
0029 /**
0030  * \class AbstractAspect
0031  * \brief Base class of all persistent objects in a Project.
0032  *
0033  * Before going into the details, it's useful to understand the ideas behind the
0034  * \ref aspect "Aspect Framework".
0035  *
0036  * Aspects organize themselves into trees, where a parent takes ownership of its children. Usually,
0037  * though not necessarily, a Project instance will sit at the root of the tree (without a Project
0038  * ancestor, project() will return 0 and undo does not work). Children are organized using
0039  * addChild(), removeChild(), child(), indexOfChild() and childCount() on the parent's side as well
0040  * as the equivalent convenience methods index() and remove() on the child's side.
0041  * In contrast to the similar feature of QObject, Aspect trees are fully undo/redo aware and provide
0042  * signals around object adding/removal.
0043  *
0044  * AbstractAspect manages for every Aspect the properties #name, #comment, #captionSpec and
0045  * #creationTime. All of these translate into the caption() as described in the documentation
0046  * of setCaptionSpec().
0047  *
0048  * If an undoStack() can be found (usually it is managed by Project), changes to the properties
0049  * as well as adding/removing children support multi-level undo/redo. In order to support undo/redo
0050  * for problem-specific data in derived classes, make sure that all changes to your data are done
0051  * by handing appropriate commands to exec().
0052  */
0053 
0054 /**
0055  * \enum AbstractAspect::ChildIndexFlag
0056  * \brief Flags which control numbering scheme of children.
0057  */
0058 /**
0059  * \var AbstractAspect::IncludeHidden
0060  * \brief Include aspects marked as "hidden" in numbering or listing children.
0061  */
0062 /**
0063  * \var AbstractAspect::Recursive
0064  * \brief Recursively handle all descendents, not just immediate children.
0065  */
0066 /**
0067  * \var AbstractAspect::Compress
0068  * \brief Remove all null pointers from the result list.
0069  */
0070 
0071 ////////////////////////////////////////////////////////////////////////////////////////////////////
0072 // documentation of template and inline methods
0073 ////////////////////////////////////////////////////////////////////////////////////////////////////
0074 
0075 /**
0076  * \fn template < class T > T *AbstractAspect::ancestor() const
0077  * \brief Return the closest ancestor of class T (or NULL if none found).
0078  */
0079 
0080 /**
0081  * \fn template < class T > QVector<T*> AbstractAspect::children(const ChildIndexFlags &flags=0) const
0082  * \brief Return list of children inheriting from class T.
0083  *
0084  * Use AbstractAspect for T in order to get all children.
0085  */
0086 
0087 /**
0088  * \fn template < class T > T *AbstractAspect::child(int index, const ChildIndexFlags &flags=0) const
0089  * \brief Return child identified by (0 based) index and class.
0090  *
0091  * Identifying objects by an index is inherently error-prone and confusing,
0092  * given that the index can be based on different criteria (viz, counting
0093  * only instances of specific classes and including/excluding hidden
0094  * aspects). Therefore, it is recommended to avoid indices wherever possible
0095  * and instead refer to aspects using AbstractAspect pointers.
0096  */
0097 
0098 /**
0099  * \fn template < class T > T *AbstractAspect::child(const QString &name) const
0100  * \brief Get child by name and class.
0101  */
0102 
0103 /**
0104  * \fn template < class T > int AbstractAspect::childCount(const ChildIndexFlags &flags=0) const
0105  * \brief Return the number of child Aspects inheriting from given class.
0106  */
0107 
0108 /**
0109  * \fn template < class T > int AbstractAspect::indexOfChild(const AbstractAspect * child, const ChildIndexFlags &flags=0) const
0110  * \brief Return (0 based) index of child in the list of children inheriting from class T.
0111  */
0112 
0113 /**
0114  * \fn void AbstractAspect::aspectDescriptionAboutToChange(const AbstractAspect *aspect)
0115  * \brief Emitted before the name, comment or caption spec is changed
0116  */
0117 
0118 /**
0119  * \fn void AbstractAspect::aspectDescriptionChanged(const AbstractAspect *aspect)
0120  * \brief Emitted after the name, comment or caption spec have changed
0121  */
0122 
0123 /**
0124  * \fn void AbstractAspect::aspectAboutToBeAdded(const AbstractAspect *parent, const AbstractAspect *before, const AbstractAspect * child)
0125  * \brief Emitted before a new child is inserted
0126  */
0127 
0128 /**
0129  * \fn void AbstractAspect::aspectAdded(const AbstractAspect *aspect)
0130  * \brief Emitted after a new Aspect has been added to the tree
0131  */
0132 
0133 /**
0134  * \fn void AbstractAspect::aspectAboutToBeRemoved(const AbstractAspect *aspect)
0135  * \brief Emitted before an aspect is removed from its parent
0136  */
0137 
0138 /**
0139  * \fn void AbstractAspect::aspectRemoved(const AbstractAspect *parent, const AbstractAspect * before, const AbstractAspect * child)
0140  * \brief Emitted from the parent after removing a child
0141  */
0142 
0143 /**
0144  * \fn void AbstractAspect::aspectHiddenAboutToChange(const AbstractAspect *aspect)
0145  * \brief Emitted before the hidden attribute is changed
0146  */
0147 
0148 /**
0149  * \fn void AbstractAspect::aspectHiddenChanged(const AbstractAspect *aspect)
0150  * \brief Emitted after the hidden attribute has changed
0151  */
0152 
0153 /**
0154  * \fn void AbstractAspect::statusInfo(const QString &text)
0155  * \brief Emitted whenever some aspect in the tree wants to give status information to the user
0156  * \sa info(const QString&)
0157  */
0158 
0159 /**
0160  * \fn protected void AbstractAspect::info(const QString &text)
0161  * \brief Implementations should call this whenever status information should be given to the user.
0162  *
0163  * This will cause statusInfo() to be emitted. Typically, this will cause the specified string
0164  * to be displayed in a status bar, a log window or some similar non-blocking way so as not to
0165  * disturb the workflow.
0166  */
0167 
0168 /**
0169  * \fn protected virtual void childSelected(const AbstractAspect*) {}
0170  * \brief called when a child's child aspect was selected in the model
0171  */
0172 
0173 /**
0174  * \fn protected virtual void childDeselected()
0175  * \brief called when a child aspect was deselected in the model
0176  */
0177 
0178 /**
0179  * \fn protected virtual void childDeselected(const AbstractAspect*)
0180  * \brief called when a child's child aspect was deselected in the model
0181  */
0182 
0183 ////////////////////////////////////////////////////////////////////////////////////////////////////
0184 // start of AbstractAspect implementation
0185 ////////////////////////////////////////////////////////////////////////////////////////////////////
0186 
0187 AbstractAspect::AbstractAspect(const QString& name, AspectType type)
0188     : m_type(type)
0189     , d(new AbstractAspectPrivate(this, name)) {
0190 }
0191 
0192 AbstractAspect::~AbstractAspect() {
0193     delete d;
0194 }
0195 
0196 QString AbstractAspect::name() const {
0197     return d->m_name;
0198 }
0199 
0200 QUuid AbstractAspect::uuid() const {
0201     return d->m_uuid;
0202 }
0203 
0204 void AbstractAspect::setSuppressWriteUuid(bool suppress) {
0205     d->m_suppressWriteUuid = suppress;
0206 }
0207 
0208 /*!
0209  * \brief AbstractAspect::setName
0210  * sets the name of the abstract aspect
0211  * \param value - the new value for the name that needs to be checked and made unique if it's not the case yet
0212  * \param autoUnique - if set to \true the new name is automatically made unique, the name is not changed and \c false is returned otherwise. default is \true.
0213  * \param skipAutoUnique - if set to \true, don't check for uniqueness, the caller has to guarantee the uniqueness. default is \false.
0214  * \return returns, if the new name is valid or not
0215  */
0216 bool AbstractAspect::setName(const QString& value, NameHandling handling, QUndoCommand* /*parent*/) {
0217     if (value.isEmpty())
0218         return setName(QLatin1String("1"), handling);
0219 
0220     if (value == d->m_name)
0221         return true; // name not changed, but the name is valid
0222 
0223     QString new_name;
0224     if ((handling == NameHandling::UniqueRequired || handling == NameHandling::AutoUnique) && d->m_parent) {
0225         new_name = d->m_parent->uniqueNameFor(value);
0226 
0227         if (handling == NameHandling::UniqueRequired && new_name.compare(value) != 0) // value is not unique, so don't change name
0228             return false; // this value is used in the dock to check if the name is valid
0229 
0230         // NameHandling::Autounique
0231         if (new_name != value)
0232             info(i18n(R"(Intended name "%1" was changed to "%2" in order to avoid name collision.)", value, new_name));
0233     } else
0234         new_name = value;
0235     exec(new AspectNameChangeCmd(this->d, new_name));
0236     return true;
0237 }
0238 
0239 QString AbstractAspect::comment() const {
0240     return d->m_comment;
0241 }
0242 
0243 void AbstractAspect::setComment(const QString& value) {
0244     if (value == d->m_comment)
0245         return;
0246     exec(new PropertyChangeCommand<QString>(i18n("%1: change comment", d->m_name), &d->m_comment, value),
0247          "aspectDescriptionAboutToChange",
0248          "aspectDescriptionChanged",
0249          QArgument<const AbstractAspect*>("const AbstractAspect*", this));
0250 }
0251 
0252 void AbstractAspect::setCreationTime(const QDateTime& time) {
0253     d->m_creation_time = time;
0254 }
0255 
0256 QDateTime AbstractAspect::creationTime() const {
0257     return d->m_creation_time;
0258 }
0259 
0260 bool AbstractAspect::hidden() const {
0261     return d->m_hidden;
0262 }
0263 
0264 /**
0265  * \brief Set "hidden" property, i.e. whether to exclude this aspect from being shown in the explorer.
0266  */
0267 void AbstractAspect::setHidden(bool value) {
0268     if (value == d->m_hidden)
0269         return;
0270     d->m_hidden = value;
0271 }
0272 
0273 /**
0274  * \brief Set "fixed" property which defines whether the object can be renamed, deleted, etc.
0275  */
0276 void AbstractAspect::setFixed(bool value) {
0277     if (value == d->m_fixed)
0278         return;
0279     d->m_fixed = value;
0280 }
0281 
0282 bool AbstractAspect::isFixed() const {
0283     return d->m_fixed;
0284 }
0285 
0286 void AbstractAspect::setMoved(bool value) {
0287     d->m_moved = value;
0288 }
0289 
0290 bool AbstractAspect::isMoved() const {
0291     return d->m_moved;
0292 }
0293 
0294 void AbstractAspect::setIsLoading(bool load) {
0295     d->m_isLoading = load;
0296 }
0297 
0298 bool AbstractAspect::isLoading() const {
0299     return d->m_isLoading;
0300 }
0301 
0302 /**
0303  * \brief Return an icon to be used for decorating my views.
0304  */
0305 QIcon AbstractAspect::icon() const {
0306     return {};
0307 }
0308 
0309 /**
0310  * \brief Return a new context menu.
0311  *
0312  * The caller takes ownership of the menu.
0313  */
0314 QMenu* AbstractAspect::createContextMenu() {
0315     QMenu* menu = new QMenu();
0316     menu->addSection(this->name());
0317 
0318     // TODO: activate this again when the functionality is implemented
0319     //  menu->addAction( KStandardAction::cut(this) );
0320 
0321     QAction* actionDuplicate = nullptr;
0322     if (!isFixed() && m_type != AspectType::Project && m_type != AspectType::CantorWorksheet) {
0323         // copy action:
0324         // don't allow to copy fixed aspects
0325         auto* action = KStandardAction::copy(this);
0326         connect(action, &QAction::triggered, this, &AbstractAspect::copy);
0327         menu->addAction(action);
0328 
0329         // duplicate action:
0330         // don't allow to duplicate legends in the plots
0331         if (m_type != AspectType::CartesianPlotLegend) {
0332             actionDuplicate = new QAction(QIcon::fromTheme(QLatin1String("edit-copy")), i18n("Duplicate Here"), this);
0333             actionDuplicate->setShortcut(Qt::CTRL | Qt::Key_D);
0334             connect(actionDuplicate, &QAction::triggered, this, &AbstractAspect::duplicate);
0335             menu->addAction(actionDuplicate);
0336         }
0337     }
0338 
0339     // paste action:
0340     // determine the aspect type of the content available in the clipboard
0341     // and enable the paste entry if the content is labplot specific
0342     // and if it can be pasted into the current aspect
0343     QString name;
0344     auto t = clipboardAspectType(name);
0345     if (t != AspectType::AbstractAspect && pasteTypes().indexOf(t) != -1) {
0346         auto* action = KStandardAction::paste(this);
0347         action->setText(i18n("Paste '%1'", name));
0348         if (actionDuplicate)
0349             menu->insertAction(actionDuplicate, action);
0350         else
0351             menu->addAction(action);
0352         connect(action, &QAction::triggered, this, &AbstractAspect::paste);
0353     }
0354     menu->addSeparator();
0355 
0356     // action to create data spreadsheet based on the results of the calculations for types that support it
0357     QAction* actionDataSpreadsheet = new QAction(QIcon::fromTheme(QLatin1String("labplot-spreadsheet")), i18n("Create Data Spreadsheet"), this);
0358 
0359     // handle types that support it
0360     bool dataAvailable = false;
0361     if (const auto* analysisCurve = dynamic_cast<XYAnalysisCurve*>(this)) {
0362         if (analysisCurve->resultAvailable()) {
0363             connect(actionDataSpreadsheet, &QAction::triggered, static_cast<XYAnalysisCurve*>(this), &XYAnalysisCurve::createDataSpreadsheet);
0364             dataAvailable = true;
0365         }
0366     } else if (const auto* equationCurve = dynamic_cast<XYEquationCurve*>(this)) {
0367         if (equationCurve->dataAvailable()) {
0368             connect(actionDataSpreadsheet, &QAction::triggered, static_cast<XYEquationCurve*>(this), &XYEquationCurve::createDataSpreadsheet);
0369             dataAvailable = true;
0370         }
0371     } else if (const auto* histogram = dynamic_cast<Histogram*>(this)) {
0372         if (histogram->bins()) {
0373             connect(actionDataSpreadsheet, &QAction::triggered, static_cast<Histogram*>(this), &Histogram::createDataSpreadsheet);
0374             dataAvailable = true;
0375         }
0376     } else if (const auto* boxPlot = dynamic_cast<BoxPlot*>(this)) {
0377         if (!boxPlot->dataColumns().isEmpty()) {
0378             connect(actionDataSpreadsheet, &QAction::triggered, static_cast<BoxPlot*>(this), &BoxPlot::createDataSpreadsheet);
0379             dataAvailable = true;
0380         }
0381     }
0382 
0383     if (dataAvailable) {
0384         menu->addAction(actionDataSpreadsheet);
0385         menu->addSeparator();
0386     }
0387 
0388     // don't allow to rename and delete fixed objects and
0389     //  - columns in live-data source
0390     //  - Mqtt subscriptions
0391     //  - Mqtt topics
0392     //  - Columns in Mqtt topics
0393     // TODO: make also these objects fixed and remove this additional handling for them here
0394     bool disabled = isFixed() || (m_type == AspectType::Column && parentAspect()->type() == AspectType::LiveDataSource)
0395 #ifdef HAVE_MQTT
0396         || (m_type == AspectType::MQTTSubscription)
0397         || (m_type == AspectType::MQTTTopic) | (m_type == AspectType::Column && parentAspect()->type() == AspectType::MQTTTopic)
0398 #endif
0399         ;
0400 
0401     if (disabled)
0402         return menu;
0403 
0404     // rename and delete actions:
0405     menu->addAction(QIcon::fromTheme(QLatin1String("edit-rename")), i18n("Rename"), this, &AbstractAspect::renameRequested);
0406     if (m_type != AspectType::Project)
0407         menu->addAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Delete"), this, QOverload<>::of(&AbstractAspect::remove));
0408 
0409     // move up and down actions:
0410     // don't shown them for worksheet elements since they implement their own "Drawing order" menu
0411     if (!dynamic_cast<WorksheetElement*>(this) && this != project()) {
0412         const auto* parent = parentAspect();
0413         int count = parent->childCount<AbstractAspect>();
0414         if (count > 1) {
0415             auto* moveMenu = new QMenu(i18n("Move"), menu);
0416             moveMenu->setIcon(QIcon::fromTheme(QStringLiteral("layer-bottom")));
0417             if (parent->indexOfChild<AbstractAspect>(this) != 0)
0418                 moveMenu->addAction(QIcon::fromTheme(QStringLiteral("draw-arrow-up")), i18n("Up"), this, &AbstractAspect::moveUp);
0419 
0420             if (parent->indexOfChild<AbstractAspect>(this) != count - 1)
0421                 moveMenu->addAction(QIcon::fromTheme(QStringLiteral("draw-arrow-down")), i18n("Down"), this, &AbstractAspect::moveDown);
0422             menu->addSeparator();
0423             menu->addMenu(moveMenu);
0424         }
0425     }
0426 
0427     return menu;
0428 }
0429 
0430 AspectType AbstractAspect::type() const {
0431     return m_type;
0432 }
0433 
0434 bool AbstractAspect::inherits(AspectType type) const {
0435     return (static_cast<quint64>(m_type) & static_cast<quint64>(type)) == static_cast<quint64>(type);
0436 }
0437 
0438 /**
0439  * \brief In the parent-child hierarchy, return the first parent of type \param type or null pointer if there is none.
0440  */
0441 AbstractAspect* AbstractAspect::parent(AspectType type) const {
0442     AbstractAspect* parent = parentAspect();
0443     if (!parent)
0444         return nullptr;
0445 
0446     if (parent->inherits(type))
0447         return parent;
0448 
0449     return parent->parent(type);
0450 }
0451 
0452 /**
0453  * \brief Return my parent Aspect or 0 if I currently don't have one.
0454  */
0455 AbstractAspect* AbstractAspect::parentAspect() const {
0456     return d->m_parent;
0457 }
0458 
0459 void AbstractAspect::setParentAspect(AbstractAspect* parent) {
0460     d->m_parent = parent;
0461 }
0462 
0463 /**
0464  * \brief Return the folder the Aspect is contained in or 0 if there is none.
0465  *
0466  * The returned folder may be the aspect itself if it inherits Folder.
0467  */
0468 Folder* AbstractAspect::folder() {
0469     if (inherits(AspectType::Folder))
0470         return static_cast<class Folder*>(this);
0471     AbstractAspect* parent_aspect = parentAspect();
0472     while (parent_aspect && !parent_aspect->inherits(AspectType::Folder))
0473         parent_aspect = parent_aspect->parentAspect();
0474     return static_cast<class Folder*>(parent_aspect);
0475 }
0476 
0477 /**
0478  * \brief Return whether the there is a path upwards to the given aspect
0479  *
0480  * This also returns true if other==this.
0481  */
0482 bool AbstractAspect::isDescendantOf(AbstractAspect* other) {
0483     if (other == this)
0484         return true;
0485     AbstractAspect* parent_aspect = parentAspect();
0486     while (parent_aspect) {
0487         if (parent_aspect == other)
0488             return true;
0489         parent_aspect = parent_aspect->parentAspect();
0490     }
0491     return false;
0492 }
0493 
0494 /**
0495  * \brief Return the Project this Aspect belongs to, or 0 if it is currently not part of one.
0496  */
0497 Project* AbstractAspect::project() {
0498     return parentAspect() ? parentAspect()->project() : nullptr;
0499 }
0500 
0501 /**
0502  * \brief Return the path that leads from the top-most Aspect (usually a Project) to me.
0503  */
0504 QString AbstractAspect::path() const {
0505     return parentAspect() ? parentAspect()->path() + QLatin1Char('/') + name() : QString();
0506 }
0507 
0508 /**
0509  * \brief Add the given Aspect to my list of children.
0510  */
0511 void AbstractAspect::addChild(AbstractAspect* child, QUndoCommand* parent) {
0512     Q_CHECK_PTR(child);
0513 
0514     const QString new_name = uniqueNameFor(child->name());
0515     bool execute = false;
0516     if (!parent) {
0517         execute = true;
0518         parent = new QUndoCommand(i18n("%1: add %2", name(), new_name));
0519     }
0520     if (new_name != child->name()) {
0521         info(i18n(R"(Renaming "%1" to "%2" in order to avoid name collision.)", child->name(), new_name));
0522         child->setName(new_name, NameHandling::AutoUnique, parent);
0523     }
0524 
0525     new AspectChildAddCmd(d, child, d->m_children.count(), parent);
0526 
0527     if (execute)
0528         exec(parent);
0529 }
0530 
0531 /**
0532  * \brief Add the given Aspect to my list of children without any checks and without putting this step onto the undo-stack
0533  */
0534 void AbstractAspect::addChildFast(AbstractAspect* child) {
0535     Q_EMIT childAspectAboutToBeAdded(this, nullptr, child); // TODO: before-pointer is 0 here, also in the commands classes. why?
0536     d->insertChild(d->m_children.count(), child);
0537     child->finalizeAdd();
0538     // PERFTRACE(Q_FUNC_INFO);
0539     Q_EMIT childAspectAdded(child);
0540     // print_callstack();
0541 }
0542 
0543 /**
0544  * \brief Insert the given Aspect at a specific position in my list of children.
0545  */
0546 void AbstractAspect::insertChildBefore(AbstractAspect* child, AbstractAspect* before, QUndoCommand* parent) {
0547     insertChild(child, d->indexOfChild(before), parent);
0548 }
0549 
0550 void AbstractAspect::insertChild(AbstractAspect* child, int index, QUndoCommand* parent) {
0551     Q_CHECK_PTR(child);
0552 
0553     if (index == -1)
0554         index = d->m_children.count();
0555 
0556     QString new_name = uniqueNameFor(child->name());
0557     bool execute = false;
0558     if (!parent) {
0559         execute = true;
0560         const auto* before = this->child<AbstractAspect>(index);
0561         parent =
0562             new QUndoCommand(before ? i18n("%1: insert %2 before %3", name(), new_name, before->name()) : i18n("%1: insert %2 before end", name(), new_name));
0563     }
0564 
0565     if (new_name != child->name()) {
0566         info(i18n(R"(Renaming "%1" to "%2" in order to avoid name collision.)", child->name(), new_name));
0567         child->setName(new_name, NameHandling::AutoUnique, parent);
0568     }
0569 
0570     new AspectChildAddCmd(d, child, index, parent);
0571 
0572     if (execute)
0573         exec(parent);
0574 }
0575 
0576 /**
0577  * \brief Insert the given Aspect at a specific position in my list of children.without any checks and without putting this step onto the undo-stack
0578  */
0579 void AbstractAspect::insertChildBeforeFast(AbstractAspect* child, AbstractAspect* before) {
0580     connect(child, &AbstractAspect::selected, this, &AbstractAspect::childSelected);
0581     connect(child, &AbstractAspect::deselected, this, &AbstractAspect::childDeselected);
0582 
0583     int index = d->indexOfChild(before);
0584     if (index == -1)
0585         index = d->m_children.count();
0586 
0587     Q_EMIT childAspectAboutToBeAdded(this, nullptr, child);
0588     d->insertChild(index, child);
0589     child->finalizeAdd();
0590     Q_EMIT childAspectAdded(child);
0591 }
0592 
0593 /**
0594  * \brief Remove the given Aspect from my list of children.
0595  *
0596  * The ownership of the child is transferred to the undo command,
0597  * i.e., the aspect is deleted by the undo command.
0598  * \sa reparent()
0599  */
0600 void AbstractAspect::removeChild(AbstractAspect* child, QUndoCommand* parent) {
0601     // QDEBUG(Q_FUNC_INFO << ", CHILD =" << child << ", PARENT =" << child->parentAspect())
0602 
0603     bool execute = false;
0604     if (!parent) {
0605         execute = true;
0606         parent = new QUndoCommand(i18n("%1: remove %2", name(), child->name()));
0607     }
0608 
0609     new AspectChildRemoveCmd(d, child, parent);
0610 
0611     if (execute)
0612         exec(parent);
0613 }
0614 
0615 /**
0616  * \brief Remove all child Aspects.
0617  */
0618 void AbstractAspect::removeAllChildren() {
0619     beginMacro(i18n("%1: remove all children", name()));
0620 
0621     QVector<AbstractAspect*> children_list = children();
0622     QVector<AbstractAspect*>::const_iterator i = children_list.constBegin();
0623     AbstractAspect *current = nullptr, *nextSibling = nullptr;
0624     if (i != children_list.constEnd()) {
0625         current = *i;
0626         if (++i != children_list.constEnd())
0627             nextSibling = *i;
0628     }
0629 
0630     while (current) {
0631         Q_EMIT childAspectAboutToBeRemoved(current);
0632         exec(new AspectChildRemoveCmd(d, current));
0633         Q_EMIT childAspectRemoved(this, nextSibling, current);
0634 
0635         current = nextSibling;
0636         if (i != children_list.constEnd() && ++i != children_list.constEnd())
0637             nextSibling = *i;
0638         else
0639             nextSibling = nullptr;
0640     }
0641 
0642     endMacro();
0643 }
0644 
0645 /**
0646  * \brief Move a child to another parent aspect and transfer ownership.
0647  */
0648 void AbstractAspect::reparent(AbstractAspect* newParent, int newIndex) {
0649     Q_ASSERT(parentAspect());
0650     Q_ASSERT(newParent);
0651     int max_index = newParent->childCount<AbstractAspect>(ChildIndexFlag::IncludeHidden);
0652     if (newIndex == -1)
0653         newIndex = max_index;
0654     Q_ASSERT(newIndex >= 0 && newIndex <= max_index);
0655 
0656     //  AbstractAspect* old_parent = parentAspect();
0657     //  int old_index = old_parent->indexOfChild<AbstractAspect>(this, IncludeHidden);
0658     //  auto* old_sibling = old_parent->child<AbstractAspect>(old_index+1, IncludeHidden);
0659     //  auto* new_sibling = newParent->child<AbstractAspect>(newIndex, IncludeHidden);
0660 
0661     //  Q_EMIT newParent->aspectAboutToBeAdded(newParent, new_sibling, this);
0662     exec(new AspectChildReparentCmd(parentAspect()->d, newParent->d, this, newIndex));
0663     //  Q_EMIT old_parent->aspectRemoved(old_parent, old_sibling, this);
0664 }
0665 
0666 QVector<AbstractAspect*> AbstractAspect::children(AspectType type, ChildIndexFlags flags) const {
0667     QVector<AbstractAspect*> result;
0668     for (auto* child : children()) {
0669         if (flags & ChildIndexFlag::IncludeHidden || !child->hidden()) {
0670             if (child->inherits(type))
0671                 result << child;
0672 
0673             if (flags & ChildIndexFlag::Recursive)
0674                 result << child->children(type, flags);
0675         }
0676     }
0677 
0678     return result;
0679 }
0680 
0681 const QVector<AbstractAspect*>& AbstractAspect::children() const {
0682     Q_ASSERT(d);
0683     return d->m_children;
0684 }
0685 
0686 /**
0687  * \brief Remove me from my parent's list of children.
0688  */
0689 void AbstractAspect::remove(QUndoCommand* parent) {
0690     if (parentAspect())
0691         parentAspect()->removeChild(this, parent);
0692 }
0693 
0694 void AbstractAspect::remove() {
0695     remove(nullptr);
0696 }
0697 
0698 void AbstractAspect::moveUp() {
0699     auto* parent = parentAspect();
0700     int index = parent->indexOfChild<AbstractAspect>(this);
0701     auto* sibling = parent->child<AbstractAspect>(index - 1);
0702     beginMacro(i18n("%1: move up", name()));
0703     setMoved(true);
0704     remove();
0705     parent->insertChildBefore(this, sibling);
0706     setMoved(false);
0707     endMacro();
0708 }
0709 
0710 void AbstractAspect::moveDown() {
0711     auto* parent = parentAspect();
0712     int index = parent->indexOfChild<AbstractAspect>(this);
0713     auto* sibling = parent->child<AbstractAspect>(index + 2);
0714     beginMacro(i18n("%1: move down", name()));
0715     setMoved(true);
0716     remove();
0717     parent->insertChildBefore(this, sibling);
0718     setMoved(false);
0719     endMacro();
0720 }
0721 
0722 /*!
0723  * returns the list of all parent aspects (folders and sub-folders)
0724  */
0725 QVector<AbstractAspect*> AbstractAspect::dependsOn() const {
0726     QVector<AbstractAspect*> aspects;
0727     if (parentAspect())
0728         aspects << parentAspect() << parentAspect()->dependsOn();
0729 
0730     return aspects;
0731 }
0732 
0733 /*!
0734  * return the list of all aspect types that can be copy&pasted into the current aspect.
0735  * returns an empty list on default, needs to be re-implemented in all derived classes
0736  * that want to allow other aspects to be pasted into.
0737  */
0738 QVector<AspectType> AbstractAspect::pasteTypes() const {
0739     return {};
0740 }
0741 
0742 void AbstractAspect::setPasted(bool pasted) {
0743     d->m_pasted = pasted;
0744 }
0745 
0746 bool AbstractAspect::pasted() const {
0747     return d->m_pasted;
0748 }
0749 
0750 /*!
0751  * copies the aspect to the clipboard. The standard XML-serialization
0752  * via AbstractAspect::load() is used.
0753  */
0754 void AbstractAspect::copy() {
0755     QString output;
0756     QXmlStreamWriter writer(&output);
0757     writer.writeStartDocument();
0758 
0759     // add LabPlot's copy&paste "identifier"
0760     writer.writeDTD(QLatin1String("<!DOCTYPE LabPlotCopyPasteXML>"));
0761     writer.writeStartElement(QStringLiteral("copy_content")); // root element
0762 
0763     // write the type of the copied aspect
0764     writer.writeStartElement(QStringLiteral("type"));
0765     writer.writeAttribute(QStringLiteral("value"), QString::number(static_cast<int>(m_type)));
0766     writer.writeEndElement();
0767 
0768     setSuppressWriteUuid(true);
0769     const auto& children = this->children(AspectType::AbstractAspect, {ChildIndexFlag::IncludeHidden, ChildIndexFlag::Recursive});
0770     for (const auto& child : children)
0771         child->setSuppressWriteUuid(true);
0772 
0773     // write the aspect itself
0774     save(&writer);
0775 
0776     for (const auto& child : children)
0777         child->setSuppressWriteUuid(false);
0778     setSuppressWriteUuid(false);
0779 
0780     writer.writeEndElement(); // end the root-element
0781     writer.writeEndDocument();
0782     QApplication::clipboard()->setText(output);
0783 }
0784 
0785 void AbstractAspect::duplicate() {
0786     copy();
0787     parentAspect()->paste(true);
0788 }
0789 
0790 /*!
0791  * in case the clipboard containts a LabPlot's specific copy&paste content,
0792  * this function deserializes the XML string and adds the created aspect as
0793  * a child to the current aspect ("paste").
0794  */
0795 void AbstractAspect::paste(bool duplicate) {
0796     const QClipboard* clipboard = QApplication::clipboard();
0797     const QMimeData* mimeData = clipboard->mimeData();
0798     if (!mimeData->hasText())
0799         return;
0800 
0801     const QString& xml = clipboard->text();
0802     if (!xml.startsWith(QLatin1String("<?xml version=\"1.0\"?><!DOCTYPE LabPlotCopyPasteXML>")))
0803         return;
0804 
0805     WAIT_CURSOR;
0806     AbstractAspect* aspect = nullptr;
0807     XmlStreamReader reader(xml);
0808     while (!reader.atEnd()) {
0809         reader.readNext();
0810 
0811         if (!reader.isStartElement())
0812             continue;
0813 
0814         if (reader.name() == QLatin1String("type")) {
0815             auto attribs = reader.attributes();
0816             auto type = static_cast<AspectType>(attribs.value(QLatin1String("value")).toInt());
0817             if (type != AspectType::AbstractAspect)
0818                 aspect = AspectFactory::createAspect(type, this);
0819         } else {
0820             if (aspect) {
0821                 aspect->setPasted(true);
0822                 aspect->load(&reader, false);
0823                 break;
0824             }
0825         }
0826     }
0827 
0828     if (aspect) {
0829         if (!duplicate)
0830             beginMacro(i18n("%1: pasted '%2'", name(), aspect->name()));
0831         else {
0832             beginMacro(i18n("%1: duplicated '%2'", name(), aspect->name()));
0833             aspect->setName(i18n("Copy of '%1'", aspect->name()));
0834         }
0835 
0836         if (aspect->type() != AspectType::CartesianPlotLegend)
0837             addChild(aspect);
0838         else {
0839             // spectial handling for the legend since only one single
0840             // legend object is allowed per plot
0841             auto* plot = static_cast<CartesianPlot*>(this);
0842             auto* legend = static_cast<CartesianPlotLegend*>(aspect);
0843             plot->addLegend(legend);
0844         }
0845 
0846         project()->restorePointers(aspect);
0847         project()->retransformElements(aspect);
0848         aspect->setPasted(false);
0849         endMacro();
0850     }
0851     RESET_CURSOR;
0852 }
0853 
0854 /*!
0855  * helper function determening whether the current content of the clipboard
0856  * contants the labplot specific copy&paste XML content. In case a valid content
0857  * is available, the aspect type of the object to be pasted is returned.
0858  * AspectType::AbstractAspect is returned otherwise.
0859  */
0860 AspectType AbstractAspect::clipboardAspectType(QString& name) {
0861     AspectType type = AspectType::AbstractAspect;
0862     const QClipboard* clipboard = QApplication::clipboard();
0863     const QMimeData* mimeData = clipboard->mimeData();
0864     if (!mimeData->hasText())
0865         return type;
0866 
0867     const QString& xml = clipboard->text();
0868     if (!xml.startsWith(QLatin1String("<?xml version=\"1.0\"?><!DOCTYPE LabPlotCopyPasteXML>")))
0869         return type;
0870 
0871     XmlStreamReader reader(xml);
0872     bool typeFound = false;
0873     while (!reader.atEnd()) {
0874         reader.readNext();
0875         if (reader.isStartElement()) {
0876             auto attribs = reader.attributes();
0877             if (reader.name() == QLatin1String("type")) {
0878                 type = static_cast<AspectType>(attribs.value(QLatin1String("value")).toInt());
0879                 typeFound = true;
0880             } else {
0881                 name = attribs.value(QLatin1String("name")).toString();
0882                 if (typeFound)
0883                     break;
0884             }
0885         }
0886     }
0887 
0888     return type;
0889 }
0890 
0891 bool AbstractAspect::isDraggable() const {
0892     return false;
0893 }
0894 
0895 QVector<AspectType> AbstractAspect::dropableOn() const {
0896     return {};
0897 }
0898 
0899 ////////////////////////////////////////////////////////////////////////////////////////////////////
0900 //! \name serialize/deserialize
0901 //@{
0902 ////////////////////////////////////////////////////////////////////////////////////////////////////
0903 
0904 /**
0905  * \fn virtual void AbstractAspect::save(QXmlStreamWriter *) const
0906  * \brief Save as XML
0907  */
0908 
0909 /**
0910  * \fn virtual bool AbstractAspect::load(XmlStreamReader *)
0911  * \brief Load from XML
0912  *
0913  * XmlStreamReader supports errors as well as warnings. If only
0914  * warnings (non-critical errors) occur, this function must return
0915  * the reader at the end element corresponding to the current
0916  * element at the time the function was called.
0917  *
0918  * This function is normally intended to be called directly
0919  * after the ctor. If you want to call load on an aspect that
0920  * has been altered, you must make sure beforehand that
0921  * it is in the same state as after creation, e.g., remove
0922  * all its child aspects.
0923  *
0924  * \return false on error
0925  */
0926 
0927 /**
0928  * \brief Save the comment to XML
0929  */
0930 void AbstractAspect::writeCommentElement(QXmlStreamWriter* writer) const {
0931     writer->writeStartElement(QLatin1String("comment"));
0932     writer->writeCharacters(comment());
0933     writer->writeEndElement();
0934 }
0935 
0936 /**
0937  * \brief Load comment from an XML element
0938  */
0939 bool AbstractAspect::readCommentElement(XmlStreamReader* reader) {
0940     d->m_comment = reader->readElementText();
0941     return true;
0942 }
0943 
0944 /**
0945  * \brief Save name and creation time to XML
0946  */
0947 void AbstractAspect::writeBasicAttributes(QXmlStreamWriter* writer) const {
0948     writer->writeAttribute(QLatin1String("creation_time"), creationTime().toString(QLatin1String("yyyy-dd-MM hh:mm:ss:zzz")));
0949     writer->writeAttribute(QLatin1String("name"), name());
0950     if (!d->m_suppressWriteUuid)
0951         writer->writeAttribute(QLatin1String("uuid"), uuid().toString());
0952 }
0953 
0954 /**
0955  * \brief Load name and creation time from XML
0956  *
0957  * \return false on error
0958  */
0959 bool AbstractAspect::readBasicAttributes(XmlStreamReader* reader) {
0960     const QXmlStreamAttributes& attribs = reader->attributes();
0961 
0962     // name
0963     QString str = attribs.value(QLatin1String("name")).toString();
0964     if (str.isEmpty())
0965         reader->raiseWarning(i18n("Attribute 'name' is missing or empty."));
0966 
0967     d->m_name = str;
0968 
0969     // creation time
0970     str = attribs.value(QLatin1String("creation_time")).toString();
0971     if (str.isEmpty()) {
0972         reader->raiseWarning(i18n("Invalid creation time for '%1'. Using current time.", name()));
0973         d->m_creation_time = QDateTime::currentDateTime();
0974     } else {
0975         QDateTime creation_time = QDateTime::fromString(str, QLatin1String("yyyy-dd-MM hh:mm:ss:zzz"));
0976         if (creation_time.isValid())
0977             d->m_creation_time = creation_time;
0978         else
0979             d->m_creation_time = QDateTime::currentDateTime();
0980     }
0981 
0982     str = attribs.value(QLatin1String("uuid")).toString();
0983     if (!str.isEmpty()) {
0984         d->m_uuid = QUuid(str);
0985     }
0986     return true;
0987 }
0988 
0989 ////////////////////////////////////////////////////////////////////////////////////////////////////
0990 //@}
0991 ////////////////////////////////////////////////////////////////////////////////////////////////////
0992 
0993 ////////////////////////////////////////////////////////////////////////////////////////////////////
0994 //! \name undo related
0995 //@{
0996 ////////////////////////////////////////////////////////////////////////////////////////////////////
0997 void AbstractAspect::setUndoAware(bool b) {
0998     d->m_undoAware = b;
0999 }
1000 
1001 /**
1002  * \brief Return the undo stack of the Project, or 0 if this Aspect is not part of a Project.
1003  *
1004  * It's also possible to construct undo-enabled Aspect trees without Project.
1005  * The only requirement is that the root Aspect reimplements undoStack() to get the
1006  * undo stack from somewhere (the default implementation just delegates to parentAspect()).
1007  */
1008 QUndoStack* AbstractAspect::undoStack() const {
1009     return parentAspect() ? parentAspect()->undoStack() : nullptr;
1010 }
1011 
1012 /**
1013  * \brief Execute the given command, pushing it on the undoStack() if available.
1014  */
1015 void AbstractAspect::exec(QUndoCommand* cmd) {
1016     Q_CHECK_PTR(cmd);
1017     if (d->m_undoAware) {
1018         QUndoStack* stack = undoStack();
1019         if (stack)
1020             stack->push(cmd);
1021         else {
1022             cmd->redo();
1023             delete cmd;
1024         }
1025 
1026         if (project())
1027             project()->setChanged(true);
1028     } else {
1029         cmd->redo();
1030         delete cmd;
1031     }
1032 }
1033 
1034 /**
1035  * \brief Execute command and arrange for signals to be sent before/after it is redone or undone.
1036  *
1037  * \arg \c command The command to be executed.
1038  * \arg \c preChangeSignal The name of the signal to be triggered before re-/undoing the command.
1039  * \arg \c postChangeSignal The name of the signal to be triggered after re-/undoing the command.
1040  * \arg <tt>val0,val1,val2,val3</tt> Arguments to the signals; to be given using Q_ARG().
1041  *
1042  * Signal arguments are given using the macro Q_ARG(typename, const value&). Since
1043  * the variable given as "value" will likely be out of scope when the signals are emitted, a copy
1044  * needs to be created. This uses QMetaType, which means that (non-trivial) argument types need to
1045  * be registered using qRegisterMetaType() before giving them to exec() (in particular, this also
1046  * goes for pointers to custom data types).
1047  *
1048  * \sa SignallingUndoCommand
1049  */
1050 void AbstractAspect::exec(QUndoCommand* command,
1051                           const char* preChangeSignal,
1052                           const char* postChangeSignal,
1053                           QGenericArgument val0,
1054                           QGenericArgument val1,
1055                           QGenericArgument val2,
1056                           QGenericArgument val3) {
1057     beginMacro(command->text());
1058     exec(new SignallingUndoCommand(QLatin1String("change signal"), this, preChangeSignal, postChangeSignal, val0, val1, val2, val3));
1059     exec(command);
1060     exec(new SignallingUndoCommand(QLatin1String("change signal"), this, postChangeSignal, preChangeSignal, val0, val1, val2, val3));
1061     endMacro();
1062 }
1063 
1064 /**
1065  * \brief Begin an undo stack macro (series of commands)
1066  */
1067 void AbstractAspect::beginMacro(const QString& text) {
1068     if (!d->m_undoAware)
1069         return;
1070 
1071     QUndoStack* stack = undoStack();
1072     if (stack)
1073         stack->beginMacro(text);
1074 }
1075 
1076 /**
1077  * \brief End the current undo stack macro
1078  */
1079 void AbstractAspect::endMacro() {
1080     if (!d->m_undoAware)
1081         return;
1082 
1083     QUndoStack* stack = undoStack();
1084     if (stack)
1085         stack->endMacro();
1086 }
1087 
1088 ////////////////////////////////////////////////////////////////////////////////////////////////////
1089 //@}
1090 ////////////////////////////////////////////////////////////////////////////////////////////////////
1091 
1092 /*!
1093  * this function is called when the selection in ProjectExplorer was changed.
1094  * forwards the selection/deselection to the parent aspect via emitting a signal.
1095  */
1096 void AbstractAspect::setSelected(bool s) {
1097     if (s)
1098         Q_EMIT selected(this);
1099     else
1100         Q_EMIT deselected(this);
1101 }
1102 
1103 void AbstractAspect::childSelected(const AbstractAspect* aspect) {
1104     // forward the signal to the highest possible level in the parent-child hierarchy
1105     // e.g. axis of a plot was selected. Don't include parent aspects here that do not
1106     // need to react on the selection of children:
1107     //* Folder
1108     //* XYFitCurve with the child column for calculated residuals
1109     //* XYSmouthCurve with the child column for calculated rough values
1110     //* CantorWorksheet with the child columns for CAS variables
1111     AbstractAspect* parent = this->parentAspect();
1112     if (parent && !parent->inherits(AspectType::Folder) && !parent->inherits(AspectType::XYFitCurve) && !parent->inherits(AspectType::XYSmoothCurve)
1113         && !parent->inherits(AspectType::CantorWorksheet))
1114         Q_EMIT this->selected(aspect);
1115 }
1116 
1117 void AbstractAspect::childDeselected(const AbstractAspect* aspect) {
1118     // forward the signal to the highest possible level in the parent-child hierarchy
1119     // e.g. axis of a plot was selected. Don't include parent aspects here that do not
1120     // need to react on the deselection of children:
1121     //* Folder
1122     //* XYFitCurve with the child column for calculated residuals
1123     //* XYSmouthCurve with the child column for calculated rough values
1124     //* CantorWorksheet with the child columns for CAS variables
1125     AbstractAspect* parent = this->parentAspect();
1126     if (parent && !parent->inherits(AspectType::Folder) && !parent->inherits(AspectType::XYFitCurve) && !parent->inherits(AspectType::XYSmoothCurve)
1127         && !parent->inherits(AspectType::CantorWorksheet))
1128         Q_EMIT this->deselected(aspect);
1129 }
1130 
1131 /**
1132  * \brief Make the specified name unique among my children by incrementing a trailing number.
1133  */
1134 QString AbstractAspect::uniqueNameFor(const QString& name) const {
1135     QStringList names;
1136     for (auto* child : children())
1137         names << child->name();
1138 
1139     return uniqueNameFor(name, names);
1140 }
1141 
1142 /*!
1143  * static helper function that makes the string \c name unique and avoids duplicates
1144  * in the list of strings \c names.
1145  */
1146 QString AbstractAspect::uniqueNameFor(const QString& name, const QStringList& names) {
1147     if (!names.contains(name))
1148         return name;
1149 
1150     QString base = name;
1151     int last_non_digit;
1152     for (last_non_digit = base.size() - 1; last_non_digit >= 0; --last_non_digit) {
1153         if (base[last_non_digit].category() == QChar::Number_DecimalDigit) {
1154             base.chop(1);
1155         } else {
1156             if (base[last_non_digit].category() == QChar::Separator_Space)
1157                 break;
1158             else {
1159                 // non-digit character is found and it's not the separator,
1160                 // the string either doesn't have any digits at all or is of
1161                 // the form "data_2020.06". In this case we don't use anything
1162                 // from the original name to increment the number
1163                 last_non_digit = 0;
1164                 base = name;
1165                 break;
1166             }
1167         }
1168     }
1169 
1170     if (last_non_digit >= 0 && base[last_non_digit].category() != QChar::Separator_Space)
1171         base.append(QLatin1Char(' '));
1172 
1173     int new_nr = QStringView(name).right(name.size() - base.size()).toInt();
1174     QString new_name;
1175     do
1176         new_name = base + QString::number(++new_nr);
1177     while (names.contains(new_name));
1178 
1179     return new_name;
1180 }
1181 
1182 void AbstractAspect::connectChild(AbstractAspect* child) {
1183     connect(child, &AbstractAspect::aspectDescriptionAboutToChange, this, &AbstractAspect::aspectDescriptionAboutToChange);
1184     connect(child, &AbstractAspect::aspectDescriptionChanged, this, &AbstractAspect::aspectDescriptionChanged);
1185     connect(child,
1186             QOverload<const AbstractAspect*, const AbstractAspect*, const AbstractAspect*>::of(&AbstractAspect::childAspectAboutToBeAdded),
1187             this,
1188             QOverload<const AbstractAspect*, const AbstractAspect*, const AbstractAspect*>::of(&AbstractAspect::childAspectAboutToBeAdded));
1189     connect(child, &AbstractAspect::childAspectAdded, this, &AbstractAspect::childAspectAdded);
1190     connect(child,
1191             QOverload<const AbstractAspect*>::of(&AbstractAspect::childAspectAboutToBeRemoved),
1192             this,
1193             QOverload<const AbstractAspect*>::of(&AbstractAspect::childAspectAboutToBeRemoved));
1194     connect(child, &AbstractAspect::childAspectRemoved, this, &AbstractAspect::childAspectRemoved);
1195     connect(child, &AbstractAspect::aspectHiddenAboutToChange, this, &AbstractAspect::aspectHiddenAboutToChange);
1196     connect(child, &AbstractAspect::aspectHiddenChanged, this, &AbstractAspect::aspectHiddenChanged);
1197     connect(child, &AbstractAspect::statusInfo, this, &AbstractAspect::statusInfo);
1198 
1199     connect(child, &AbstractAspect::selected, this, &AbstractAspect::childSelected);
1200     connect(child, &AbstractAspect::deselected, this, &AbstractAspect::childDeselected);
1201 }
1202 
1203 // ##############################################################################
1204 // ######################  Private implementation ###############################
1205 // ##############################################################################
1206 AbstractAspectPrivate::AbstractAspectPrivate(AbstractAspect* owner, const QString& name)
1207     : m_name(name.isEmpty() ? QLatin1String("1") : name)
1208     , q(owner) {
1209     m_creation_time = QDateTime::currentDateTime();
1210 }
1211 
1212 AbstractAspectPrivate::~AbstractAspectPrivate() {
1213     for (auto* child : qAsConst(m_children))
1214         delete child;
1215 }
1216 
1217 void AbstractAspectPrivate::insertChild(int index, AbstractAspect* child) {
1218     m_children.insert(index, child);
1219 
1220     // Always remove from any previous parent before adding to a new one!
1221     // Can't handle this case here since two undo commands have to be created.
1222     Q_ASSERT(child->parentAspect() == nullptr);
1223     child->setParentAspect(q);
1224     q->connectChild(child);
1225 }
1226 
1227 int AbstractAspectPrivate::indexOfChild(const AbstractAspect* child) const {
1228     for (int i = 0; i < m_children.size(); ++i)
1229         if (m_children.at(i) == child)
1230             return i;
1231 
1232     return -1;
1233 }