Still not having added Fl_Tree and Fl_Table to Fluid, I remembered the plugin concept we had early on. It occured to me that writing plugins must not be difficult, and that FLTK already has everything needed. So here it is, a plugin implementation for FLTK. The MSWindows/Cygwin implementation is untested due to lack of a machine. The dynamic loading still needs a test implementation. Comments welcome.

git-svn-id: file:///fltk/svn/fltk/branches/branch-1.3@7023 ea41ed52-d2ee-0310-a9c1-e6b18d33e121
This commit is contained in:
Matthias Melcher 2010-01-24 00:16:33 +00:00
parent 51acfa41eb
commit 416f5b0dcd
3 changed files with 316 additions and 10 deletions

105
FL/Fl_Plugin.H Normal file
View File

@ -0,0 +1,105 @@
//
// "$Id: Fl_Plugin.H 6995 2010-01-12 08:48:55Z matt $"
//
// A Plugin system for FLTK, implemented in Fl_Preferences.cxx.
//
// Copyright 2002-2010 by Matthias Melcher.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA.
//
// Please report all bugs and problems on the following page:
//
// http://www.fltk.org/str.php
//
/* \file
Fl_Plugin class . */
#ifndef Fl_Plugin_H
# define Fl_Plugin_H
# include "Fl_Preferences.H"
/**
\brief Fl_Plugin allows link-time and run-time integration of binary modules.
Fl_Plugin and Fl_Plugin_Manager provide a small and simple solution for
linking C++ classes at run-time, or optionally linking modules at compile
time without the need to change the main application.
Fl_Plugin_Manager uses static initialisation to create the plugin interface
early during startup. Plugins are stored in a temporary database, organized
in classes.
Plugins should derive a new class from Fl_Plugin as a base:
\code
class My_Plugin : public Fl_Plugin {
public:
My_Plugin() : Fl_Plugin("effects", "blur") { }
void do_something(...);
};
My_Plugin blur_plugin();
\endcode
Plugins can be put into modules and either linked befor distribution, or loaded
from dynamically linkable files. An Fl_Plugin_Manager is used to list and
access all currently loaded plugins.
\code
Fl_Plugin_Manager mgr("effects");
int i, n = mgr.plugins();
for (i=0; i<n; i++) {
My_Plugin *pin = (My_Plugin*)mgr.plugin(i);
pin->do_something();
}
\endcode
*/
class FL_EXPORT Fl_Plugin
{
Fl_Preferences::ID id;
public:
Fl_Plugin(const char *klass, const char *name);
virtual ~Fl_Plugin();
};
/**
\brief Fl_Plugin_Manager manages link-time and run-time plugin binaries.
\see Fl_Plugin
*/
class FL_EXPORT Fl_Plugin_Manager : public Fl_Preferences
{
public:
Fl_Plugin_Manager(const char *klass);
~Fl_Plugin_Manager();
/** \brief Return the number of plugins in the klass.
*/
int plugins() { return groups(); }
Fl_Plugin *plugin(int index);
Fl_Preferences::ID addPlugin(const char *name, Fl_Plugin *plugin);
static void removePlugin(Fl_Preferences::ID id);
static int load(const char *filename);
static int loadAll(const char *filepath, const char *pattern=0);
};
#endif // !Fl_Preferences_H
//
// End of "$Id: Fl_Preferences.H 6995 2010-01-12 08:48:55Z matt $".
//

View File

@ -61,7 +61,7 @@
reasons. One application can have multiple preferences files.
Extensive binary data however should be stored in separate
files: see getUserdataPath().
\note Starting with FLTK 1.3, preference databases are expected to
be in utf8 encoding. Previous databases were stored in the
current chracter set or code page which renders them incompatible
@ -97,12 +97,17 @@ public:
Fl_Preferences( Fl_Preferences *parent, const char *group );
Fl_Preferences( Fl_Preferences &parent, int groupIndex );
Fl_Preferences( Fl_Preferences *parent, int groupIndex );
Fl_Preferences(const Fl_Preferences&);
Fl_Preferences( ID id );
~Fl_Preferences();
/** Return an ID that can later be reused to open more references to this dataset.
*/
ID id() { return (ID)node; }
/** Remove the group with this ID from a databse.
*/
static char remove(ID id) { return ((Node*)id)->remove(); }
/** Return the name of this entry.
*/
@ -189,13 +194,12 @@ public:
private:
// make the following functions unavailable
Fl_Preferences();
Fl_Preferences(const Fl_Preferences&);
Fl_Preferences() : node(0), rootNode(0) { }
Fl_Preferences &operator=(const Fl_Preferences&);
static char nameBuffer[128];
static char uuidBuffer[40];
static Fl_Preferences *runtimePrefs;
class RootNode;
@ -253,6 +257,7 @@ private:
public:
RootNode( Fl_Preferences *, Root root, const char *vendor, const char *application );
RootNode( Fl_Preferences *, const char *path, const char *vendor, const char *application );
RootNode( Fl_Preferences * );
~RootNode();
int read();
int write();
@ -260,9 +265,10 @@ private:
};
friend class RootNode;
protected:
Node *node;
RootNode *rootNode;
};

