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 ¤t_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