1896 lines
57 KiB
C++
1896 lines
57 KiB
C++
//
|
|
// Preferences methods for the Fast Light Tool Kit (FLTK).
|
|
//
|
|
// Copyright 2011-2022 by Bill Spitzak and others.
|
|
// Copyright 2002-2010 by Matthias Melcher.
|
|
//
|
|
// This library is free software. Distribution and use rights are outlined in
|
|
// the file "COPYING" which should have been included with this file. If this
|
|
// file is missing or damaged, see the license at:
|
|
//
|
|
// https://www.fltk.org/COPYING.php
|
|
//
|
|
// Please see the following page on how to report bugs and issues:
|
|
//
|
|
// https://www.fltk.org/bugs.php
|
|
//
|
|
|
|
#include <FL/Fl.H>
|
|
#include "Fl_System_Driver.H"
|
|
#include <FL/Fl_Preferences.H>
|
|
#include <FL/Fl_Plugin.H>
|
|
#include <FL/filename.H>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <FL/fl_utf8.h>
|
|
#include <FL/fl_string_functions.h>
|
|
#include "flstring.h"
|
|
|
|
|
|
char Fl_Preferences::nameBuffer[128];
|
|
char Fl_Preferences::uuidBuffer[40];
|
|
Fl_Preferences *Fl_Preferences::runtimePrefs = 0;
|
|
unsigned int Fl_Preferences::fileAccess_ = Fl_Preferences::ALL;
|
|
|
|
static int clocale_snprintf(char *buffer, size_t buffer_size, const char *format, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
int retval = Fl::system_driver()->clocale_vsnprintf(buffer, buffer_size, format, args);
|
|
va_end(args);
|
|
return retval;
|
|
}
|
|
|
|
static int clocale_sscanf(const char *input, const char *format, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, format);
|
|
int retval = Fl::system_driver()->clocale_vsscanf(input, format, args);
|
|
va_end(args);
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
Returns a UUID as generated by the system.
|
|
|
|
A UUID is a "universally unique identifier" which is commonly used in
|
|
configuration files to create identities. A UUID in ASCII looks like this:
|
|
<tt>937C4900-51AA-4C11-8DD3-7AB59944F03E</tt>. It has always 36 bytes plus
|
|
a trailing zero.
|
|
|
|
\return a pointer to a static buffer containing the new UUID in ASCII format.
|
|
The buffer is overwritten during every call to this function!
|
|
*/
|
|
const char *Fl_Preferences::new_UUID() {
|
|
Fl::system_driver()->newUUID(uuidBuffer);
|
|
return uuidBuffer;
|
|
}
|
|
|
|
/**
|
|
Tell the FLTK preferences system which files in the file system it may read, create, or write.
|
|
|
|
The FLTK core library will try to read or even create or write preference files
|
|
when calling Fl::option(), Fl_File_Chooser, the printing panel, and possibly
|
|
some other internal functions. If your application wants to keep FLTK from
|
|
touching the file system, call this function before making any other FLTK calls:
|
|
|
|
\code
|
|
// neither FLTK nor the app may read, create, or write preference files
|
|
Fl_Preferences::file_access( Fl_Preferences::NONE );
|
|
\endcode
|
|
|
|
or
|
|
|
|
\code
|
|
// FLTK may not read, create, or write preference files, but the application may
|
|
Fl_Preferences::file_access( Fl_Preferences::APP_OK );
|
|
\endcode
|
|
|
|
All flags can be combined using an OR operator. If flags are not set, that
|
|
specific access to the file system will not be allowed. By default, all access
|
|
is granted. To clear one or more flags from the default setting, use:
|
|
\code
|
|
Fl_Preferences::file_access( Fl_Preferences::file_access()
|
|
&~ Fl_Preferences::SYSTEM_WRITE );
|
|
\endcode
|
|
|
|
If preferences are created using a filename (instead of Fl_Preferences::USER or
|
|
Fl_Preferences::SYSTEM), file access is handled as if the Fl_Preferences::USER
|
|
flag was set.
|
|
|
|
\see Fl_Preferences::NONE and others for a list of flags.
|
|
\see Fl_Preferences::file_access()
|
|
*/
|
|
void Fl_Preferences::file_access(unsigned int flags)
|
|
{
|
|
fileAccess_ = flags;
|
|
}
|
|
|
|
/**
|
|
Return the current file access permissions for the FLTK preferences system.
|
|
|
|
\see Fl_Preferences::file_access(unsigned int)
|
|
*/
|
|
unsigned int Fl_Preferences::file_access()
|
|
{
|
|
return fileAccess_;
|
|
}
|
|
|
|
/**
|
|
Determine the file name and path to preferences that would be openend with
|
|
these parameters.
|
|
|
|
Find the possible location of a preference file on disk without touching any
|
|
of the pathname components. This can be used to check if a preference file
|
|
already exists.
|
|
|
|
\param[out] buffer write the resulting path into this buffer
|
|
\param[in] buffer_size size of the `buffer` in bytes
|
|
\param[in] root can be \c USER_L or \c SYSTEM_L for user specific or system
|
|
wide preferences
|
|
\param[in] vendor unique text describing the company or author of this file,
|
|
must be a valid filepath segment
|
|
\param[in] application unique text describing the application, must be a
|
|
valid filepath segment
|
|
\return the input root value, or Fl_Preferences::UNKNOWN_ROOT_TYPE if the path
|
|
could not be determined.
|
|
\see Fl_Preferences( Root root, const char *vendor, const char *application )
|
|
*/
|
|
Fl_Preferences::Root Fl_Preferences::filename( char *buffer, size_t buffer_size, Root root, const char *vendor, const char *application )
|
|
{
|
|
Root ret = UNKNOWN_ROOT_TYPE;
|
|
if (buffer && buffer_size>0) {
|
|
char *fn = Fl::system_driver()->preference_rootnode(NULL, root, vendor, application);
|
|
if (fn) {
|
|
fl_strlcpy(buffer, fn, buffer_size);
|
|
// FLTK always returns forward slashes in paths
|
|
{ char *s; for ( s = buffer; *s; s++ ) if ( *s == '\\' ) *s = '/'; }
|
|
ret = root;
|
|
} else {
|
|
buffer[0] = 0;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
The constructor creates a group that manages key/value pairs and
|
|
child groups.
|
|
|
|
Preferences can be stored per user using the root type
|
|
`Fl_Preferences::USER_L`, or stored system-wide using
|
|
`Fl_Preferences::SYSTEM_L`.
|
|
|
|
Groups and key/value pairs can be read and written randomly. Reading undefined
|
|
values will return the default value. Writing undefined values will create
|
|
all required groups and key/vlaue pairs.
|
|
|
|
This constructor creates the <i>base</i> instance for all following entries
|
|
and reads the database from disk into memory if it exists.
|
|
The vendor argument is a unique text string identifying the development team
|
|
or vendor of an application. A domain name or an EMail address (replacing
|
|
the '@' with a '.') are great unique names, e.g. "research.matthiasm.com" or
|
|
"fluid.fltk.org".
|
|
The application argument can be the working title or final name of your
|
|
application.
|
|
Both vendor and application must be valid UNIX path segments as they become
|
|
parts of the preference file path and may contain forward slashes to create
|
|
deeper file structures.
|
|
|
|
\note On \b Windows, the directory is constructed by querying the
|
|
<i>Common AppData</i> or <i>AppData</i> key of the
|
|
<tt>Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders</tt>
|
|
registry entry.
|
|
The filename and path is then constructed as
|
|
<tt>\$(query)/\$(vendor)/\$(application).prefs</tt> .
|
|
If the query call fails, data will be stored in RAM only.
|
|
It will be lost when the app exits.
|
|
|
|
\par In FLTK versions before 1.4.0, if querying the registry failed,
|
|
preferences would be written to
|
|
<tt>C:\\FLTK\\\$(vendor)\\\$(application).prefs</tt> .
|
|
|
|
\note On \b Linux, the \c USER directory is constructed by reading \c $HOME .
|
|
If \c $HOME is not set or not pointing to an existing directory, FLTK will
|
|
check the path member of the passwd struct returned by \c getpwuid(getuid()) .
|
|
If all attempts fail, data will be stored in RAM only and be lost when the
|
|
app exits.
|
|
|
|
The \c SYSTEM preference filename is hardcoded as
|
|
<tt>/etc/fltk/\$(vendor)/\$(application).prefs</tt> .
|
|
|
|
For backward compatibility, the old \c USER `.prefs` file naming scheme
|
|
<tt>\$(directory)/.fltk/\$(vendor)/\$(application).prefs</tt> is checked first.
|
|
If that file does not exist, the environment variable `$XDG_CONFIG_HOME` is
|
|
read as a base directory. If `$XDG_CONFIG_HOME` not set, the base directory
|
|
defaults to `$HOME/.config/`.
|
|
|
|
The user preferences will be stored in
|
|
<tt>\$(directory)/\$(vendor)/\$(application).prefs</tt>. The user data path
|
|
will be
|
|
<tt>\$(directory)/\$(vendor)/\$(application)/</tt>.
|
|
|
|
In FLTK versions before 1.4.0, if \c $HOME was not set, the \c USER path
|
|
would be empty, generating <tt>\$(vendor)/\$(application).prefs</tt>, which
|
|
was used relative to the current working directory.
|
|
|
|
\note On \b macOS, the \c USER directory is constructed by reading \c $HOME .
|
|
If \c $HOME is not set or not pointing to an existing directory, we check the
|
|
path returned by \c NSHomeDirectory() , and finally checking the path member
|
|
of the passwd struct returned by \c getpwuid(getuid()) .
|
|
If all attempts fail, data will be stored in RAM only and be lost when the app exits.
|
|
The filename and path is then constructed as
|
|
<tt>\$(directory)/Library/Preferences/\$(vendor)/\$(application).prefs</tt> .
|
|
The \c SYSTEM directory is hardcoded as
|
|
<tt>/Library/Preferences/\$(vendor)/\$(application).prefs</tt> .
|
|
|
|
\par In FLTK versions before 1.4.0, if \c $HOME was not set, the \c USER path
|
|
would be \c NULL , generating
|
|
<tt>\<null\>/Library/Preferences/\$(vendor)/\$(application).prefs</tt>,
|
|
which would silently fail to create a preference file.
|
|
|
|
\param[in] root can be \c USER_L or \c SYSTEM_L for user specific or system wide preferences
|
|
\param[in] vendor unique text describing the company or author of this file, must be a valid filepath segment
|
|
\param[in] application unique text describing the application, must be a valid filepath segment
|
|
|
|
\see Fl_Preferences(Fl_Preferences *parent, const char *group) with parent set to NULL
|
|
*/
|
|
Fl_Preferences::Fl_Preferences( Root root, const char *vendor, const char *application ) {
|
|
node = new Node( "." );
|
|
rootNode = new RootNode( this, root, vendor, application );
|
|
node->setRoot(rootNode);
|
|
}
|
|
|
|
/**
|
|
\brief Use this constructor to create or read a preference file at an
|
|
arbitrary position in the file system.
|
|
|
|
The file name is generated in the form <tt>\$(path)/\$(application).prefs</tt>.
|
|
If \p application is \c NULL, \p path is taken literally as the file path and name.
|
|
|
|
\param[in] path path to the directory that contains the preference file
|
|
\param[in] vendor unique text describing the company or author of this file, must be a valid filepath segment
|
|
\param[in] application unique text describing the application, must be a valid filepath segment
|
|
*/
|
|
Fl_Preferences::Fl_Preferences( const char *path, const char *vendor, const char *application ) {
|
|
node = new Node( "." );
|
|
rootNode = new RootNode( this, path, vendor, application );
|
|
node->setRoot(rootNode);
|
|
}
|
|
|
|
/**
|
|
\brief Generate or read a new group of entries within another group.
|
|
|
|
Use the \p group argument to name the group that you would like to access.
|
|
\p Group can also contain a path to a group further down the hierarchy by
|
|
separating group names with a forward slash '/'.
|
|
|
|
\param[in] parent reference object for the new group
|
|
\param[in] group name of the group to access (may contain '/'s)
|
|
*/
|
|
Fl_Preferences::Fl_Preferences( Fl_Preferences &parent, const char *group ) {
|
|
rootNode = parent.rootNode;
|
|
node = parent.node->addChild( group );
|
|
}
|
|
|
|
/**
|
|
\brief Create or access a group of preferences using a name.
|
|
|
|
Parent should point to a previously created parent preferences group to
|
|
create a preferences hierarchy.
|
|
|
|
If `parent` is set to `NULL`, an unnamed database will be accessed that exists
|
|
only in local memory and is not associated with a file on disk. The root type
|
|
of this database is set to `Fl_Preferences::MEMORY`.
|
|
|
|
- the memory database is \em not shared among multiple instances of the same app
|
|
- memory databases are \em not thread safe
|
|
- all data will be lost when the app quits
|
|
|
|
```
|
|
void some_function() {
|
|
Fl_Preferences guide( NULL, "Guide" );
|
|
guide.set("answer", 42);
|
|
}
|
|
void other_function() {
|
|
int x;
|
|
Fl_Preferences guide( NULL, "Guide" );
|
|
guide.get("answer", x, -1);
|
|
}
|
|
```
|
|
|
|
FLTK uses the memory database to manage plugins. See `Fl_Plugin`.
|
|
|
|
\param[in] parent the parameter parent is a pointer to the parent group.
|
|
If \p parent is \p NULL, the new preferences item refers to an
|
|
application internal database ("runtime prefs") which exists only
|
|
once, and remains in RAM only until the application quits.
|
|
This database is used to manage plugins and other data indexes
|
|
by strings. Runtime prefs are \em not thread-safe.
|
|
\param[in] group a group name that is used as a key into the database
|
|
\see Fl_Preferences( Fl_Preferences&, const char *group )
|
|
*/
|
|
Fl_Preferences::Fl_Preferences( Fl_Preferences *parent, const char *group ) {
|
|
if (parent==NULL) {
|
|
if (!runtimePrefs) {
|
|
runtimePrefs = new Fl_Preferences();
|
|
runtimePrefs->node = new Node( "." );
|
|
runtimePrefs->rootNode = new RootNode( runtimePrefs );
|
|
runtimePrefs->node->setRoot(runtimePrefs->rootNode);
|
|
}
|
|
parent = runtimePrefs;
|
|
}
|
|
rootNode = parent->rootNode;
|
|
node = parent->node->addChild( group );
|
|
}
|
|
|
|
/**
|
|
\brief Open a child group using a given index.
|
|
|
|
Use the \p groupIndex argument to find the group that you would like to access.
|
|
If the given index is invalid (negative or too high), a new group is created
|
|
with a UUID as a name.
|
|
|
|
The index needs to be fixed. It is currently backward. Index 0 points
|
|
to the last member in the 'list' of preferences.
|
|
|
|
\param[in] parent reference object for the new group
|
|
\param[in] groupIndex zero based index into child groups
|
|
*/
|
|
Fl_Preferences::Fl_Preferences( Fl_Preferences &parent, int groupIndex ) {
|
|
rootNode = parent.rootNode;
|
|
if (groupIndex<0 || groupIndex>=parent.groups()) {
|
|
node = parent.node->addChild( newUUID() );
|
|
} else {
|
|
node = parent.node->childNode( groupIndex );
|
|
}
|
|
}
|
|
|
|
/**
|
|
\see Fl_Preferences( Fl_Preferences&, int groupIndex )
|
|
*/
|
|
Fl_Preferences::Fl_Preferences( Fl_Preferences *parent, int groupIndex ) {
|
|
rootNode = parent->rootNode;
|
|
if (groupIndex<0 || groupIndex>=parent->groups()) {
|
|
node = parent->node->addChild( newUUID() );
|
|
} else {
|
|
node = parent->node->childNode( groupIndex );
|
|
}
|
|
}
|
|
|
|
/**
|
|
Create a new dataset access point using a dataset ID.
|
|
|
|
ID's are a great way to remember shortcuts to database entries that are deeply
|
|
nested in a preferences database, as long as the database root is not deleted.
|
|
An ID can be retrieved from any Fl_Preferences dataset, and can then be used
|
|
to create multiple new references to the same dataset.
|
|
|
|
ID's can be very helpful when put into the <tt>user_data()</tt> field of
|
|
widget callbacks.
|
|
*/
|
|
Fl_Preferences::Fl_Preferences( Fl_Preferences::ID id ) {
|
|
node = (Node*)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
|
|
\em base preferences group, the destructor flushes all changes
|
|
to the preference file and deletes all internal databases.
|
|
|
|
The destructor does not remove any data from the database. It merely
|
|
deletes your reference to the database.
|
|
*/
|
|
Fl_Preferences::~Fl_Preferences() {
|
|
if (node && !node->parent()) delete rootNode;
|
|
// DO NOT delete nodes! The root node will do that after writing the preferences.
|
|
// Zero all pointer to avoid memory errors, even though
|
|
// Valgrind does not complain (Cygwin does though)
|
|
node = 0L;
|
|
rootNode = 0L;
|
|
}
|
|
|
|
/**
|
|
Return the file name and path to the preference file.
|
|
|
|
If the preferences have not changed or have not been flushed, the file
|
|
or directory may not have been created yet.
|
|
|
|
\param[out] buffer write the resulting path into this buffer
|
|
\param[in] buffer_size size of the `buffer` in bytes
|
|
\return the root type at creation type, or MEMORY for runtime prefs, it does
|
|
not return CORE or LOCALE flags.
|
|
*/
|
|
Fl_Preferences::Root Fl_Preferences::filename( char *buffer, size_t buffer_size)
|
|
{
|
|
if (!buffer || buffer_size==0)
|
|
return UNKNOWN_ROOT_TYPE;
|
|
RootNode *rn = rootNode;
|
|
if (!rn)
|
|
return UNKNOWN_ROOT_TYPE;
|
|
if (rn->root()==MEMORY)
|
|
return MEMORY;
|
|
char *fn = rn->filename();
|
|
if (!fn)
|
|
return UNKNOWN_ROOT_TYPE;
|
|
fl_strlcpy(buffer, fn, buffer_size);
|
|
if (buffer[0]==0)
|
|
return UNKNOWN_ROOT_TYPE;
|
|
return (Root)(rn->root() & ROOT_MASK);
|
|
}
|
|
|
|
/**
|
|
Returns the number of groups that are contained within a group.
|
|
|
|
\return 0 for no groups at all
|
|
*/
|
|
int Fl_Preferences::groups() {
|
|
return node->nChildren();
|
|
}
|
|
|
|
/**
|
|
Returns the name of the Nth (\p num_group) group.
|
|
There is no guaranteed order of group names. The index must
|
|
be within the range given by groups().
|
|
|
|
\param[in] num_group number indexing the requested group
|
|
\return 'C' string pointer to the group name
|
|
*/
|
|
const char *Fl_Preferences::group( int num_group ) {
|
|
return node->child( num_group );
|
|
}
|
|
|
|
/**
|
|
Returns non-zero if a group with this name exists.
|
|
Group names are relative to the Fl_Preferences node and can contain a path.
|
|
"." describes the current node, "./" describes the topmost node.
|
|
By preceding a groupname with a "./" its path becomes relative to the topmost node.
|
|
|
|
\param[in] key name of group that is searched for
|
|
\return 0 if no group by that name was found
|
|
*/
|
|
char Fl_Preferences::group_exists( const char *key ) {
|
|
return node->search( key ) ? 1 : 0 ;
|
|
}
|
|
|
|
/**
|
|
Deletes a group.
|
|
|
|
Removes a group and all keys and groups within that group
|
|
from the database.
|
|
|
|
\param[in] group name of the group to delete
|
|
\return 0 if call failed
|
|
*/
|
|
char Fl_Preferences::delete_group( const char *group ) {
|
|
Node *nd = node->search( group );
|
|
if ( nd ) return nd->remove();
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
Delete all groups.
|
|
*/
|
|
char Fl_Preferences::delete_all_groups() {
|
|
node->deleteAllChildren();
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
Returns the number of entries (name/value pairs) in a group.
|
|
|
|
\return number of entries
|
|
*/
|
|
int Fl_Preferences::entries() {
|
|
return node->nEntry();
|
|
}
|
|
|
|
/**
|
|
Returns the name of an entry. There is no guaranteed order of entry
|
|
names. The index must be within the range given by entries().
|
|
|
|
\param[in] index number indexing the requested entry
|
|
\return pointer to value cstring
|
|
*/
|
|
const char *Fl_Preferences::entry( int index ) {
|
|
return node->entry(index).name;
|
|
}
|
|
|
|
/**
|
|
Returns non-zero if an entry with this name exists.
|
|
|
|
\param[in] key name of entry that is searched for
|
|
\return 0 if entry was not found
|
|
*/
|
|
char Fl_Preferences::entry_exists( const char *key ) {
|
|
return node->getEntry( key )>=0 ? 1 : 0 ;
|
|
}
|
|
|
|
/**
|
|
Deletes a single name/value pair.
|
|
|
|
This function removes the entry \p key from the database.
|
|
|
|
\param[in] key name of entry to delete
|
|
\return 0 if deleting the entry failed
|
|
*/
|
|
char Fl_Preferences::delete_entry( const char *key ) {
|
|
return node->deleteEntry( key );
|
|
}
|
|
|
|
/**
|
|
Delete all entries.
|
|
*/
|
|
char Fl_Preferences::delete_all_entries() {
|
|
node->deleteAllEntries();
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
Delete all groups and all entries.
|
|
*/
|
|
char Fl_Preferences::clear() {
|
|
char ret1 = deleteAllGroups();
|
|
char ret2 = deleteAllEntries();
|
|
return ret1 & ret2;
|
|
}
|
|
|
|
/**
|
|
Reads an entry from the group. A default value must be
|
|
supplied. The return value indicates if the value was available
|
|
(non-zero) or the default was used (0).
|
|
|
|
\param[in] key name of entry
|
|
\param[out] value returned from preferences or default value if none was set
|
|
\param[in] defaultValue default value to be used if no preference was set
|
|
\return 0 if the default value was used
|
|
*/
|
|
char Fl_Preferences::get( const char *key, int &value, int defaultValue ) {
|
|
const char *v = node->get( key );
|
|
value = v ? atoi( v ) : defaultValue;
|
|
return ( v != 0 );
|
|
}
|
|
|
|
/**
|
|
Sets an entry (name/value pair). The return value indicates if there
|
|
was a problem storing the data in memory. However it does not reflect
|
|
if the value was actually stored in the preference file.
|
|
|
|
\param[in] key name of entry
|
|
\param[in] value set this entry to \p value
|
|
\return 0 if setting the value failed
|
|
*/
|
|
char Fl_Preferences::set( const char *key, int value ) {
|
|
sprintf( nameBuffer, "%d", value );
|
|
node->set( key, nameBuffer );
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
Reads an entry from the group. A default value must be
|
|
supplied. The return value indicates if the value was available
|
|
(non-zero) or the default was used (0).
|
|
|
|
\param[in] key name of entry
|
|
\param[out] value returned from preferences or default value if none was set
|
|
\param[in] defaultValue default value to be used if no preference was set
|
|
\return 0 if the default value was used
|
|
*/
|
|
char Fl_Preferences::get( const char *key, float &value, float defaultValue ) {
|
|
const char *v = node->get( key );
|
|
if (v) {
|
|
if (rootNode->root() & C_LOCALE) {
|
|
clocale_sscanf(v, "%g", &value);
|
|
} else {
|
|
value = (float)atof(v);
|
|
}
|
|
} else {
|
|
value = defaultValue;
|
|
}
|
|
return ( v != NULL );
|
|
}
|
|
|
|
/**
|
|
Sets an entry (name/value pair). The return value indicates if there
|
|
was a problem storing the data in memory. However it does not reflect
|
|
if the value was actually stored in the preference file.
|
|
|
|
\param[in] key name of entry
|
|
\param[in] value set this entry to \p value
|
|
\return 0 if setting the value failed
|
|
*/
|
|
char Fl_Preferences::set( const char *key, float value ) {
|
|
if (rootNode->root() & C_LOCALE) {
|
|
clocale_snprintf( nameBuffer, sizeof(nameBuffer), "%g", value );
|
|
} else {
|
|
snprintf( nameBuffer, sizeof(nameBuffer), "%g", value );
|
|
}
|
|
node->set( key, nameBuffer );
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
Sets an entry (name/value pair). The return value indicates if there
|
|
was a problem storing the data in memory. However it does not reflect
|
|
if the value was actually stored in the preference file.
|
|
|
|
\param[in] key name of entry
|
|
\param[in] value set this entry to \p value
|
|
\param[in] precision number of decimal digits to represent value
|
|
\return 0 if setting the value failed
|
|
*/
|
|
char Fl_Preferences::set( const char *key, float value, int precision ) {
|
|
if (rootNode->root() & C_LOCALE) {
|
|
clocale_snprintf( nameBuffer, sizeof(nameBuffer), "%.*g", precision, value );
|
|
} else {
|
|
snprintf( nameBuffer, sizeof(nameBuffer), "%.*g", precision, value );
|
|
}
|
|
node->set( key, nameBuffer );
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
Reads an entry from the group. A default value must be
|
|
supplied. The return value indicates if the value was available
|
|
(non-zero) or the default was used (0).
|
|
|
|
\param[in] key name of entry
|
|
\param[out] value returned from preferences or default value if none was set
|
|
\param[in] defaultValue default value to be used if no preference was set
|
|
\return 0 if the default value was used
|
|
*/
|
|
char Fl_Preferences::get( const char *key, double &value, double defaultValue ) {
|
|
const char *v = node->get( key );
|
|
if (v) {
|
|
if (rootNode->root() & C_LOCALE) {
|
|
clocale_sscanf(v, "%lg", &value);
|
|
} else {
|
|
value = atof(v);
|
|
}
|
|
} else {
|
|
value = defaultValue;
|
|
}
|
|
return ( v != NULL );
|
|
}
|
|
|
|
/**
|
|
Sets an entry (name/value pair). The return value indicates if there
|
|
was a problem storing the data in memory. However it does not reflect
|
|
if the value was actually stored in the preference file.
|
|
|
|
\param[in] key name of entry
|
|
\param[in] value set this entry to \p value
|
|
\return 0 if setting the value failed
|
|
*/
|
|
char Fl_Preferences::set( const char *key, double value ) {
|
|
if (rootNode->root() & C_LOCALE) {
|
|
clocale_snprintf( nameBuffer, sizeof(nameBuffer), "%lg", value );
|
|
} else {
|
|
snprintf( nameBuffer, sizeof(nameBuffer), "%lg", value );
|
|
}
|
|
node->set( key, nameBuffer );
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
Sets an entry (name/value pair). The return value indicates if there
|
|
was a problem storing the data in memory. However it does not reflect
|
|
if the value was actually stored in the preference file.
|
|
|
|
\param[in] key name of entry
|
|
\param[in] value set this entry to \p value
|
|
\param[in] precision number of decimal digits to represent value
|
|
\return 0 if setting the value failed
|
|
*/
|
|
char Fl_Preferences::set( const char *key, double value, int precision ) {
|
|
if (rootNode->root() & C_LOCALE) {
|
|
clocale_snprintf( nameBuffer, sizeof(nameBuffer), "%.*lg", precision, value );
|
|
} else {
|
|
snprintf( nameBuffer, sizeof(nameBuffer), "%.*lg", precision, value );
|
|
}
|
|
node->set( key, nameBuffer );
|
|
return 1;
|
|
}
|
|
|
|
// remove control sequences from a string
|
|
static char *decodeText( const char *src ) {
|
|
int len = 0;
|
|
const char *s = src;
|
|
for ( ; *s; s++, len++ ) {
|
|
if ( *s == '\\' ) {
|
|
if ( isdigit( s[1] ) ) {
|
|
s+=3;
|
|
} else {
|
|
s+=1;
|
|
}
|
|
}
|
|
}
|
|
char *dst = (char*)malloc( len+1 ), *d = dst;
|
|
for ( s = src; *s; s++ ) {
|
|
char c = *s;
|
|
if ( c == '\\' ) {
|
|
if ( s[1] == '\\' ) { *d++ = c; s++; }
|
|
else if ( s[1] == 'n' ) { *d++ = '\n'; s++; }
|
|
else if ( s[1] == 'r' ) { *d++ = '\r'; s++; }
|
|
else if ( isdigit( s[1] ) ) { *d++ = ((s[1]-'0')<<6) + ((s[2]-'0')<<3) + (s[3]-'0'); s+=3; }
|
|
else s++; // error
|
|
}
|
|
else
|
|
*d++ = c;
|
|
}
|
|
*d = 0;
|
|
return dst;
|
|
}
|
|
|
|
/**
|
|
Reads an entry from the group. A default value must be
|
|
supplied. The return value indicates if the value was available
|
|
(non-zero) or the default was used (0).
|
|
'maxSize' is the maximum length of text that will be read.
|
|
The text buffer must allow for one additional byte for a trailing zero.
|
|
|
|
\param[in] key name of entry
|
|
\param[out] text returned from preferences or default value if none was set
|
|
\param[in] defaultValue default value to be used if no preference was set
|
|
\param[in] maxSize maximum length of value plus one byte for a trailing zero
|
|
\return 0 if the default value was used
|
|
*/
|
|
char Fl_Preferences::get( const char *key, char *text, const char *defaultValue, int maxSize ) {
|
|
const char *v = node->get( key );
|
|
if ( v && strchr( v, '\\' ) ) {
|
|
char *w = decodeText( v );
|
|
strlcpy(text, w, maxSize);
|
|
free( w );
|
|
return 1;
|
|
}
|
|
if ( !v ) v = defaultValue;
|
|
if ( v ) strlcpy(text, v, maxSize);
|
|
else *text = 0;
|
|
return ( v != defaultValue );
|
|
}
|
|
|
|
/**
|
|
Reads an entry from the group. A default value must be
|
|
supplied. The return value indicates if the value was available
|
|
(non-zero) or the default was used (0). get() allocates memory of
|
|
sufficient size to hold the value. The buffer must be free'd by
|
|
the developer using 'free(value)'.
|
|
|
|
\param[in] key name of entry
|
|
\param[out] text returned from preferences or default value if none was set
|
|
\param[in] defaultValue default value to be used if no preference was set
|
|
\return 0 if the default value was used
|
|
*/
|
|
char Fl_Preferences::get( const char *key, char *&text, const char *defaultValue ) {
|
|
const char *v = node->get( key );
|
|
if ( v && strchr( v, '\\' ) ) {
|
|
text = decodeText( v );
|
|
return 1;
|
|
}
|
|
if ( !v ) v = defaultValue;
|
|
if ( v )
|
|
text = fl_strdup( v );
|
|
else
|
|
text = 0;
|
|
return ( v != defaultValue );
|
|
}
|
|
|
|
/**
|
|
Sets an entry (name/value pair). The return value indicates if there
|
|
was a problem storing the data in memory. However it does not
|
|
reflect if the value was actually stored in the preference file.
|
|
|
|
\param[in] key name of entry
|
|
\param[in] text set this entry to \p value
|
|
\return 0 if setting the value failed
|
|
*/
|
|
char Fl_Preferences::set( const char *key, const char *text ) {
|
|
const char *s = text ? text : "";
|
|
int n=0, ns=0;
|
|
for ( ; *s; s++ ) { n++; if ( *s<32 || *s=='\\' || *s==0x7f ) ns+=4; }
|
|
if ( ns ) {
|
|
char *buffer = (char*)malloc( n+ns+1 ), *d = buffer;
|
|
for ( s=text; *s; ) {
|
|
char c = *s;
|
|
if ( c=='\\' ) { *d++ = '\\'; *d++ = '\\'; s++; }
|
|
else if ( c=='\n' ) { *d++ = '\\'; *d++ = 'n'; s++; }
|
|
else if ( c=='\r' ) { *d++ = '\\'; *d++ = 'r'; s++; }
|
|
else if ( c<32 || c==0x7f )
|
|
{ *d++ = '\\'; *d++ = '0'+((c>>6)&3); *d++ = '0'+((c>>3)&7); *d++ = '0'+(c&7); s++; }
|
|
else *d++ = *s++;
|
|
}
|
|
*d = 0;
|
|
node->set( key, buffer );
|
|
free( buffer );
|
|
}
|
|
else
|
|
node->set( key, text );
|
|
return 1;
|
|
}
|
|
|
|
// convert a hex string to binary data
|
|
static void *decodeHex( const char *src, int &size ) {
|
|
size = (int) strlen( src )/2;
|
|
unsigned char *data = (unsigned char*)malloc( size ), *d = data;
|
|
const char *s = src;
|
|
for ( int i=size; i>0; i-- ) {
|
|
int v;
|
|
char x = tolower(*s++);
|
|
if ( x >= 'a' ) v = x-'a'+10; else v = x-'0';
|
|
v = v<<4;
|
|
x = tolower(*s++);
|
|
if ( x >= 'a' ) v += x-'a'+10; else v += x-'0';
|
|
*d++ = (uchar)v;
|
|
}
|
|
return (void*)data;
|
|
}
|
|
|
|
/**
|
|
Reads a binary entry from the group, encoded in hexadecimal blocks.
|
|
|
|
\param[in] key name of entry
|
|
\param[out] data value returned from preferences or default value if none was set
|
|
\param[in] defaultValue default value
|
|
\param[in] defaultSize size of default value array
|
|
\param[in] maxSize maximum length of value, to receive the number of bytes
|
|
read, use the function below instead.
|
|
\return 0 if the default value was used
|
|
|
|
\see Fl_Preferences::get( const char *key, void *data, const void *defaultValue, int defaultSize, int *maxSize )
|
|
*/
|
|
char Fl_Preferences::get( const char *key, void *data, const void *defaultValue, int defaultSize, int maxSize ) {
|
|
const char *v = node->get( key );
|
|
if ( v ) {
|
|
int dsize;
|
|
void *w = decodeHex( v, dsize );
|
|
memmove( data, w, dsize>maxSize?maxSize:dsize );
|
|
free( w );
|
|
return 1;
|
|
}
|
|
if ( defaultValue )
|
|
memmove( data, defaultValue, defaultSize>maxSize?maxSize:defaultSize );
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
Reads a binary entry from the group, encoded in hexadecimal blocks.
|
|
A binary (not hex) default value can be supplied.
|
|
The return value indicates if the value was available (non-zero) or the
|
|
default was used (0).
|
|
`maxSize` is the maximum length of text that will be read and returns the
|
|
actual number of bytes read.
|
|
|
|
\param[in] key name of entry
|
|
\param[out] data value returned from preferences or default value if none was set
|
|
\param[in] defaultValue default value to be used if no preference was set
|
|
\param[in] defaultSize size of default value array
|
|
\param[inout] maxSize maximum length of value and actual number of bytes set
|
|
\return 0 if the default value was used
|
|
*/
|
|
char Fl_Preferences::get( const char *key, void *data, const void *defaultValue, int defaultSize, int *maxSize ) {
|
|
if (!maxSize || !data)
|
|
return -1;
|
|
int capacity = *maxSize;
|
|
const char *v = node->get( key );
|
|
if ( v ) {
|
|
int nFound;
|
|
void *w = decodeHex( v, nFound );
|
|
int nWrite = (nFound>capacity) ? capacity : nFound;
|
|
memmove( data, w, nWrite);
|
|
free( w );
|
|
*maxSize = nWrite;
|
|
return 1;
|
|
}
|
|
if ( defaultValue ) {
|
|
int nWrite = (defaultSize>capacity) ? capacity : defaultSize;
|
|
memmove( data, defaultValue, nWrite );
|
|
*maxSize = nWrite;
|
|
} else {
|
|
*maxSize = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
Reads an entry from the group. A default value must be
|
|
supplied. The return value indicates if the value was available
|
|
(non-zero) or the default was used (0). get() allocates memory of
|
|
sufficient size to hold the value. The buffer must be free'd by
|
|
the developer using 'free(value)'.
|
|
|
|
\param[in] key name of entry
|
|
\param[out] data returned from preferences or default value if none was set
|
|
\param[in] defaultValue default value to be used if no preference was set
|
|
\param[in] defaultSize size of default value array
|
|
\return 0 if the default value was used
|
|
*/
|
|
char Fl_Preferences::get( const char *key, void *&data, const void *defaultValue, int defaultSize ) {
|
|
const char *v = node->get( key );
|
|
if ( v ) {
|
|
int dsize;
|
|
data = decodeHex( v, dsize );
|
|
return 1;
|
|
}
|
|
if ( defaultValue ) {
|
|
data = (void*)malloc( defaultSize );
|
|
memmove( data, defaultValue, defaultSize );
|
|
}
|
|
else
|
|
data = 0;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
Sets an entry (name/value pair). The return value indicates if there
|
|
was a problem storing the data in memory. However it does not
|
|
reflect if the value was actually stored in the preference file.
|
|
|
|
\param[in] key name of entry
|
|
\param[in] data set this entry to \p value
|
|
\param[in] dsize size of data array
|
|
\return 0 if setting the value failed
|
|
*/
|
|
char Fl_Preferences::set( const char *key, const void *data, int dsize ) {
|
|
char *buffer = (char*)malloc( dsize*2+1 ), *d = buffer;;
|
|
unsigned char *s = (unsigned char*)data;
|
|
for ( ; dsize>0; dsize-- ) {
|
|
static char lu[] = "0123456789abcdef";
|
|
unsigned char v = *s++;
|
|
*d++ = lu[v>>4];
|
|
*d++ = lu[v&0xf];
|
|
}
|
|
*d = 0;
|
|
node->set( key, buffer );
|
|
free( buffer );
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
Returns the size of the value part of an entry.
|
|
|
|
\param[in] key name of entry
|
|
\return size of value
|
|
*/
|
|
int Fl_Preferences::size( const char *key ) {
|
|
const char *v = node->get( key );
|
|
return (int) (v ? strlen( v ) : 0);
|
|
}
|
|
|
|
/**
|
|
\brief Creates a path that is related to the preference file and
|
|
that is usable for additional application data.
|
|
|
|
This function creates a directory that is named after the preferences
|
|
database without the \c .prefs extension and located in the same directory.
|
|
It then fills the given buffer with the complete path name.
|
|
|
|
There is no way to verify that the path name fit into the buffer.
|
|
If the name is too long, it will be clipped.
|
|
|
|
This function can be used with direct paths that don't end in \c .prefs .
|
|
\a getUserDataPath() will remove any extension and end the path with a \c / .
|
|
If the file name has no extension, \a getUserDataPath() will append \c .data/
|
|
to the path name.
|
|
|
|
Example:
|
|
\code
|
|
Fl_Preferences prefs( USER, "matthiasm.com", "test" );
|
|
char path[FL_PATH_MAX];
|
|
prefs.getUserdataPath( path, FL_PATH_MAX );
|
|
\endcode
|
|
creates the preferences database in the directory (User 'matt' on Linux):
|
|
\code
|
|
/Users/matt/.fltk/matthiasm.com/test.prefs
|
|
\endcode
|
|
..and returns the userdata path:
|
|
\code
|
|
/Users/matt/.fltk/matthiasm.com/test/
|
|
\endcode
|
|
|
|
\param[out] path buffer for user data path
|
|
\param[in] pathlen size of path buffer (should be at least \c FL_PATH_MAX )
|
|
\return 1 if there is no filename (\a path will be unmodified)
|
|
\return 1 if \a pathlen is 0 (\a path will be unmodified)
|
|
\return 1 if a path was created successfully, \a path will contain the path name ending in a '/'
|
|
\return 0 if path was not created for some reason; \a path will contain the pathname that could not be created
|
|
|
|
\see Fl_Preferences::Fl_Preferences(Root, const char*, const char*)
|
|
*/
|
|
char Fl_Preferences::get_userdata_path( char *path, int pathlen ) {
|
|
if ( rootNode )
|
|
return rootNode->getPath( path, pathlen );
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
Writes preferences to disk if they were modified.
|
|
|
|
This method can be used to verify that writing a preference file went well.
|
|
Deleting the base preferences object will also write the contents of the
|
|
database to disk.
|
|
|
|
\return -1 if anything went wrong, i.e. file could not be opened, permissions
|
|
blocked writing, etc.
|
|
\return 0 if the file was written to disk. This does not check if the disk ran
|
|
out of space and the file is truncated.
|
|
\return 1 if no data was written to the database and no write attempt
|
|
to disk was made.
|
|
*/
|
|
int Fl_Preferences::flush() {
|
|
int ret = dirty();
|
|
if (ret != 1)
|
|
return ret;
|
|
return rootNode->write();
|
|
}
|
|
|
|
/**
|
|
Check if there were changes to the database that need to be written to disk.
|
|
|
|
\return 1 if the database will be written to disk by `flush` or destructor.
|
|
\return 0 if the database is unchanged since the last write operation.
|
|
\return -1 f there is an internal database error.
|
|
*/
|
|
int Fl_Preferences::dirty() {
|
|
Node *n = node;
|
|
while (n && n->parent())
|
|
n = n->parent();
|
|
if (!n)
|
|
return -1;
|
|
return n->dirty();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// helper class to create dynamic group and entry names on the fly
|
|
//
|
|
|
|
/**
|
|
Creates a group name or entry name on the fly.
|
|
|
|
This version creates a simple unsigned integer as an entry name.
|
|
|
|
\code
|
|
int n, i;
|
|
Fl_Preferences prev( appPrefs, "PreviousFiles" );
|
|
prev.get( "n", 0 );
|
|
for ( i=0; i<n; i++ )
|
|
prev.get( Fl_Preferences::Name(i), prevFile[i], "" );
|
|
\endcode
|
|
*/
|
|
Fl_Preferences::Name::Name( unsigned int n ) {
|
|
data_ = (char*)malloc(20);
|
|
sprintf(data_, "%u", n);
|
|
}
|
|
|
|
/**
|
|
Creates a group name or entry name on the fly.
|
|
|
|
This version creates entry names as in 'printf'.
|
|
|
|
\code
|
|
int n, i;
|
|
Fl_Preferences prefs( USER, "matthiasm.com", "test" );
|
|
prev.get( "nFiles", 0 );
|
|
for ( i=0; i<n; i++ )
|
|
prev.get( Fl_Preferences::Name( "File%d", i ), prevFile[i], "" );
|
|
\endcode
|
|
*/
|
|
Fl_Preferences::Name::Name( const char *format, ... ) {
|
|
data_ = (char*)malloc(1024);
|
|
va_list args;
|
|
va_start(args, format);
|
|
vsnprintf(data_, 1024, format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
// delete the name
|
|
Fl_Preferences::Name::~Name() {
|
|
if (data_) {
|
|
free(data_);
|
|
data_ = 0L;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// internal methods, do not modify or use as they will change without notice
|
|
//
|
|
|
|
int Fl_Preferences::Node::lastEntrySet = -1;
|
|
|
|
// 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),
|
|
root_type_(root)
|
|
{
|
|
char *filename = Fl::system_driver()->preference_rootnode(prefs, root, vendor, application);
|
|
filename_ = filename ? fl_strdup(filename) : 0L;
|
|
vendor_ = fl_strdup(vendor);
|
|
application_ = fl_strdup(application);
|
|
read();
|
|
}
|
|
|
|
// 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),
|
|
root_type_(Fl_Preferences::USER)
|
|
{
|
|
|
|
if (!vendor)
|
|
vendor = "unknown";
|
|
if (!application) {
|
|
application = "unknown";
|
|
filename_ = fl_strdup(path);
|
|
} else {
|
|
char filename[ FL_PATH_MAX ]; filename[0] = 0;
|
|
snprintf(filename, sizeof(filename), "%s/%s.prefs", path, application);
|
|
filename_ = fl_strdup(filename);
|
|
}
|
|
vendor_ = fl_strdup(vendor);
|
|
application_ = fl_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),
|
|
root_type_(Fl_Preferences::MEMORY)
|
|
{
|
|
}
|
|
|
|
// destroy the root node and all depending nodes
|
|
Fl_Preferences::RootNode::~RootNode() {
|
|
if ( prefs_->node->dirty() )
|
|
write();
|
|
if ( filename_ ) {
|
|
free( filename_ );
|
|
filename_ = 0L;
|
|
}
|
|
if ( vendor_ ) {
|
|
free( vendor_ );
|
|
vendor_ = 0L;
|
|
}
|
|
if ( application_ ) {
|
|
free( application_ );
|
|
application_ = 0L;
|
|
}
|
|
delete prefs_->node;
|
|
prefs_->node = 0L;
|
|
}
|
|
|
|
// read a preference file and construct the group tree and all entry leaves
|
|
int Fl_Preferences::RootNode::read() {
|
|
if (!filename_) // RUNTIME preferences, or filename could not be created
|
|
return -1;
|
|
if ( (root_type_ & Fl_Preferences::CORE) && !(fileAccess_ & Fl_Preferences::CORE_READ_OK) ) {
|
|
prefs_->node->clearDirtyFlags();
|
|
return -1;
|
|
}
|
|
if ( ((root_type_&Fl_Preferences::ROOT_MASK)==Fl_Preferences::USER) && !(fileAccess_ & Fl_Preferences::USER_READ_OK) ) {
|
|
prefs_->node->clearDirtyFlags();
|
|
return -1;
|
|
}
|
|
if ( ((root_type_&Fl_Preferences::ROOT_MASK)==Fl_Preferences::SYSTEM) && !(fileAccess_ & Fl_Preferences::SYSTEM_READ_OK) ) {
|
|
prefs_->node->clearDirtyFlags();
|
|
return -1;
|
|
}
|
|
char buf[1024];
|
|
FILE *f = fl_fopen( filename_, "rb" );
|
|
if ( !f )
|
|
return -1;
|
|
if (fgets( buf, 1024, f )==0) { /* ignore */ }
|
|
if (fgets( buf, 1024, f )==0) { /* ignore */ }
|
|
if (fgets( buf, 1024, f )==0) { /* ignore */ }
|
|
Node *nd = prefs_->node;
|
|
for (;;) {
|
|
if ( !fgets( buf, 1024, f ) ) break; // EOF or Error
|
|
if ( buf[0]=='[' ) { // read a new group
|
|
size_t end = strcspn( buf+1, "]\n\r" );
|
|
buf[ end+1 ] = 0;
|
|
nd = prefs_->node->find( buf+1 );
|
|
} else if ( buf[0]=='+' ) { // value of previous name/value pair spans multiple lines
|
|
size_t end = strcspn( buf+1, "\n\r" );
|
|
if ( end != 0 ) { // if entry is not empty
|
|
buf[ end+1 ] = 0;
|
|
nd->add( buf+1 );
|
|
}
|
|
} else { // read a name/value pair
|
|
size_t end = strcspn( buf, "\n\r" );
|
|
if ( end != 0 ) { // if entry is not empty
|
|
buf[ end ] = 0;
|
|
nd->set( buf );
|
|
}
|
|
}
|
|
}
|
|
fclose( f );
|
|
prefs_->node->clearDirtyFlags();
|
|
return 0;
|
|
}
|
|
|
|
// write the group tree and all entry leaves
|
|
int Fl_Preferences::RootNode::write() {
|
|
if (!filename_) // RUNTIME preferences, or filename could not be created
|
|
return -1;
|
|
if ( (root_type_ & Fl_Preferences::CORE) && !(fileAccess_ & Fl_Preferences::CORE_WRITE_OK) )
|
|
return -1;
|
|
if ( ((root_type_&Fl_Preferences::ROOT_MASK)==Fl_Preferences::USER) && !(fileAccess_ & Fl_Preferences::USER_WRITE_OK) )
|
|
return -1;
|
|
if ( ((root_type_&Fl_Preferences::ROOT_MASK)==Fl_Preferences::SYSTEM) && !(fileAccess_ & Fl_Preferences::SYSTEM_WRITE_OK) )
|
|
return -1;
|
|
fl_make_path_for_file(filename_);
|
|
FILE *f = fl_fopen( filename_, "wb" );
|
|
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_ );
|
|
prefs_->node->write( f );
|
|
fclose( f );
|
|
if (Fl::system_driver()->preferences_need_protection_check()) {
|
|
// unix: make sure that system prefs are user-readable
|
|
if (strncmp(filename_, "/etc/fltk/", 10) == 0) {
|
|
char *p;
|
|
p = filename_ + 9;
|
|
do { // for each directory to the pref file
|
|
*p = 0;
|
|
fl_chmod(filename_, 0755); // rwxr-xr-x
|
|
*p = '/';
|
|
p = strchr(p+1, '/');
|
|
} while (p);
|
|
fl_chmod(filename_, 0644); // rw-r--r--
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// get the path to the preferences directory
|
|
// - copy the path into the buffer at "path"
|
|
// - if the resulting path is longer than "pathlen", it will be cropped
|
|
char Fl_Preferences::RootNode::getPath( char *path, int pathlen ) {
|
|
if (!filename_) // RUNTIME preferences. or filename could not be created
|
|
return 1; // return 1 (not -1) to be consistent with fl_make_path()
|
|
|
|
if (pathlen<=0)
|
|
return 1;
|
|
|
|
// copy the root filepath into the provided buffer
|
|
strlcpy( path, filename_, pathlen);
|
|
|
|
char *name = 0L, *ext = 0L;
|
|
|
|
// use Unix style separators
|
|
{ char *s; for ( s = path; *s; s++ ) if ( *s == '\\' ) *s = '/'; }
|
|
|
|
// find the start of the filename inside the path
|
|
name = strrchr( path, '/' );
|
|
// this is a safety measure. The root path should be absolute and contain '/'s
|
|
if (name)
|
|
name++; // point right after the '/' character
|
|
else
|
|
name = path; // point at the first character of a filename-only path
|
|
|
|
// find the last '.' which may be the start of a file extension
|
|
ext = strrchr( path, '.' );
|
|
|
|
if ( (ext==0L) || (ext<name) ) {
|
|
if (strlen(name)==0) {
|
|
// empty filenames will create a path with "data/" as a directory name
|
|
strlcat( path, "data", pathlen );
|
|
} else {
|
|
// filenames without extension will create a path with a ".data" extension
|
|
strlcat( path, ".data", pathlen );
|
|
}
|
|
} else {
|
|
// filenames with an existing extension will create a path without it
|
|
*ext = 0; // end the string right here
|
|
}
|
|
|
|
char ret = fl_make_path( path );
|
|
// unix: make sure that system prefs dir. is user-readable
|
|
if (Fl::system_driver()->preferences_need_protection_check() && strncmp(path, "/etc/fltk/", 10) == 0) {
|
|
fl_chmod(path, 0755); // rwxr-xr-x
|
|
}
|
|
strlcat( path, "/", pathlen );
|
|
return ret;
|
|
}
|
|
|
|
// create a node that represents a group
|
|
// - path must be a single word, preferable alnum(), dot and underscore only. Space is ok.
|
|
Fl_Preferences::Node::Node( const char *path ) {
|
|
if ( path ) path_ = fl_strdup( path ); else path_ = 0;
|
|
first_child_ = 0; next_ = 0; parent_ = 0;
|
|
entry_ = 0;
|
|
nEntry_ = NEntry_ = 0;
|
|
dirty_ = 0;
|
|
top_ = 0;
|
|
indexed_ = 0;
|
|
index_ = 0;
|
|
nIndex_ = NIndex_ = 0;
|
|
}
|
|
|
|
void Fl_Preferences::Node::deleteAllChildren() {
|
|
Node *next_node = NULL;
|
|
for ( Node *current_node = first_child_; current_node; current_node = next_node ) {
|
|
next_node = current_node->next_;
|
|
delete current_node;
|
|
}
|
|
first_child_ = NULL;
|
|
dirty_ = 1;
|
|
updateIndex();
|
|
}
|
|
|
|
void Fl_Preferences::Node::deleteAllEntries() {
|
|
if ( entry_ ) {
|
|
for ( int i = 0; i < nEntry_; i++ ) {
|
|
if ( entry_[i].name ) {
|
|
::free( entry_[i].name );
|
|
entry_[i].name = NULL;
|
|
}
|
|
if ( entry_[i].value ) {
|
|
::free( entry_[i].value );
|
|
entry_[i].value = NULL;
|
|
}
|
|
}
|
|
free( entry_ );
|
|
entry_ = NULL;
|
|
nEntry_ = 0;
|
|
NEntry_ = 0;
|
|
}
|
|
dirty_ = 1;
|
|
}
|
|
|
|
// delete this and all depending nodes
|
|
Fl_Preferences::Node::~Node() {
|
|
next_ = NULL;
|
|
parent_ = NULL;
|
|
deleteAllChildren();
|
|
deleteAllEntries();
|
|
deleteIndex();
|
|
if ( path_ ) {
|
|
::free( path_ );
|
|
path_ = NULL;
|
|
}
|
|
}
|
|
|
|
// recursively check if any entry is dirty (was changed after loading a fresh prefs file)
|
|
char Fl_Preferences::Node::dirty() {
|
|
if ( dirty_ ) return 1;
|
|
if ( next_ && next_->dirty() ) return 1;
|
|
if ( first_child_ && first_child_->dirty() ) return 1;
|
|
return 0;
|
|
}
|
|
|
|
// recursively clear all dirty flags
|
|
void Fl_Preferences::Node::clearDirtyFlags() {
|
|
Fl_Preferences::Node *nd = this;
|
|
while (nd) {
|
|
nd->dirty_ = 0;
|
|
if ( nd->first_child_ ) nd->first_child_->clearDirtyFlags();
|
|
nd = nd->next_;
|
|
}
|
|
}
|
|
|
|
// write this node (recursively from the last neighbor back to this)
|
|
// write all entries
|
|
// write all children
|
|
int Fl_Preferences::Node::write( FILE *f ) {
|
|
if ( next_ ) next_->write( f );
|
|
fprintf( f, "\n[%s]\n\n", path_ );
|
|
for ( int i = 0; i < nEntry_; i++ ) {
|
|
char *src = entry_[i].value;
|
|
if ( src ) { // hack it into smaller pieces if needed
|
|
fprintf( f, "%s:", entry_[i].name );
|
|
size_t cnt, written = 0;
|
|
for ( cnt = 0; cnt < 60; cnt++ )
|
|
if ( src[cnt]==0 ) break;
|
|
written += fwrite( src, cnt, 1, f );
|
|
fprintf( f, "\n" );
|
|
src += cnt;
|
|
for (;*src;) {
|
|
for ( cnt = 0; cnt < 80; cnt++ )
|
|
if ( src[cnt]==0 ) break;
|
|
fputc( '+', f );
|
|
written += fwrite( src, cnt, 1, f );
|
|
fputc( '\n', f );
|
|
src += cnt;
|
|
}
|
|
}
|
|
else
|
|
fprintf( f, "%s\n", entry_[i].name );
|
|
}
|
|
if ( first_child_ ) first_child_->write( f );
|
|
dirty_ = 0;
|
|
return 0;
|
|
}
|
|
|
|
// set the parent node and create the full path
|
|
void Fl_Preferences::Node::setParent( Node *pn ) {
|
|
parent_ = pn;
|
|
next_ = pn->first_child_;
|
|
pn->first_child_ = this;
|
|
sprintf( nameBuffer, "%s/%s", pn->path_, path_ );
|
|
free( path_ );
|
|
path_ = fl_strdup( nameBuffer );
|
|
}
|
|
|
|
// find the corresponding root node
|
|
Fl_Preferences::RootNode *Fl_Preferences::Node::findRoot() {
|
|
Node *n = this;
|
|
do {
|
|
if (n->top_)
|
|
return n->root_node_;
|
|
n = n->parent();
|
|
} while (n);
|
|
return 0L;
|
|
}
|
|
|
|
// add a child to this node and set its path (try to find it first...)
|
|
Fl_Preferences::Node *Fl_Preferences::Node::addChild( const char *path ) {
|
|
sprintf( nameBuffer, "%s/%s", path_, path );
|
|
char *name = fl_strdup( nameBuffer );
|
|
Node *nd = find( name );
|
|
free( name );
|
|
updateIndex();
|
|
return nd;
|
|
}
|
|
|
|
// create and set, or change an entry within this node
|
|
void Fl_Preferences::Node::set( const char *name, const char *value )
|
|
{
|
|
for ( int i=0; i<nEntry_; i++ ) {
|
|
if ( strcmp( name, entry_[i].name ) == 0 ) {
|
|
if ( !value ) return; // annotation
|
|
if ( strcmp( value, entry_[i].value ) != 0 ) {
|
|
if ( entry_[i].value )
|
|
free( entry_[i].value );
|
|
entry_[i].value = fl_strdup( value );
|
|
dirty_ = 1;
|
|
}
|
|
lastEntrySet = i;
|
|
return;
|
|
}
|
|
}
|
|
if ( NEntry_==nEntry_ ) {
|
|
NEntry_ = NEntry_ ? NEntry_*2 : 10;
|
|
entry_ = (Entry*)realloc( entry_, NEntry_ * sizeof(Entry) );
|
|
}
|
|
entry_[ nEntry_ ].name = fl_strdup( name );
|
|
entry_[ nEntry_ ].value = value?fl_strdup(value):0;
|
|
lastEntrySet = nEntry_;
|
|
nEntry_++;
|
|
dirty_ = 1;
|
|
}
|
|
|
|
// create or set a value (or annotation) from a single line in the file buffer
|
|
void Fl_Preferences::Node::set( const char *line ) {
|
|
// hmm. If we assume that we always read this file in the beginning,
|
|
// we can handle the dirty flag 'quick and dirty'
|
|
char dirt = dirty_;
|
|
if ( line[0]==';' || line[0]==0 || line[0]=='#' ) {
|
|
set( line, 0 );
|
|
} else {
|
|
const char *c = strchr( line, ':' );
|
|
if ( c ) {
|
|
size_t len = c-line+1;
|
|
if ( len >= sizeof( nameBuffer ) )
|
|
len = sizeof( nameBuffer );
|
|
strlcpy( nameBuffer, line, len );
|
|
set( nameBuffer, c+1 );
|
|
} else {
|
|
set( line, "" );
|
|
}
|
|
}
|
|
dirty_ = dirt;
|
|
}
|
|
|
|
// Append data to an existing node. This is only used in read operations when
|
|
// a single entry stretches over multiple lines in the prefs file.
|
|
void Fl_Preferences::Node::add( const char *line ) {
|
|
if ( lastEntrySet<0 || lastEntrySet>=nEntry_ ) return;
|
|
char *&dst = entry_[ lastEntrySet ].value;
|
|
size_t a = strlen( dst );
|
|
size_t b = strlen( line );
|
|
dst = (char*)realloc( dst, a+b+1 );
|
|
memcpy( dst+a, line, b+1 );
|
|
}
|
|
|
|
// get the value for a name, returns 0 if no such name
|
|
const char *Fl_Preferences::Node::get( const char *name ) {
|
|
int i = getEntry( name );
|
|
return i>=0 ? entry_[i].value : 0 ;
|
|
}
|
|
|
|
// find the index of an entry, returns -1 if no such entry
|
|
int Fl_Preferences::Node::getEntry( const char *name ) {
|
|
for ( int i=0; i<nEntry_; i++ ) {
|
|
if ( strcmp( name, entry_[i].name ) == 0 ) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// remove one entry form this group
|
|
char Fl_Preferences::Node::deleteEntry( const char *name ) {
|
|
int ix = getEntry( name );
|
|
if ( ix == -1 ) return 0;
|
|
memmove( entry_+ix, entry_+ix+1, (nEntry_-ix-1) * sizeof(Entry) );
|
|
nEntry_--;
|
|
dirty_ = 1;
|
|
return 1;
|
|
}
|
|
|
|
// find a group somewhere in the tree starting here
|
|
// - this method will always return a valid node (except for memory allocation problems)
|
|
// - if the node was not found, 'find' will create the required branch
|
|
Fl_Preferences::Node *Fl_Preferences::Node::find( const char *path ) {
|
|
int len = (int) strlen( path_ );
|
|
if ( strncmp( path, path_, len ) == 0 ) {
|
|
if ( path[ len ] == 0 )
|
|
return this;
|
|
if ( path[ len ] == '/' ) {
|
|
Node *nd;
|
|
for ( nd = first_child_; nd; nd = nd->next_ ) {
|
|
Node *nn = nd->find( path );
|
|
if ( nn ) return nn;
|
|
}
|
|
const char *s = path+len+1;
|
|
const char *e = strchr( s, '/' );
|
|
if (e) strlcpy( nameBuffer, s, e-s+1 );
|
|
else strlcpy( nameBuffer, s, sizeof(nameBuffer));
|
|
nd = new Node( nameBuffer );
|
|
nd->setParent( this );
|
|
dirty_ = 1;
|
|
return nd->find( path );
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// find a group somewhere in the tree starting here
|
|
// caller must not set 'offset' argument
|
|
// - if the node does not exist, 'search' returns NULL
|
|
// - if the pathname is "." (current node) return this node
|
|
// - if the pathname is "./" (root node) return the topmost node
|
|
// - if the pathname starts with "./", start the search at the root node instead
|
|
Fl_Preferences::Node *Fl_Preferences::Node::search( const char *path, int offset ) {
|
|
if ( offset == 0 ) {
|
|
if ( path[0] == '.' ) {
|
|
if ( path[1] == 0 ) {
|
|
return this; // user was searching for current node
|
|
} else if ( path[1] == '/' ) {
|
|
Node *nn = this;
|
|
while ( nn->parent() ) nn = nn->parent();
|
|
if ( path[2]==0 ) { // user is searching for root ( "./" )
|
|
return nn;
|
|
}
|
|
return nn->search( path+2, 2 ); // do a relative search on the root node
|
|
}
|
|
}
|
|
offset = (int) strlen( path_ ) + 1;
|
|
}
|
|
int len = (int) strlen( path_ );
|
|
if ( len < offset-1 ) return 0;
|
|
len -= offset;
|
|
if ( ( len <= 0 ) || ( strncmp( path, path_+offset, len ) == 0 ) ) {
|
|
if ( len > 0 && path[ len ] == 0 )
|
|
return this;
|
|
if ( len <= 0 || path[ len ] == '/' ) {
|
|
for ( Node *nd = first_child_; nd; nd = nd->next_ ) {
|
|
Node *nn = nd->search( path, offset );
|
|
if ( nn ) return nn;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// return the number of child nodes (groups)
|
|
int Fl_Preferences::Node::nChildren() {
|
|
if (indexed_) {
|
|
return nIndex_;
|
|
} else {
|
|
int cnt = 0;
|
|
for ( Node *nd = first_child_; nd; nd = nd->next_ )
|
|
cnt++;
|
|
return cnt;
|
|
}
|
|
}
|
|
|
|
// return the node name
|
|
const char *Fl_Preferences::Node::name() {
|
|
if ( path_ ) {
|
|
char *r = strrchr( path_, '/' );
|
|
return r ? r+1 : path_ ;
|
|
} else {
|
|
return 0L ;
|
|
}
|
|
}
|
|
|
|
// return the n'th child node's name
|
|
const char *Fl_Preferences::Node::child( int ix ) {
|
|
Node *nd = childNode( ix );
|
|
if ( nd )
|
|
return nd->name();
|
|
else
|
|
return 0L ;
|
|
}
|
|
|
|
// return the n'th child node
|
|
Fl_Preferences::Node *Fl_Preferences::Node::childNode( int ix ) {
|
|
createIndex();
|
|
if (indexed_) {
|
|
// usually faster access in correct order, but needing more memory
|
|
return index_[ix];
|
|
} else {
|
|
// slow access and reverse order
|
|
int n = nChildren();
|
|
ix = n - ix -1;
|
|
Node *nd;
|
|
for ( nd = first_child_; nd; nd = nd->next_ ) {
|
|
if ( !ix-- ) break;
|
|
if ( !nd ) break;
|
|
}
|
|
return nd;
|
|
}
|
|
}
|
|
|
|
// remove myself from the list and delete me (and all children)
|
|
char Fl_Preferences::Node::remove() {
|
|
Node *nd = NULL, *np = NULL;
|
|
Node *parent_node = parent();
|
|
if ( parent_node ) {
|
|
nd = parent_node->first_child_; np = NULL;
|
|
for ( ; nd; np = nd, nd = nd->next_ ) {
|
|
if ( nd == this ) {
|
|
if ( np )
|
|
np->next_ = next_;
|
|
else
|
|
parent_node->first_child_ = next_;
|
|
next_ = NULL;
|
|
break;
|
|
}
|
|
}
|
|
parent_node->dirty_ = 1;
|
|
parent_node->updateIndex();
|
|
}
|
|
delete this;
|
|
return ( nd != NULL );
|
|
}
|
|
|
|
void Fl_Preferences::Node::createIndex() {
|
|
if (indexed_) return;
|
|
int n = nChildren();
|
|
if (n>NIndex_) {
|
|
NIndex_ = n + 16;
|
|
index_ = (Node**)realloc(index_, NIndex_*sizeof(Node*));
|
|
}
|
|
Node *nd;
|
|
int i = 0;
|
|
for (nd = first_child_; nd; nd = nd->next_, i++) {
|
|
index_[n-i-1] = nd;
|
|
}
|
|
nIndex_ = n;
|
|
indexed_ = 1;
|
|
}
|
|
|
|
void Fl_Preferences::Node::updateIndex() {
|
|
indexed_ = 0;
|
|
}
|
|
|
|
void Fl_Preferences::Node::deleteIndex() {
|
|
if (index_)
|
|
::free(index_);
|
|
index_ = NULL;
|
|
NIndex_ = nIndex_ = 0;
|
|
indexed_ = 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) {
|
|
#ifdef FL_PLUGIN_VERBOSE
|
|
printf("Fl_Plugin: creating a plugin, class \"%s\", name \"%s\"\n",
|
|
klass, name);
|
|
#endif
|
|
Fl_Plugin_Manager pm(klass);
|
|
id = pm.addPlugin(name, this);
|
|
}
|
|
|
|
/**
|
|
\brief Clear the plugin and remove it from the database.
|
|
*/
|
|
Fl_Plugin::~Fl_Plugin() {
|
|
#ifdef FL_PLUGIN_VERBOSE
|
|
printf("Fl_Plugin: deleting a plugin\n");
|
|
#endif
|
|
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)) {
|
|
#ifdef FL_PLUGIN_VERBOSE
|
|
printf("Fl_Plugin: creating a plugin manager for class \"%s\"\n", klass);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
\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() {
|
|
#ifdef FL_PLUGIN_VERBOSE
|
|
printf("Fl_Plugin: deleting a plugin manager\n");
|
|
#endif
|
|
}
|
|
|
|
static unsigned char x2i(char hi, char lo) {
|
|
return ((hi-'A')<<4) | (lo-'A');
|
|
}
|
|
|
|
static void i2x(unsigned char v, char *d) {
|
|
d[0] = ((v>>4)&0x0f)+'A'; d[1] = (v&0x0f)+'A';
|
|
}
|
|
|
|
static void *a2p(const char *s) {
|
|
union { void *ret; unsigned char d[sizeof(void*)]; } v;
|
|
v.ret = 0L;
|
|
int i=0, n=sizeof(void*);
|
|
for (i=0; i<n; i++) {
|
|
v.d[i] = x2i(s[2*i], s[2*i+1]);
|
|
}
|
|
return v.ret;
|
|
}
|
|
|
|
static void p2a(void *vp, char *d) {
|
|
union { void *vp; unsigned char s[sizeof(void*)]; } v;
|
|
v.vp = vp;
|
|
int i=0, n=sizeof(void*);
|
|
for (i=0; i<n; i++) {
|
|
i2x(v.s[i], d+i*2);
|
|
}
|
|
d[2*i] = 0;
|
|
}
|
|
|
|
/**
|
|
\brief Return the address of a plugin by index.
|
|
*/
|
|
Fl_Plugin *Fl_Plugin_Manager::plugin(int index) {
|
|
char buf[34];
|
|
Fl_Plugin *ret = 0;
|
|
Fl_Preferences pin(this, index);
|
|
pin.get("address", buf, "", 34);
|
|
if (buf[0]=='@') ret = (Fl_Plugin*)a2p(buf+1);
|
|
#ifdef FL_PLUGIN_VERBOSE
|
|
printf("Fl_Plugin: returning plugin at index %d: (%s) %p\n", index, buf, ret);
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
\brief Return the address of a plugin by name.
|
|
*/
|
|
Fl_Plugin *Fl_Plugin_Manager::plugin(const char *name) {
|
|
char buf[34];
|
|
Fl_Plugin *ret = 0;
|
|
if (groupExists(name)) {
|
|
Fl_Preferences pin(this, name);
|
|
pin.get("address", buf, "", 34);
|
|
if (buf[0]=='@') ret = (Fl_Plugin*)a2p(buf+1);
|
|
#ifdef FL_PLUGIN_VERBOSE
|
|
printf("Fl_Plugin: returning plugin named \"%s\": (%s) %p\n", name, buf, ret);
|
|
#endif
|
|
return ret;
|
|
} else {
|
|
#ifdef FL_PLUGIN_VERBOSE
|
|
printf("Fl_Plugin: no plugin found named \"%s\"\n", name);
|
|
#endif
|
|
return 0L;
|
|
}
|
|
}
|
|
|
|
/**
|
|
\brief This function adds a new plugin to the database.
|
|
|
|
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[34];
|
|
#ifdef FL_PLUGIN_VERBOSE
|
|
printf("Fl_Plugin: adding plugin named \"%s\" at 0x%p\n", name, plugin);
|
|
#endif
|
|
Fl_Preferences pin(this, name);
|
|
buf[0] = '@'; p2a(plugin, buf+1);
|
|
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 automatically load plugins that are defined:
|
|
// Fl_My_Plugin plugin();
|
|
void *dl = Fl::system_driver()->load(filename);
|
|
// 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;
|
|
}
|