View File

@ -28,6 +28,7 @@
#include <FL/Fl.H>
#include <FL/Fl_Preferences.H>
#include <FL/Fl_Plugin.H>
#include <FL/Fl_Tree.H>
#include <FL/filename.H>
@ -40,6 +41,7 @@
#include <time.h>
#if defined(WIN32) && !defined(__CYGWIN__)
# include <windows.h>
# include <direct.h>
# include <io.h>
// Visual C++ 2005 incorrectly displays a warning about the use of POSIX APIs
@ -49,8 +51,10 @@
#elif defined (__APPLE__)
# include <Carbon/Carbon.h>
# include <unistd.h>
# include <dlfcn.h>
#else
# include <unistd.h>
# include <dlfcn.h>
#endif
#ifdef WIN32
@ -65,6 +69,7 @@ typedef RPC_STATUS (WINAPI* uuid_func)(UUID __RPC_FAR *Uuid);
char Fl_Preferences::nameBuffer[128];
char Fl_Preferences::uuidBuffer[40];
Fl_Preferences *Fl_Preferences::runtimePrefs = 0;
/**
* Returns a UUID as generated by the system.
@ -198,8 +203,13 @@ const char *Fl_Preferences::newUUID()
name of your application. Both vendor and
application must be valid relative UNIX pathnames and
may contain '/'s to create deeper file structures.
A set of Preferences marked "run-time" exists exactly one per application and
only as long as the application runs. It can be used as a database for
volatile information. FLTK uses it to register plugins at run-time.
\param[in] root can be \c USER or \c SYSTEM for user specific or system wide preferences
\param[in] root can be \c USER or \c SYSTEM for user specific or system wide
preferences
\param[in] vendor unique text describing the company or author of this file
\param[in] application unique text describing the application
*/
@ -249,10 +259,26 @@ Fl_Preferences::Fl_Preferences( Fl_Preferences &parent, const char *group )
/**
\brief Create or access a group of preferences using a name.
\param[in] parent the parameter parent is a pointer to the parent group.
\p Parent may be \p NULL. It then refers to an application internal
database which exists only once, and remains in RAM only until the
application quits. This databse is used to manage plugins and other
data indexes by strings.
\param[in] group a group name that is used as a key into the databse
\see Fl_Preferences( Fl_Preferences&, const char *group )
*/
Fl_Preferences::Fl_Preferences( Fl_Preferences *parent, const char *group )
{
if (parent==0) {
if (!runtimePrefs) {
runtimePrefs = new Fl_Preferences();
runtimePrefs->node = new Node( "." );
runtimePrefs->rootNode = new RootNode( runtimePrefs );
runtimePrefs->node->setRoot(rootNode);
}
parent = runtimePrefs;
}
rootNode = parent->rootNode;
node = parent->node->addChild( group );
}
@ -313,6 +339,25 @@ Fl_Preferences::Fl_Preferences( Fl_Preferences::ID id )
rootNode = node->findRoot();
}
/**
Create another reference to a Preferences group.
*/
Fl_Preferences::Fl_Preferences(const Fl_Preferences &rhs)
: node(rhs.node),
rootNode(rhs.rootNode)
{ }
/**
Assign another reference to a Preference group.
*/
Fl_Preferences &Fl_Preferences::operator=(const Fl_Preferences &rhs) {
if (&rhs != this) {
node = rhs.node;
rootNode = rhs.rootNode;
}
return *this;
}
/**
The destructor removes allocated resources. When used on the
@ -1025,6 +1070,10 @@ static void makePathForFile( const char *path )
// create the root node
// - construct the name of the file that will hold our preferences
Fl_Preferences::RootNode::RootNode( Fl_Preferences *prefs, Root root, const char *vendor, const char *application )
: prefs_(prefs),
filename_(0L),
vendor_(0L),
application_(0L)
{
char filename[ FL_PATH_MAX ]; filename[0] = 0;
#ifdef WIN32
@ -1141,7 +1190,6 @@ Fl_Preferences::RootNode::RootNode( Fl_Preferences *prefs, Root root, const char
"%s/%s.prefs", vendor, application);
#endif
prefs_ = prefs;
filename_ = strdup(filename);
vendor_ = strdup(vendor);
application_ = strdup(application);
@ -1152,6 +1200,10 @@ Fl_Preferences::RootNode::RootNode( Fl_Preferences *prefs, Root root, const char
// create the root node
// - construct the name of the file that will hold our preferences
Fl_Preferences::RootNode::RootNode( Fl_Preferences *prefs, const char *path, const char *vendor, const char *application )
: prefs_(prefs),
filename_(0L),
vendor_(0L),
application_(0L)
{
if (!vendor)
vendor = "unknown";
@ -1163,13 +1215,22 @@ Fl_Preferences::RootNode::RootNode( Fl_Preferences *prefs, const char *path, con
snprintf(filename, sizeof(filename), "%s/%s.prefs", path, application);
filename_ = strdup(filename);
}
prefs_ = prefs;
vendor_ = strdup(vendor);
application_ = strdup(application);
read();
}
// create a root node that exists only on RAM and can not be read or written to
// a file
Fl_Preferences::RootNode::RootNode( Fl_Preferences *prefs )
: prefs_(prefs),
filename_(0L),
vendor_(0L),
application_(0L)
{
}
// destroy the root node and all depending nodes
Fl_Preferences::RootNode::~RootNode()
{
@ -1194,9 +1255,14 @@ Fl_Preferences::RootNode::~RootNode()
// read a preferences file and construct the group tree and with all entry leafs
int Fl_Preferences::RootNode::read()
{
if (!filename_) // RUNTIME preferences
return -1;
char buf[1024];
FILE *f = fl_fopen( filename_, "rb" );
if ( !f ) return 0;
if ( !f )
return -1;
fgets( buf, 1024, f );
fgets( buf, 1024, f );
fgets( buf, 1024, f );
@ -1236,9 +1302,14 @@ int Fl_Preferences::RootNode::read()
// write the group tree and all entry leafs
int Fl_Preferences::RootNode::write()
{
if (!filename_) // RUNTIME preferences
return -1;
fl_make_path_for_file(filename_);
FILE *f = fl_fopen( filename_, "wb" );
if ( !f ) return 1;
if ( !f )
return -1;
fprintf( f, "; FLTK preferences file format 1.0\n" );
fprintf( f, "; vendor: %s\n", vendor_ );
fprintf( f, "; application: %s\n", application_ );
@ -1250,6 +1321,9 @@ int Fl_Preferences::RootNode::write()
// get the path to the preferences directory
char Fl_Preferences::RootNode::getPath( char *path, int pathlen )
{
if (!filename_) // RUNTIME preferences
return -1;
strlcpy( path, filename_, pathlen);
char *s;
@ -1673,6 +1747,127 @@ char Fl_Preferences::Node::copyTo(Fl_Tree *tree, Fl_Tree_Item *ti)
return 0;
}
/**
* \brief Create a plugin.
*
* \param[in] klass plugins are grouped in classes
* \param[in] name every plugin should have a unique name
*/
Fl_Plugin::Fl_Plugin(const char *klass, const char *name)
: id(0)
{
Fl_Plugin_Manager pm(klass);
id = pm.addPlugin(name, this);
}
/**
* \brief Clear the plugin and remove it from the database.
*/
Fl_Plugin::~Fl_Plugin()
{
if (id)
Fl_Plugin_Manager::remove(id);
}
/**
* \brief Manage all plugins belonging to one class.
*/
Fl_Plugin_Manager::Fl_Plugin_Manager(const char *klass)
: Fl_Preferences(0, Fl_Preferences::Name("%s/%s", "plugins", klass))
{
}
/**
* \brief Remove the plugin manager.
*
* Calling this does not remove the database itself or any plugins. It just
* removes the reference to the database.
*/
Fl_Plugin_Manager::~Fl_Plugin_Manager()
{
}
/**
* \brief Return the address of a plugin by index.
*/
Fl_Plugin *Fl_Plugin_Manager::plugin(int index)
{
char buf[32];
Fl_Plugin *ret = 0;
Fl_Preferences pin(this, index);
pin.get("address", buf, "@0", 32);
sscanf(buf, "@%p", &ret);
return ret;
}
/**
* \brief This function adds a new plugin to the databse.
*
* There is no need to call this function explicitly. Every Fl_Plugin constructor
* will call this function at initialization time.
*/
Fl_Preferences::ID Fl_Plugin_Manager::addPlugin(const char *name, Fl_Plugin *plugin)
{
char buf[32];
Fl_Preferences pin(this, name);
snprintf(buf, 32, "@%p", plugin);
pin.set("address", buf);
return pin.id();
}
/**
* \brief Remove any plugin.
*
* There is no need to call this function explicitly. Every Fl_Plugin destructor
* will call this function at destruction time.
*/
void Fl_Plugin_Manager::removePlugin(Fl_Preferences::ID id)
{
Fl_Preferences::remove(id);
}
/**
* \brief Load a module from disk.
*
* A module must be a dynamically linkable file for the given operating system.
* When loading a module, its +init function will be called which in turn calls
* the constructor of all statically initialized Fl_Plugin classes and adds
* them to the database.
*/
int Fl_Plugin_Manager::load(const char *filename)
{
// the functions below will autmaticaly load plugins that are defined:
// Fl_My_Plugin plugin();
#ifdef WIN32
HMODULE dl = LoadLibrary(filename);
#else
void * dl = dlopen(filename, RTLD_LAZY);
#endif
// There is no way of unloading a plugin!
return (dl!=0) ? 0 : -1;
}
/**
* \brief Use this function to load a whole directory full of modules.
*/
int Fl_Plugin_Manager::loadAll(const char *filepath, const char *pattern)
{
struct dirent **dir;
int i, n = fl_filename_list(filepath, &dir);
for (i=0; i<n; i++) {
struct dirent *e = dir[i];
if (pattern==0 || fl_filename_match(e->d_name, pattern)) {
load(Fl_Preferences::Name("%s%s", filepath, e->d_name));
}
free(e);
}
free(dir);
return 0;
}
//
// End of "$Id$".
//