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 $".
+//