Include a stripped-down version of FLTK in tree and add a USE_INCLUDED_FLTK option to build against it.
git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@4603 3789f03b-4d11-0410-bbf8-ca57d06f2519
diff --git a/common/fltk/src/Fl_Preferences.cxx b/common/fltk/src/Fl_Preferences.cxx
new file mode 100644
index 0000000..e343821
--- /dev/null
+++ b/common/fltk/src/Fl_Preferences.cxx
@@ -0,0 +1,1803 @@
+//
+// "$Id: Fl_Preferences.cxx 8291 2011-01-19 06:33:48Z manolo $"
+//
+// Preferences methods for the Fast Light Tool Kit (FLTK).
+//
+// 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
+//
+
+#include <FL/Fl.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 "flstring.h"
+#include <sys/stat.h>
+#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
+// on Windows, which is supposed to be POSIX compliant...
+# define access _access
+# define mkdir _mkdir
+#elif defined (__APPLE__)
+# include <ApplicationServices/ApplicationServices.h>
+# include <unistd.h>
+# include <dlfcn.h>
+#else
+# include <unistd.h>
+# include <dlfcn.h>
+#endif
+
+#ifdef WIN32
+# include <windows.h>
+# include <rpc.h>
+// function pointer for the UuidCreate Function
+// RPC_STATUS RPC_ENTRY UuidCreate(UUID __RPC_FAR *Uuid);
+typedef RPC_STATUS (WINAPI* uuid_func)(UUID __RPC_FAR *Uuid);
+#else
+# include <sys/time.h>
+#endif // WIN32
+
+#ifdef __CYGWIN__
+# include <wchar.h>
+#endif
+
+char Fl_Preferences::nameBuffer[128];
+char Fl_Preferences::uuidBuffer[40];
+Fl_Preferences *Fl_Preferences::runtimePrefs = 0;
+
+/**
+ * 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::newUUID() {
+#ifdef __APPLE__
+ CFUUIDRef theUUID = CFUUIDCreate(NULL);
+ CFUUIDBytes b = CFUUIDGetUUIDBytes(theUUID);
+ sprintf(uuidBuffer, "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X",
+ b.byte0, b.byte1, b.byte2, b.byte3, b.byte4, b.byte5, b.byte6, b.byte7,
+ b.byte8, b.byte9, b.byte10, b.byte11, b.byte12, b.byte13, b.byte14, b.byte15);
+ CFRelease(theUUID);
+#elif defined (WIN32)
+ // First try and use the win API function UuidCreate(), but if that is not
+ // available, fall back to making something up from scratch.
+ // We do not want to link against the Rpcrt4.dll, as we will rarely use it,
+ // so we load the DLL dynamically, if it is available, and work from there.
+ static HMODULE hMod = NULL;
+ UUID ud;
+ UUID *pu = &ud;
+ int got_uuid = 0;
+
+ if (!hMod) { // first time in?
+ hMod = LoadLibrary("Rpcrt4.dll");
+ }
+
+ if (hMod) { // do we have a usable handle to Rpcrt4.dll?
+ uuid_func uuid_crt = (uuid_func)GetProcAddress(hMod, "UuidCreate");
+ if (uuid_crt != NULL) {
+ RPC_STATUS rpc_res = uuid_crt(pu);
+ if ( // is the return status OK for our needs?
+ (rpc_res == RPC_S_OK) || // all is well
+ (rpc_res == RPC_S_UUID_LOCAL_ONLY) || // only unique to this machine
+ (rpc_res == RPC_S_UUID_NO_ADDRESS) // probably only locally unique
+ ) {
+ got_uuid = -1;
+ sprintf(uuidBuffer, "%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X",
+ pu->Data1, pu->Data2, pu->Data3, pu->Data4[0], pu->Data4[1],
+ pu->Data4[2], pu->Data4[3], pu->Data4[4],
+ pu->Data4[5], pu->Data4[6], pu->Data4[7]);
+ }
+ }
+ }
+ if (got_uuid == 0) { // did not make a UUID - use fallback logic
+ unsigned char b[16];
+ time_t t = time(0); // first 4 byte
+ b[0] = (unsigned char)t;
+ b[1] = (unsigned char)(t>>8);
+ b[2] = (unsigned char)(t>>16);
+ b[3] = (unsigned char)(t>>24);
+ int r = rand(); // four more bytes
+ b[4] = (unsigned char)r;
+ b[5] = (unsigned char)(r>>8);
+ b[6] = (unsigned char)(r>>16);
+ b[7] = (unsigned char)(r>>24);
+ // Now we try to find 4 more "random" bytes. We extract the
+ // lower 4 bytes from the address of t - it is created on the
+ // stack so *might* be in a different place each time...
+ // This is now done via a union to make it compile OK on 64-bit systems.
+ union { void *pv; unsigned char a[sizeof(void*)]; } v;
+ v.pv = (void *)(&t);
+ // NOTE: This assume that all WinXX systems are little-endian
+ b[8] = v.a[0];
+ b[9] = v.a[1];
+ b[10] = v.a[2];
+ b[11] = v.a[3];
+ TCHAR name[MAX_COMPUTERNAME_LENGTH + 1]; // only used to make last four bytes
+ DWORD nSize = MAX_COMPUTERNAME_LENGTH + 1;
+ // GetComputerName() does not depend on any extra libs, and returns something
+ // analogous to gethostname()
+ GetComputerName(name, &nSize);
+ // use the first 4 TCHAR's of the name to create the last 4 bytes of our UUID
+ for (int ii = 0; ii < 4; ii++) {
+ b[12 + ii] = (unsigned char)name[ii];
+ }
+ sprintf(uuidBuffer, "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X",
+ b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
+ b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]);
+ }
+#else
+ // warning Unix implementation of Fl_Preferences::newUUID() incomplete!
+ // #include <uuid/uuid.h>
+ // void uuid_generate(uuid_t out);
+ unsigned char b[16];
+ time_t t = time(0); // first 4 byte
+ b[0] = (unsigned char)t;
+ b[1] = (unsigned char)(t>>8);
+ b[2] = (unsigned char)(t>>16);
+ b[3] = (unsigned char)(t>>24);
+ int r = rand(); // four more bytes
+ b[4] = (unsigned char)r;
+ b[5] = (unsigned char)(r>>8);
+ b[6] = (unsigned char)(r>>16);
+ b[7] = (unsigned char)(r>>24);
+ unsigned long a = (unsigned long)&t; // four more bytes
+ b[8] = (unsigned char)a;
+ b[9] = (unsigned char)(a>>8);
+ b[10] = (unsigned char)(a>>16);
+ b[11] = (unsigned char)(a>>24);
+ // Now we try to find 4 more "random" bytes. We extract the
+ // lower 4 bytes from the address of t - it is created on the
+ // stack so *might* be in a different place each time...
+ // This is now done via a union to make it compile OK on 64-bit systems.
+ union { void *pv; unsigned char a[sizeof(void*)]; } v;
+ v.pv = (void *)(&t);
+ // NOTE: May need to handle big- or little-endian systems here
+# if WORDS_BIGENDIAN
+ b[8] = v.a[sizeof(void*) - 1];
+ b[9] = v.a[sizeof(void*) - 2];
+ b[10] = v.a[sizeof(void*) - 3];
+ b[11] = v.a[sizeof(void*) - 4];
+# else /* data ordered for a little-endian system */
+ b[8] = v.a[0];
+ b[9] = v.a[1];
+ b[10] = v.a[2];
+ b[11] = v.a[3];
+# endif
+ char name[80]; // last four bytes
+ gethostname(name, 79);
+ memcpy(b+12, name, 4);
+ sprintf(uuidBuffer, "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X",
+ b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
+ b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]);
+#endif
+
+ return uuidBuffer;
+}
+
+/**
+ The constructor creates a group that manages name/value pairs and
+ child groups. Groups are ready for reading and writing at any time.
+ The root argument is either Fl_Preferences::USER
+ or Fl_Preferences::SYSTEM.
+
+ This constructor creates the <i>base</i> instance for all
+ following entries and reads existing databases into memory. The
+ vendor argument is a unique text string identifying the
+ development team or vendor of an application. A domain name or
+ an EMail address are great unique names, e.g.
+ "researchATmatthiasm.com" or "fltk.org". The
+ application argument can be the working title or final
+ 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] vendor unique text describing the company or author of this file
+ \param[in] application unique text describing the application
+*/
+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 preferences file at an
+ arbitrary position in the file system.
+
+ The file name is generated in the form
+ <tt><i>path</i>/<i>application</i>.prefs</tt>. If \p application
+ is \c NULL, \p path must contain the full file name.
+
+ \param[in] path path to the directory that contains the preferences file
+ \param[in] vendor unique text describing the company or author of this file
+ \param[in] application unique text describing the application
+ */
+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.
+ \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 database 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 database
+ \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 );
+}
+
+/**
+ \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 put 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 preferences 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 (Cygwind does though)
+ node = 0L;
+ rootNode = 0L;
+}
+
+/**
+ 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 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::groupExists( 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::deleteGroup( const char *group ) {
+ Node *nd = node->search( group );
+ if ( nd ) return nd->remove();
+ return 0;
+}
+
+/**
+ Delete all groups.
+ */
+char Fl_Preferences::deleteAllGroups() {
+ 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::entryExists( 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::deleteEntry( const char *key ) {
+ return node->deleteEntry( key );
+}
+
+/**
+ Delete all entries.
+ */
+char Fl_Preferences::deleteAllEntries() {
+ 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 preferences
+ 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 );
+ value = v ? (float)atof( 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 preferences
+ 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 ) {
+ sprintf( 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 preferences
+ 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 ) {
+ sprintf( 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 );
+ value = v ? atof( 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 preferences
+ 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 ) {
+ sprintf( 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 preferences
+ 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 ) {
+ sprintf( nameBuffer, "%.*g", 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 trailling 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 = 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 preferences
+ 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 = 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 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.
+
+ \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[in] maxSize maximum length of value
+ \return 0 if the default value was used
+
+ \todo maxSize should receive the number of bytes that were read.
+ */
+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 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 preferences
+ 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 v ? strlen( v ) : 0 ;
+}
+
+/**
+ \brief Creates a path that is related to the preferences 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.
+
+ Exmaple:
+ \code
+ Fl_Preferences prefs( USER, "matthiasm.com", "test" );
+ char path[FL_PATH_MAX];
+ prefs.getUserdataPath( path );
+ \endcode
+ creates the preferences database in (MS Windows):
+ \code
+ c:/Documents and Settings/matt/Application Data/matthiasm.com/test.prefs
+ \endcode
+ and returns the userdata path:
+ \code
+ c:/Documents and Settings/matt/Application Data/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 0 if path was not created or pathname can't fit into buffer
+ */
+char Fl_Preferences::getUserdataPath( char *path, int pathlen ) {
+ if ( rootNode )
+ return rootNode->getPath( path, pathlen );
+ return 0;
+}
+
+/**
+ Writes all preferences to disk. This function works only with
+ the base preferences group. This function is rarely used as
+ deleting the base preferences flushes automatically.
+ */
+void Fl_Preferences::flush() {
+ if ( rootNode && node->dirty() )
+ rootNode->write();
+}
+
+//-----------------------------------------------------------------------------
+// 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;
+
+// recursively create a path in the file system
+static char makePath( const char *path ) {
+ if (access(path, 0)) {
+ const char *s = strrchr( path, '/' );
+ if ( !s ) return 0;
+ int len = s-path;
+ char *p = (char*)malloc( len+1 );
+ memcpy( p, path, len );
+ p[len] = 0;
+ makePath( p );
+ free( p );
+#if defined(WIN32) && !defined(__CYGWIN__)
+ return ( mkdir( path ) == 0 );
+#else
+ return ( mkdir( path, 0777 ) == 0 );
+#endif // WIN32 && !__CYGWIN__
+ }
+ return 1;
+}
+
+#if 0
+// strip the filename and create a path
+static void makePathForFile( const char *path ) {
+ const char *s = strrchr( path, '/' );
+ if ( !s ) return;
+ int len = s-path;
+ char *p = (char*)malloc( len+1 );
+ memcpy( p, path, len );
+ p[len] = 0;
+ makePath( p );
+ free( p );
+}
+#endif
+
+// 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
+# define FLPREFS_RESOURCE "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"
+# define FLPREFS_RESOURCEW L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"
+ int appDataLen = strlen(vendor) + strlen(application) + 8;
+ DWORD type, nn;
+ LONG err;
+ HKEY key;
+
+ switch (root) {
+ case SYSTEM:
+ err = RegOpenKeyW( HKEY_LOCAL_MACHINE, FLPREFS_RESOURCEW, &key );
+ if (err == ERROR_SUCCESS) {
+ nn = FL_PATH_MAX - appDataLen;
+ err = RegQueryValueExW( key, L"Common AppData", 0L, &type,
+ (BYTE*)filename, &nn );
+ if ( ( err != ERROR_SUCCESS ) && ( type == REG_SZ ) ) {
+ filename[0] = 0;
+ filename[1] = 0;
+ }
+ RegCloseKey(key);
+ }
+ break;
+ case USER:
+ err = RegOpenKeyW( HKEY_CURRENT_USER, FLPREFS_RESOURCEW, &key );
+ if (err == ERROR_SUCCESS) {
+ nn = FL_PATH_MAX - appDataLen;
+ err = RegQueryValueExW( key, L"AppData", 0L, &type,
+ (BYTE*)filename, &nn );
+ if ( ( err != ERROR_SUCCESS ) && ( type == REG_SZ ) ) {
+ filename[0] = 0;
+ filename[1] = 0;
+ }
+ RegCloseKey(key);
+ }
+ break;
+ }
+ if (!filename[1] && !filename[0]) {
+ strcpy(filename, "C:\\FLTK");
+ } else {
+#if 0
+ xchar *b = (xchar*)_wcsdup((xchar *)filename);
+#else
+ // cygwin does not come with _wcsdup. Use malloc + wcscpy.
+ // For implementation of wcsdup functionality See
+ // - http://linenum.info/p/glibc/2.7/wcsmbs/wcsdup.c
+ xchar *b = (xchar*) malloc((wcslen((xchar *) filename) + 1) * sizeof(xchar));
+ wcscpy(b, (xchar *) filename);
+#endif
+ // filename[fl_unicode2utf(b, wcslen((xchar*)b), filename)] = 0;
+ unsigned len = fl_utf8fromwc(filename, (FL_PATH_MAX-1), b, wcslen(b));
+ filename[len] = 0;
+ free(b);
+ }
+ snprintf(filename + strlen(filename), sizeof(filename) - strlen(filename),
+ "/%s/%s.prefs", vendor, application);
+ for (char *s = filename; *s; s++) if (*s == '\\') *s = '/';
+#elif defined ( __APPLE__ )
+ // TODO: verify that this is the Apple sanctioned way of finding these folders
+ // (On MSWindows, this frequently leads to issues with internationalized systems)
+ // Carbon: err = FindFolder( kLocalDomain, kPreferencesFolderType, 1, &spec.vRefNum, &spec.parID );
+ switch (root) {
+ case SYSTEM:
+ strcpy(filename, "/Library/Preferences");
+ break;
+ case USER:
+ sprintf(filename, "%s/Library/Preferences", fl_getenv("HOME"));
+ break;
+ }
+ snprintf(filename + strlen(filename), sizeof(filename) - strlen(filename),
+ "/%s/%s.prefs", vendor, application );
+#else
+ const char *e;
+ switch (root) {
+ case USER:
+ if ((e = fl_getenv("HOME")) != NULL) {
+ strlcpy(filename, e, sizeof(filename));
+
+ if (filename[strlen(filename)-1] != '/') {
+ strlcat(filename, "/.fltk/", sizeof(filename));
+ } else {
+ strlcat(filename, ".fltk/", sizeof(filename));
+ }
+ break;
+ }
+ case SYSTEM:
+ strcpy(filename, "/etc/fltk/");
+ break;
+ }
+ snprintf(filename + strlen(filename), sizeof(filename) - strlen(filename),
+ "%s/%s.prefs", vendor, application);
+#endif
+ filename_ = strdup(filename);
+ vendor_ = strdup(vendor);
+ application_ = 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) {
+
+ if (!vendor)
+ vendor = "unknown";
+ if (!application) {
+ application = "unknown";
+ filename_ = strdup(path);
+ } else {
+ char filename[ FL_PATH_MAX ]; filename[0] = 0;
+ snprintf(filename, sizeof(filename), "%s/%s.prefs", path, application);
+ filename_ = strdup(filename);
+ }
+ 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() {
+ 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 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 -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
+ int 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
+ int 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
+ int end = strcspn( buf, "\n\r" );
+ if ( end != 0 ) { // if entry is not empty
+ buf[ end ] = 0;
+ nd->set( buf );
+ }
+ }
+ }
+ fclose( f );
+ return 0;
+}
+
+// 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;
+ 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 !(defined(__APPLE__) || defined(WIN32))
+ // 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--
+ }
+#endif
+ return 0;
+}
+
+// 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;
+ for ( s = path; *s; s++ ) if ( *s == '\\' ) *s = '/';
+ s = strrchr( path, '.' );
+ if ( !s ) return 0;
+ *s = 0;
+ char ret = fl_make_path( path );
+#if !(defined(__APPLE__) || defined(WIN32))
+ // unix: make sure that system prefs dir. is user-readable
+ if (strncmp(path, "/etc/fltk/", 10) == 0) {
+ fl_chmod(path, 0755); // rwxr-xr-x
+ }
+#endif
+ strcpy( s, "/" );
+ return ret;
+}
+
+// create a node that represents a group
+// - path must be a single word, prferable alnum(), dot and underscore only. Space is ok.
+Fl_Preferences::Node::Node( const char *path ) {
+ if ( path ) path_ = strdup( path ); else path_ = 0;
+ 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 *nx;
+ for ( Node *nd = child_; nd; nd = nx ) {
+ nx = nd->next_;
+ delete nd;
+ }
+ child_ = 0L;
+ 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 = 0L;
+ }
+ if ( entry_[i].value ) {
+ free( entry_[i].value );
+ entry_[i].value = 0L;
+ }
+ }
+ free( entry_ );
+ entry_ = 0L;
+ nEntry_ = 0;
+ NEntry_ = 0;
+ }
+ dirty_ = 1;
+}
+
+// delete this and all depending nodes
+Fl_Preferences::Node::~Node() {
+ deleteAllChildren();
+ deleteAllEntries();
+ deleteIndex();
+ if ( path_ ) {
+ free( path_ );
+ path_ = 0L;
+ }
+ next_ = 0L;
+ parent_ = 0L;
+}
+
+// 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 ( child_ && child_->dirty() ) return 1;
+ return 0;
+}
+
+// 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 );
+ int cnt, written;
+ 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 ( child_ ) 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->child_;
+ pn->child_ = this;
+ sprintf( nameBuffer, "%s/%s", pn->path_, path_ );
+ free( path_ );
+ path_ = strdup( nameBuffer );
+}
+
+// find the corresponding root node
+Fl_Preferences::RootNode *Fl_Preferences::Node::findRoot() {
+ Node *n = this;
+ do {
+ if (n->top_)
+ return n->root_;
+ 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 = strdup( nameBuffer );
+ Node *nd = find( name );
+ free( name );
+ dirty_ = 1;
+ 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 = 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 = strdup( name );
+ entry_[ nEntry_ ].value = value?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 ) {
+ unsigned int len = c-line+1;
+ if ( len >= sizeof( nameBuffer ) )
+ len = sizeof( nameBuffer );
+ strlcpy( nameBuffer, line, len );
+ set( nameBuffer, c+1 );
+ } else {
+ set( line, "" );
+ }
+ }
+ dirty_ = dirt;
+}
+
+// add more data to an existing entry
+void Fl_Preferences::Node::add( const char *line ) {
+ if ( lastEntrySet<0 || lastEntrySet>=nEntry_ ) return;
+ char *&dst = entry_[ lastEntrySet ].value;
+ int a = strlen( dst );
+ int b = strlen( line );
+ dst = (char*)realloc( dst, a+b+1 );
+ memcpy( dst+a, line, b+1 );
+ dirty_ = 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 = strlen( path_ );
+ if ( strncmp( path, path_, len ) == 0 ) {
+ if ( path[ len ] == 0 )
+ return this;
+ if ( path[ len ] == '/' ) {
+ Node *nd;
+ for ( nd = 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 );
+ 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 = strlen( path_ ) + 1;
+ }
+ int len = 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 = 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 = 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 = 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 = 0, *np;
+ if ( parent() ) {
+ nd = parent()->child_; np = 0L;
+ for ( ; nd; np = nd, nd = nd->next_ ) {
+ if ( nd == this ) {
+ if ( np )
+ np->next_ = nd->next_;
+ else
+ parent()->child_ = nd->next_;
+ break;
+ }
+ }
+ parent()->dirty_ = 1;
+ parent()->updateIndex();
+ }
+ delete this;
+ return ( nd != 0 );
+}
+
+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 = 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_);
+ NIndex_ = nIndex_ = 0;
+ index_ = 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 autmaticaly load plugins that are defined:
+ // Fl_My_Plugin plugin();
+#if defined(WIN32) && !defined(__CYGWIN__)
+ 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: Fl_Preferences.cxx 8291 2011-01-19 06:33:48Z manolo $".
+//