You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

541 lines
16 KiB

/*
XINX
Copyright (C) 2007-2011 by Ulrich Van Den Hekke
xinx@shadoware.org
This program 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.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Xinx header
#include "core/xinxcore.h"
#include "core/xinxconfig.h"
#include "plugins/xinxpluginsloader.h"
#include "editors/xinxformatscheme.h"
#include "editors/xinxlanguagefactory.h"
#include <plugins/xinxpluginelement.h>
#include <plugins/interfaces/files.h>
// Qt header
#include <QPluginLoader>
#include <QApplication>
#include <QDebug>
#include <QDomDocument>
// QCodeEdit header
#include <qlanguagefactory.h>
#include <qnfadefinition.h>
/*!
* \page XinxPlugin Writing a plugin
*
* \section preface Preface
*
* This document explain how to use the XINX Library to create a plugins. This document can be used
* to known how XINX work too.
*
* You can find in the menu, documented class present in the libxinxcmp and libsharedxinx. The first
* library contains components, the second, contains shared object beetween XINX and plugins. Source of
* existing plugins can be use too, as documentation.
*
* Plugins is used to extend XINX in different way.
* - Add a revision control system
* - Add Syntax Highlighter on file editor
* - Add a new editor (text, graphics, ...)
*
* \section pluginbase Base of plugin
*
* A plugins must implements at least one interface : \e IXinxPlugin. This interface is used to know
* informations about the plugins. A plugin that implement only this interface do nothing but is
* recognize by XINX (see EmptyPlugin exemple).
*
* To implemente the interface you must write some code like this :
* \code
*
* class FooPlugin : public QObject, public IXinxPlugin
* {
* Q_OBJECT
* Q_INTERFACES(IXinxPlugin)
* public:
* FooPlugin();
* virtual ~FooPlugin();
*
* virtual bool initializePlugin( const QString & lang );
* virtual QVariant getPluginAttribute( const enum IXinxPlugin::PluginAttribute & attr );
* };
* \endcode
*
* The implementation of this class can be :
*
* \code
* FooPlugin::FooPlugin()
* {
* // Make some initialization
* }
*
* FooPlugin::~FooPlugin()
* {
* // Make some cleanup
* }
*
* bool FooPlugin::initializePlugin( const QString & lang )
* {
* // Make some initialization about the localization
* return true;
* }
*
* QVariant FooPlugin::getPluginAttribute( const enum IXinxPlugin::PluginAttribute & attr )
* {
* switch( attr )
* {
* case PLG_NAME:
* return tr("A foo plugins");
* case PLG_DESCRIPTION:
* return tr("Just a foo plugins that do nothing.");
* case PLG_AUTHOR:
* return "Ulrich Van Den Hekke";
* case PLG_VERSION:
* return "0.1";
* case PLG_LICENCE:
* return "GPL v2.0 or later";
* }
* return QVariant();
* }
*
* Q_EXPORT_PLUGIN2(fooplugin, FooPlugin)
* \endcode
*
* The plugins must inherits from the \e QObject class (see Qt documentation), and must call the \e Q_OBJECT macro.
* \e QObject class give to the class some meta-object methods (to know the name of the class, ... dynamicaly).
* He can inherits \e IXinxPlugin directly or indirectly (some interface as \e IRCSPlugin inherits from \e IXinxPlugin).
*
* The plugin must call the \e Q_EXPORT_PLUGIN2 macro in the implementation. This create a static variable for the
* plugin, created when the library is loading.
*
* If the plugin need to call new tools, you can reimplement \e IXinxPlugin::pluginTools() and returns the list of
* tool needed by your plugin with the default value (you can have a default value for Gnu/linux and for Windows).
*
* \code
* QList&lt; QPair&lt;QString,QString&gt; &gt; CVSPlugin::pluginTools()
* {
* QList&lt; QPair&lt;QString,QString&gt; &gt; tools;
* #ifdef Q_WS_WIN
* tools.append( qMakePair( QString("mytool"), QString("%1/Tool/mytool.exe").arg( "C:/Program Files" ) ) );
* #else
* tools.append( qMakePair( QString("cvs"), QString("/usr/bin/mytool") ) );
* #endif // Q_WS_WIN
* return tools;
* }
* \endcode
*
* You can also reimplement \e IXinxPlugin::initializeProject() and \e IXinxPlugin::destroyProject() if you have
* specifique code when the current project is opened or destroyed. This two fonction can be replaced by whatching the
* signal \e XinxProject::Manager::changed().
*
* if \e XinxProject::Manager::self()->project() is null the plugin is destroyed else, he is created.
*
* \section rcsplugin The Revision Control System Plugin
*
* A RCS Plugin must implemente the \e IRCSPlugin interface.
*
* \code
* class FooPlugin : public QObject, public IRCSPlugin
* {
* Q_OBJECT
* Q_INTERFACES(IXinxPlugin)
* Q_INTERFACES(IRCSPlugin)
* public:
* FooPlugin();
* virtual ~FooPlugin();
*
* virtual bool initializePlugin( const QString & lang );
* virtual QVariant getPluginAttribute( const enum IXinxPlugin::PluginAttribute & attr );
*
* virtual QStringList rcs();
* virtual QString descriptionOfRCS( const QString & rcs );
* virtual RCS * createRCS( const QString & rcs, const QString & basePath );
* };
* \endcode
*
* In the implementation method create a derivated object of \e RCS
*
* \code
* QStringList FooPlugin::rcs()
* {
* return QStringList() << "foo";
* }
*
* QString FooPlugin::descriptionOfRCS( const QString & rcs )
* {
* if( rcs.toLower() == "foo" )
* return tr( "Foo - Foo Version System" );
* return QString();
* }
* RCS * FooPlugin::createRCS( const QString & rcs, const QString & basePath )
* {
* if( rcs.toLower() == "foo" )
* {
* return new RCS_Foo( basePath );
* }
* return NULL;
* }
* \endcode
*
* Then, in the object RCS_Foo (that inherits from RCS) :
*
* \code
* class RCS_Foo : public RCS
* {
* Q_OBJECT
* public:
* RCS_Foo( const QString & base );
* virtual ~RCS_Foo();
*
* virtual struct_rcs_infos infos( const QString & path );
* virtual RCS::FilesOperation operations( const QStringList & path );
* virtual void update( const QStringList & path );
* virtual void commit( const FilesOperation & path, const QString & message );
* virtual void add( const QStringList & path );
* virtual void remove( const QStringList & path );
* virtual void updateToRevision( const QString & path, const QString & revision, QString * content = 0 );
* public slots:
* virtual void abort();
* private slots:
* private:
* };
* \endcode
*
* Operations \e RCS::update(), \e RCS::commit(), \e RCS::add(), \e RCS::remove() and
* \e RCS::updateToRevision() is the correspponding opperation in the revision manager.
*
* \e RCS::operations() describe the file than can be Commited, Removed or Added.
*
* \section projectconfiguration Project configuration plugins
*
* We can configure a plugin throw the \e IXinxPluginConfiguration interface, if the
* configuration is global, or throw the \e IXinxPluginProjectConfiguration interface,
* if the configuration depends on the project.
*
* \code
* class FooPlugin : public QObject, public IRCSPlugin, public IRCSPluginProjectConfiguration
* {
* Q_OBJECT
* Q_INTERFACES(IXinxPlugin)
* Q_INTERFACES(IRCSPlugin)
* Q_INTERFACES(IRCSPluginProjectConfiguration)
* public:
* FooPlugin();
* virtual ~FooPlugin();
* \endcode
*
* Methods \e IXinxPluginConfiguration::createSettingsDialog() and \e IXinxPluginProjectConfiguration::createProjectSettingsPage()
* return a widget that will be include in the project configuration box, or in the configuration dialog.
*
* \code
* virtual QWidget * createProjectSettingsPage()
* {
* return new FooProjectImpl();
* }
* \endcode
*
* The class \e FooProjectImpl is a dialog box that contains the different CheckBox, RadioButton, ...
* for configure the dialog.
*
* Settings are load and saved with methods \e IXinxPluginConfiguration::loadSettingsDialog(),
* \e IXinxPluginProjectConfiguration::loadProjectSettingsPage(), \e IXinxPluginConfiguration::saveSettingsDialog(),
* and \e IXinxPluginProjectConfiguration::saveProjectSettingsPage().
*
* \code
* bool loadProjectSettingsPage( QWidget * widget )
* {
* FooProjectImpl * page = qobject_cast<FooProjectImpl*>( widget );
* Q_ASSERT( page );
*
* XinxProject::Parameters options = XinxProject::Manager::self()->project()->options();
* page->m_monoption->setChecked( options.testFlag( XinxProject::hasSpecifiques ) );
* return true;
* }
*
* bool saveProjectSettingsPage( QWidget * widget )
* {
* FooProjectImpl * page = qobject_cast<FooProjectImpl*>( widget );
* Q_ASSERT( page );
*
* XinxProject::Parameters options = XinxProject::Manager::self()->project()->options();
* if( page->m_monoption->isChecked() )
* options |= XinxProject::hasSpecifiques;
* else
* options &= ~XinxProject::hasSpecifiques;
* XinxProject::Manager::self()->project()->setOptions( options );
* return true;
* }
* \endcode
*
* If you want to add page in the new project wizard dialog, the plugin \e IXinxPluginProjectConfiguration
* give you too, two method who works in the same way that previous :
* * \e createNewProjectSettingsPages()
* * \e saveNewProjectSettingsPage()
*
* \section neweditor Creation of a new editor
*
* It's possible to create a new editor with the interface \e IFileTypePlugin and \e IFilePlugin.
* The interface \e IFileTypePlugin define for one type of file the mask to use (ie: *.foo) with
* the function IFileTypePlugin::match(), a description (used in open/save dialog box and new menu),
* an icon and some default properties (than user can modify).
*
* Then the two principal method \e IFileTypePlugin::createEditor() and \e IFileTypePlugin::createElement()
* is used to create the editor, and a content element (used in import, in the content view list).
*
* Interface \e IFilePlugin is just a list of IFileTypePlugin.
*
*/
/* Static function */
static bool pluginsLessThan(XinxPluginElement * s1, XinxPluginElement * s2)
{
if (s1->isStatic() && (!s2->isStatic())) return true;
if ((!s1->isStatic()) && s2->isStatic()) return false;
return s1->name() < s2->name();
}
/* XinxPluginsLoader */
XinxPluginsLoader::XinxPluginsLoader()
{
}
XinxPluginsLoader::~XinxPluginsLoader()
{
qDeleteAll(plugins());
}
QList<XinxPluginElement*> XinxPluginsLoader::plugins() const
{
QList<XinxPluginElement*> result = m_plugins.values();
qSort(result.begin(), result.end(), pluginsLessThan);
return result;
}
XinxPluginElement * XinxPluginsLoader::plugin(const QString & name)
{
return m_plugins.value(name);
}
void XinxPluginsLoader::addPlugin(QObject * plugin, bool staticLoaded)
{
/* Manage all XINX Plugin */
IXinxPlugin * iXinxPlugin = qobject_cast<IXinxPlugin*>(plugin);
if (! iXinxPlugin) return;
QString name = plugin->metaObject()->className();
// Initialize the plugin (if needed)
if (! iXinxPlugin->initializePlugin(XINXConfig::self()->config().language))
{
qCritical() << "Can't load " << name << " plugin.";
return;
}
// Create a glue to use the plugin with XinxPluginsLoader
XinxPluginElement * element = new XinxPluginElement(plugin, staticLoaded);
// Set the status of the plugin with the configuration
element->setActivated(XINXConfig::self()->config().plugins.value(name, true));
m_plugins.insert(name, element);
//! \todo move this line in setActivated method of the plugin
// Create default tools given by the plugin
QPair<QString,QString> tools;
foreach(tools, iXinxPlugin->pluginTools())
XINXConfig::self()->addDefaultTool(tools.first, tools.second);
// Create possible extention definition
IFilePlugin * interface = qobject_cast<IFilePlugin*>(plugin);
if (interface)
{
foreach(IFileTypePlugin * t, interface->fileTypes())
{
// If the plugin contains format and language description, we loaded it.
IFileTextPlugin * textPlugin = dynamic_cast<IFileTextPlugin*>(t);
if (textPlugin)
{
// Format
QFormatScheme * scheme = textPlugin->createFormatScheme(XINXConfig::self());
if (! scheme)
{
scheme = XINXConfig::self()->languageFactory()->defaultFormatScheme();
}
else
{
XINXConfig::self()->addFormatScheme(textPlugin->highlighterId(), qobject_cast<XinxFormatScheme*>(scheme));
}
// Language
QDomDocument doc;
QLanguageFactory::LangData data;
doc.setContent(textPlugin->createLanguageDescription());
QNFADefinition::load(doc, &data, scheme);
XINXConfig::self()->languageFactory()->addLanguage(data);
}
}
}
}
void XinxPluginsLoader::loadPlugins()
{
foreach(QObject * plugin, QPluginLoader::staticInstances())
{
addPlugin(plugin, true);
}
foreach(const QString & directory, QDir::searchPaths("plugins"))
{
QDir pluginsDir = QDir(directory);
foreach(const QString & fileName, pluginsDir.entryList(QStringList() << "*.dll" << "*.so", QDir::Files))
{
QPluginLoader loader(pluginsDir.absoluteFilePath(fileName));
QObject * plugin = loader.instance();
if (plugin)
addPlugin(plugin);
else
qDebug() << loader.errorString();
}
}
}
QList<IFileTypePlugin*> XinxPluginsLoader::fileTypes() const
{
QList<IFileTypePlugin*> result;
foreach(XinxPluginElement * element, plugins())
{
IFilePlugin * interface = qobject_cast<IFilePlugin*>(element->plugin());
if (element->isActivated() && interface)
{
result += interface->fileTypes();
}
}
return result;
}
IFileTypePlugin* XinxPluginsLoader::fileType(const QString & name) const
{
QList<IFileTypePlugin*> list = fileTypes();
foreach(IFileTypePlugin * type, list)
{
if (type->name() == name)
{
return type;
}
}
return NULL;
}
QList<IFileTypePlugin*> XinxPluginsLoader::matchedFileType(const QString & filename) const
{
QList<IFileTypePlugin*> types = fileTypes(), result;
foreach(IFileTypePlugin* plugin, types)
{
QStringList patterns = plugin->match().split(" ");
foreach(const QString & match, patterns)
{
QRegExp pattern(match, Qt::CaseInsensitive, QRegExp::Wildcard);
if (pattern.exactMatch(filename))
result << plugin;
}
}
return result;
}
QString XinxPluginsLoader::allManagedFileFilter() const
{
QStringList result;
QList<IFileTypePlugin*> types = fileTypes();
foreach(IFileTypePlugin* plugin, types)
{
result += plugin->match();
}
return tr("All managed files (%1)").arg(result.join(" "));
}
QString XinxPluginsLoader::fileTypeFilter(IFileTypePlugin * fileType)
{
return tr("All %1 (%2)").arg(fileType->description()).arg(fileType->match());
}
QString XinxPluginsLoader::exampleOfHighlighter(const QString & name) const
{
foreach(XinxPluginElement * element, plugins())
{
IFilePlugin * interface = qobject_cast<IFilePlugin*>(element->plugin());
if (element->isActivated() && interface)
{
foreach(IFileTypePlugin * p, interface->fileTypes())
{
IFileTextPlugin * textFileType = dynamic_cast<IFileTextPlugin*>(p);
if (textFileType && textFileType->highlighterId().toLower() == name.toLower())
{
return textFileType->fileExample();
}
}
}
}
return QString();
}
XinxFormatScheme * XinxPluginsLoader::scheme(const QString & highlighter, XINXConfig * config)
{
foreach(XinxPluginElement * element, plugins())
{
IFilePlugin * interface = qobject_cast<IFilePlugin*>(element->plugin());
if (element->isActivated() && interface)
{
foreach(IFileTypePlugin * p, interface->fileTypes())
{
IFileTextPlugin * textFileType = dynamic_cast<IFileTextPlugin*>(p);
if (textFileType && textFileType->highlighterId().toLower() == highlighter.toLower())
{
return textFileType->createFormatScheme(config);
}
}
}
}
return 0;
}
QStringList XinxPluginsLoader::openDialogBoxFilters() const
{
QStringList result;
QList<IFileTypePlugin*> types = fileTypes();
result += allManagedFileFilter();
foreach(IFileTypePlugin* plugin, types)
{
result += fileTypeFilter(plugin);
}
return result;
}
QStringList XinxPluginsLoader::managedFilters() const
{
QStringList result;
foreach(IFileTypePlugin* plugin, fileTypes())
{
result += plugin->match().split(" ");
}
return result;
}