1 /***************************************************************************
2     File                 : AbstractAspect.cpp
3     Project              : SciDAVis
4     --------------------------------------------------------------------
5     Copyright            : (C) 2007 by Knut Franke, Tilman Benkert
6     Email (use @ for *)  : knut.franke*gmx.de, thzs*gmx.net
7     Description          : Base class for all persistent objects in a Project.
8 
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 #include "core/AbstractAspect.h"
30 #include "core/AspectPrivate.h"
31 #include "core/aspectcommands.h"
32 #include "core/future_Folder.h"
33 #include "lib/XmlStreamReader.h"
34 
35 #include <QIcon>
36 #include <QMenu>
37 #include <QMessageBox>
38 #include <QStyle>
39 #include <QApplication>
40 #include <QXmlStreamWriter>
41 
AbstractAspect(const QString & name)42 AbstractAspect::AbstractAspect(const QString &name) : d_aspect_private(new Private(this, name)) { }
43 
~AbstractAspect()44 AbstractAspect::~AbstractAspect()
45 {
46     delete d_aspect_private;
47 }
48 
writeCommentElement(QXmlStreamWriter * writer) const49 void AbstractAspect::writeCommentElement(QXmlStreamWriter *writer) const
50 {
51     writer->writeStartElement("comment");
52     QString temp = comment();
53     temp.replace("\n", "\\n");
54     writer->writeCDATA(temp);
55     writer->writeEndElement();
56 }
57 
readCommentElement(XmlStreamReader * reader)58 bool AbstractAspect::readCommentElement(XmlStreamReader *reader)
59 {
60     Q_ASSERT(reader->isStartElement() && reader->name() == "comment");
61     QString temp = reader->readElementText();
62     temp.replace("\\n", "\n");
63     setComment(temp);
64     return true;
65 }
66 
writeBasicAttributes(QXmlStreamWriter * writer) const67 void AbstractAspect::writeBasicAttributes(QXmlStreamWriter *writer) const
68 {
69     writer->writeAttribute("creation_time", creationTime().toString("yyyy-dd-MM hh:mm:ss:zzz"));
70     writer->writeAttribute("caption_spec", captionSpec());
71     writer->writeAttribute("name", name());
72 }
73 
readBasicAttributes(XmlStreamReader * reader)74 bool AbstractAspect::readBasicAttributes(XmlStreamReader *reader)
75 {
76     QString prefix(tr("XML read error: ", "prefix for XML error messages"));
77     QString postfix(tr(" (non-critical)", "postfix for XML error messages"));
78 
79     QXmlStreamAttributes attribs = reader->attributes();
80     QString str;
81 
82     // read name
83     str = attribs.value(reader->namespaceUri().toString(), "name").toString();
84     if (str.isEmpty()) {
85         reader->raiseWarning(prefix + tr("aspect name missing or empty") + postfix);
86     }
87     setName(str);
88     // read creation time
89     str = attribs.value(reader->namespaceUri().toString(), "creation_time").toString();
90     QDateTime creation_time = QDateTime::fromString(str, "yyyy-dd-MM hh:mm:ss:zzz");
91     if (str.isEmpty() || !creation_time.isValid()) {
92         reader->raiseWarning(tr("Invalid creation time for '%1'. Using current time.").arg(name()));
93         setCreationTime(QDateTime::currentDateTime());
94     } else
95         setCreationTime(creation_time);
96     // read caption spec
97     str = attribs.value(reader->namespaceUri().toString(), "caption_spec").toString();
98     setCaptionSpec(str);
99 
100     return true;
101 }
102 
parentAspect() const103 AbstractAspect *AbstractAspect::parentAspect() const
104 {
105     return d_aspect_private->parent();
106 }
107 
addChild(AbstractAspect * child)108 void AbstractAspect::addChild(AbstractAspect *child)
109 {
110     Q_CHECK_PTR(child);
111     QString new_name = d_aspect_private->uniqueNameFor(child->name());
112     beginMacro(tr("%1: add %2.").arg(name()).arg(new_name));
113     if (new_name != child->name()) {
114         info(tr("Renaming \"%1\" to \"%2\" in order to avoid name collision.")
115                      .arg(child->name())
116                      .arg(new_name));
117         child->setName(new_name);
118     }
119     exec(new AspectChildAddCmd(d_aspect_private, child, d_aspect_private->childCount()));
120     completeAspectInsertion(child, d_aspect_private->childCount() - 1);
121     endMacro();
122 }
123 
insertChild(AbstractAspect * child,int index)124 void AbstractAspect::insertChild(AbstractAspect *child, int index)
125 {
126     Q_CHECK_PTR(child);
127     QString new_name = d_aspect_private->uniqueNameFor(child->name());
128     beginMacro(tr("%1: insert %2 at position %3.").arg(name()).arg(new_name).arg(index + 1));
129     if (new_name != child->name()) {
130         info(tr("Renaming \"%1\" to \"%2\" in order to avoid name collision.")
131                      .arg(child->name())
132                      .arg(new_name));
133         child->setName(new_name);
134     }
135     exec(new AspectChildAddCmd(d_aspect_private, child, index));
136     completeAspectInsertion(child, index);
137     endMacro();
138 }
139 
removeChild(AbstractAspect * child,bool detach)140 void AbstractAspect::removeChild(AbstractAspect *child, bool detach)
141 {
142     Q_ASSERT(indexOfChild(child) != -1);
143     beginMacro(tr("%1: remove %2.").arg(name()).arg(child->name()));
144     prepareAspectRemoval(child);
145     exec(new AspectChildRemoveCmd(d_aspect_private, child, detach));
146     endMacro();
147 }
148 
reparentChild(AbstractAspect * new_parent,AbstractAspect * child)149 void AbstractAspect::reparentChild(AbstractAspect *new_parent, AbstractAspect *child)
150 {
151     Q_ASSERT(new_parent != NULL);
152     reparentChild(new_parent, child, new_parent->childCount());
153 }
154 
reparentChild(AbstractAspect * new_parent,AbstractAspect * child,int new_index)155 void AbstractAspect::reparentChild(AbstractAspect *new_parent, AbstractAspect *child, int new_index)
156 {
157     Q_ASSERT(indexOfChild(child) != -1);
158     Q_ASSERT(new_index > 0 && new_index <= new_parent->childCount());
159     Q_ASSERT(new_parent != NULL);
160     QString new_name = new_parent->d_aspect_private->uniqueNameFor(child->name());
161     beginMacro(tr("%1: move %2 to %3.").arg(name()).arg(child->name()).arg(new_parent->name()));
162     if (new_name != child->name()) {
163         info(tr("Renaming \"%1\" to \"%2\" in order to avoid name collision.")
164                      .arg(child->name())
165                      .arg(new_name));
166         child->setName(new_name);
167     }
168     prepareAspectRemoval(child);
169     exec(new AspectChildReparentCmd(d_aspect_private, new_parent->d_aspect_private, child,
170                                     new_index));
171     new_parent->completeAspectInsertion(child, new_index);
172     endMacro();
173 }
174 
removeChild(int index)175 void AbstractAspect::removeChild(int index)
176 {
177     Q_ASSERT(index >= 0 && index <= childCount());
178     removeChild(d_aspect_private->child(index));
179 }
180 
child(int index) const181 AbstractAspect *AbstractAspect::child(int index) const
182 {
183     Q_ASSERT(index >= 0 && index <= childCount());
184     return d_aspect_private->child(index);
185 }
186 
childCount() const187 int AbstractAspect::childCount() const
188 {
189     return d_aspect_private->childCount();
190 }
191 
indexOfChild(const AbstractAspect * child) const192 int AbstractAspect::indexOfChild(const AbstractAspect *child) const
193 {
194     return d_aspect_private->indexOfChild(child);
195 }
196 
moveChild(int from,int to)197 void AbstractAspect::moveChild(int from, int to)
198 {
199     Q_ASSERT(0 <= from && from < d_aspect_private->childCount());
200     Q_ASSERT(0 <= to && to < d_aspect_private->childCount());
201     exec(new AspectChildMoveCmd(d_aspect_private, from, to));
202 }
203 
exec(QUndoCommand * cmd)204 void AbstractAspect::exec(QUndoCommand *cmd)
205 {
206     Q_CHECK_PTR(cmd);
207     QUndoStack *stack = undoStack();
208     if (stack)
209         stack->push(cmd);
210     else {
211         cmd->redo();
212         delete cmd;
213     }
214 }
215 
beginMacro(const QString & text)216 void AbstractAspect::beginMacro(const QString &text)
217 {
218     QUndoStack *stack = undoStack();
219     if (stack)
220         stack->beginMacro(text);
221 }
222 
endMacro()223 void AbstractAspect::endMacro()
224 {
225     QUndoStack *stack = undoStack();
226     if (stack)
227         stack->endMacro();
228 }
229 
name() const230 QString AbstractAspect::name() const
231 {
232     return d_aspect_private->name();
233 }
234 
setName(const QString & value)235 void AbstractAspect::setName(const QString &value)
236 {
237     if (value.isEmpty()) {
238         setObjectName("1");
239         return;
240     }
241     if (value == d_aspect_private->name())
242         return;
243     // Until we get around to completely sanitizing the project file format, we have to remove
244     // characters that can easily break file save/restore.
245     // FIXME: once the project file format is fully XML-based (i.e. able to escape special
246     // characters), this can be removed
247     QString sanitized_value = value;
248     sanitized_value.remove(QChar('\n'));
249     sanitized_value.remove(QChar('\r'));
250     sanitized_value.remove(QChar('\t'));
251     if (sanitized_value != value)
252         info(tr("Tabs and line breaks in object names are currently not supported. They have been "
253                 "removed."));
254     if (d_aspect_private->parent()) {
255         QString new_name = d_aspect_private->parent()->uniqueNameFor(sanitized_value);
256         if (new_name != sanitized_value)
257             info(tr("Intended name \"%1\" diverted to \"%2\" in order to avoid name collision.")
258                          .arg(sanitized_value)
259                          .arg(new_name));
260         exec(new AspectNameChangeCmd(d_aspect_private, new_name));
261     } else
262         exec(new AspectNameChangeCmd(d_aspect_private, sanitized_value));
263 }
264 
comment() const265 QString AbstractAspect::comment() const
266 {
267     return d_aspect_private->comment();
268 }
269 
setComment(const QString & value)270 void AbstractAspect::setComment(const QString &value)
271 {
272     if (value == d_aspect_private->comment())
273         return;
274     exec(new AspectCommentChangeCmd(d_aspect_private, value));
275 }
276 
captionSpec() const277 QString AbstractAspect::captionSpec() const
278 {
279     return d_aspect_private->captionSpec();
280 }
281 
setCaptionSpec(const QString & value)282 void AbstractAspect::setCaptionSpec(const QString &value)
283 {
284     if (value == d_aspect_private->captionSpec())
285         return;
286     exec(new AspectCaptionSpecChangeCmd(d_aspect_private, value));
287 }
288 
setCreationTime(const QDateTime & time)289 void AbstractAspect::setCreationTime(const QDateTime &time)
290 {
291     if (time == d_aspect_private->creationTime())
292         return;
293     exec(new AspectCreationTimeChangeCmd(d_aspect_private, time));
294 }
295 
creationTime() const296 QDateTime AbstractAspect::creationTime() const
297 {
298     return d_aspect_private->creationTime();
299 }
300 
caption() const301 QString AbstractAspect::caption() const
302 {
303     return d_aspect_private->caption();
304 }
305 
icon() const306 QIcon AbstractAspect::icon() const
307 {
308     return QIcon();
309 }
310 
createContextMenu() const311 QMenu *AbstractAspect::createContextMenu() const
312 {
313     QMenu *menu = new QMenu();
314 
315     const QStyle *widget_style = qApp->style();
316     QAction *action_temp;
317 
318     action_temp = menu->addAction(QObject::tr("&Remove"), this, SLOT(remove()));
319     action_temp->setIcon(widget_style->standardIcon(QStyle::SP_TrashIcon));
320 
321     return menu;
322 }
323 
folder()324 future::Folder *AbstractAspect::folder()
325 {
326     if (inherits("future::Folder"))
327         return static_cast<future::Folder *>(this);
328     AbstractAspect *parent_aspect = parentAspect();
329     while (parent_aspect && !parent_aspect->inherits("future::Folder"))
330         parent_aspect = parent_aspect->parentAspect();
331     return static_cast<future::Folder *>(parent_aspect);
332 }
333 
isDescendantOf(AbstractAspect * other)334 bool AbstractAspect::isDescendantOf(AbstractAspect *other)
335 {
336     if (other == this)
337         return true;
338     AbstractAspect *parent_aspect = parentAspect();
339     while (parent_aspect) {
340         if (parent_aspect == other)
341             return true;
342         parent_aspect = parent_aspect->parentAspect();
343     }
344     return false;
345 }
346 
uniqueNameFor(const QString & current_name) const347 QString AbstractAspect::uniqueNameFor(const QString &current_name) const
348 {
349     return d_aspect_private->uniqueNameFor(current_name);
350 }
351 
descendantsThatInherit(const char * class_name)352 QList<AbstractAspect *> AbstractAspect::descendantsThatInherit(const char *class_name)
353 {
354     QList<AbstractAspect *> list;
355     if (inherits(class_name))
356         list << this;
357     for (int i = 0; i < childCount(); i++)
358         list << child(i)->descendantsThatInherit(class_name);
359     return list;
360 }
361 
removeAllChildAspects()362 void AbstractAspect::removeAllChildAspects()
363 {
364     beginMacro(tr("%1: remove all children.").arg(name()));
365     for (int i = childCount() - 1; i >= 0; i--)
366         removeChild(i);
367     endMacro();
368 }
369 
global(const QString & key)370 QVariant AbstractAspect::global(const QString &key)
371 {
372     QString qualified_key = QString(staticMetaObject.className()) + "/" + key;
373     QVariant result = ApplicationWindow::getSettings().value(qualified_key);
374     if (result.isValid())
375         return result;
376     else
377         return Private::g_defaults[qualified_key];
378 }
379 
setGlobal(const QString & key,const QVariant & value)380 void AbstractAspect::setGlobal(const QString &key, const QVariant &value)
381 {
382     ApplicationWindow::getSettings().setValue(QString(staticMetaObject.className()) + "/" + key,
383                                               value);
384 }
385 
setGlobalDefault(const QString & key,const QVariant & value)386 void AbstractAspect::setGlobalDefault(const QString &key, const QVariant &value)
387 {
388     Private::g_defaults[QString(staticMetaObject.className()) + "/" + key] = value;
389 }
390