File indexing completed on 2024-03-24 15:37:24

0001 /***************************************************************************
0002  * actioncollection.cpp
0003  * This file is part of the KDE project
0004  * copyright (C)2004-2006 by Sebastian Sauer (mail@dipe.org)
0005  *
0006  * This program is free software; you can redistribute it and/or
0007  * modify it under the terms of the GNU Library General Public
0008  * License as published by the Free Software Foundation; either
0009  * version 2 of the License, or (at your option) any later version.
0010  * This program is distributed in the hope that it will be useful,
0011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013  * Library General Public License for more details.
0014  * You should have received a copy of the GNU Library General Public License
0015  * along with this program; see the file COPYING.  If not, write to
0016  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017  * Boston, MA 02110-1301, USA.
0018  ***************************************************************************/
0019 
0020 #include "actioncollection.h"
0021 #include "manager.h"
0022 #include "kross_debug.h"
0023 
0024 #include <QHash>
0025 #include <QStringList>
0026 #include <QPointer>
0027 #include <QIODevice>
0028 #include <QFile>
0029 #include <QFileInfo>
0030 #include <QDomAttr>
0031 
0032 #include <klocalizedstring.h>
0033 
0034 using namespace Kross;
0035 
0036 namespace Kross
0037 {
0038 
0039 /// \internal d-pointer class.
0040 class ActionCollection::Private
0041 {
0042 public:
0043     QPointer<ActionCollection> parent;
0044     QHash< QString, QPointer<ActionCollection> > collections;
0045     QStringList collectionnames;
0046 
0047     QList< Action * > actionList;
0048     QHash< QString, Action * > actionMap;
0049 
0050     QString text;
0051     QString description;
0052     QString iconname;
0053     bool enabled;
0054     bool blockupdated;
0055 
0056     Private(ActionCollection *const p) : parent(p) {}
0057 };
0058 
0059 }
0060 
0061 ActionCollection::ActionCollection(const QString &name, ActionCollection *parent)
0062     : QObject(nullptr)
0063     , d(new Private(nullptr))
0064 {
0065     setObjectName(name);
0066     d->text = name;
0067     d->enabled = true;
0068     d->blockupdated = false;
0069 
0070     setParentCollection(parent);
0071 }
0072 
0073 ActionCollection::~ActionCollection()
0074 {
0075     if (d->parent) {
0076         emit d->parent->collectionToBeRemoved(this, d->parent);
0077         d->parent->unregisterCollection(objectName());
0078         emit d->parent->collectionRemoved(this, d->parent);
0079     }
0080     delete d;
0081 }
0082 
0083 QString ActionCollection::name() const
0084 {
0085     return objectName();
0086 }
0087 
0088 QString ActionCollection::text() const
0089 {
0090     return d->text;
0091 }
0092 void ActionCollection::setText(const QString &text)
0093 {
0094     d->text = text;
0095     emit dataChanged(this);
0096     emitUpdated();
0097 }
0098 
0099 QString ActionCollection::description() const
0100 {
0101     return d->description;
0102 }
0103 void ActionCollection::setDescription(const QString &description)
0104 {
0105     d->description = description;
0106     emit dataChanged(this);
0107     emitUpdated();
0108 }
0109 
0110 QString ActionCollection::iconName() const
0111 {
0112     return d->iconname;
0113 }
0114 void ActionCollection::setIconName(const QString &iconname)
0115 {
0116     d->iconname = iconname;
0117     emit dataChanged(this);
0118 }
0119 QIcon ActionCollection::icon() const
0120 {
0121     return QIcon::fromTheme(d->iconname);
0122 }
0123 
0124 bool ActionCollection::isEnabled() const
0125 {
0126     return d->enabled;
0127 }
0128 void ActionCollection::setEnabled(bool enabled)
0129 {
0130     d->enabled = enabled;
0131     emit dataChanged(this);
0132     emitUpdated();
0133 }
0134 
0135 ActionCollection *ActionCollection::parentCollection() const
0136 {
0137     return d->parent;
0138 }
0139 
0140 void ActionCollection::setParentCollection(ActionCollection *parent)
0141 {
0142     if (d->parent) {
0143         emit d->parent->collectionToBeRemoved(this, d->parent);
0144         d->parent->unregisterCollection(objectName());
0145         setParent(nullptr);
0146         emit d->parent->collectionRemoved(this, d->parent);
0147         d->parent = nullptr;
0148     }
0149     setParent(nullptr);
0150     if (parent) {
0151         emit parent->collectionToBeInserted(this, parent);
0152         setParent(parent);
0153         d->parent = parent;
0154         parent->registerCollection(this);
0155         emit parent->collectionInserted(this, parent);
0156     }
0157     emitUpdated();
0158 }
0159 
0160 bool ActionCollection::hasCollection(const QString &name) const
0161 {
0162     return d->collections.contains(name);
0163 }
0164 
0165 ActionCollection *ActionCollection::collection(const QString &name) const
0166 {
0167     return d->collections.contains(name) ? d->collections[name] : QPointer<ActionCollection>(nullptr);
0168 }
0169 
0170 QStringList ActionCollection::collections() const
0171 {
0172     return d->collectionnames;
0173 }
0174 
0175 void ActionCollection::registerCollection(ActionCollection *collection)
0176 {
0177     Q_ASSERT(collection);
0178     const QString name = collection->objectName();
0179     //Q_ASSERT( !name.isNull() );
0180     if (!d->collections.contains(name)) {
0181         d->collections.insert(name, collection);
0182         d->collectionnames.append(name);
0183     }
0184     connectSignals(collection, true);
0185     emitUpdated();
0186 }
0187 
0188 void ActionCollection::unregisterCollection(const QString &name)
0189 {
0190     if (! d->collections.contains(name)) {
0191         return;
0192     }
0193     ActionCollection *collection = d->collections[name];
0194     d->collectionnames.removeAll(name);
0195     d->collections.remove(name);
0196     connectSignals(collection, false);
0197     emitUpdated();
0198 }
0199 
0200 QList<Action *> ActionCollection::actions() const
0201 {
0202     return d->actionList;
0203 }
0204 
0205 Action *ActionCollection::action(const QString &name) const
0206 {
0207     return d->actionMap.contains(name) ? d->actionMap[name] : nullptr;
0208 }
0209 
0210 void ActionCollection::addAction(Action *action)
0211 {
0212     Q_ASSERT(action && ! action->objectName().isEmpty());
0213     addAction(action->objectName(), action);
0214 }
0215 
0216 void ActionCollection::addAction(const QString &name, Action *action)
0217 {
0218     Q_ASSERT(action && ! name.isEmpty());
0219     emit actionToBeInserted(action, this);
0220     if (d->actionMap.contains(name)) {
0221         d->actionList.removeAll(d->actionMap[name]);
0222     }
0223     d->actionMap.insert(name, action);
0224     d->actionList.append(action);
0225     action->setParent(this); // in case it is not set
0226     connectSignals(action, true);
0227     emit actionInserted(action, this);
0228     emitUpdated();
0229 }
0230 
0231 void ActionCollection::removeAction(const QString &name)
0232 {
0233     if (! d->actionMap.contains(name)) {
0234         return;
0235     }
0236     Action *action = d->actionMap[name];
0237     connectSignals(action, false);
0238     emit actionToBeRemoved(action, this);
0239     d->actionList.removeAll(action);
0240     d->actionMap.remove(name);
0241     //krossdebug( QString("ActionCollection::removeAction: %1 %2").arg(action->name()).arg(action->parent()->objectName()) );
0242     action->setParent(nullptr);
0243     emit actionRemoved(action, this);
0244     emitUpdated();
0245 }
0246 
0247 void ActionCollection::removeAction(Action *action)
0248 {
0249     Q_ASSERT(action && ! action->objectName().isEmpty());
0250     if (! d->actionMap.contains(action->objectName())) {
0251         Q_ASSERT(! d->actionList.contains(action));
0252         return;
0253     }
0254     removeAction(action->objectName());
0255 }
0256 
0257 void ActionCollection::connectSignals(Action *action, bool conn)
0258 {
0259     if (conn) {
0260         connect(action, SIGNAL(dataChanged(Action*)), this, SIGNAL(dataChanged(Action*)));
0261         connect(action, SIGNAL(updated()), this, SLOT(emitUpdated()));
0262     } else {
0263         disconnect(action, SIGNAL(dataChanged(Action*)), this, SIGNAL(dataChanged(Action*)));
0264         disconnect(action, SIGNAL(updated()), this, SLOT(emitUpdated()));
0265     }
0266 }
0267 
0268 void ActionCollection::connectSignals(ActionCollection *collection, bool conn)
0269 {
0270     if (conn) {
0271         connect(collection, SIGNAL(dataChanged(Action*)), this, SIGNAL(dataChanged(Action*)));
0272         connect(collection, SIGNAL(dataChanged(ActionCollection*)), this, SIGNAL(dataChanged(ActionCollection*)));
0273 
0274         connect(collection, SIGNAL(collectionToBeInserted(ActionCollection*,ActionCollection*)), this, SIGNAL(collectionToBeInserted(ActionCollection*,ActionCollection*)));
0275         connect(collection, SIGNAL(collectionInserted(ActionCollection*,ActionCollection*)), this, SIGNAL(collectionInserted(ActionCollection*,ActionCollection*)));
0276         connect(collection, SIGNAL(collectionToBeRemoved(ActionCollection*,ActionCollection*)), this, SIGNAL(collectionToBeRemoved(ActionCollection*,ActionCollection*)));
0277         connect(collection, SIGNAL(collectionRemoved(ActionCollection*,ActionCollection*)), this, SIGNAL(collectionRemoved(ActionCollection*,ActionCollection*)));
0278 
0279         connect(collection, SIGNAL(actionToBeInserted(Action*,ActionCollection*)), this, SIGNAL(actionToBeInserted(Action*,ActionCollection*)));
0280         connect(collection, SIGNAL(actionInserted(Action*,ActionCollection*)), this, SIGNAL(actionInserted(Action*,ActionCollection*)));
0281         connect(collection, SIGNAL(actionToBeRemoved(Action*,ActionCollection*)), this, SIGNAL(actionToBeRemoved(Action*,ActionCollection*)));
0282         connect(collection, SIGNAL(actionRemoved(Action*,ActionCollection*)), this, SIGNAL(actionRemoved(Action*,ActionCollection*)));
0283         connect(collection, SIGNAL(updated()), this, SLOT(emitUpdated()));
0284     } else {
0285         disconnect(collection, SIGNAL(dataChanged(ActionCollection*)), this, SIGNAL(dataChanged(ActionCollection*)));
0286 
0287         disconnect(collection, SIGNAL(collectionToBeInserted(ActionCollection*,ActionCollection*)), this, SIGNAL(collectionToBeInserted(ActionCollection*,ActionCollection*)));
0288         disconnect(collection, SIGNAL(collectionInserted(ActionCollection*,ActionCollection*)), this, SIGNAL(collectionInserted(ActionCollection*,ActionCollection*)));
0289         disconnect(collection, SIGNAL(collectionToBeRemoved(ActionCollection*,ActionCollection*)), this, SIGNAL(collectionToBeRemoved(ActionCollection*,ActionCollection*)));
0290         disconnect(collection, SIGNAL(collectionRemoved(ActionCollection*,ActionCollection*)), this, SIGNAL(collectionRemoved(ActionCollection*,ActionCollection*)));
0291 
0292         disconnect(collection, SIGNAL(actionToBeInserted(Action*,ActionCollection*)), this, SIGNAL(actionToBeInserted(Action*,ActionCollection*)));
0293         disconnect(collection, SIGNAL(actionInserted(Action*,ActionCollection*)), this, SIGNAL(actionInserted(Action*,ActionCollection*)));
0294         disconnect(collection, SIGNAL(actionToBeRemoved(Action*,ActionCollection*)), this, SIGNAL(actionToBeRemoved(Action*,ActionCollection*)));
0295         disconnect(collection, SIGNAL(actionRemoved(Action*,ActionCollection*)), this, SIGNAL(actionRemoved(Action*,ActionCollection*)));
0296         disconnect(collection, SIGNAL(updated()), this, SLOT(emitUpdated()));
0297     }
0298 }
0299 
0300 void ActionCollection::emitUpdated()
0301 {
0302     if (!d->blockupdated) {
0303         emit updated();
0304     }
0305 }
0306 
0307 /*********************************************************************
0308  * Unserialize from XML / QIODevice / file / resource to child
0309  * ActionCollection's and Action's this ActionCollection has.
0310  */
0311 
0312 bool ActionCollection::readXml(const QDomElement &element, const QDir &directory)
0313 {
0314     return readXml(element, QStringList(directory.absolutePath()));
0315 }
0316 
0317 bool ActionCollection::readXml(const QDomElement &element, const QStringList &searchPath)
0318 {
0319 #ifdef KROSS_ACTIONCOLLECTION_DEBUG
0320     qCDebug(KROSS_LOG) << "ActionCollection::readXml tagName=\"" << element.tagName() << "\"";
0321 #endif
0322 
0323     d->blockupdated = true; // block updated() signals and emit it only once if everything is done
0324     bool ok = true;
0325     QDomNodeList list = element.childNodes();
0326     const int size = list.size();
0327     for (int i = 0; i < size; ++i) {
0328         QDomElement elem = list.item(i).toElement();
0329         if (elem.isNull()) {
0330             continue;
0331         }
0332 
0333 #ifdef KROSS_ACTIONCOLLECTION_DEBUG
0334         qCDebug(KROSS_LOG) << "  ActionCollection::readXml child=" <<
0335             i << " tagName=\"" << elem.tagName() << "\"";
0336 #endif
0337 
0338         if (elem.tagName() == "collection") {
0339             const QString name = elem.attribute("name");
0340             const QByteArray text = elem.attribute("text").toUtf8();
0341             const QByteArray description = elem.attribute("comment").toUtf8();
0342             const QString iconname = elem.attribute("icon");
0343             bool enabled = QVariant(elem.attribute("enabled", "true")).toBool();
0344             ActionCollection *c = d->collections.contains(name) ? d->collections[name] : QPointer<ActionCollection>(nullptr);
0345             if (! c) {
0346                 c = new ActionCollection(name, this);
0347             }
0348 
0349             c->setText(text.isEmpty() ? name : i18nd(KLocalizedString::applicationDomain().constData(), text.constData()));
0350             c->setDescription(description.isEmpty() ? c->text() : i18nd(KLocalizedString::applicationDomain().constData(), description.constData()));
0351             c->setIconName(iconname);
0352 
0353             if (! enabled) {
0354                 c->setEnabled(false);
0355             }
0356             if (! c->readXml(elem, searchPath)) {
0357                 ok = false;
0358             }
0359         } else if (elem.tagName() == "script") {
0360             QString name = elem.attribute("name");
0361             Action *a = dynamic_cast< Action * >(action(name));
0362             if (a) {
0363 #ifdef KROSS_ACTIONCOLLECTION_DEBUG
0364                 qCDebug(KROSS_LOG) << "  ActionCollection::readXml Updating Action " << a->objectName();
0365 #endif
0366             } else {
0367 #ifdef KROSS_ACTIONCOLLECTION_DEBUG
0368                 qCDebug(KROSS_LOG) << "  ActionCollection::readXml Creating Action " << name;
0369 #endif
0370 
0371                 a = new Action(this, name);
0372                 addAction(name, a);
0373                 connect(a, SIGNAL(started(Kross::Action*)), &Manager::self(), SIGNAL(started(Kross::Action*)));
0374                 connect(a, SIGNAL(finished(Kross::Action*)), &Manager::self(), SIGNAL(finished(Kross::Action*)));
0375             }
0376             a->fromDomElement(elem, searchPath);
0377         }
0378         //else if( ! fromXml(elem) ) ok = false;
0379     }
0380 
0381     d->blockupdated = false; // unblock signals
0382     emitUpdated();
0383     return ok;
0384 }
0385 
0386 bool ActionCollection::readXml(QIODevice *device, const QDir &directory)
0387 {
0388     return readXml(device, QStringList(directory.absolutePath()));
0389 }
0390 
0391 bool ActionCollection::readXml(QIODevice *device, const QStringList &searchPath)
0392 {
0393     QString errMsg;
0394     int errLine, errCol;
0395     QDomDocument document;
0396     bool ok = document.setContent(device, false, &errMsg, &errLine, &errCol);
0397     if (! ok) {
0398 #ifdef KROSS_ACTIONCOLLECTION_DEBUG
0399         qCWarning(KROSS_LOG) << QStringLiteral("ActionCollection::readXml Error at line %1 in col %2: %3")
0400             .arg(errLine).arg(errCol).arg(errMsg);
0401 #endif
0402         return false;
0403     }
0404     return readXml(document.documentElement(), searchPath);
0405 }
0406 
0407 bool ActionCollection::readXmlFile(const QString &file)
0408 {
0409 #ifdef KROSS_ACTIONCOLLECTION_DEBUG
0410     qCDebug(KROSS_LOG) << "ActionCollection::readXmlFile file=" << file;
0411 #endif
0412 
0413     QFile f(file);
0414     if (! f.open(QIODevice::ReadOnly)) {
0415 #ifdef KROSS_ACTIONCOLLECTION_DEBUG
0416         qCWarning(KROSS_LOG) << "ActionCollection::readXmlFile failed to read file " << file;
0417 #endif
0418         return false;
0419     }
0420     bool ok = readXml(&f, QFileInfo(file).dir());
0421     f.close();
0422 
0423 #ifdef KROSS_ACTIONCOLLECTION_DEBUG
0424     if (! ok) {
0425         qCWarning(KROSS_LOG) << "ActionCollection::readXmlFile failed to parse XML content of file " << file;
0426     }
0427 #endif
0428     return ok;
0429 }
0430 
0431 /*********************************************************************
0432  * Serialize from child ActionCollection's and Action's this
0433  * ActionCollection has to XML / QIODevice / file / resource.
0434  */
0435 
0436 QDomElement ActionCollection::writeXml()
0437 {
0438     return writeXml(QStringList());
0439 }
0440 
0441 QDomElement ActionCollection::writeXml(const QStringList &searchPath)
0442 {
0443 #ifdef KROSS_ACTIONCOLLECTION_DEBUG
0444     qCDebug(KROSS_LOG) << "ActionCollection::writeXml collection.objectName=" << objectName();
0445 #endif
0446 
0447     QDomDocument document;
0448     QDomElement element = document.createElement("collection");
0449     if (! objectName().isNull()) {
0450         element.setAttribute("name", objectName());
0451     }
0452     if (! text().isNull() && text() != objectName()) {
0453         element.setAttribute("text", text());
0454     }
0455     if (! d->description.isNull()) {
0456         element.setAttribute("comment", d->description);
0457     }
0458     if (! d->iconname.isNull()) {
0459         element.setAttribute("icon", d->iconname);
0460     }
0461     if (! d->enabled) {
0462         element.setAttribute("enabled", d->enabled);
0463     }
0464 
0465     foreach (Action *a, actions()) {
0466         Q_ASSERT(a);
0467 #ifdef KROSS_ACTIONCOLLECTION_DEBUG
0468         qCDebug(KROSS_LOG) << "  ActionCollection::writeXml action.objectName=" <<
0469             a->objectName() << " action.file=" << a->file();
0470 #endif
0471         QDomElement e = a->toDomElement(searchPath);
0472         if (! e.isNull()) {
0473             element.appendChild(e);
0474         }
0475     }
0476 
0477     foreach (const QString &name, d->collectionnames) {
0478         ActionCollection *c = d->collections[name];
0479         if (! c) {
0480             continue;
0481         }
0482         QDomElement e = c->writeXml(searchPath);
0483         if (! e.isNull()) {
0484             element.appendChild(e);
0485         }
0486     }
0487 
0488     return element;
0489 }
0490 
0491 bool ActionCollection::writeXml(QIODevice *device, int indent)
0492 {
0493     return writeXml(device, indent, QStringList());
0494 }
0495 
0496 bool ActionCollection::writeXml(QIODevice *device, int indent, const QStringList &searchPath)
0497 {
0498     QDomDocument document;
0499     QDomElement root = document.createElement("KrossScripting");
0500 
0501     foreach (Action *a, actions()) {
0502         QDomElement e = a->toDomElement(searchPath);
0503         if (! e.isNull()) {
0504             root.appendChild(e);
0505         }
0506     }
0507 
0508     foreach (const QString &name, d->collectionnames) {
0509         ActionCollection *c = d->collections[name];
0510         if (! c) {
0511             continue;
0512         }
0513         QDomElement e = c->writeXml(searchPath);
0514         if (! e.isNull()) {
0515             root.appendChild(e);
0516         }
0517     }
0518 
0519     document.appendChild(root);
0520     return device->write(document.toByteArray(indent)) != -1;
0521 }
0522 
0523 #include "moc_actioncollection.cpp"