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 }