labplot  2.8.2
About: LabPlot is an application for plotting and analysis of 2D and 3D functions and data. It is a complete rewrite of LabPlot1 and lacks in the first release a lot of features available in the predecessor. On the other hand, the GUI and the usability is more superior.
  Fossies Dox: labplot-2.8.2.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

AbstractAspect.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  File : AbstractAspect.cpp
3  Project : LabPlot
4  --------------------------------------------------------------------
5  Copyright : (C) 2007-2009 by Tilman Benkert (thzs@gmx.net)
6  Copyright : (C) 2007-2010 by Knut Franke (knut.franke@gmx.de)
7  Copyright : (C) 2011-2016 by Alexander Semke (alexander.semke@web.de)
8  Description : Base class for all objects in a Project.
9  ***************************************************************************/
10 
11 /***************************************************************************
12  * *
13  * This program is free software; you can redistribute it and/or modify *
14  * it under the terms of the GNU General Public License as published by *
15  * the Free Software Foundation; either version 2 of the License, or *
16  * (at your option) any later version. *
17  * *
18  * This program is distributed in the hope that it will be useful, *
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
21  * GNU General Public License for more details. *
22  * *
23  * You should have received a copy of the GNU General Public License *
24  * along with this program; if not, write to the Free Software *
25  * Foundation, Inc., 51 Franklin Street, Fifth Floor, *
26  * Boston, MA 02110-1301 USA *
27  * *
28  ***************************************************************************/
29 
33 #include "backend/core/Project.h"
40 #ifdef HAVE_MQTT
44 #endif
45 
46 #include <QMenu>
47 
48 /**
49  * \class AbstractAspect
50  * \brief Base class of all persistent objects in a Project.
51  *
52  * Before going into the details, it's useful to understand the ideas behind the
53  * \ref aspect "Aspect Framework".
54  *
55  * Aspects organize themselves into trees, where a parent takes ownership of its children. Usually,
56  * though not necessarily, a Project instance will sit at the root of the tree (without a Project
57  * ancestor, project() will return 0 and undo does not work). Children are organized using
58  * addChild(), removeChild(), child(), indexOfChild() and childCount() on the parent's side as well
59  * as the equivalent convenience methods index() and remove() on the child's side.
60  * In contrast to the similar feature of QObject, Aspect trees are fully undo/redo aware and provide
61  * signals around object adding/removal.
62  *
63  * AbstractAspect manages for every Aspect the properties #name, #comment, #captionSpec and
64  * #creationTime. All of these translate into the caption() as described in the documentation
65  * of setCaptionSpec().
66  *
67  * If an undoStack() can be found (usually it is managed by Project), changes to the properties
68  * as well as adding/removing children support multi-level undo/redo. In order to support undo/redo
69  * for problem-specific data in derived classes, make sure that all changes to your data are done
70  * by handing appropriate commands to exec().
71  */
72 
73 /**
74  * \enum AbstractAspect::ChildIndexFlag
75  * \brief Flags which control numbering scheme of children.
76  */
77 /**
78  * \var AbstractAspect::IncludeHidden
79  * \brief Include aspects marked as "hidden" in numbering or listing children.
80  */
81 /**
82  * \var AbstractAspect::Recursive
83  * \brief Recursively handle all descendents, not just immediate children.
84  */
85 /**
86  * \var AbstractAspect::Compress
87  * \brief Remove all null pointers from the result list.
88  */
89 
90 ////////////////////////////////////////////////////////////////////////////////////////////////////
91 // documentation of template and inline methods
92 ////////////////////////////////////////////////////////////////////////////////////////////////////
93 
94 /**
95  * \fn template < class T > T *AbstractAspect::ancestor() const
96  * \brief Return the closest ancestor of class T (or NULL if none found).
97  */
98 
99 /**
100  * \fn template < class T > QVector<T*> AbstractAspect::children(const ChildIndexFlags &flags=0) const
101  * \brief Return list of children inheriting from class T.
102  *
103  * Use AbstractAspect for T in order to get all children.
104  */
105 
106 /**
107  * \fn template < class T > T *AbstractAspect::child(int index, const ChildIndexFlags &flags=0) const
108  * \brief Return child identified by (0 based) index and class.
109  *
110  * Identifying objects by an index is inherently error-prone and confusing,
111  * given that the index can be based on different criteria (viz, counting
112  * only instances of specific classes and including/excluding hidden
113  * aspects). Therefore, it is recommended to avoid indices wherever possible
114  * and instead refer to aspects using AbstractAspect pointers.
115  */
116 
117 /**
118  * \fn template < class T > T *AbstractAspect::child(const QString &name) const
119  * \brief Get child by name and class.
120  */
121 
122 /**
123  * \fn template < class T > int AbstractAspect::childCount(const ChildIndexFlags &flags=0) const
124  * \brief Return the number of child Aspects inheriting from given class.
125  */
126 
127 /**
128  * \fn template < class T > int AbstractAspect::indexOfChild(const AbstractAspect * child, const ChildIndexFlags &flags=0) const
129  * \brief Return (0 based) index of child in the list of children inheriting from class T.
130  */
131 
132 /**
133  * \fn void AbstractAspect::aspectDescriptionAboutToChange(const AbstractAspect *aspect)
134  * \brief Emitted before the name, comment or caption spec is changed
135  */
136 
137 /**
138  * \fn void AbstractAspect::aspectDescriptionChanged(const AbstractAspect *aspect)
139  * \brief Emitted after the name, comment or caption spec have changed
140  */
141 
142 /**
143  * \fn void AbstractAspect::aspectAboutToBeAdded(const AbstractAspect *parent, const AbstractAspect *before, const AbstractAspect * child)
144  * \brief Emitted before a new child is inserted
145  */
146 
147 /**
148  * \fn void AbstractAspect::aspectAdded(const AbstractAspect *aspect)
149  * \brief Emitted after a new Aspect has been added to the tree
150  */
151 
152 /**
153  * \fn void AbstractAspect::aspectAboutToBeRemoved(const AbstractAspect *aspect)
154  * \brief Emitted before an aspect is removed from its parent
155  */
156 
157 /**
158  * \fn void AbstractAspect::aspectRemoved(const AbstractAspect *parent, const AbstractAspect * before, const AbstractAspect * child)
159  * \brief Emitted from the parent after removing a child
160  */
161 
162 /**
163  * \fn void AbstractAspect::aspectHiddenAboutToChange(const AbstractAspect *aspect)
164  * \brief Emitted before the hidden attribute is changed
165  */
166 
167 /**
168  * \fn void AbstractAspect::aspectHiddenChanged(const AbstractAspect *aspect)
169  * \brief Emitted after the hidden attribute has changed
170  */
171 
172 /**
173  * \fn void AbstractAspect::statusInfo(const QString &text)
174  * \brief Emitted whenever some aspect in the tree wants to give status information to the user
175  * \sa info(const QString&)
176  */
177 
178 /**
179  * \fn protected void AbstractAspect::info(const QString &text)
180  * \brief Implementations should call this whenever status information should be given to the user.
181  *
182  * This will cause statusInfo() to be emitted. Typically, this will cause the specified string
183  * to be displayed in a status bar, a log window or some similar non-blocking way so as not to
184  * disturb the workflow.
185  */
186 
187 /**
188  * \fn protected virtual void childSelected(const AbstractAspect*) {}
189  * \brief called when a child's child aspect was selected in the model
190  */
191 
192 /**
193  * \fn protected virtual void childDeselected()
194  * \brief called when a child aspect was deselected in the model
195  */
196 
197 /**
198  * \fn protected virtual void childDeselected(const AbstractAspect*)
199  * \brief called when a child's child aspect was deselected in the model
200  */
201 
202 ////////////////////////////////////////////////////////////////////////////////////////////////////
203 // start of AbstractAspect implementation
204 ////////////////////////////////////////////////////////////////////////////////////////////////////
205 
207  : m_type(type), d(new AbstractAspectPrivate(this, name)) {
208 }
209 
211  delete d;
212 }
213 
214 QString AbstractAspect::name() const {
215  return d->m_name;
216 }
217 
218 /*!
219  * \brief AbstractAspect::setName
220  * sets the name of the abstract aspect
221  * \param value
222  * \param autoUnique
223  * \return returns, if the new name is valid or not
224  */
225 bool AbstractAspect::setName(const QString &value, bool autoUnique) {
226  if (value.isEmpty())
227  return setName(QLatin1String("1"), autoUnique);
228 
229  if (value == d->m_name)
230  return true; // name not changed, but the name is valid
231 
232  QString new_name;
233  if (d->m_parent) {
234  new_name = d->m_parent->uniqueNameFor(value);
235 
236  if (!autoUnique && new_name.compare(value) != 0) // value is not unique, so don't change name
237  return false; // this value is used in the dock to check if the name is valid
238 
239 
240  if (new_name != value)
241  info(i18n(R"(Intended name "%1" was changed to "%2" in order to avoid name collision.)", value, new_name));
242  } else
243  new_name = value;
244 
245  exec(new PropertyChangeCommand<QString>(i18n("%1: rename to %2", d->m_name, new_name),
246  &d->m_name, new_name),
247  "aspectDescriptionAboutToChange", "aspectDescriptionChanged", Q_ARG(const AbstractAspect*,this));
248  return true;
249 }
250 
251 QString AbstractAspect::comment() const {
252  return d->m_comment;
253 }
254 
255 void AbstractAspect::setComment(const QString& value) {
256  if (value == d->m_comment) return;
257  exec(new PropertyChangeCommand<QString>(i18n("%1: change comment", d->m_name),
258  &d->m_comment, value),
259  "aspectDescriptionAboutToChange", "aspectDescriptionChanged", Q_ARG(const AbstractAspect*,this));
260 }
261 
262 void AbstractAspect::setCreationTime(const QDateTime& time) {
263  d->m_creation_time = time;
264 }
265 
266 QDateTime AbstractAspect::creationTime() const {
267  return d->m_creation_time;
268 }
269 
271  return d->m_hidden;
272 }
273 
274 /**
275  * \brief Set "hidden" property, i.e. whether to exclude this aspect from being shown in the explorer.
276  */
277 void AbstractAspect::setHidden(bool value) {
278  if (value == d->m_hidden)
279  return;
280  d->m_hidden = value;
281 }
282 
284  d->m_isLoading = load;
285 }
286 
288  return d->m_isLoading;
289 }
290 
291 /**
292  * \brief Return an icon to be used for decorating my views.
293  */
294 QIcon AbstractAspect::icon() const {
295  return QIcon();
296 }
297 
298 /**
299  * \brief Return a new context menu.
300  *
301  * The caller takes ownership of the menu.
302  */
304  QMenu* menu = new QMenu();
305  menu->addSection(this->name());
306 
307  //TODO: activate this again when the functionality is implemented
308 // menu->addAction( KStandardAction::cut(this) );
309 // menu->addAction(KStandardAction::copy(this));
310 // menu->addAction(KStandardAction::paste(this));
311 // menu->addSeparator();
312 
313  //don't allow to rename and delete
314  // - data spreadsheets of datapicker curves
315  // - columns in data spreadsheets of datapicker curves
316  // - columns in live-data source
317  // - Mqtt subscriptions
318  // - Mqtt topics
319  // - Columns in Mqtt topics
320  bool enabled = !(dynamic_cast<const Spreadsheet*>(this) && dynamic_cast<const DatapickerCurve*>(this->parentAspect()))
321  && !(dynamic_cast<const Column*>(this) && this->parentAspect()->parentAspect() && dynamic_cast<const DatapickerCurve*>(this->parentAspect()->parentAspect()))
322  && !(dynamic_cast<const Column*>(this) && dynamic_cast<const LiveDataSource*>(this->parentAspect()))
323 #ifdef HAVE_MQTT
324  && !dynamic_cast<const MQTTSubscription*>(this)
325  && !dynamic_cast<const MQTTTopic*>(this)
326  && !(dynamic_cast<const Column*>(this) && dynamic_cast<const MQTTTopic*>(this->parentAspect()))
327 #endif
328  ;
329 
330  if(enabled) {
331  menu->addAction(QIcon::fromTheme(QLatin1String("edit-rename")), i18n("Rename"), this, SIGNAL(renameRequested()));
332  if (type() != AspectType::Project)
333  menu->addAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Delete"), this, SLOT(remove()));
334  }
335 
336  return menu;
337 }
338 
340  return m_type;
341 }
342 
343 
345  return (static_cast<quint64>(m_type) & static_cast<quint64>(type)) == static_cast<quint64>(type);
346 }
347 
348 /**
349  * \brief In the parent-child hierarchy, return the first parent of type \param type or null pointer if there is none.
350  */
353  if (!parent)
354  return nullptr;
355 
356  if (parent->inherits(type))
357  return parent;
358 
359  return parent->parent(type);
360 }
361 
362 /**
363  * \brief Return my parent Aspect or 0 if I currently don't have one.
364  */
366  return d->m_parent;
367 }
368 
370  d->m_parent = parent;
371 }
372 
373 /**
374  * \brief Return the folder the Aspect is contained in or 0 if there is none.
375  *
376  * The returned folder may be the aspect itself if it inherits Folder.
377  */
379  if (inherits(AspectType::Folder)) return static_cast<class Folder*>(this);
380  AbstractAspect* parent_aspect = parentAspect();
381  while (parent_aspect && !parent_aspect->inherits(AspectType::Folder))
382  parent_aspect = parent_aspect->parentAspect();
383  return static_cast<class Folder*>(parent_aspect);
384 }
385 
386 /**
387  * \brief Return whether the there is a path upwards to the given aspect
388  *
389  * This also returns true if other==this.
390  */
392  if (other == this) return true;
393  AbstractAspect* parent_aspect = parentAspect();
394  while (parent_aspect) {
395  if (parent_aspect == other) return true;
396  parent_aspect = parent_aspect->parentAspect();
397  }
398  return false;
399 }
400 
401 /**
402  * \brief Return the Project this Aspect belongs to, or 0 if it is currently not part of one.
403  */
405  return parentAspect() ? parentAspect()->project() : nullptr;
406 }
407 
408 /**
409  * \brief Return the path that leads from the top-most Aspect (usually a Project) to me.
410  */
411 QString AbstractAspect::path() const {
412  return parentAspect() ? parentAspect()->path() + QLatin1Char('/') + name() : QString();
413 }
414 
415 /**
416  * \brief Add the given Aspect to my list of children.
417  */
419  Q_CHECK_PTR(child);
420 
421  QString new_name = uniqueNameFor(child->name());
422  beginMacro(i18n("%1: add %2", name(), new_name));
423  if (new_name != child->name()) {
424  info(i18n(R"(Renaming "%1" to "%2" in order to avoid name collision.)", child->name(), new_name));
425  child->setName(new_name);
426  }
427 
428  exec(new AspectChildAddCmd(d, child, d->m_children.count()));
429  child->finalizeAdd();
430  endMacro();
431 }
432 
433 /**
434  * \brief Add the given Aspect to my list of children without any checks and without putting this step onto the undo-stack
435  */
437  emit aspectAboutToBeAdded(this, nullptr, child); //TODO: before-pointer is 0 here, also in the commands classes. why?
438  d->insertChild(d->m_children.count(), child);
439  child->finalizeAdd();
440  emit aspectAdded(child);
441 }
442 
443 /**
444  * \brief Insert the given Aspect at a specific position in my list of children.
445  */
447  Q_CHECK_PTR(child);
448 
449  QString new_name = uniqueNameFor(child->name());
450  beginMacro(before ? i18n("%1: insert %2 before %3", name(), new_name, before->name()) : i18n("%1: insert %2 before end", name(), new_name));
451  if (new_name != child->name()) {
452  info(i18n(R"(Renaming "%1" to "%2" in order to avoid name collision.)", child->name(), new_name));
453  child->setName(new_name);
454  }
455  int index = d->indexOfChild(before);
456  if (index == -1)
457  index = d->m_children.count();
458 
459  exec(new AspectChildAddCmd(d, child, index));
460  endMacro();
461 }
462 
463 /**
464  * \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
465  */
469 
470  int index = d->indexOfChild(before);
471  if (index == -1)
472  index = d->m_children.count();
473 
474  emit aspectAboutToBeAdded(this, nullptr, child);
475  d->insertChild(index, child);
476  emit aspectAdded(child);
477 }
478 
479 /**
480  * \brief Remove the given Aspect from my list of children.
481  *
482  * The ownership of the child is transferred to the undo command,
483  * i.e., the aspect is deleted by the undo command.
484  * \sa reparent()
485  */
487  Q_ASSERT(child->parentAspect() == this);
488 
489  //when the child being removed is a LiveDataSource or a MQTT client,
490  //stop reading from the source before removing the child from the project
491  if (child->type() == AspectType::LiveDataSource)
492  static_cast<LiveDataSource*>(child)->pauseReading();
493 #ifdef HAVE_MQTT
494  else if (child->type() == AspectType::MQTTClient)
495  static_cast<MQTTClient*>(child)->pauseReading();
496 #endif
497 
498  beginMacro(i18n("%1: remove %2", name(), child->name()));
500  endMacro();
501 }
502 
503 /**
504  * \brief Remove all child Aspects.
505  */
507  beginMacro(i18n("%1: remove all children", name()));
508 
509  QVector<AbstractAspect*> children_list = children();
510  QVector<AbstractAspect*>::const_iterator i = children_list.constBegin();
511  AbstractAspect *current = nullptr, *nextSibling = nullptr;
512  if (i != children_list.constEnd()) {
513  current = *i;
514  if (++i != children_list.constEnd())
515  nextSibling = *i;
516  }
517 
518  while (current) {
519  emit aspectAboutToBeRemoved(current);
520  exec(new AspectChildRemoveCmd(d, current));
521  emit aspectRemoved(this, nextSibling, current);
522 
523  current = nextSibling;
524  if (i != children_list.constEnd() && ++i != children_list.constEnd())
525  nextSibling = *i;
526  else
527  nextSibling = nullptr;
528  }
529 
530  endMacro();
531 }
532 
533 /**
534  * \brief Move a child to another parent aspect and transfer ownership.
535  */
536 void AbstractAspect::reparent(AbstractAspect* newParent, int newIndex) {
537  Q_ASSERT(parentAspect());
538  Q_ASSERT(newParent);
539  int max_index = newParent->childCount<AbstractAspect>(ChildIndexFlag::IncludeHidden);
540  if (newIndex == -1)
541  newIndex = max_index;
542  Q_ASSERT(newIndex >= 0 && newIndex <= max_index);
543 
544 // AbstractAspect* old_parent = parentAspect();
545 // int old_index = old_parent->indexOfChild<AbstractAspect>(this, IncludeHidden);
546 // auto* old_sibling = old_parent->child<AbstractAspect>(old_index+1, IncludeHidden);
547 // auto* new_sibling = newParent->child<AbstractAspect>(newIndex, IncludeHidden);
548 
549 // emit newParent->aspectAboutToBeAdded(newParent, new_sibling, this);
550  exec(new AspectChildReparentCmd(parentAspect()->d, newParent->d, this, newIndex));
551 // emit old_parent->aspectRemoved(old_parent, old_sibling, this);
552 }
553 
556  for (auto* child : children()) {
557  if (flags & ChildIndexFlag::IncludeHidden || !child->hidden()) {
558  if (child->inherits(type) || !(flags & ChildIndexFlag::Compress)) {
559  result << child;
560  if (flags & ChildIndexFlag::Recursive) {
561  result << child->children(type, flags);
562  }
563  }
564  }
565  }
566  return result;
567 }
568 
570  Q_ASSERT(d);
571  return d->m_children;
572 }
573 
574 /**
575  * \brief Remove me from my parent's list of children.
576  */
578  if (parentAspect())
579  parentAspect()->removeChild(this);
580 }
581 
582 /*!
583  * returns the list of all parent aspects (folders and sub-folders)
584  */
586  QVector<AbstractAspect*> aspects;
587  if (parentAspect())
588  aspects << parentAspect() << parentAspect()->dependsOn();
589 
590  return aspects;
591 }
592 
594  return false;
595 }
596 
598  return QVector<AspectType>();
599 }
600 
601 ////////////////////////////////////////////////////////////////////////////////////////////////////
602 //! \name serialize/deserialize
603 //@{
604 ////////////////////////////////////////////////////////////////////////////////////////////////////
605 
606 /**
607  * \fn virtual void AbstractAspect::save(QXmlStreamWriter *) const
608  * \brief Save as XML
609  */
610 
611 /**
612  * \fn virtual bool AbstractAspect::load(XmlStreamReader *)
613  * \brief Load from XML
614  *
615  * XmlStreamReader supports errors as well as warnings. If only
616  * warnings (non-critical errors) occur, this function must return
617  * the reader at the end element corresponding to the current
618  * element at the time the function was called.
619  *
620  * This function is normally intended to be called directly
621  * after the ctor. If you want to call load on an aspect that
622  * has been altered, you must make sure beforehand that
623  * it is in the same state as after creation, e.g., remove
624  * all its child aspects.
625  *
626  * \return false on error
627  */
628 
629 /**
630  * \brief Save the comment to XML
631  */
632 void AbstractAspect::writeCommentElement(QXmlStreamWriter * writer) const{
633  writer->writeStartElement(QLatin1String("comment"));
634  writer->writeCharacters(comment());
635  writer->writeEndElement();
636 }
637 
638 /**
639  * \brief Load comment from an XML element
640  */
642  setComment(reader->readElementText());
643  return true;
644 }
645 
646 /**
647  * \brief Save name and creation time to XML
648  */
649 void AbstractAspect::writeBasicAttributes(QXmlStreamWriter* writer) const {
650  writer->writeAttribute(QLatin1String("creation_time") , creationTime().toString(QLatin1String("yyyy-dd-MM hh:mm:ss:zzz")));
651  writer->writeAttribute(QLatin1String("name"), name());
652 }
653 
654 /**
655  * \brief Load name and creation time from XML
656  *
657  * \return false on error
658  */
660  const QXmlStreamAttributes& attribs = reader->attributes();
661 
662  // name
663  QString str = attribs.value(QLatin1String("name")).toString();
664  if (str.isEmpty())
665  reader->raiseWarning(i18n("Attribute 'name' is missing or empty."));
666 
667  d->m_name = str;
668 
669  // creation time
670  str = attribs.value(QLatin1String("creation_time")).toString();
671  if (str.isEmpty()) {
672  reader->raiseWarning(i18n("Invalid creation time for '%1'. Using current time.", name()));
673  d->m_creation_time = QDateTime::currentDateTime();
674  } else {
675  QDateTime creation_time = QDateTime::fromString(str, QLatin1String("yyyy-dd-MM hh:mm:ss:zzz"));
676  if (creation_time.isValid())
677  d->m_creation_time = creation_time;
678  else
679  d->m_creation_time = QDateTime::currentDateTime();
680  }
681 
682  return true;
683 }
684 
685 ////////////////////////////////////////////////////////////////////////////////////////////////////
686 //@}
687 ////////////////////////////////////////////////////////////////////////////////////////////////////
688 
689 
690 
691 ////////////////////////////////////////////////////////////////////////////////////////////////////
692 //! \name undo related
693 //@{
694 ////////////////////////////////////////////////////////////////////////////////////////////////////
696  d->m_undoAware = b;
697 }
698 
699 /**
700  * \brief Return the undo stack of the Project, or 0 if this Aspect is not part of a Project.
701  *
702  * It's also possible to construct undo-enabled Aspect trees without Project.
703  * The only requirement is that the root Aspect reimplements undoStack() to get the
704  * undo stack from somewhere (the default implementation just delegates to parentAspect()).
705  */
706 QUndoStack* AbstractAspect::undoStack() const {
707  return parentAspect() ? parentAspect()->undoStack() : nullptr;
708 }
709 
710 /**
711  * \brief Execute the given command, pushing it on the undoStack() if available.
712  */
713 void AbstractAspect::exec(QUndoCommand* cmd) {
714  Q_CHECK_PTR(cmd);
715  if (d->m_undoAware) {
716  QUndoStack *stack = undoStack();
717  if (stack)
718  stack->push(cmd);
719  else {
720  cmd->redo();
721  delete cmd;
722  }
723 
724  if (project())
725  project()->setChanged(true);
726  } else {
727  cmd->redo();
728  delete cmd;
729  }
730 }
731 
732 /**
733  * \brief Execute command and arrange for signals to be sent before/after it is redone or undone.
734  *
735  * \arg \c command The command to be executed.
736  * \arg \c preChangeSignal The name of the signal to be triggered before re-/undoing the command.
737  * \arg \c postChangeSignal The name of the signal to be triggered after re-/undoing the command.
738  * \arg <tt>val0,val1,val2,val3</tt> Arguments to the signals; to be given using Q_ARG().
739  *
740  * Signal arguments are given using the macro Q_ARG(typename, const value&). Since
741  * the variable given as "value" will likely be out of scope when the signals are emitted, a copy
742  * needs to be created. This uses QMetaType, which means that (non-trivial) argument types need to
743  * be registered using qRegisterMetaType() before giving them to exec() (in particular, this also
744  * goes for pointers to custom data types).
745  *
746  * \sa SignallingUndoCommand
747  */
748 void AbstractAspect::exec(QUndoCommand* command,
749  const char* preChangeSignal, const char* postChangeSignal,
750  QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3) {
751  beginMacro(command->text());
752  exec(new SignallingUndoCommand(QLatin1String("change signal"), this,
753  preChangeSignal, postChangeSignal, val0, val1, val2, val3));
754  exec(command);
755  exec(new SignallingUndoCommand(QLatin1String("change signal"), this,
756  postChangeSignal, preChangeSignal, val0, val1, val2, val3));
757  endMacro();
758 }
759 
760 /**
761  * \brief Begin an undo stack macro (series of commands)
762  */
763 void AbstractAspect::beginMacro(const QString& text) {
764  if (!d->m_undoAware)
765  return;
766 
767  QUndoStack* stack = undoStack();
768  if (stack)
769  stack->beginMacro(text);
770 }
771 
772 /**
773  * \brief End the current undo stack macro
774  */
776  if (!d->m_undoAware)
777  return;
778 
779  QUndoStack* stack = undoStack();
780  if (stack)
781  stack->endMacro();
782 }
783 
784 ////////////////////////////////////////////////////////////////////////////////////////////////////
785 //@}
786 ////////////////////////////////////////////////////////////////////////////////////////////////////
787 
788 
789 /*!
790  * this function is called when the selection in ProjectExplorer was changed.
791  * forwards the selection/deselection to the parent aspect via emitting a signal.
792  */
794  if (s)
795  emit selected(this);
796  else
797  emit deselected(this);
798 }
799 
801  //forward the signal to the highest possible level in the parent-child hierarchy
802  //e.g. axis of a plot was selected. Don't include parent aspects here that do not
803  //need to react on the selection of children:
804  //* Folder
805  //* XYFitCurve with the child column for calculated residuals
806  //* XYSmouthCurve with the child column for calculated rough values
807  //* CantorWorksheet with the child columns for CAS variables
808  if (aspect->parentAspect()
813  emit aspect->parentAspect()->selected(aspect);
814 }
815 
817  //forward the signal to the highest possible level in the parent-child hierarchy
818  //e.g. axis of a plot was selected. Don't include parent aspects here that do not
819  //need to react on the deselection of children:
820  //* Folder
821  //* XYFitCurve with the child column for calculated residuals
822  //* XYSmouthCurve with the child column for calculated rough values
823  //* CantorWorksheet with the child columns for CAS variables
824  if (aspect->parentAspect()
829  emit aspect->parentAspect()->deselected(aspect);
830 }
831 
832 /**
833  * \brief Make the specified name unique among my children by incrementing a trailing number.
834  */
835 QString AbstractAspect::uniqueNameFor(const QString& current_name) const {
836  QStringList child_names;
837  for (auto* child : children())
838  child_names << child->name();
839 
840  if (!child_names.contains(current_name))
841  return current_name;
842 
843  QString base = current_name;
844  int last_non_digit;
845  for (last_non_digit = base.size() - 1; last_non_digit >= 0; --last_non_digit) {
846  if (base[last_non_digit].category() == QChar::Number_DecimalDigit) {
847  base.chop(1);
848  } else {
849  if (base[last_non_digit].category() == QChar::Separator_Space)
850  break;
851  else {
852  //non-digit character is found and it's not the separator,
853  //the string either doesn't have any digits at all or is of
854  //the form "data_2020.06". In this case we don't use anything
855  //from the original name to increment the number
856  last_non_digit = 0;
857  base = current_name;
858  break;
859  }
860  }
861  }
862 
863  if (last_non_digit >=0 && base[last_non_digit].category() != QChar::Separator_Space)
864  base.append(" ");
865 
866  int new_nr = current_name.rightRef(current_name.size() - base.size()).toInt();
867  QString new_name;
868  do
869  new_name = base + QString::number(++new_nr);
870  while (child_names.contains(new_name));
871 
872  return new_name;
873 }
874 
885 
888 }
889 
890 //##############################################################################
891 //###################### Private implementation ###############################
892 //##############################################################################
894  : m_name(name.isEmpty() ? QLatin1String("1") : name), q(owner) {
895  m_creation_time = QDateTime::currentDateTime();
896 }
897 
899  for (auto* child : m_children)
900  delete child;
901 }
902 
904  m_children.insert(index, child);
905 
906  // Always remove from any previous parent before adding to a new one!
907  // Can't handle this case here since two undo commands have to be created.
908  Q_ASSERT(child->parentAspect() == nullptr);
909  child->setParentAspect(q);
910  q->connectChild(child);
911 }
912 
914  for (int i = 0; i < m_children.size(); ++i)
915  if (m_children.at(i) == child) return i;
916 
917  return -1;
918 }
919 
921  int index = indexOfChild(child);
922  Q_ASSERT(index != -1);
923  m_children.removeAll(child);
924  QObject::disconnect(child, nullptr, q, nullptr);
925  child->setParentAspect(nullptr);
926  return index;
927 }
AspectType
int removeChild(AbstractAspect *)
AbstractAspect *const q
Definition: AspectPrivate.h:53
AbstractAspect * m_parent
Definition: AspectPrivate.h:54
void insertChild(int index, AbstractAspect *)
QVector< AbstractAspect * > m_children
Definition: AspectPrivate.h:48
AbstractAspectPrivate(AbstractAspect *owner, const QString &name)
int indexOfChild(const AbstractAspect *) const
Base class of all persistent objects in a Project.
void setIsLoading(bool)
virtual bool load(XmlStreamReader *, bool preview)=0
Load from XML.
void info(const QString &text)
Implementations should call this whenever status information should be given to the user.
virtual QIcon icon() const
Return an icon to be used for decorating my views.
AspectType type() const
void setUndoAware(bool)
void aspectAdded(const AbstractAspect *)
Emitted after a new Aspect has been added to the tree.
bool readCommentElement(XmlStreamReader *)
Load comment from an XML element.
void reparent(AbstractAspect *newParent, int newIndex=-1)
Move a child to another parent aspect and transfer ownership.
AbstractAspect(const QString &name, AspectType type)
Folder * folder()
Return the folder the Aspect is contained in or 0 if there is none.
@ Recursive
Recursively handle all descendents, not just immediate children.
@ IncludeHidden
Include aspects marked as "hidden" in numbering or listing children.
@ Compress
Remove all null pointers from the result list.
void aspectHiddenAboutToChange(const AbstractAspect *)
Emitted before the hidden attribute is changed.
void aspectDescriptionChanged(const AbstractAspect *)
Emitted after the name, comment or caption spec have changed.
virtual void childDeselected(const AbstractAspect *)
void addChildFast(AbstractAspect *)
Add the given Aspect to my list of children without any checks and without putting this step onto the...
void addChild(AbstractAspect *)
Add the given Aspect to my list of children.
bool isDescendantOf(AbstractAspect *other)
Return whether the there is a path upwards to the given aspect.
AbstractAspect * parent(AspectType type) const
In the parent-child hierarchy, return the first parent of type.
friend class AspectChildRemoveCmd
void aspectAboutToBeRemoved(const AbstractAspect *)
Emitted before an aspect is removed from its parent.
void remove()
Remove me from my parent's list of children.
virtual bool isDraggable() const
void deselected(const AbstractAspect *)
void setCreationTime(const QDateTime &)
void aspectHiddenChanged(const AbstractAspect *)
Emitted after the hidden attribute has changed.
QString name() const
const QVector< AbstractAspect * > & children() const
void aspectAboutToBeAdded(const AbstractAspect *parent, const AbstractAspect *before, const AbstractAspect *child)
Emitted before a new child is inserted.
void statusInfo(const QString &)
Emitted whenever some aspect in the tree wants to give status information to the user.
int childCount(ChildIndexFlags flags={}) const
Return the number of child Aspects inheriting from given class.
void connectChild(AbstractAspect *)
virtual QMenu * createContextMenu()
Return a new context menu.
QString comment() const
bool hidden() const
void writeBasicAttributes(QXmlStreamWriter *) const
Save name and creation time to XML.
void renameRequested()
void selected(const AbstractAspect *)
bool inherits(AspectType type) const
QString uniqueNameFor(const QString &) const
Make the specified name unique among my children by incrementing a trailing number.
T * child(int index, ChildIndexFlags flags={}) const
bool setName(const QString &, bool autoUnique=true)
AbstractAspect::setName sets the name of the abstract aspect.
void setParentAspect(AbstractAspect *)
void aspectRemoved(const AbstractAspect *parent, const AbstractAspect *before, const AbstractAspect *child)
Emitted from the parent after removing a child.
AbstractAspect * parentAspect() const
Return my parent Aspect or 0 if I currently don't have one.
void beginMacro(const QString &text)
Begin an undo stack macro (series of commands)
void insertChildBeforeFast(AbstractAspect *child, AbstractAspect *before)
Insert the given Aspect at a specific position in my list of children.without any checks and without ...
QDateTime creationTime() const
void setSelected(bool)
virtual QString path() const
Return the path that leads from the top-most Aspect (usually a Project) to me.
void exec(QUndoCommand *)
Execute the given command, pushing it on the undoStack() if available.
bool isLoading() const
friend class AspectChildAddCmd
bool readBasicAttributes(XmlStreamReader *)
Load name and creation time from XML.
void writeCommentElement(QXmlStreamWriter *) const
Save the comment to XML.
void insertChildBefore(AbstractAspect *child, AbstractAspect *before)
Insert the given Aspect at a specific position in my list of children.
virtual QVector< AbstractAspect * > dependsOn() const
const AspectType m_type
void removeChild(AbstractAspect *)
Remove the given Aspect from my list of children.
virtual void childSelected(const AbstractAspect *)
virtual QUndoStack * undoStack() const
Return the undo stack of the Project, or 0 if this Aspect is not part of a Project.
void aspectDescriptionAboutToChange(const AbstractAspect *)
Emitted before the name, comment or caption spec is changed.
~AbstractAspect() override
virtual Project * project()
Return the Project this Aspect belongs to, or 0 if it is currently not part of one.
void setComment(const QString &)
AbstractAspectPrivate * d
void endMacro()
End the current undo stack macro.
void setHidden(bool)
Set "hidden" property, i.e. whether to exclude this aspect from being shown in the explorer.
void removeAllChildren()
Remove all child Aspects.
virtual QVector< AspectType > dropableOn() const
Aspect that manages a column.
Definition: Column.h:42
Top-level container for Curve-Point and Datasheet/Spreadsheet of datapicker.
Folder in a project.
Definition: Folder.h:35
Represents data stored in a file. Reading and writing is done with the help of appropriate I/O-filter...
The MQTT Client connects to the broker set in ImportFileWidget. It manages the MQTTSubscriptions,...
Definition: MQTTClient.h:48
void pauseReading()
Pause the reading from messages.
Definition: MQTTClient.cpp:97
Represents a subscription made in a MQTTClient object. It plays a role in managing MQTTTopic objects ...
Represents a topic of a subscription made in MQTTClient.
Definition: MQTTTopic.h:39
Represents a project.
Definition: Project.h:42
void setChanged(const bool value=true)
Definition: Project.cpp:193
Generic undo command changing a single variable.
An undo command calling a method/signal/slot on a QObject on redo/undo.
Aspect providing a spreadsheet table with column logic.
Definition: Spreadsheet.h:40
XML stream parser that supports errors as well as warnings. This class also adds line and column numb...
void raiseWarning(const QString &)
#define i18n(m)
Definition: nsl_common.h:38