File indexing completed on 2024-05-12 15:26:37

0001 /***************************************************************************
0002     File                 : AbstractAspect.cpp
0003     Project              : LabPlot
0004     --------------------------------------------------------------------
0005     Copyright            : (C) 2007-2009 by Tilman Benkert (thzs@gmx.net)
0006     Copyright            : (C) 2007-2010 by Knut Franke (knut.franke@gmx.de)
0007     Copyright            : (C) 2011-2016 by Alexander Semke (alexander.semke@web.de)
0008     Description          : Base class for all objects in a Project.
0009  ***************************************************************************/
0010 
0011 /***************************************************************************
0012  *                                                                         *
0013  *  This program is free software; you can redistribute it and/or modify   *
0014  *  it under the terms of the GNU General Public License as published by   *
0015  *  the Free Software Foundation; either version 2 of the License, or      *
0016  *  (at your option) any later version.                                    *
0017  *                                                                         *
0018  *  This program is distributed in the hope that it will be useful,        *
0019  *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
0020  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
0021  *  GNU General Public License for more details.                           *
0022  *                                                                         *
0023  *   You should have received a copy of the GNU General Public License     *
0024  *   along with this program; if not, write to the Free Software           *
0025  *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
0026  *   Boston, MA  02110-1301  USA                                           *
0027  *                                                                         *
0028  ***************************************************************************/
0029 
0030 #include "backend/core/AbstractAspect.h"
0031 #include "backend/core/AspectPrivate.h"
0032 #include "backend/core/aspectcommands.h"
0033 #include "backend/core/Project.h"
0034 #include "backend/datapicker/DatapickerCurve.h"
0035 #include "backend/datasources/LiveDataSource.h"
0036 #include "backend/spreadsheet/Spreadsheet.h"
0037 #include "backend/lib/XmlStreamReader.h"
0038 #include "backend/lib/SignallingUndoCommand.h"
0039 #include "backend/lib/PropertyChangeCommand.h"
0040 #ifdef HAVE_MQTT
0041 #include "backend/datasources/MQTTClient.h"
0042 #include "backend/datasources/MQTTSubscription.h"
0043 #include "backend/datasources/MQTTTopic.h"
0044 #endif
0045 
0046 #include <QMenu>
0047 
0048 /**
0049  * \class AbstractAspect
0050  * \brief Base class of all persistent objects in a Project.
0051  *
0052  * Before going into the details, it's useful to understand the ideas behind the
0053  * \ref aspect "Aspect Framework".
0054  *
0055  * Aspects organize themselves into trees, where a parent takes ownership of its children. Usually,
0056  * though not necessarily, a Project instance will sit at the root of the tree (without a Project
0057  * ancestor, project() will return 0 and undo does not work). Children are organized using
0058  * addChild(), removeChild(), child(), indexOfChild() and childCount() on the parent's side as well
0059  * as the equivalent convenience methods index() and remove() on the child's side.
0060  * In contrast to the similar feature of QObject, Aspect trees are fully undo/redo aware and provide
0061  * signals around object adding/removal.
0062  *
0063  * AbstractAspect manages for every Aspect the properties #name, #comment, #captionSpec and
0064  * #creationTime. All of these translate into the caption() as described in the documentation
0065  * of setCaptionSpec().
0066  *
0067  * If an undoStack() can be found (usually it is managed by Project), changes to the properties
0068  * as well as adding/removing children support multi-level undo/redo. In order to support undo/redo
0069  * for problem-specific data in derived classes, make sure that all changes to your data are done
0070  * by handing appropriate commands to exec().
0071  */
0072 
0073 /**
0074  * \enum AbstractAspect::ChildIndexFlag
0075  * \brief Flags which control numbering scheme of children.
0076  */
0077 /**
0078  * \var AbstractAspect::IncludeHidden
0079  * \brief Include aspects marked as "hidden" in numbering or listing children.
0080  */
0081 /**
0082  * \var AbstractAspect::Recursive
0083  * \brief Recursively handle all descendents, not just immediate children.
0084  */
0085 /**
0086  * \var AbstractAspect::Compress
0087  * \brief Remove all null pointers from the result list.
0088  */
0089 
0090 ////////////////////////////////////////////////////////////////////////////////////////////////////
0091 // documentation of template and inline methods
0092 ////////////////////////////////////////////////////////////////////////////////////////////////////
0093 
0094 /**
0095  * \fn template < class T > T *AbstractAspect::ancestor() const
0096  * \brief Return the closest ancestor of class T (or NULL if none found).
0097  */
0098 
0099 /**
0100  * \fn template < class T > QVector<T*> AbstractAspect::children(const ChildIndexFlags &flags=0) const
0101  * \brief Return list of children inheriting from class T.
0102  *
0103  * Use AbstractAspect for T in order to get all children.
0104  */
0105 
0106 /**
0107  * \fn template < class T > T *AbstractAspect::child(int index, const ChildIndexFlags &flags=0) const
0108  * \brief Return child identified by (0 based) index and class.
0109  *
0110  * Identifying objects by an index is inherently error-prone and confusing,
0111  * given that the index can be based on different criteria (viz, counting
0112  * only instances of specific classes and including/excluding hidden
0113  * aspects). Therefore, it is recommended to avoid indices wherever possible
0114  * and instead refer to aspects using AbstractAspect pointers.
0115  */
0116 
0117 /**
0118  * \fn template < class T > T *AbstractAspect::child(const QString &name) const
0119  * \brief Get child by name and class.
0120  */
0121 
0122 /**
0123  * \fn template < class T > int AbstractAspect::childCount(const ChildIndexFlags &flags=0) const
0124  * \brief Return the number of child Aspects inheriting from given class.
0125  */
0126 
0127 /**
0128  * \fn template < class T > int AbstractAspect::indexOfChild(const AbstractAspect * child, const ChildIndexFlags &flags=0) const
0129  * \brief Return (0 based) index of child in the list of children inheriting from class T.
0130  */
0131 
0132 /**
0133  * \fn void AbstractAspect::aspectDescriptionAboutToChange(const AbstractAspect *aspect)
0134  * \brief Emitted before the name, comment or caption spec is changed
0135  */
0136 
0137 /**
0138  * \fn void AbstractAspect::aspectDescriptionChanged(const AbstractAspect *aspect)
0139  * \brief Emitted after the name, comment or caption spec have changed
0140  */
0141 
0142 /**
0143  * \fn void AbstractAspect::aspectAboutToBeAdded(const AbstractAspect *parent, const AbstractAspect *before, const AbstractAspect * child)
0144  * \brief Emitted before a new child is inserted
0145  */
0146 
0147 /**
0148  * \fn void AbstractAspect::aspectAdded(const AbstractAspect *aspect)
0149  * \brief Emitted after a new Aspect has been added to the tree
0150  */
0151 
0152 /**
0153  * \fn void AbstractAspect::aspectAboutToBeRemoved(const AbstractAspect *aspect)
0154  * \brief Emitted before an aspect is removed from its parent
0155  */
0156 
0157 /**
0158  * \fn void AbstractAspect::aspectRemoved(const AbstractAspect *parent, const AbstractAspect * before, const AbstractAspect * child)
0159  * \brief Emitted from the parent after removing a child
0160  */
0161 
0162 /**
0163  * \fn void AbstractAspect::aspectHiddenAboutToChange(const AbstractAspect *aspect)
0164  * \brief Emitted before the hidden attribute is changed
0165  */
0166 
0167 /**
0168  * \fn void AbstractAspect::aspectHiddenChanged(const AbstractAspect *aspect)
0169  * \brief Emitted after the hidden attribute has changed
0170  */
0171 
0172 /**
0173  * \fn void AbstractAspect::statusInfo(const QString &text)
0174  * \brief Emitted whenever some aspect in the tree wants to give status information to the user
0175  * \sa info(const QString&)
0176  */
0177 
0178 /**
0179  * \fn protected void AbstractAspect::info(const QString &text)
0180  * \brief Implementations should call this whenever status information should be given to the user.
0181  *
0182  * This will cause statusInfo() to be emitted. Typically, this will cause the specified string
0183  * to be displayed in a status bar, a log window or some similar non-blocking way so as not to
0184  * disturb the workflow.
0185  */
0186 
0187 /**
0188  * \fn protected virtual void childSelected(const AbstractAspect*) {}
0189  * \brief called when a child's child aspect was selected in the model
0190  */
0191 
0192 /**
0193  * \fn protected virtual void childDeselected()
0194  * \brief called when a child aspect was deselected in the model
0195  */
0196 
0197 /**
0198  * \fn protected virtual void childDeselected(const AbstractAspect*)
0199  * \brief called when a child's child aspect was deselected in the model
0200  */
0201 
0202 ////////////////////////////////////////////////////////////////////////////////////////////////////
0203 // start of AbstractAspect implementation
0204 ////////////////////////////////////////////////////////////////////////////////////////////////////
0205 
0206 AbstractAspect::AbstractAspect(const QString &name, AspectType type)
0207     : m_type(type), d(new AbstractAspectPrivate(this, name)) {
0208 }
0209 
0210 AbstractAspect::~AbstractAspect() {
0211     delete d;
0212 }
0213 
0214 QString AbstractAspect::name() const {
0215     return d->m_name;
0216 }
0217 
0218 /*!
0219  * \brief AbstractAspect::setName
0220  * sets the name of the abstract aspect
0221  * \param value
0222  * \param autoUnique
0223  * \return returns, if the new name is valid or not
0224  */
0225 bool AbstractAspect::setName(const QString &value, bool autoUnique) {
0226     if (value.isEmpty())
0227         return setName(QLatin1String("1"), autoUnique);
0228 
0229     if (value == d->m_name)
0230         return true; // name not changed, but the name is valid
0231 
0232     QString new_name;
0233     if (d->m_parent) {
0234             new_name = d->m_parent->uniqueNameFor(value);
0235 
0236         if (!autoUnique && new_name.compare(value) != 0) // value is not unique, so don't change name
0237             return false; // this value is used in the dock to check if the name is valid
0238 
0239 
0240         if (new_name != value)
0241             info(i18n(R"(Intended name "%1" was changed to "%2" in order to avoid name collision.)", value, new_name));
0242     } else
0243         new_name = value;
0244 
0245     exec(new PropertyChangeCommand<QString>(i18n("%1: rename to %2", d->m_name, new_name),
0246                 &d->m_name, new_name),
0247             "aspectDescriptionAboutToChange", "aspectDescriptionChanged", Q_ARG(const AbstractAspect*,this));
0248     return true;
0249 }
0250 
0251 QString AbstractAspect::comment() const {
0252     return d->m_comment;
0253 }
0254 
0255 void AbstractAspect::setComment(const QString& value) {
0256     if (value == d->m_comment) return;
0257     exec(new PropertyChangeCommand<QString>(i18n("%1: change comment", d->m_name),
0258                 &d->m_comment, value),
0259             "aspectDescriptionAboutToChange", "aspectDescriptionChanged", Q_ARG(const AbstractAspect*,this));
0260 }
0261 
0262 void AbstractAspect::setCreationTime(const QDateTime& time) {
0263     d->m_creation_time = time;
0264 }
0265 
0266 QDateTime AbstractAspect::creationTime() const {
0267     return d->m_creation_time;
0268 }
0269 
0270 bool AbstractAspect::hidden() const {
0271     return d->m_hidden;
0272 }
0273 
0274 /**
0275  * \brief Set "hidden" property, i.e. whether to exclude this aspect from being shown in the explorer.
0276  */
0277 void AbstractAspect::setHidden(bool value) {
0278     if (value == d->m_hidden)
0279         return;
0280     d->m_hidden = value;
0281 }
0282 
0283 void AbstractAspect::setIsLoading(bool load) {
0284     d->m_isLoading = load;
0285 }
0286 
0287 bool AbstractAspect::isLoading() const {
0288     return d->m_isLoading;
0289 }
0290 
0291 /**
0292  * \brief Return an icon to be used for decorating my views.
0293  */
0294 QIcon AbstractAspect::icon() const {
0295     return QIcon();
0296 }
0297 
0298 /**
0299  * \brief Return a new context menu.
0300  *
0301  * The caller takes ownership of the menu.
0302  */
0303 QMenu* AbstractAspect::createContextMenu() {
0304     QMenu* menu = new QMenu();
0305     menu->addSection(this->name());
0306 
0307     //TODO: activate this again when the functionality is implemented
0308 //  menu->addAction( KStandardAction::cut(this) );
0309 //  menu->addAction(KStandardAction::copy(this));
0310 //  menu->addAction(KStandardAction::paste(this));
0311 //  menu->addSeparator();
0312 
0313     //don't allow to rename and delete
0314     // - data spreadsheets of datapicker curves
0315     // - columns in data spreadsheets of datapicker curves
0316     // - columns in live-data source
0317     // - Mqtt subscriptions
0318     // - Mqtt topics
0319     // - Columns in Mqtt topics
0320     bool enabled = !(dynamic_cast<const Spreadsheet*>(this) && dynamic_cast<const DatapickerCurve*>(this->parentAspect()))
0321         && !(dynamic_cast<const Column*>(this) && this->parentAspect()->parentAspect() && dynamic_cast<const DatapickerCurve*>(this->parentAspect()->parentAspect()))
0322         && !(dynamic_cast<const Column*>(this) && dynamic_cast<const LiveDataSource*>(this->parentAspect()))
0323 #ifdef HAVE_MQTT
0324         && !dynamic_cast<const MQTTSubscription*>(this)
0325         && !dynamic_cast<const MQTTTopic*>(this)
0326         && !(dynamic_cast<const Column*>(this) && dynamic_cast<const MQTTTopic*>(this->parentAspect()))
0327 #endif
0328         ;
0329 
0330     if(enabled) {
0331         menu->addAction(QIcon::fromTheme(QLatin1String("edit-rename")), i18n("Rename"), this, SIGNAL(renameRequested()));
0332         if (type() != AspectType::Project)
0333             menu->addAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Delete"), this, SLOT(remove()));
0334     }
0335 
0336     return menu;
0337 }
0338 
0339 AspectType AbstractAspect::type() const {
0340     return m_type;
0341 }
0342 
0343 
0344 bool AbstractAspect::inherits(AspectType type) const {
0345     return (static_cast<quint64>(m_type) & static_cast<quint64>(type)) == static_cast<quint64>(type);
0346 }
0347 
0348 /**
0349  * \brief In the parent-child hierarchy, return the first parent of type \param type or null pointer if there is none.
0350  */
0351 AbstractAspect* AbstractAspect::parent(AspectType type) const {
0352     AbstractAspect* parent = parentAspect();
0353     if (!parent)
0354         return nullptr;
0355 
0356     if (parent->inherits(type))
0357         return parent;
0358 
0359     return parent->parent(type);
0360 }
0361 
0362 /**
0363  * \brief Return my parent Aspect or 0 if I currently don't have one.
0364  */
0365 AbstractAspect* AbstractAspect::parentAspect() const {
0366     return d->m_parent;
0367 }
0368 
0369 void AbstractAspect::setParentAspect(AbstractAspect* parent) {
0370     d->m_parent = parent;
0371 }
0372 
0373 /**
0374  * \brief Return the folder the Aspect is contained in or 0 if there is none.
0375  *
0376  * The returned folder may be the aspect itself if it inherits Folder.
0377  */
0378 Folder* AbstractAspect::folder() {
0379     if (inherits(AspectType::Folder)) return static_cast<class Folder*>(this);
0380     AbstractAspect* parent_aspect = parentAspect();
0381     while (parent_aspect && !parent_aspect->inherits(AspectType::Folder))
0382         parent_aspect = parent_aspect->parentAspect();
0383     return static_cast<class Folder*>(parent_aspect);
0384 }
0385 
0386 /**
0387  * \brief Return whether the there is a path upwards to the given aspect
0388  *
0389  * This also returns true if other==this.
0390  */
0391 bool AbstractAspect::isDescendantOf(AbstractAspect* other) {
0392     if (other == this) return true;
0393     AbstractAspect* parent_aspect = parentAspect();
0394     while (parent_aspect) {
0395         if (parent_aspect == other) return true;
0396         parent_aspect = parent_aspect->parentAspect();
0397     }
0398     return false;
0399 }
0400 
0401 /**
0402  * \brief Return the Project this Aspect belongs to, or 0 if it is currently not part of one.
0403  */
0404 Project* AbstractAspect::project() {
0405     return parentAspect() ? parentAspect()->project() : nullptr;
0406 }
0407 
0408 /**
0409  * \brief Return the path that leads from the top-most Aspect (usually a Project) to me.
0410  */
0411 QString AbstractAspect::path() const {
0412     return parentAspect() ? parentAspect()->path() + QLatin1Char('/') + name() : QString();
0413 }
0414 
0415 /**
0416  * \brief Add the given Aspect to my list of children.
0417  */
0418 void AbstractAspect::addChild(AbstractAspect* child) {
0419     Q_CHECK_PTR(child);
0420 
0421     QString new_name = uniqueNameFor(child->name());
0422     beginMacro(i18n("%1: add %2", name(), new_name));
0423     if (new_name != child->name()) {
0424         info(i18n(R"(Renaming "%1" to "%2" in order to avoid name collision.)", child->name(), new_name));
0425         child->setName(new_name);
0426     }
0427 
0428     exec(new AspectChildAddCmd(d, child, d->m_children.count()));
0429     child->finalizeAdd();
0430     endMacro();
0431 }
0432 
0433 /**
0434  * \brief Add the given Aspect to my list of children without any checks and without putting this step onto the undo-stack
0435  */
0436 void AbstractAspect::addChildFast(AbstractAspect* child) {
0437     emit aspectAboutToBeAdded(this, nullptr, child); //TODO: before-pointer is 0 here, also in the commands classes. why?
0438     d->insertChild(d->m_children.count(), child);
0439     child->finalizeAdd();
0440     emit aspectAdded(child);
0441 }
0442 
0443 /**
0444  * \brief Insert the given Aspect at a specific position in my list of children.
0445  */
0446 void AbstractAspect::insertChildBefore(AbstractAspect* child, AbstractAspect* before) {
0447     Q_CHECK_PTR(child);
0448 
0449     QString new_name = uniqueNameFor(child->name());
0450     beginMacro(before ? i18n("%1: insert %2 before %3", name(), new_name, before->name()) : i18n("%1: insert %2 before end", name(), new_name));
0451     if (new_name != child->name()) {
0452         info(i18n(R"(Renaming "%1" to "%2" in order to avoid name collision.)", child->name(), new_name));
0453         child->setName(new_name);
0454     }
0455     int index = d->indexOfChild(before);
0456     if (index == -1)
0457         index = d->m_children.count();
0458 
0459     exec(new AspectChildAddCmd(d, child, index));
0460     endMacro();
0461 }
0462 
0463 /**
0464  * \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
0465  */
0466 void AbstractAspect::insertChildBeforeFast(AbstractAspect* child, AbstractAspect* before) {
0467     connect(child, &AbstractAspect::selected, this, &AbstractAspect::childSelected);
0468     connect(child, &AbstractAspect::deselected, this, &AbstractAspect::childDeselected);
0469 
0470     int index = d->indexOfChild(before);
0471     if (index == -1)
0472         index = d->m_children.count();
0473 
0474     emit aspectAboutToBeAdded(this, nullptr, child);
0475     d->insertChild(index, child);
0476     emit aspectAdded(child);
0477 }
0478 
0479 /**
0480  * \brief Remove the given Aspect from my list of children.
0481  *
0482  * The ownership of the child is transferred to the undo command,
0483  * i.e., the aspect is deleted by the undo command.
0484  * \sa reparent()
0485  */
0486 void AbstractAspect::removeChild(AbstractAspect* child) {
0487     Q_ASSERT(child->parentAspect() == this);
0488 
0489     //when the child being removed is a LiveDataSource or a MQTT client,
0490     //stop reading from the source before removing the child from the project
0491     if (child->type() == AspectType::LiveDataSource)
0492         static_cast<LiveDataSource*>(child)->pauseReading();
0493 #ifdef HAVE_MQTT
0494     else if (child->type() == AspectType::MQTTClient)
0495         static_cast<MQTTClient*>(child)->pauseReading();
0496 #endif
0497 
0498     beginMacro(i18n("%1: remove %2", name(), child->name()));
0499     exec(new AspectChildRemoveCmd(d, child));
0500     endMacro();
0501 }
0502 
0503 /**
0504  * \brief Remove all child Aspects.
0505  */
0506 void AbstractAspect::removeAllChildren() {
0507     beginMacro(i18n("%1: remove all children", name()));
0508 
0509     QVector<AbstractAspect*> children_list = children();
0510     QVector<AbstractAspect*>::const_iterator i = children_list.constBegin();
0511     AbstractAspect *current = nullptr, *nextSibling = nullptr;
0512     if (i != children_list.constEnd()) {
0513         current = *i;
0514         if (++i != children_list.constEnd())
0515             nextSibling = *i;
0516     }
0517 
0518     while (current) {
0519         emit aspectAboutToBeRemoved(current);
0520         exec(new AspectChildRemoveCmd(d, current));
0521         emit aspectRemoved(this, nextSibling, current);
0522 
0523         current = nextSibling;
0524         if (i != children_list.constEnd() && ++i != children_list.constEnd())
0525             nextSibling = *i;
0526         else
0527             nextSibling = nullptr;
0528     }
0529 
0530     endMacro();
0531 }
0532 
0533 /**
0534  * \brief Move a child to another parent aspect and transfer ownership.
0535  */
0536 void AbstractAspect::reparent(AbstractAspect* newParent, int newIndex) {
0537     Q_ASSERT(parentAspect());
0538     Q_ASSERT(newParent);
0539     int max_index = newParent->childCount<AbstractAspect>(ChildIndexFlag::IncludeHidden);
0540     if (newIndex == -1)
0541         newIndex = max_index;
0542     Q_ASSERT(newIndex >= 0 && newIndex <= max_index);
0543 
0544 //  AbstractAspect* old_parent = parentAspect();
0545 //  int old_index = old_parent->indexOfChild<AbstractAspect>(this, IncludeHidden);
0546 //  auto* old_sibling = old_parent->child<AbstractAspect>(old_index+1, IncludeHidden);
0547 //  auto* new_sibling = newParent->child<AbstractAspect>(newIndex, IncludeHidden);
0548 
0549 //  emit newParent->aspectAboutToBeAdded(newParent, new_sibling, this);
0550     exec(new AspectChildReparentCmd(parentAspect()->d, newParent->d, this, newIndex));
0551 //  emit old_parent->aspectRemoved(old_parent, old_sibling, this);
0552 }
0553 
0554 QVector<AbstractAspect*> AbstractAspect::children(AspectType type, ChildIndexFlags flags) const {
0555     QVector<AbstractAspect*> result;
0556     for (auto* child : children()) {
0557         if (flags & ChildIndexFlag::IncludeHidden || !child->hidden()) {
0558             if (child->inherits(type) || !(flags & ChildIndexFlag::Compress)) {
0559                 result << child;
0560                 if (flags & ChildIndexFlag::Recursive) {
0561                     result << child->children(type, flags);
0562                 }
0563             }
0564         }
0565     }
0566     return result;
0567 }
0568 
0569 const QVector<AbstractAspect*>& AbstractAspect::children() const {
0570     Q_ASSERT(d);
0571     return d->m_children;
0572 }
0573 
0574 /**
0575  * \brief Remove me from my parent's list of children.
0576  */
0577 void AbstractAspect::remove() {
0578     if (parentAspect())
0579         parentAspect()->removeChild(this);
0580 }
0581 
0582 /*!
0583  * returns the list of all parent aspects (folders and sub-folders)
0584  */
0585 QVector<AbstractAspect*> AbstractAspect::dependsOn() const {
0586     QVector<AbstractAspect*> aspects;
0587     if (parentAspect())
0588         aspects << parentAspect() << parentAspect()->dependsOn();
0589 
0590     return aspects;
0591 }
0592 
0593 bool AbstractAspect::isDraggable() const {
0594     return false;
0595 }
0596 
0597 QVector<AspectType> AbstractAspect::dropableOn() const {
0598     return QVector<AspectType>();
0599 }
0600 
0601 ////////////////////////////////////////////////////////////////////////////////////////////////////
0602 //! \name serialize/deserialize
0603 //@{
0604 ////////////////////////////////////////////////////////////////////////////////////////////////////
0605 
0606 /**
0607  * \fn virtual void AbstractAspect::save(QXmlStreamWriter *) const
0608  * \brief Save as XML
0609  */
0610 
0611 /**
0612  * \fn virtual bool AbstractAspect::load(XmlStreamReader *)
0613  * \brief Load from XML
0614  *
0615  * XmlStreamReader supports errors as well as warnings. If only
0616  * warnings (non-critical errors) occur, this function must return
0617  * the reader at the end element corresponding to the current
0618  * element at the time the function was called.
0619  *
0620  * This function is normally intended to be called directly
0621  * after the ctor. If you want to call load on an aspect that
0622  * has been altered, you must make sure beforehand that
0623  * it is in the same state as after creation, e.g., remove
0624  * all its child aspects.
0625  *
0626  * \return false on error
0627  */
0628 
0629 /**
0630  * \brief Save the comment to XML
0631  */
0632 void AbstractAspect::writeCommentElement(QXmlStreamWriter * writer) const{
0633     writer->writeStartElement(QLatin1String("comment"));
0634     writer->writeCharacters(comment());
0635     writer->writeEndElement();
0636 }
0637 
0638 /**
0639  * \brief Load comment from an XML element
0640  */
0641 bool AbstractAspect::readCommentElement(XmlStreamReader * reader) {
0642     setComment(reader->readElementText());
0643     return true;
0644 }
0645 
0646 /**
0647  * \brief Save name and creation time to XML
0648  */
0649 void AbstractAspect::writeBasicAttributes(QXmlStreamWriter* writer) const {
0650     writer->writeAttribute(QLatin1String("creation_time") , creationTime().toString(QLatin1String("yyyy-dd-MM hh:mm:ss:zzz")));
0651     writer->writeAttribute(QLatin1String("name"), name());
0652 }
0653 
0654 /**
0655  * \brief Load name and creation time from XML
0656  *
0657  * \return false on error
0658  */
0659 bool AbstractAspect::readBasicAttributes(XmlStreamReader* reader) {
0660     const QXmlStreamAttributes& attribs = reader->attributes();
0661 
0662     // name
0663     QString str = attribs.value(QLatin1String("name")).toString();
0664     if (str.isEmpty())
0665         reader->raiseWarning(i18n("Attribute 'name' is missing or empty."));
0666 
0667     d->m_name = str;
0668 
0669     // creation time
0670     str = attribs.value(QLatin1String("creation_time")).toString();
0671     if (str.isEmpty()) {
0672         reader->raiseWarning(i18n("Invalid creation time for '%1'. Using current time.", name()));
0673         d->m_creation_time = QDateTime::currentDateTime();
0674     } else {
0675         QDateTime creation_time = QDateTime::fromString(str, QLatin1String("yyyy-dd-MM hh:mm:ss:zzz"));
0676         if (creation_time.isValid())
0677             d->m_creation_time = creation_time;
0678         else
0679             d->m_creation_time = QDateTime::currentDateTime();
0680     }
0681 
0682     return true;
0683 }
0684 
0685 ////////////////////////////////////////////////////////////////////////////////////////////////////
0686 //@}
0687 ////////////////////////////////////////////////////////////////////////////////////////////////////
0688 
0689 
0690 
0691 ////////////////////////////////////////////////////////////////////////////////////////////////////
0692 //! \name undo related
0693 //@{
0694 ////////////////////////////////////////////////////////////////////////////////////////////////////
0695 void AbstractAspect::setUndoAware(bool b) {
0696     d->m_undoAware = b;
0697 }
0698 
0699 /**
0700  * \brief Return the undo stack of the Project, or 0 if this Aspect is not part of a Project.
0701  *
0702  * It's also possible to construct undo-enabled Aspect trees without Project.
0703  * The only requirement is that the root Aspect reimplements undoStack() to get the
0704  * undo stack from somewhere (the default implementation just delegates to parentAspect()).
0705  */
0706 QUndoStack* AbstractAspect::undoStack() const {
0707     return parentAspect() ? parentAspect()->undoStack() : nullptr;
0708 }
0709 
0710 /**
0711  * \brief Execute the given command, pushing it on the undoStack() if available.
0712  */
0713 void AbstractAspect::exec(QUndoCommand* cmd) {
0714     Q_CHECK_PTR(cmd);
0715     if (d->m_undoAware) {
0716         QUndoStack *stack = undoStack();
0717         if (stack)
0718             stack->push(cmd);
0719         else {
0720             cmd->redo();
0721             delete cmd;
0722         }
0723 
0724         if (project())
0725             project()->setChanged(true);
0726     } else {
0727         cmd->redo();
0728         delete cmd;
0729     }
0730 }
0731 
0732 /**
0733  * \brief Execute command and arrange for signals to be sent before/after it is redone or undone.
0734  *
0735  * \arg \c command The command to be executed.
0736  * \arg \c preChangeSignal The name of the signal to be triggered before re-/undoing the command.
0737  * \arg \c postChangeSignal The name of the signal to be triggered after re-/undoing the command.
0738  * \arg <tt>val0,val1,val2,val3</tt> Arguments to the signals; to be given using Q_ARG().
0739  *
0740  * Signal arguments are given using the macro Q_ARG(typename, const value&). Since
0741  * the variable given as "value" will likely be out of scope when the signals are emitted, a copy
0742  * needs to be created. This uses QMetaType, which means that (non-trivial) argument types need to
0743  * be registered using qRegisterMetaType() before giving them to exec() (in particular, this also
0744  * goes for pointers to custom data types).
0745  *
0746  * \sa SignallingUndoCommand
0747  */
0748 void AbstractAspect::exec(QUndoCommand* command,
0749         const char* preChangeSignal, const char* postChangeSignal,
0750         QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3) {
0751     beginMacro(command->text());
0752     exec(new SignallingUndoCommand(QLatin1String("change signal"), this,
0753                 preChangeSignal, postChangeSignal, val0, val1, val2, val3));
0754     exec(command);
0755     exec(new SignallingUndoCommand(QLatin1String("change signal"), this,
0756                 postChangeSignal, preChangeSignal, val0, val1, val2, val3));
0757     endMacro();
0758 }
0759 
0760 /**
0761  * \brief Begin an undo stack macro (series of commands)
0762  */
0763 void AbstractAspect::beginMacro(const QString& text) {
0764     if (!d->m_undoAware)
0765         return;
0766 
0767     QUndoStack* stack = undoStack();
0768     if (stack)
0769         stack->beginMacro(text);
0770 }
0771 
0772 /**
0773  * \brief End the current undo stack macro
0774  */
0775 void AbstractAspect::endMacro() {
0776     if (!d->m_undoAware)
0777         return;
0778 
0779     QUndoStack* stack = undoStack();
0780     if (stack)
0781         stack->endMacro();
0782 }
0783 
0784 ////////////////////////////////////////////////////////////////////////////////////////////////////
0785 //@}
0786 ////////////////////////////////////////////////////////////////////////////////////////////////////
0787 
0788 
0789 /*!
0790  * this function is called when the selection in ProjectExplorer was changed.
0791  * forwards the selection/deselection to the parent aspect via emitting a signal.
0792  */
0793 void AbstractAspect::setSelected(bool s) {
0794     if (s)
0795         emit selected(this);
0796     else
0797         emit deselected(this);
0798 }
0799 
0800 void AbstractAspect::childSelected(const AbstractAspect* aspect) {
0801     //forward the signal to the highest possible level in the parent-child hierarchy
0802     //e.g. axis of a plot was selected. Don't include parent aspects here that do not
0803     //need to react on the selection of children:
0804     //* Folder
0805     //* XYFitCurve with the child column for calculated residuals
0806     //* XYSmouthCurve with the child column for calculated rough values
0807     //* CantorWorksheet with the child columns for CAS variables
0808     if (aspect->parentAspect()
0809         && !aspect->parentAspect()->inherits(AspectType::Folder)
0810         && !aspect->parentAspect()->inherits(AspectType::XYFitCurve)
0811         && !aspect->parentAspect()->inherits(AspectType::XYSmoothCurve)
0812         && !aspect->parentAspect()->inherits(AspectType::CantorWorksheet))
0813         emit aspect->parentAspect()->selected(aspect);
0814 }
0815 
0816 void AbstractAspect::childDeselected(const AbstractAspect* aspect) {
0817     //forward the signal to the highest possible level in the parent-child hierarchy
0818     //e.g. axis of a plot was selected. Don't include parent aspects here that do not
0819     //need to react on the deselection of children:
0820     //* Folder
0821     //* XYFitCurve with the child column for calculated residuals
0822     //* XYSmouthCurve with the child column for calculated rough values
0823     //* CantorWorksheet with the child columns for CAS variables
0824     if (aspect->parentAspect()
0825         && !aspect->parentAspect()->inherits(AspectType::Folder)
0826         && !aspect->parentAspect()->inherits(AspectType::XYFitCurve)
0827         && !aspect->parentAspect()->inherits(AspectType::XYSmoothCurve)
0828         && !aspect->parentAspect()->inherits(AspectType::CantorWorksheet))
0829         emit aspect->parentAspect()->deselected(aspect);
0830 }
0831 
0832 /**
0833  * \brief Make the specified name unique among my children by incrementing a trailing number.
0834  */
0835 QString AbstractAspect::uniqueNameFor(const QString& current_name) const {
0836     QStringList child_names;
0837     for (auto* child : children())
0838         child_names << child->name();
0839 
0840     if (!child_names.contains(current_name))
0841         return current_name;
0842 
0843     QString base = current_name;
0844     int last_non_digit;
0845     for (last_non_digit = base.size() - 1; last_non_digit >= 0; --last_non_digit) {
0846         if (base[last_non_digit].category() == QChar::Number_DecimalDigit) {
0847             base.chop(1);
0848         } else {
0849             if (base[last_non_digit].category() == QChar::Separator_Space)
0850                 break;
0851             else {
0852                 //non-digit character is found and it's not the separator,
0853                 //the string either doesn't have any digits at all or is of
0854                 //the form "data_2020.06". In this case we don't use anything
0855                 //from the original name to increment the number
0856                 last_non_digit = 0;
0857                 base = current_name;
0858                 break;
0859             }
0860         }
0861     }
0862 
0863     if (last_non_digit >=0 && base[last_non_digit].category() != QChar::Separator_Space)
0864         base.append(" ");
0865 
0866     int new_nr = current_name.rightRef(current_name.size() - base.size()).toInt();
0867     QString new_name;
0868     do
0869         new_name = base + QString::number(++new_nr);
0870     while (child_names.contains(new_name));
0871 
0872     return new_name;
0873 }
0874 
0875 void AbstractAspect::connectChild(AbstractAspect* child) {
0876     connect(child, &AbstractAspect::aspectDescriptionAboutToChange, this, &AbstractAspect::aspectDescriptionAboutToChange);
0877     connect(child, &AbstractAspect::aspectDescriptionChanged, this, &AbstractAspect::aspectDescriptionChanged);
0878     connect(child, &AbstractAspect::aspectAboutToBeAdded, this, &AbstractAspect::aspectAboutToBeAdded);
0879     connect(child, &AbstractAspect::aspectAdded, this, &AbstractAspect::aspectAdded);
0880     connect(child, &AbstractAspect::aspectAboutToBeRemoved, this, &AbstractAspect::aspectAboutToBeRemoved);
0881     connect(child, &AbstractAspect::aspectRemoved, this, &AbstractAspect::aspectRemoved);
0882     connect(child, &AbstractAspect::aspectHiddenAboutToChange, this, &AbstractAspect::aspectHiddenAboutToChange);
0883     connect(child, &AbstractAspect::aspectHiddenChanged, this, &AbstractAspect::aspectHiddenChanged);
0884     connect(child, &AbstractAspect::statusInfo, this, &AbstractAspect::statusInfo);
0885 
0886     connect(child, &AbstractAspect::selected, this, &AbstractAspect::childSelected);
0887     connect(child, &AbstractAspect::deselected, this, &AbstractAspect::childDeselected);
0888 }
0889 
0890 //##############################################################################
0891 //######################  Private implementation ###############################
0892 //##############################################################################
0893 AbstractAspectPrivate::AbstractAspectPrivate(AbstractAspect* owner, const QString& name)
0894     : m_name(name.isEmpty() ? QLatin1String("1") : name), q(owner) {
0895     m_creation_time = QDateTime::currentDateTime();
0896 }
0897 
0898 AbstractAspectPrivate::~AbstractAspectPrivate() {
0899     for (auto* child : m_children)
0900         delete child;
0901 }
0902 
0903 void AbstractAspectPrivate::insertChild(int index, AbstractAspect* child) {
0904     m_children.insert(index, child);
0905 
0906     // Always remove from any previous parent before adding to a new one!
0907     // Can't handle this case here since two undo commands have to be created.
0908     Q_ASSERT(child->parentAspect() == nullptr);
0909     child->setParentAspect(q);
0910     q->connectChild(child);
0911 }
0912 
0913 int AbstractAspectPrivate::indexOfChild(const AbstractAspect* child) const {
0914     for (int i = 0; i < m_children.size(); ++i)
0915         if (m_children.at(i) == child) return i;
0916 
0917     return -1;
0918 }
0919 
0920 int AbstractAspectPrivate::removeChild(AbstractAspect* child) {
0921     int index = indexOfChild(child);
0922     Q_ASSERT(index != -1);
0923     m_children.removeAll(child);
0924     QObject::disconnect(child, nullptr, q, nullptr);
0925     child->setParentAspect(nullptr);
0926     return index;
0927 }