diff --git a/src/libcalamaresui/utils/PluginFactory.cpp b/src/libcalamaresui/utils/PluginFactory.cpp new file mode 100644 index 000000000..2834e5b4a --- /dev/null +++ b/src/libcalamaresui/utils/PluginFactory.cpp @@ -0,0 +1,123 @@ +/* === This file is part of Calamares - === + * + * Copyright 2015, Teo Mrnjavac + * + * Based on KPluginFactory from KCoreAddons, KDE project + * Copyright 2007, Matthias Kretz + * Copyright 2007, Bernhard Loos + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#include "PluginFactory.h" +#include "PluginFactory_p.h" + +#include +#include + +Q_GLOBAL_STATIC(QObjectCleanupHandler, factorycleanup) + +extern int kLibraryDebugArea(); + +namespace Calamares +{ + +PluginFactory::PluginFactory() + : d_ptr(new PluginFactoryPrivate) +{ + Q_D(PluginFactory); + d->q_ptr = this; + + factorycleanup()->add(this); +} + +PluginFactory::PluginFactory(PluginFactoryPrivate &d) + : d_ptr(&d) +{ + factorycleanup()->add(this); +} + +PluginFactory::~PluginFactory() +{ + delete d_ptr; +} + +void PluginFactory::doRegisterPlugin(const QString &keyword, const QMetaObject *metaObject, CreateInstanceFunction instanceFunction) +{ + Q_D(PluginFactory); + + Q_ASSERT(metaObject); + + // we allow different interfaces to be registered without keyword + if (!keyword.isEmpty()) { + if (d->createInstanceHash.contains(keyword)) { + qWarning() << "A plugin with the keyword" << keyword << "was already registered. A keyword must be unique!"; + } + d->createInstanceHash.insert(keyword, PluginFactoryPrivate::Plugin(metaObject, instanceFunction)); + } else { + QList clashes(d->createInstanceHash.values(keyword)); + const QMetaObject *superClass = metaObject->superClass(); + if (superClass) { + foreach (const PluginFactoryPrivate::Plugin &plugin, clashes) { + for (const QMetaObject *otherSuper = plugin.first->superClass(); otherSuper; + otherSuper = otherSuper->superClass()) { + if (superClass == otherSuper) { + qWarning() << "Two plugins with the same interface(" << superClass->className() << ") were registered. Use keywords to identify the plugins."; + } + } + } + } + foreach (const PluginFactoryPrivate::Plugin &plugin, clashes) { + superClass = plugin.first->superClass(); + if (superClass) { + for (const QMetaObject *otherSuper = metaObject->superClass(); otherSuper; + otherSuper = otherSuper->superClass()) { + if (superClass == otherSuper) { + qWarning() << "Two plugins with the same interface(" << superClass->className() << ") were registered. Use keywords to identify the plugins."; + } + } + } + } + d->createInstanceHash.insertMulti(keyword, PluginFactoryPrivate::Plugin(metaObject, instanceFunction)); + } +} + +QObject *PluginFactory::create(const char *iface, QWidget *parentWidget, QObject *parent, const QString &keyword) +{ + Q_D(PluginFactory); + + QObject *obj = 0; + + const QList candidates(d->createInstanceHash.values(keyword)); + // for !keyword.isEmpty() candidates.count() is 0 or 1 + + foreach (const PluginFactoryPrivate::Plugin &plugin, candidates) { + for (const QMetaObject *current = plugin.first; current; current = current->superClass()) { + if (0 == qstrcmp(iface, current->className())) { + if (obj) { + qWarning() << "ambiguous interface requested from a DSO containing more than one plugin"; + } + obj = plugin.second(parentWidget, parent); + break; + } + } + } + + if (obj) { + emit objectCreated(obj); + } + return obj; +} + +} diff --git a/src/libcalamaresui/utils/PluginFactory.h b/src/libcalamaresui/utils/PluginFactory.h new file mode 100644 index 000000000..a77d9594f --- /dev/null +++ b/src/libcalamaresui/utils/PluginFactory.h @@ -0,0 +1,370 @@ +/* === This file is part of Calamares - === + * + * Copyright 2015, Teo Mrnjavac + * + * Based on KPluginFactory from KCoreAddons, KDE project + * Copyright 2007, Matthias Kretz + * Copyright 2007, Bernhard Loos + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef CALAMARESPLUGINFACTORY_H +#define CALAMARESPLUGINFACTORY_H + +#include "UiDllMacro.h" + +#include +#include +#include + +namespace Calamares +{ +class PluginFactoryPrivate; +} + +#define CalamaresPluginFactory_iid "io.calamares.PluginFactory" + +#define CALAMARES_PLUGIN_FACTORY_DECLARATION_WITH_BASEFACTORY_SKEL(name, baseFactory, ...) \ + class name : public Calamares::PluginFactory \ + { \ + Q_OBJECT \ + Q_INTERFACES(Calamares::PluginFactory) \ + __VA_ARGS__ \ + public: \ + explicit name(); \ + ~name(); \ + private: \ + void init(); \ + }; + +#define CALAMARES_PLUGIN_FACTORY_DECLARATION_WITH_BASEFACTORY(name, baseFactory) \ + CALAMARES_PLUGIN_FACTORY_DECLARATION_WITH_BASEFACTORY_SKEL(name, baseFactory, Q_PLUGIN_METADATA(IID CalamaresPluginFactory_iid)) + +#define CALAMARES_PLUGIN_FACTORY_DEFINITION_WITH_BASEFACTORY(name, baseFactory, pluginRegistrations) \ + name::name() \ + { \ + pluginRegistrations \ + } \ + name::~name() {} + +#define CALAMARES_PLUGIN_FACTORY_WITH_BASEFACTORY(name, baseFactory, pluginRegistrations) \ + CALAMARES_PLUGIN_FACTORY_DECLARATION_WITH_BASEFACTORY(name, baseFactory) \ + CALAMARES_PLUGIN_FACTORY_DEFINITION_WITH_BASEFACTORY(name, baseFactory, pluginRegistrations) + +#define CALAMARES_PLUGIN_FACTORY_DECLARATION(name) CALAMARES_PLUGIN_FACTORY_DECLARATION_WITH_BASEFACTORY(name, Calamares::PluginFactory) +#define CALAMARES_PLUGIN_FACTORY_DEFINITION(name, pluginRegistrations) CALAMARES_PLUGIN_FACTORY_DEFINITION_WITH_BASEFACTORY(name, Calamares::PluginFactory, pluginRegistrations) + +/** + * \relates PluginFactory + * + * Create a PluginFactory subclass and export it as the root plugin object. + * + * \param name The name of the PluginFactory derived class. + * + * \param pluginRegistrations Code to be inserted into the constructor of the + * class. Usually a series of registerPlugin() calls. + * + * Example: + * \code + * #include + * #include + * + * class MyPlugin : public PluginInterface + * { + * public: + * MyPlugin(QObject *parent, const QVariantList &args) + * : PluginInterface(parent) + * {} + * }; + * + * CALAMARES_PLUGIN_FACTORY(MyPluginFactory, + * registerPlugin(); + * ) + * + * #include + * \endcode + * + * \see CALAMARES_PLUGIN_FACTORY_DECLARATION + * \see CALAMARES_PLUGIN_FACTORY_DEFINITION + */ +#define CALAMARES_PLUGIN_FACTORY(name, pluginRegistrations) CALAMARES_PLUGIN_FACTORY_WITH_BASEFACTORY(name, Calamares::PluginFactory, pluginRegistrations) + +/** + * \relates PluginFactory + * + * CALAMARES_PLUGIN_FACTORY_DECLARATION declares the PluginFactory subclass. This macro + * can be used in a header file. + * + * \param name The name of the PluginFactory derived class. + * + * \see CALAMARES_PLUGIN_FACTORY + * \see CALAMARES_PLUGIN_FACTORY_DEFINITION + */ +#define CALAMARES_PLUGIN_FACTORY_DECLARATION(name) CALAMARES_PLUGIN_FACTORY_DECLARATION_WITH_BASEFACTORY(name, Calamares::PluginFactory) + +/** + * \relates PluginFactory + * CALAMARES_PLUGIN_FACTORY_DEFINITION defines the PluginFactory subclass. This macro + * can not be used in a header file. + * + * \param name The name of the PluginFactory derived class. + * + * \param pluginRegistrations Code to be inserted into the constructor of the + * class. Usually a series of registerPlugin() calls. + * + * \see CALAMARES_PLUGIN_FACTORY + * \see CALAMARES_PLUGIN_FACTORY_DECLARATION + */ +#define CALAMARES_PLUGIN_FACTORY_DEFINITION(name, pluginRegistrations) CALAMARES_PLUGIN_FACTORY_DEFINITION_WITH_BASEFACTORY(name, Calamares::PluginFactory, pluginRegistrations) + +namespace Calamares +{ + +/** + * \class PluginFactory PluginFactory.h + * + * PluginFactory provides a convenient way to provide factory-style plugins. + * Qt plugins provide a singleton object, but a common pattern is for plugins + * to generate as many objects of a particular type as the application requires. + * By using PluginFactory, you can avoid implementing the factory pattern + * yourself. + * + * PluginFactory also allows plugins to provide multiple different object + * types, indexed by keywords. + * + * The objects created by PluginFactory must inherit QObject, and must have a + * standard constructor pattern: + * \li if the object is a KPart::Part, it must be of the form + * \code + * T(QWidget *parentWidget, QObject *parent, const QVariantList &args) + * \endcode + * \li if it is a QWidget, it must be of the form + * \code + * T(QWidget *parent, const QVariantList &args) + * \endcode + * \li otherwise it must be of the form + * \code + * T(QObject *parent, const QVariantList &args) + * \endcode + * + * You should typically use either CALAMARES_PLUGIN_FACTORY() or + * CALAMARES_PLUGIN_FACTORY_WITH_JSON() in your plugin code to create the factory. The + * typical pattern is + * + * \code + * #include + * #include + * + * class MyPlugin : public PluginInterface + * { + * public: + * MyPlugin(QObject *parent, const QVariantList &args) + * : PluginInterface(parent) + * {} + * }; + * + * CALAMARES_PLUGIN_FACTORY(MyPluginFactory, + * registerPlugin(); + * ) + * #include + * \endcode + * + * If you want to load a library use KPluginLoader. + * The application that wants to instantiate plugin classes can do the following: + * \code + * PluginFactory *factory = KPluginLoader("libraryname").factory(); + * if (factory) { + * PluginInterface *p1 = factory->create(parent); + * OtherInterface *p2 = factory->create(parent); + * NextInterface *p3 = factory->create("keyword1", parent); + * NextInterface *p3 = factory->create("keyword2", parent); + * } + * \endcode + * + * \author Matthias Kretz + * \author Bernhard Loos + */ +class UIDLLEXPORT PluginFactory : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(PluginFactory) +public: + /** + * This constructor creates a factory for a plugin. + */ + explicit PluginFactory(); + + /** + * This destroys the PluginFactory. + */ + virtual ~PluginFactory(); + + /** + * Use this method to create an object. It will try to create an object which inherits + * \p T. If it has multiple choices, you will get a fatal error (kFatal()), so be careful + * to request a unique interface or use keywords. + * + * \tparam T The interface for which an object should be created. The object will inherit \p T. + * \param parent The parent of the object. If \p parent is a widget type, it will also passed + * to the parentWidget argument of the CreateInstanceFunction for the object. + * \returns A pointer to the created object is returned, or 0 if an error occurred. + */ + template + T *create(QObject *parent = 0); + + /** + * Use this method to create an object. It will try to create an object which inherits + * \p T and was registered with \p keyword. + * + * \tparam T The interface for which an object should be created. The object will inherit \p T. + * \param keyword The keyword of the object. + * \param parent The parent of the object. If \p parent is a widget type, it will also passed + * to the parentWidget argument of the CreateInstanceFunction for the object. + * \returns A pointer to the created object is returned, or 0 if an error occurred. + */ + template + T *create(const QString &keyword, QObject *parent = 0); + +Q_SIGNALS: + void objectCreated(QObject *object); + +protected: + /** + * Function pointer type to a function that instantiates a plugin. + */ + typedef QObject *(*CreateInstanceFunction)(QWidget *, QObject *); + + /** + * This is used to detect the arguments need for the constructor of plugin classes. + * You can inherit it, if you want to add new classes and still keep support for the old ones. + */ + template + struct InheritanceChecker { + CreateInstanceFunction createInstanceFunction(QWidget *) + { + return &createInstance; + } + CreateInstanceFunction createInstanceFunction(...) + { + return &createInstance; + } + }; + + explicit PluginFactory(PluginFactoryPrivate &dd); + + /** + * Registers a plugin with the factory. Call this function from the constructor of the + * PluginFactory subclass to make the create function able to instantiate the plugin when asked + * for an interface the plugin implements. + * + * \tparam T the name of the plugin class + * + * \param keyword An optional keyword as unique identifier for the plugin. This allows you to + * put more than one plugin with the same interface into the same library using the same + * factory. X-KDE-PluginKeyword is a convenient way to specify the keyword in a desktop file. + * + * \param instanceFunction A function pointer to a function that creates an instance of the + * plugin. The default function that will be used depends on the type of interface. If the + * interface inherits from + * \li \c KParts::Part the function will call + * \code + * new T(QWidget *parentWidget, QObject *parent) + * \endcode + * \li \c QWidget the function will call + * \code + * new T(QWidget *parent) + * \endcode + * \li else the function will call + * \code + * new T(QObject *parent) + * \endcode + */ + template + void registerPlugin(const QString &keyword = QString(), + CreateInstanceFunction instanceFunction + = InheritanceChecker().createInstanceFunction(reinterpret_cast(0))) + { + doRegisterPlugin(keyword, &T::staticMetaObject, instanceFunction); + } + + PluginFactoryPrivate *const d_ptr; + + /** + * This function is called when the factory asked to create an Object. + * + * You may reimplement it to provide a very flexible factory. This is especially useful to + * provide generic factories for plugins implemeted using a scripting language. + * + * \param iface The staticMetaObject::className() string identifying the plugin interface that + * was requested. E.g. for KCModule plugins this string will be "KCModule". + * \param parentWidget Only used if the requested plugin is a KPart. + * \param parent The parent object for the plugin object. + * \param args A plugin specific list of arbitrary arguments. + * \param keyword A string that uniquely identifies the plugin. If a KService is used this + * keyword is read from the X-KDE-PluginKeyword entry in the .desktop file. + */ + virtual QObject *create(const char *iface, QWidget *parentWidget, QObject *parent, const QString &keyword); + + template + static QObject *createInstance(QWidget *parentWidget, QObject *parent) + { + Q_UNUSED(parentWidget); + ParentType *p = 0; + if (parent) { + p = qobject_cast(parent); + Q_ASSERT(p); + } + return new impl(p); + } + +private: + void doRegisterPlugin(const QString &keyword, const QMetaObject *metaObject, CreateInstanceFunction instanceFunction); +}; + +template +inline T *PluginFactory::create(QObject *parent) +{ + QObject *o = create(T::staticMetaObject.className(), + parent && parent->isWidgetType() ? reinterpret_cast(parent) : 0, + parent, + QString()); + + T *t = qobject_cast(o); + if (!t) { + delete o; + } + return t; +} + +template +inline T *PluginFactory::create(const QString &keyword, QObject *parent) +{ + QObject *o = create(T::staticMetaObject.className(), + parent && parent->isWidgetType() ? reinterpret_cast(parent) : 0, + parent, + keyword); + + T *t = qobject_cast(o); + if (!t) { + delete o; + } + return t; +} + +} + +Q_DECLARE_INTERFACE(Calamares::PluginFactory, CalamaresPluginFactory_iid) + +#endif // CALAMARESPLUGINFACTORY_H diff --git a/src/libcalamaresui/utils/PluginFactory_p.h b/src/libcalamaresui/utils/PluginFactory_p.h new file mode 100644 index 000000000..b03da9b26 --- /dev/null +++ b/src/libcalamaresui/utils/PluginFactory_p.h @@ -0,0 +1,53 @@ +/* === This file is part of Calamares - === + * + * Copyright 2015, Teo Mrnjavac + * + * Based on KPluginFactory from KCoreAddons, KDE project + * Copyright 2007, Matthias Kretz + * Copyright 2007, Bernhard Loos + * + * Calamares is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Calamares is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Calamares. If not, see . + */ + +#ifndef CALAMARESPLUGINFACTORY_P_H +#define CALAMARESPLUGINFACTORY_P_H + +#include "PluginFactory.h" + +#include + +namespace Calamares +{ + +class PluginFactoryPrivate +{ + Q_DECLARE_PUBLIC(PluginFactory) +protected: + typedef QPair Plugin; + + PluginFactoryPrivate() : catalogInitialized(false) {} + ~PluginFactoryPrivate() + { + } + + QHash createInstanceHash; + QString catalogName; + bool catalogInitialized; + + PluginFactory *q_ptr; +}; + +} + +#endif // CALAMARESPLUGINFACTORY_P_H