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