1 /***************************************************************************
2  * actioncollection.cpp
3  * This file is part of the KDE project
4  * copyright (C)2004-2006 by Sebastian Sauer (mail@dipe.org)
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  * You should have received a copy of the GNU Library General Public License
15  * along with this program; see the file COPYING.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  ***************************************************************************/
19 
20 #include "actioncollection.h"
21 #include "manager.h"
22 #include "kross_debug.h"
23 
24 #include <QHash>
25 #include <QStringList>
26 #include <QPointer>
27 #include <QIODevice>
28 #include <QFile>
29 #include <QFileInfo>
30 #include <QDomAttr>
31 
32 #include <klocalizedstring.h>
33 
34 using namespace Kross;
35 
36 namespace Kross
37 {
38 
39 /// \internal d-pointer class.
40 class ActionCollection::Private
41 {
42 public:
43     QPointer<ActionCollection> parent;
44     QHash< QString, QPointer<ActionCollection> > collections;
45     QStringList collectionnames;
46 
47     QList< Action * > actionList;
48     QHash< QString, Action * > actionMap;
49 
50     QString text;
51     QString description;
52     QString iconname;
53     bool enabled;
54     bool blockupdated;
55 
Private(ActionCollection * const p)56     Private(ActionCollection *const p) : parent(p) {}
57 };
58 
59 }
60 
ActionCollection(const QString & name,ActionCollection * parent)61 ActionCollection::ActionCollection(const QString &name, ActionCollection *parent)
62     : QObject(nullptr)
63     , d(new Private(nullptr))
64 {
65     setObjectName(name);
66     d->text = name;
67     d->enabled = true;
68     d->blockupdated = false;
69 
70     setParentCollection(parent);
71 }
72 
~ActionCollection()73 ActionCollection::~ActionCollection()
74 {
75     if (d->parent) {
76         emit d->parent->collectionToBeRemoved(this, d->parent);
77         d->parent->unregisterCollection(objectName());
78         emit d->parent->collectionRemoved(this, d->parent);
79     }
80     delete d;
81 }
82 
name() const83 QString ActionCollection::name() const
84 {
85     return objectName();
86 }
87 
text() const88 QString ActionCollection::text() const
89 {
90     return d->text;
91 }
setText(const QString & text)92 void ActionCollection::setText(const QString &text)
93 {
94     d->text = text;
95     emit dataChanged(this);
96     emitUpdated();
97 }
98 
description() const99 QString ActionCollection::description() const
100 {
101     return d->description;
102 }
setDescription(const QString & description)103 void ActionCollection::setDescription(const QString &description)
104 {
105     d->description = description;
106     emit dataChanged(this);
107     emitUpdated();
108 }
109 
iconName() const110 QString ActionCollection::iconName() const
111 {
112     return d->iconname;
113 }
setIconName(const QString & iconname)114 void ActionCollection::setIconName(const QString &iconname)
115 {
116     d->iconname = iconname;
117     emit dataChanged(this);
118 }
icon() const119 QIcon ActionCollection::icon() const
120 {
121     return QIcon::fromTheme(d->iconname);
122 }
123 
isEnabled() const124 bool ActionCollection::isEnabled() const
125 {
126     return d->enabled;
127 }
setEnabled(bool enabled)128 void ActionCollection::setEnabled(bool enabled)
129 {
130     d->enabled = enabled;
131     emit dataChanged(this);
132     emitUpdated();
133 }
134 
parentCollection() const135 ActionCollection *ActionCollection::parentCollection() const
136 {
137     return d->parent;
138 }
139 
setParentCollection(ActionCollection * parent)140 void ActionCollection::setParentCollection(ActionCollection *parent)
141 {
142     if (d->parent) {
143         emit d->parent->collectionToBeRemoved(this, d->parent);
144         d->parent->unregisterCollection(objectName());
145         setParent(nullptr);
146         emit d->parent->collectionRemoved(this, d->parent);
147         d->parent = nullptr;
148     }
149     setParent(nullptr);
150     if (parent) {
151         emit parent->collectionToBeInserted(this, parent);
152         setParent(parent);
153         d->parent = parent;
154         parent->registerCollection(this);
155         emit parent->collectionInserted(this, parent);
156     }
157     emitUpdated();
158 }
159 
hasCollection(const QString & name) const160 bool ActionCollection::hasCollection(const QString &name) const
161 {
162     return d->collections.contains(name);
163 }
164 
collection(const QString & name) const165 ActionCollection *ActionCollection::collection(const QString &name) const
166 {
167     return d->collections.contains(name) ? d->collections[name] : QPointer<ActionCollection>(nullptr);
168 }
169 
collections() const170 QStringList ActionCollection::collections() const
171 {
172     return d->collectionnames;
173 }
174 
registerCollection(ActionCollection * collection)175 void ActionCollection::registerCollection(ActionCollection *collection)
176 {
177     Q_ASSERT(collection);
178     const QString name = collection->objectName();
179     //Q_ASSERT( !name.isNull() );
180     if (!d->collections.contains(name)) {
181         d->collections.insert(name, collection);
182         d->collectionnames.append(name);
183     }
184     connectSignals(collection, true);
185     emitUpdated();
186 }
187 
unregisterCollection(const QString & name)188 void ActionCollection::unregisterCollection(const QString &name)
189 {
190     if (! d->collections.contains(name)) {
191         return;
192     }
193     ActionCollection *collection = d->collections[name];
194     d->collectionnames.removeAll(name);
195     d->collections.remove(name);
196     connectSignals(collection, false);
197     emitUpdated();
198 }
199 
actions() const200 QList<Action *> ActionCollection::actions() const
201 {
202     return d->actionList;
203 }
204 
action(const QString & name) const205 Action *ActionCollection::action(const QString &name) const
206 {
207     return d->actionMap.contains(name) ? d->actionMap[name] : nullptr;
208 }
209 
addAction(Action * action)210 void ActionCollection::addAction(Action *action)
211 {
212     Q_ASSERT(action && ! action->objectName().isEmpty());
213     addAction(action->objectName(), action);
214 }
215 
addAction(const QString & name,Action * action)216 void ActionCollection::addAction(const QString &name, Action *action)
217 {
218     Q_ASSERT(action && ! name.isEmpty());
219     emit actionToBeInserted(action, this);
220     if (d->actionMap.contains(name)) {
221         d->actionList.removeAll(d->actionMap[name]);
222     }
223     d->actionMap.insert(name, action);
224     d->actionList.append(action);
225     action->setParent(this); // in case it is not set
226     connectSignals(action, true);
227     emit actionInserted(action, this);
228     emitUpdated();
229 }
230 
removeAction(const QString & name)231 void ActionCollection::removeAction(const QString &name)
232 {
233     if (! d->actionMap.contains(name)) {
234         return;
235     }
236     Action *action = d->actionMap[name];
237     connectSignals(action, false);
238     emit actionToBeRemoved(action, this);
239     d->actionList.removeAll(action);
240     d->actionMap.remove(name);
241     //krossdebug( QString("ActionCollection::removeAction: %1 %2").arg(action->name()).arg(action->parent()->objectName()) );
242     action->setParent(nullptr);
243     emit actionRemoved(action, this);
244     emitUpdated();
245 }
246 
removeAction(Action * action)247 void ActionCollection::removeAction(Action *action)
248 {
249     Q_ASSERT(action && ! action->objectName().isEmpty());
250     if (! d->actionMap.contains(action->objectName())) {
251         Q_ASSERT(! d->actionList.contains(action));
252         return;
253     }
254     removeAction(action->objectName());
255 }
256 
connectSignals(Action * action,bool conn)257 void ActionCollection::connectSignals(Action *action, bool conn)
258 {
259     if (conn) {
260         connect(action, SIGNAL(dataChanged(Action*)), this, SIGNAL(dataChanged(Action*)));
261         connect(action, SIGNAL(updated()), this, SLOT(emitUpdated()));
262     } else {
263         disconnect(action, SIGNAL(dataChanged(Action*)), this, SIGNAL(dataChanged(Action*)));
264         disconnect(action, SIGNAL(updated()), this, SLOT(emitUpdated()));
265     }
266 }
267 
connectSignals(ActionCollection * collection,bool conn)268 void ActionCollection::connectSignals(ActionCollection *collection, bool conn)
269 {
270     if (conn) {
271         connect(collection, SIGNAL(dataChanged(Action*)), this, SIGNAL(dataChanged(Action*)));
272         connect(collection, SIGNAL(dataChanged(ActionCollection*)), this, SIGNAL(dataChanged(ActionCollection*)));
273 
274         connect(collection, SIGNAL(collectionToBeInserted(ActionCollection*,ActionCollection*)), this, SIGNAL(collectionToBeInserted(ActionCollection*,ActionCollection*)));
275         connect(collection, SIGNAL(collectionInserted(ActionCollection*,ActionCollection*)), this, SIGNAL(collectionInserted(ActionCollection*,ActionCollection*)));
276         connect(collection, SIGNAL(collectionToBeRemoved(ActionCollection*,ActionCollection*)), this, SIGNAL(collectionToBeRemoved(ActionCollection*,ActionCollection*)));
277         connect(collection, SIGNAL(collectionRemoved(ActionCollection*,ActionCollection*)), this, SIGNAL(collectionRemoved(ActionCollection*,ActionCollection*)));
278 
279         connect(collection, SIGNAL(actionToBeInserted(Action*,ActionCollection*)), this, SIGNAL(actionToBeInserted(Action*,ActionCollection*)));
280         connect(collection, SIGNAL(actionInserted(Action*,ActionCollection*)), this, SIGNAL(actionInserted(Action*,ActionCollection*)));
281         connect(collection, SIGNAL(actionToBeRemoved(Action*,ActionCollection*)), this, SIGNAL(actionToBeRemoved(Action*,ActionCollection*)));
282         connect(collection, SIGNAL(actionRemoved(Action*,ActionCollection*)), this, SIGNAL(actionRemoved(Action*,ActionCollection*)));
283         connect(collection, SIGNAL(updated()), this, SLOT(emitUpdated()));
284     } else {
285         disconnect(collection, SIGNAL(dataChanged(ActionCollection*)), this, SIGNAL(dataChanged(ActionCollection*)));
286 
287         disconnect(collection, SIGNAL(collectionToBeInserted(ActionCollection*,ActionCollection*)), this, SIGNAL(collectionToBeInserted(ActionCollection*,ActionCollection*)));
288         disconnect(collection, SIGNAL(collectionInserted(ActionCollection*,ActionCollection*)), this, SIGNAL(collectionInserted(ActionCollection*,ActionCollection*)));
289         disconnect(collection, SIGNAL(collectionToBeRemoved(ActionCollection*,ActionCollection*)), this, SIGNAL(collectionToBeRemoved(ActionCollection*,ActionCollection*)));
290         disconnect(collection, SIGNAL(collectionRemoved(ActionCollection*,ActionCollection*)), this, SIGNAL(collectionRemoved(ActionCollection*,ActionCollection*)));
291 
292         disconnect(collection, SIGNAL(actionToBeInserted(Action*,ActionCollection*)), this, SIGNAL(actionToBeInserted(Action*,ActionCollection*)));
293         disconnect(collection, SIGNAL(actionInserted(Action*,ActionCollection*)), this, SIGNAL(actionInserted(Action*,ActionCollection*)));
294         disconnect(collection, SIGNAL(actionToBeRemoved(Action*,ActionCollection*)), this, SIGNAL(actionToBeRemoved(Action*,ActionCollection*)));
295         disconnect(collection, SIGNAL(actionRemoved(Action*,ActionCollection*)), this, SIGNAL(actionRemoved(Action*,ActionCollection*)));
296         disconnect(collection, SIGNAL(updated()), this, SLOT(emitUpdated()));
297     }
298 }
299 
emitUpdated()300 void ActionCollection::emitUpdated()
301 {
302     if (!d->blockupdated) {
303         emit updated();
304     }
305 }
306 
307 /*********************************************************************
308  * Unserialize from XML / QIODevice / file / resource to child
309  * ActionCollection's and Action's this ActionCollection has.
310  */
311 
readXml(const QDomElement & element,const QDir & directory)312 bool ActionCollection::readXml(const QDomElement &element, const QDir &directory)
313 {
314     return readXml(element, QStringList(directory.absolutePath()));
315 }
316 
readXml(const QDomElement & element,const QStringList & searchPath)317 bool ActionCollection::readXml(const QDomElement &element, const QStringList &searchPath)
318 {
319 #ifdef KROSS_ACTIONCOLLECTION_DEBUG
320     qCDebug(KROSS_LOG) << "ActionCollection::readXml tagName=\"" << element.tagName() << "\"";
321 #endif
322 
323     d->blockupdated = true; // block updated() signals and emit it only once if everything is done
324     bool ok = true;
325     QDomNodeList list = element.childNodes();
326     const int size = list.size();
327     for (int i = 0; i < size; ++i) {
328         QDomElement elem = list.item(i).toElement();
329         if (elem.isNull()) {
330             continue;
331         }
332 
333 #ifdef KROSS_ACTIONCOLLECTION_DEBUG
334         qCDebug(KROSS_LOG) << "  ActionCollection::readXml child=" <<
335             i << " tagName=\"" << elem.tagName() << "\"";
336 #endif
337 
338         if (elem.tagName() == "collection") {
339             const QString name = elem.attribute("name");
340             const QByteArray text = elem.attribute("text").toUtf8();
341             const QByteArray description = elem.attribute("comment").toUtf8();
342             const QString iconname = elem.attribute("icon");
343             bool enabled = QVariant(elem.attribute("enabled", "true")).toBool();
344             ActionCollection *c = d->collections.contains(name) ? d->collections[name] : QPointer<ActionCollection>(nullptr);
345             if (! c) {
346                 c = new ActionCollection(name, this);
347             }
348 
349             c->setText(text.isEmpty() ? name : i18nd(KLocalizedString::applicationDomain().constData(), text.constData()));
350             c->setDescription(description.isEmpty() ? c->text() : i18nd(KLocalizedString::applicationDomain().constData(), description.constData()));
351             c->setIconName(iconname);
352 
353             if (! enabled) {
354                 c->setEnabled(false);
355             }
356             if (! c->readXml(elem, searchPath)) {
357                 ok = false;
358             }
359         } else if (elem.tagName() == "script") {
360             QString name = elem.attribute("name");
361             Action *a = dynamic_cast< Action * >(action(name));
362             if (a) {
363 #ifdef KROSS_ACTIONCOLLECTION_DEBUG
364                 qCDebug(KROSS_LOG) << "  ActionCollection::readXml Updating Action " << a->objectName();
365 #endif
366             } else {
367 #ifdef KROSS_ACTIONCOLLECTION_DEBUG
368                 qCDebug(KROSS_LOG) << "  ActionCollection::readXml Creating Action " << name;
369 #endif
370 
371                 a = new Action(this, name);
372                 addAction(name, a);
373                 connect(a, SIGNAL(started(Kross::Action*)), &Manager::self(), SIGNAL(started(Kross::Action*)));
374                 connect(a, SIGNAL(finished(Kross::Action*)), &Manager::self(), SIGNAL(finished(Kross::Action*)));
375             }
376             a->fromDomElement(elem, searchPath);
377         }
378         //else if( ! fromXml(elem) ) ok = false;
379     }
380 
381     d->blockupdated = false; // unblock signals
382     emitUpdated();
383     return ok;
384 }
385 
readXml(QIODevice * device,const QDir & directory)386 bool ActionCollection::readXml(QIODevice *device, const QDir &directory)
387 {
388     return readXml(device, QStringList(directory.absolutePath()));
389 }
390 
readXml(QIODevice * device,const QStringList & searchPath)391 bool ActionCollection::readXml(QIODevice *device, const QStringList &searchPath)
392 {
393     QString errMsg;
394     int errLine, errCol;
395     QDomDocument document;
396     bool ok = document.setContent(device, false, &errMsg, &errLine, &errCol);
397     if (! ok) {
398 #ifdef KROSS_ACTIONCOLLECTION_DEBUG
399         qCWarning(KROSS_LOG) << QStringLiteral("ActionCollection::readXml Error at line %1 in col %2: %3")
400             .arg(errLine).arg(errCol).arg(errMsg);
401 #endif
402         return false;
403     }
404     return readXml(document.documentElement(), searchPath);
405 }
406 
readXmlFile(const QString & file)407 bool ActionCollection::readXmlFile(const QString &file)
408 {
409 #ifdef KROSS_ACTIONCOLLECTION_DEBUG
410     qCDebug(KROSS_LOG) << "ActionCollection::readXmlFile file=" << file;
411 #endif
412 
413     QFile f(file);
414     if (! f.open(QIODevice::ReadOnly)) {
415 #ifdef KROSS_ACTIONCOLLECTION_DEBUG
416         qCWarning(KROSS_LOG) << "ActionCollection::readXmlFile failed to read file " << file;
417 #endif
418         return false;
419     }
420     bool ok = readXml(&f, QFileInfo(file).dir());
421     f.close();
422 
423 #ifdef KROSS_ACTIONCOLLECTION_DEBUG
424     if (! ok) {
425         qCWarning(KROSS_LOG) << "ActionCollection::readXmlFile failed to parse XML content of file " << file;
426     }
427 #endif
428     return ok;
429 }
430 
431 /*********************************************************************
432  * Serialize from child ActionCollection's and Action's this
433  * ActionCollection has to XML / QIODevice / file / resource.
434  */
435 
writeXml()436 QDomElement ActionCollection::writeXml()
437 {
438     return writeXml(QStringList());
439 }
440 
writeXml(const QStringList & searchPath)441 QDomElement ActionCollection::writeXml(const QStringList &searchPath)
442 {
443 #ifdef KROSS_ACTIONCOLLECTION_DEBUG
444     qCDebug(KROSS_LOG) << "ActionCollection::writeXml collection.objectName=" << objectName();
445 #endif
446 
447     QDomDocument document;
448     QDomElement element = document.createElement("collection");
449     if (! objectName().isNull()) {
450         element.setAttribute("name", objectName());
451     }
452     if (! text().isNull() && text() != objectName()) {
453         element.setAttribute("text", text());
454     }
455     if (! d->description.isNull()) {
456         element.setAttribute("comment", d->description);
457     }
458     if (! d->iconname.isNull()) {
459         element.setAttribute("icon", d->iconname);
460     }
461     if (! d->enabled) {
462         element.setAttribute("enabled", d->enabled);
463     }
464 
465     foreach (Action *a, actions()) {
466         Q_ASSERT(a);
467 #ifdef KROSS_ACTIONCOLLECTION_DEBUG
468         qCDebug(KROSS_LOG) << "  ActionCollection::writeXml action.objectName=" <<
469             a->objectName() << " action.file=" << a->file();
470 #endif
471         QDomElement e = a->toDomElement(searchPath);
472         if (! e.isNull()) {
473             element.appendChild(e);
474         }
475     }
476 
477     foreach (const QString &name, d->collectionnames) {
478         ActionCollection *c = d->collections[name];
479         if (! c) {
480             continue;
481         }
482         QDomElement e = c->writeXml(searchPath);
483         if (! e.isNull()) {
484             element.appendChild(e);
485         }
486     }
487 
488     return element;
489 }
490 
writeXml(QIODevice * device,int indent)491 bool ActionCollection::writeXml(QIODevice *device, int indent)
492 {
493     return writeXml(device, indent, QStringList());
494 }
495 
writeXml(QIODevice * device,int indent,const QStringList & searchPath)496 bool ActionCollection::writeXml(QIODevice *device, int indent, const QStringList &searchPath)
497 {
498     QDomDocument document;
499     QDomElement root = document.createElement("KrossScripting");
500 
501     foreach (Action *a, actions()) {
502         QDomElement e = a->toDomElement(searchPath);
503         if (! e.isNull()) {
504             root.appendChild(e);
505         }
506     }
507 
508     foreach (const QString &name, d->collectionnames) {
509         ActionCollection *c = d->collections[name];
510         if (! c) {
511             continue;
512         }
513         QDomElement e = c->writeXml(searchPath);
514         if (! e.isNull()) {
515             root.appendChild(e);
516         }
517     }
518 
519     document.appendChild(root);
520     return device->write(document.toByteArray(indent)) != -1;
521 }
522 
523