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