1 //
2 // "$Id: Fl_Preferences.cxx 6015 2008-01-09 21:23:51Z matt $"
3 //
4 // Preferences methods for the Fast Light Tool Kit (FLTK).
5 //
6 // Copyright 2002-2005 by Matthias Melcher.
7 //
8 // This library is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU Library General Public
10 // License as published by the Free Software Foundation; either
11 // version 2 of the License, or (at your option) any later version.
12 //
13 // This library is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 // Library General Public License for more details.
17 //
18 // You should have received a copy of the GNU Library General Public
19 // License along with this library; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
21 // USA.
22 //
23 // Please report all bugs and problems on the following page:
24 //
25 //     http://www.fltk.org/str.php
26 //
27 
28 
29 #include <FL/Fl.H>
30 #include <FL/Fl_Preferences.H>
31 #include <FL/filename.H>
32 
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <stdarg.h>
36 #include "flstring.h"
37 #include <sys/stat.h>
38 
39 #if defined(WIN32) && !defined(__CYGWIN__)
40 #  include <direct.h>
41 #  include <io.h>
42 // Visual C++ 2005 incorrectly displays a warning about the use of POSIX APIs
43 // on Windows, which is supposed to be POSIX compliant...
44 #  define access _access
45 #  define mkdir _mkdir
46 #elif defined (__APPLE__)
47 #  include <Carbon/Carbon.h>
48 #  include <unistd.h>
49 #else
50 #  include <unistd.h>
51 #endif
52 
53 
54 char Fl_Preferences::nameBuffer[128];
55 
56 
57 /**
58  * create the initial preferences base
59  * - root: machine or user preferences
60  * - vendor: unique identification of author or vendor of application
61  *     Must be a valid directory name.
62  * - application: vendor unique application name, i.e. "PreferencesTest"
63  *     multiple preferences files can be created per application.
64  *     Must be a valid file name.
65  * example: Fl_Preferences base( Fl_Preferences::USER, "fltk.org", "test01");
66  */
Fl_Preferences(Root root,const char * vendor,const char * application)67 Fl_Preferences::Fl_Preferences( Root root, const char *vendor, const char *application )
68 {
69   node = new Node( "." );
70   rootNode = new RootNode( this, root, vendor, application );
71 }
72 
73 
74 /**
75  * create the initial preferences base
76  * - path: an application-supplied path
77  * example: Fl_Preferences base( "/usr/foo" );
78  */
Fl_Preferences(const char * path,const char * vendor,const char * application)79 Fl_Preferences::Fl_Preferences( const char *path, const char *vendor, const char *application )
80 {
81   node = new Node( "." );
82   rootNode = new RootNode( this, path, vendor, application );
83 }
84 
85 
86 /**
87  * create a Preferences node in relation to a parent node for reading and writing
88  * - parent: base name for group
89  * - group: group name (can contain '/' seperated group names)
90  * example: Fl_Preferences colors( base, "setup/colors" );
91  */
Fl_Preferences(Fl_Preferences & parent,const char * key)92 Fl_Preferences::Fl_Preferences( Fl_Preferences &parent, const char *key )
93 {
94   rootNode = parent.rootNode;
95   node = parent.node->addChild( key );
96 }
97 
98 
99 /**
100  * create a Preferences node in relation to a parent node for reading and writing
101  * - parent: base name for group
102  * - group: group name (can contain '/' seperated group names)
103  * example: Fl_Preferences colors( base, "setup/colors" );
104  */
Fl_Preferences(Fl_Preferences * parent,const char * key)105 Fl_Preferences::Fl_Preferences( Fl_Preferences *parent, const char *key )
106 {
107   rootNode = parent->rootNode;
108   node = parent->node->addChild( key );
109 }
110 
111 
112 /**
113  * destroy individual keys
114  * - destroying the base preferences will flush changes to the prefs file
115  * - after destroying the base, none of the depending preferences must be read or written
116  */
~Fl_Preferences()117 Fl_Preferences::~Fl_Preferences()
118 {
119   if (node && !node->parent()) delete rootNode;
120   // DO NOT delete nodes! The root node will do that after writing the preferences
121   // zero all pointer to avoid memory errors, event though
122   // Valgrind does not complain (Cygwind does though)
123   node = 0L;
124   rootNode = 0L;
125 }
126 
127 
128 /**
129  * return the number of groups that are contained within a group
130  * example: int n = base.groups();
131  */
groups()132 int Fl_Preferences::groups()
133 {
134   return node->nChildren();
135 }
136 
137 
138 /**
139  * return the group name of the n'th group
140  * - there is no guaranteed order of group names
141  * - the index must be within the range given by groups()
142  * example: printf( "Group(%d)='%s'\n", ix, base.group(ix) );
143  */
group(int ix)144 const char *Fl_Preferences::group( int ix )
145 {
146   return node->child( ix );
147 }
148 
149 
150 /**
151  * return 1, if a group with this name exists
152  * example: if ( base.groupExists( "setup/colors" ) ) ...
153  */
groupExists(const char * key)154 char Fl_Preferences::groupExists( const char *key )
155 {
156   return node->search( key ) ? 1 : 0 ;
157 }
158 
159 
160 /**
161  * delete a group
162  * example: setup.deleteGroup( "colors/buttons" );
163  */
deleteGroup(const char * key)164 char Fl_Preferences::deleteGroup( const char *key )
165 {
166   Node *nd = node->search( key );
167   if ( nd ) return nd->remove();
168   return 0;
169 }
170 
171 
172 /**
173  * return the number of entries (name/value) pairs for a group
174  * example: int n = buttonColor.entries();
175  */
entries()176 int Fl_Preferences::entries()
177 {
178   return node->nEntry;
179 }
180 
181 
182 /**
183  * return the name of an entry
184  * - there is no guaranteed order of entry names
185  * - the index must be within the range given by entries()
186  * example: printf( "Entry(%d)='%s'\n", ix, buttonColor.entry(ix) );
187  */
entry(int ix)188 const char *Fl_Preferences::entry( int ix )
189 {
190   return node->entry[ix].name;
191 }
192 
193 
194 /**
195  * return 1, if an entry with this name exists
196  * example: if ( buttonColor.entryExists( "red" ) ) ...
197  */
entryExists(const char * key)198 char Fl_Preferences::entryExists( const char *key )
199 {
200   return node->getEntry( key )>=0 ? 1 : 0 ;
201 }
202 
203 
204 /**
205  * remove a single entry (name/value pair)
206  * example: buttonColor.deleteEntry( "red" );
207  */
deleteEntry(const char * key)208 char Fl_Preferences::deleteEntry( const char *key )
209 {
210   return node->deleteEntry( key );
211 }
212 
213 
214 /**
215  * read an entry from the group
216  */
get(const char * key,int & value,int defaultValue)217 char Fl_Preferences::get( const char *key, int &value, int defaultValue )
218 {
219   const char *v = node->get( key );
220   value = v ? atoi( v ) : defaultValue;
221   return ( v != 0 );
222 }
223 
224 
225 /**
226  * set an entry (name/value pair)
227  */
set(const char * key,int value)228 char Fl_Preferences::set( const char *key, int value )
229 {
230   sprintf( nameBuffer, "%d", value );
231   node->set( key, nameBuffer );
232   return 1;
233 }
234 
235 
236 /**
237  * read an entry from the group
238  */
get(const char * key,float & value,float defaultValue)239 char Fl_Preferences::get( const char *key, float &value, float defaultValue )
240 {
241   const char *v = node->get( key );
242   value = v ? (float)atof( v ) : defaultValue;
243   return ( v != 0 );
244 }
245 
246 
247 /**
248  * set an entry (name/value pair)
249  */
set(const char * key,float value)250 char Fl_Preferences::set( const char *key, float value )
251 {
252   sprintf( nameBuffer, "%g", value );
253   node->set( key, nameBuffer );
254   return 1;
255 }
256 
257 
258 /**
259  * set an entry (name/value pair)
260  */
set(const char * key,float value,int precision)261 char Fl_Preferences::set( const char *key, float value, int precision )
262 {
263   sprintf( nameBuffer, "%.*g", precision, value );
264   node->set( key, nameBuffer );
265   return 1;
266 }
267 
268 
269 /**
270  * read an entry from the group
271  */
get(const char * key,double & value,double defaultValue)272 char Fl_Preferences::get( const char *key, double &value, double defaultValue )
273 {
274   const char *v = node->get( key );
275   value = v ? atof( v ) : defaultValue;
276   return ( v != 0 );
277 }
278 
279 
280 /**
281  * set an entry (name/value pair)
282  */
set(const char * key,double value)283 char Fl_Preferences::set( const char *key, double value )
284 {
285   sprintf( nameBuffer, "%g", value );
286   node->set( key, nameBuffer );
287   return 1;
288 }
289 
290 
291 /**
292  * set an entry (name/value pair)
293  */
set(const char * key,double value,int precision)294 char Fl_Preferences::set( const char *key, double value, int precision )
295 {
296   sprintf( nameBuffer, "%.*g", precision, value );
297   node->set( key, nameBuffer );
298   return 1;
299 }
300 
301 
302 // remove control sequences from a string
decodeText(const char * src)303 static char *decodeText( const char *src )
304 {
305   int len = 0;
306   const char *s = src;
307   for ( ; *s; s++, len++ )
308   {
309     if ( *s == '\\' )
310       if ( isdigit( s[1] ) ) s+=3; else s+=1;
311   }
312   char *dst = (char*)malloc( len+1 ), *d = dst;
313   for ( s = src; *s; s++ )
314   {
315     char c = *s;
316     if ( c == '\\' )
317     {
318       if ( s[1] == '\\' ) { *d++ = c; s++; }
319       else if ( s[1] == 'n' ) { *d++ = '\n'; s++; }
320       else if ( s[1] == 'r' ) { *d++ = '\r'; s++; }
321       else if ( isdigit( s[1] ) ) { *d++ = ((s[1]-'0')<<6) + ((s[2]-'0')<<3) + (s[3]-'0'); s+=3; }
322       else s++; // error
323     }
324     else
325       *d++ = c;
326   }
327   *d = 0;
328   return dst;
329 }
330 
331 
332 /**
333  * read a text entry from the group
334  * the text will be moved into the given text buffer
335  * text will be clipped to the buffer size
336  */
get(const char * key,char * text,const char * defaultValue,int maxSize)337 char Fl_Preferences::get( const char *key, char *text, const char *defaultValue, int maxSize )
338 {
339   const char *v = node->get( key );
340   if ( v && strchr( v, '\\' ) ) {
341     char *w = decodeText( v );
342     strlcpy(text, w, maxSize);
343     free( w );
344     return 1;
345   }
346   if ( !v ) v = defaultValue;
347   if ( v ) strlcpy(text, v, maxSize);
348   else text = 0;
349   return ( v != defaultValue );
350 }
351 
352 
353 /**
354  * read a text entry from the group
355  * 'text' will be changed to point to a new text buffer
356  * the text buffer must be deleted with 'free(text)' by the user.
357  */
get(const char * key,char * & text,const char * defaultValue)358 char Fl_Preferences::get( const char *key, char *&text, const char *defaultValue )
359 {
360   const char *v = node->get( key );
361   if ( v && strchr( v, '\\' ) )
362   {
363     text = decodeText( v );
364     return 1;
365   }
366   if ( !v ) v = defaultValue;
367   if ( v )
368     text = strdup( v );
369   else
370     text = 0;
371   return ( v != defaultValue );
372 }
373 
374 
375 /**
376  * set an entry (name/value pair)
377  */
set(const char * key,const char * text)378 char Fl_Preferences::set( const char *key, const char *text )
379 {
380   const char *s = text ? text : "";
381   int n=0, ns=0;
382   for ( ; *s; s++ ) { n++; if ( *s<32 || *s=='\\' || *s==0x7f ) ns+=4; }
383   if ( ns )
384   {
385     char *buffer = (char*)malloc( n+ns+1 ), *d = buffer;
386     for ( s=text; *s; )
387     {
388       char c = *s;
389       if ( c=='\\' ) { *d++ = '\\'; *d++ = '\\'; s++; }
390       else if ( c=='\n' ) { *d++ = '\\'; *d++ = 'n'; s++; }
391       else if ( c=='\r' ) { *d++ = '\\'; *d++ = 'r'; s++; }
392       else if ( c<32 || c==0x7f )
393 	{ *d++ = '\\'; *d++ = '0'+((c>>6)&3); *d++ = '0'+((c>>3)&7); *d++ = '0'+(c&7);  s++; }
394       else *d++ = *s++;
395     }
396     *d = 0;
397     node->set( key, buffer );
398     free( buffer );
399   }
400   else
401     node->set( key, text );
402   return 1;
403 }
404 
405 
406 // convert a hex string to binary data
decodeHex(const char * src,int & size)407 static void *decodeHex( const char *src, int &size )
408 {
409   size = strlen( src )/2;
410   unsigned char *data = (unsigned char*)malloc( size ), *d = data;
411   const char *s = src;
412   int i;
413 
414   for ( i=size; i>0; i-- )
415   {
416     int v;
417     char x = tolower(*s++);
418     if ( x >= 'a' ) v = x-'a'+10; else v = x-'0';
419     v = v<<4;
420     x = tolower(*s++);
421     if ( x >= 'a' ) v += x-'a'+10; else v += x-'0';
422     *d++ = (uchar)v;
423   }
424 
425   return (void*)data;
426 }
427 
428 
429 /**
430  * read a binary entry from the group
431  * the data will be moved into the given destination buffer
432  * data will be clipped to the buffer size
433  */
get(const char * key,void * data,const void * defaultValue,int defaultSize,int maxSize)434 char Fl_Preferences::get( const char *key, void *data, const void *defaultValue, int defaultSize, int maxSize )
435 {
436   const char *v = node->get( key );
437   if ( v )
438   {
439     int dsize;
440     void *w = decodeHex( v, dsize );
441     memmove( data, w, dsize>maxSize?maxSize:dsize );
442     free( w );
443     return 1;
444   }
445   if ( defaultValue )
446     memmove( data, defaultValue, defaultSize>maxSize?maxSize:defaultSize );
447   return 0;
448 }
449 
450 
451 /**
452  * read a binary entry from the group
453  * 'data' will be changed to point to a new data buffer
454  * the data buffer must be deleted with 'free(data)' by the user.
455  */
get(const char * key,void * & data,const void * defaultValue,int defaultSize)456 char Fl_Preferences::get( const char *key, void *&data, const void *defaultValue, int defaultSize )
457 {
458   const char *v = node->get( key );
459   if ( v )
460   {
461     int dsize;
462     data = decodeHex( v, dsize );
463     return 1;
464   }
465   if ( defaultValue )
466   {
467     data = (void*)malloc( defaultSize );
468     memmove( data, defaultValue, defaultSize );
469   }
470   else
471     data = 0;
472   return 0;
473 }
474 
475 
476 /**
477  * set an entry (name/value pair)
478  */
set(const char * key,const void * data,int dsize)479 char Fl_Preferences::set( const char *key, const void *data, int dsize )
480 {
481   char *buffer = (char*)malloc( dsize*2+1 ), *d = buffer;;
482   unsigned char *s = (unsigned char*)data;
483   for ( ; dsize>0; dsize-- )
484   {
485     static char lu[] = "0123456789abcdef";
486     unsigned char v = *s++;
487     *d++ = lu[v>>4];
488     *d++ = lu[v&0xf];
489   }
490   *d = 0;
491   node->set( key, buffer );
492   free( buffer );
493   return 1;
494 }
495 
496 
497 /**
498  * return the size of the value part of an entry
499  */
size(const char * key)500 int Fl_Preferences::size( const char *key )
501 {
502   const char *v = node->get( key );
503   return v ? strlen( v ) : 0 ;
504 }
505 
506 /**
507  * creates a path that is related to the preferences file
508  * and that is usable for application data beyond what is covered
509  * by Fl_Preferences.
510  * - 'getUserdataPath' actually creates the directory
511  * - 'path' must be large enough to receive a complete file path
512  * example:
513  *   Fl_Preferences prefs( USER, "matthiasm.com", "test" );
514  *   char path[FL_PATH_MAX];
515  *   prefs.getUserdataPath( path );
516  * sample returns:
517  *   Win32: c:/Documents and Settings/matt/Application Data/matthiasm.com/test/
518  *   prefs: c:/Documents and Settings/matt/Application Data/matthiasm.com/test.prefs
519  */
getUserdataPath(char * path,int pathlen)520 char Fl_Preferences::getUserdataPath( char *path, int pathlen )
521 {
522   if ( rootNode )
523     return rootNode->getPath( path, pathlen );
524   return 0;
525 }
526 
527 /**
528  * write all preferences to disk
529  * - this function works only with the base preference group
530  * - this function is rarely used as deleting the base preferences flushes automatically
531  */
flush()532 void Fl_Preferences::flush()
533 {
534   if ( rootNode && node->dirty() )
535     rootNode->write();
536 }
537 
538 //-----------------------------------------------------------------------------
539 // helper class to create dynamic group and entry names on the fly
540 //
541 
542 /**
543  * create a group name or entry name on the fly
544  * - this version creates a simple unsigned integer as an entry name
545  * example:
546  *   int n, i;
547  *   Fl_Preferences prev( appPrefs, "PreviousFiles" );
548  *   prev.get( "n", 0 );
549  *   for ( i=0; i<n; i++ )
550  *     prev.get( Fl_Preferences::Name(i), prevFile[i], "" );
551  */
Name(unsigned int n)552 Fl_Preferences::Name::Name( unsigned int n )
553 {
554   data_ = (char*)malloc(20);
555   sprintf(data_, "%u", n);
556 }
557 
558 /**
559  * create a group name or entry name on the fly
560  * - this version creates entry names as in 'printf'
561  * example:
562  *   int n, i;
563  *   Fl_Preferences prefs( USER, "matthiasm.com", "test" );
564  *   prev.get( "nFiles", 0 );
565  *   for ( i=0; i<n; i++ )
566  *     prev.get( Fl_Preferences::Name( "File%d", i ), prevFile[i], "" );
567  */
Name(const char * format,...)568 Fl_Preferences::Name::Name( const char *format, ... )
569 {
570   data_ = (char*)malloc(1024);
571   va_list args;
572   va_start(args, format);
573   vsnprintf(data_, 1024, format, args);
574   va_end(args);
575 }
576 
577 // delete the name
~Name()578 Fl_Preferences::Name::~Name()
579 {
580   if (data_) {
581     free(data_);
582     data_ = 0L;
583   }
584 }
585 
586 //-----------------------------------------------------------------------------
587 // internal methods, do not modify or use as they will change without notice
588 //
589 
590 int Fl_Preferences::Node::lastEntrySet = -1;
591 
592 // recursively create a path in the file system
makePath(const char * path)593 static char makePath( const char *path ) {
594   if (access(path, 0)) {
595     const char *s = strrchr( path, '/' );
596     if ( !s ) return 0;
597     int len = s-path;
598     char *p = (char*)malloc( len+1 );
599     memcpy( p, path, len );
600     p[len] = 0;
601     makePath( p );
602     free( p );
603 #if defined(WIN32) && !defined(__CYGWIN__)
604     return ( mkdir( path ) == 0 );
605 #else
606     return ( mkdir( path, 0777 ) == 0 );
607 #endif // WIN32 && !__CYGWIN__
608   }
609   return 1;
610 }
611 
612 // strip the filename and create a path
makePathForFile(const char * path)613 static void makePathForFile( const char *path )
614 {
615   const char *s = strrchr( path, '/' );
616   if ( !s ) return;
617   int len = s-path;
618   char *p = (char*)malloc( len+1 );
619   memcpy( p, path, len );
620   p[len] = 0;
621   makePath( p );
622   free( p );
623 }
624 
625 // create the root node
626 // - construct the name of the file that will hold our preferences
RootNode(Fl_Preferences * prefs,Root root,const char * vendor,const char * application)627 Fl_Preferences::RootNode::RootNode( Fl_Preferences *prefs, Root root, const char *vendor, const char *application )
628 {
629   char filename[ FL_PATH_MAX ]; filename[0] = 0;
630 #ifdef WIN32
631 #  define FLPREFS_RESOURCE	"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"
632   int appDataLen = strlen(vendor) + strlen(application) + 8;
633   DWORD type, nn;
634   LONG err;
635   HKEY key;
636 
637   switch (root) {
638     case SYSTEM:
639       err = RegOpenKey( HKEY_LOCAL_MACHINE, FLPREFS_RESOURCE, &key );
640       if (err == ERROR_SUCCESS) {
641 	nn = FL_PATH_MAX - appDataLen;
642 	err = RegQueryValueEx( key, "Common AppData", 0L, &type, (BYTE*)filename, &nn );
643 	if ( ( err != ERROR_SUCCESS ) && ( type == REG_SZ ) )
644 	  filename[0] = 0;
645         RegCloseKey(key);
646       }
647       break;
648     case USER:
649       err = RegOpenKey( HKEY_CURRENT_USER, FLPREFS_RESOURCE, &key );
650       if (err == ERROR_SUCCESS) {
651 	nn = FL_PATH_MAX - appDataLen;
652 	err = RegQueryValueEx( key, "AppData", 0L, &type, (BYTE*)filename, &nn );
653 	if ( ( err != ERROR_SUCCESS ) && ( type == REG_SZ ) )
654 	{
655 	  err = RegQueryValueEx( key, "Personal", 0L, &type, (BYTE*)filename, &nn );
656 	  if ( ( err != ERROR_SUCCESS ) && ( type == REG_SZ ) )
657 	    filename[0] = 0;
658 	}
659         RegCloseKey(key);
660       }
661       break;
662   }
663 
664   if (!filename[0]) {
665     strcpy(filename, "C:\\FLTK");
666   }
667 
668   snprintf(filename + strlen(filename), sizeof(filename) - strlen(filename),
669            "/%s/%s.prefs", vendor, application);
670   for (char *s = filename; *s; s++) if (*s == '\\') *s = '/';
671 #elif defined ( __APPLE__ )
672   FSSpec spec = { 0 };
673   FSRef ref;
674   OSErr err = fnfErr;
675   switch (root) {
676     case SYSTEM:
677       err = FindFolder( kLocalDomain, kPreferencesFolderType,
678 			1, &spec.vRefNum, &spec.parID );
679       break;
680     case USER:
681       err = FindFolder( kUserDomain, kPreferencesFolderType,
682 			1, &spec.vRefNum, &spec.parID );
683       break;
684   }
685   FSpMakeFSRef( &spec, &ref );
686   FSRefMakePath( &ref, (UInt8*)filename, FL_PATH_MAX );
687   snprintf(filename + strlen(filename), sizeof(filename) - strlen(filename),
688            "/%s/%s.prefs", vendor, application );
689 #else
690   const char *e;
691   switch (root) {
692     case USER:
693       if ((e = getenv("HOME")) != NULL) {
694 	strlcpy(filename, e, sizeof(filename));
695 
696 	if (filename[strlen(filename)-1] != '/') {
697 	  strlcat(filename, "/.fltk/", sizeof(filename));
698 	} else {
699 	  strlcat(filename, ".fltk/", sizeof(filename));
700 	}
701 	break;
702       }
703 
704     case SYSTEM:
705       strcpy(filename, "/etc/fltk/");
706       break;
707   }
708 
709   snprintf(filename + strlen(filename), sizeof(filename) - strlen(filename),
710            "%s/%s.prefs", vendor, application);
711 #endif
712 
713   prefs_       = prefs;
714   filename_    = strdup(filename);
715   vendor_      = strdup(vendor);
716   application_ = strdup(application);
717 
718   read();
719 }
720 
721 // create the root node
722 // - construct the name of the file that will hold our preferences
RootNode(Fl_Preferences * prefs,const char * path,const char * vendor,const char * application)723 Fl_Preferences::RootNode::RootNode( Fl_Preferences *prefs, const char *path, const char *vendor, const char *application )
724 {
725   if (!vendor)
726     vendor = "unknown";
727   if (!application) {
728     application = "unknown";
729     filename_ = strdup(path);
730   } else {
731     char filename[ FL_PATH_MAX ]; filename[0] = 0;
732     snprintf(filename, sizeof(filename), "%s/%s.prefs", path, application);
733     filename_  = strdup(filename);
734   }
735   prefs_       = prefs;
736   vendor_      = strdup(vendor);
737   application_ = strdup(application);
738 
739   read();
740 }
741 
742 // destroy the root node and all depending nodes
~RootNode()743 Fl_Preferences::RootNode::~RootNode()
744 {
745   if ( prefs_->node->dirty() )
746     write();
747   if ( filename_ ) {
748     free( filename_ );
749     filename_ = 0L;
750   }
751   if ( vendor_ ) {
752     free( vendor_ );
753     vendor_ = 0L;
754   }
755   if ( application_ ) {
756     free( application_ );
757     application_ = 0L;
758   }
759   delete prefs_->node;
760   prefs_->node = 0L;
761 }
762 
763 // read a preferences file and construct the group tree and with all entry leafs
read()764 int Fl_Preferences::RootNode::read()
765 {
766   char buf[1024];
767   FILE *f = fopen( filename_, "rb" );
768   if ( !f ) return 0;
769   fgets( buf, 1024, f );
770   fgets( buf, 1024, f );
771   fgets( buf, 1024, f );
772   Node *nd = prefs_->node;
773   for (;;)
774   {
775     if ( !fgets( buf, 1024, f ) ) break;	// EOF or Error
776     if ( buf[0]=='[' ) // read a new group
777     {
778       int end = strcspn( buf+1, "]\n\r" );
779       buf[ end+1 ] = 0;
780       nd = prefs_->node->find( buf+1 );
781     }
782     else if ( buf[0]=='+' ) //
783     { // value of previous name/value pair spans multiple lines
784       int end = strcspn( buf+1, "\n\r" );
785       if ( end != 0 ) // if entry is not empty
786       {
787 	buf[ end+1 ] = 0;
788 	nd->add( buf+1 );
789       }
790     }
791     else // read a name/value pair
792     {
793       int end = strcspn( buf, "\n\r" );
794       if ( end != 0 ) // if entry is not empty
795       {
796 	buf[ end ] = 0;
797 	nd->set( buf );
798       }
799     }
800   }
801   fclose( f );
802   return 0;
803 }
804 
805 // write the group tree and all entry leafs
write()806 int Fl_Preferences::RootNode::write()
807 {
808   makePathForFile(filename_);
809   FILE *f = fopen( filename_, "wb" );
810   if ( !f ) return 1;
811   fprintf( f, "; FLTK preferences file format 1.0\n" );
812   fprintf( f, "; vendor: %s\n", vendor_ );
813   fprintf( f, "; application: %s\n", application_ );
814   prefs_->node->write( f );
815   fclose( f );
816   return 0;
817 }
818 
819 // get the path to the preferences directory
getPath(char * path,int pathlen)820 char Fl_Preferences::RootNode::getPath( char *path, int pathlen )
821 {
822   strlcpy( path, filename_, pathlen);
823 
824   char *s;
825   for ( s = path; *s; s++ ) if ( *s == '\\' ) *s = '/';
826   s = strrchr( path, '.' );
827   if ( !s ) return 0;
828   *s = 0;
829   char ret = makePath( path );
830   strcpy( s, "/" );
831   return ret;
832 }
833 
834 // create a node that represents a group
835 // - path must be a single word, prferable alnum(), dot and underscore only. Space is ok.
Node(const char * path)836 Fl_Preferences::Node::Node( const char *path )
837 {
838   if ( path ) path_ = strdup( path ); else path_ = 0;
839   child_ = 0; next_ = 0; parent_ = 0;
840   entry = 0;
841   nEntry = NEntry = 0;
842   dirty_ = 0;
843 }
844 
845 // delete this and all depending nodes
~Node()846 Fl_Preferences::Node::~Node()
847 {
848   Node *nx;
849   for ( Node *nd = child_; nd; nd = nx )
850   {
851     nx = nd->next_;
852     delete nd;
853   }
854   child_ = 0L;
855   if ( entry )
856   {
857     for ( int i = 0; i < nEntry; i++ )
858     {
859       if ( entry[i].name ) {
860 	free( entry[i].name );
861 	entry[i].name = 0L;
862       }
863       if ( entry[i].value ) {
864 	free( entry[i].value );
865 	entry[i].value = 0L;
866       }
867     }
868     free( entry );
869     entry = 0L;
870     nEntry = 0;
871   }
872   if ( path_ ) {
873     free( path_ );
874     path_ = 0L;
875   }
876   next_ = 0L;
877   parent_ = 0L;
878 }
879 
880 // recursively check if any entry is dirty (was changed after loading a fresh prefs file)
dirty()881 char Fl_Preferences::Node::dirty()
882 {
883   if ( dirty_ ) return 1;
884   if ( next_ && next_->dirty() ) return 1;
885   if ( child_ && child_->dirty() ) return 1;
886   return 0;
887 }
888 
889 // write this node (recursively from the last neighbor back to this)
890 // write all entries
891 // write all children
write(FILE * f)892 int Fl_Preferences::Node::write( FILE *f )
893 {
894   if ( next_ ) next_->write( f );
895   fprintf( f, "\n[%s]\n\n", path_ );
896   for ( int i = 0; i < nEntry; i++ )
897   {
898     char *src = entry[i].value;
899     if ( src )
900     { // hack it into smaller pieces if needed
901       fprintf( f, "%s:", entry[i].name );
902       int cnt;
903       for ( cnt = 0; cnt < 60; cnt++ )
904 	if ( src[cnt]==0 ) break;
905       fwrite( src, cnt, 1, f );
906       fprintf( f, "\n" );
907       src += cnt;
908       for (;*src;)
909       {
910 	for ( cnt = 0; cnt < 80; cnt++ )
911 	  if ( src[cnt]==0 ) break;
912         fputc( '+', f );
913 	fwrite( src, cnt, 1, f );
914         fputc( '\n', f );
915 	src += cnt;
916       }
917     }
918     else
919       fprintf( f, "%s\n", entry[i].name );
920   }
921   if ( child_ ) child_->write( f );
922   dirty_ = 0;
923   return 0;
924 }
925 
926 // set the parent node and create the full path
setParent(Node * pn)927 void Fl_Preferences::Node::setParent( Node *pn )
928 {
929   parent_ = pn;
930   next_ = pn->child_;
931   pn->child_ = this;
932   sprintf( nameBuffer, "%s/%s", pn->path_, path_ );
933   free( path_ );
934   path_ = strdup( nameBuffer );
935 }
936 
937 // add a child to this node and set its path (try to find it first...)
addChild(const char * path)938 Fl_Preferences::Node *Fl_Preferences::Node::addChild( const char *path )
939 {
940   sprintf( nameBuffer, "%s/%s", path_, path );
941   char *name = strdup( nameBuffer );
942   Node *nd = find( name );
943   free( name );
944   dirty_ = 1;
945   return nd;
946 }
947 
948 // create and set, or change an entry within this node
set(const char * name,const char * value)949 void Fl_Preferences::Node::set( const char *name, const char *value )
950 {
951   for ( int i=0; i<nEntry; i++ )
952   {
953     if ( strcmp( name, entry[i].name ) == 0 )
954     {
955       if ( !value ) return; // annotation
956       if ( strcmp( value, entry[i].value ) != 0 )
957       {
958 	if ( entry[i].value )
959 	  free( entry[i].value );
960 	entry[i].value = strdup( value );
961 	dirty_ = 1;
962       }
963       lastEntrySet = i;
964       return;
965     }
966   }
967   if ( NEntry==nEntry )
968   {
969     NEntry = NEntry ? NEntry*2 : 10;
970     entry = (Entry*)realloc( entry, NEntry * sizeof(Entry) );
971   }
972   entry[ nEntry ].name = strdup( name );
973   entry[ nEntry ].value = value?strdup( value ):0;
974   lastEntrySet = nEntry;
975   nEntry++;
976   dirty_ = 1;
977 }
978 
979 // create or set a value (or annotation) from a single line in the file buffer
set(const char * line)980 void Fl_Preferences::Node::set( const char *line )
981 {
982   // hmm. If we assume that we always read this file in the beginning,
983   // we can handle the dirty flag 'quick and dirty'
984   char dirt = dirty_;
985   if ( line[0]==';' || line[0]==0 || line[0]=='#' )
986   {
987     set( line, 0 );
988   }
989   else
990   {
991     const char *c = strchr( line, ':' );
992     if ( c )
993     {
994       unsigned int len = c-line+1;
995       if ( len >= sizeof( nameBuffer ) )
996         len = sizeof( nameBuffer );
997       strlcpy( nameBuffer, line, len );
998       set( nameBuffer, c+1 );
999     }
1000     else
1001       set( line, "" );
1002   }
1003   dirty_ = dirt;
1004 }
1005 
1006 // add more data to an existing entry
add(const char * line)1007 void Fl_Preferences::Node::add( const char *line )
1008 {
1009   if ( lastEntrySet<0 || lastEntrySet>=nEntry ) return;
1010   char *&dst = entry[ lastEntrySet ].value;
1011   int a = strlen( dst );
1012   int b = strlen( line );
1013   dst = (char*)realloc( dst, a+b+1 );
1014   memcpy( dst+a, line, b+1 );
1015   dirty_ = 1;
1016 }
1017 
1018 // get the value for a name, returns 0 if no such name
get(const char * name)1019 const char *Fl_Preferences::Node::get( const char *name )
1020 {
1021   int i = getEntry( name );
1022   return i>=0 ? entry[i].value : 0 ;
1023 }
1024 
1025 // find the index of an entry, returns -1 if no such entry
getEntry(const char * name)1026 int Fl_Preferences::Node::getEntry( const char *name )
1027 {
1028   for ( int i=0; i<nEntry; i++ )
1029   {
1030     if ( strcmp( name, entry[i].name ) == 0 )
1031     {
1032       return i;
1033     }
1034   }
1035   return -1;
1036 }
1037 
1038 // remove one entry form this group
deleteEntry(const char * name)1039 char Fl_Preferences::Node::deleteEntry( const char *name )
1040 {
1041   int ix = getEntry( name );
1042   if ( ix == -1 ) return 0;
1043   memmove( entry+ix, entry+ix+1, (nEntry-ix-1) * sizeof(Entry) );
1044   nEntry--;
1045   dirty_ = 1;
1046   return 1;
1047 }
1048 
1049 // find a group somewhere in the tree starting here
1050 // - this method will always return a valid node (except for memory allocation problems)
1051 // - if the node was not found, 'find' will create the required branch
find(const char * path)1052 Fl_Preferences::Node *Fl_Preferences::Node::find( const char *path )
1053 {
1054   int len = strlen( path_ );
1055   if ( strncmp( path, path_, len ) == 0 )
1056   {
1057     if ( path[ len ] == 0 )
1058       return this;
1059     if ( path[ len ] == '/' )
1060     {
1061       Node *nd;
1062       for ( nd = child_; nd; nd = nd->next_ )
1063       {
1064 	Node *nn = nd->find( path );
1065 	if ( nn ) return nn;
1066       }
1067       const char *s = path+len+1;
1068       const char *e = strchr( s, '/' );
1069       if (e) strlcpy( nameBuffer, s, e-s+1 );
1070       else strlcpy( nameBuffer, s, sizeof(nameBuffer));
1071       nd = new Node( nameBuffer );
1072       nd->setParent( this );
1073       return nd->find( path );
1074     }
1075   }
1076   return 0;
1077 }
1078 
1079 // find a group somewhere in the tree starting here
1080 // caller must not set 'offset' argument
1081 // - if the node does not exist, 'search' returns NULL
1082 // - if the pathname is "." (current node) return this node
1083 // - if the pathname is "./" (root node) return the topmost node
1084 // - if the pathname starts with "./", start the search at the root node instead
search(const char * path,int offset)1085 Fl_Preferences::Node *Fl_Preferences::Node::search( const char *path, int offset )
1086 {
1087 
1088   if ( offset == 0 )
1089   {
1090     if ( path[0] == '.' )
1091     {
1092       if ( path[1] == 0 )
1093       {
1094 	return this; // user was searching for current node
1095       }
1096       else if ( path[1] == '/' )
1097       {
1098 	Node *nn = this;
1099 	while ( nn->parent_ ) nn = nn->parent_;
1100 	if ( path[2]==0 )
1101 	{ // user is searching for root ( "./" )
1102 	  return nn;
1103 	}
1104 	return nn->search( path+2, 2 ); // do a relative search on the root node
1105       }
1106     }
1107     offset = strlen( path_ ) + 1;
1108   }
1109 
1110   int len = strlen( path_ );
1111   if ( len < offset-1 ) return 0;
1112   len -= offset;
1113   if ( ( len <= 0 ) || ( strncmp( path, path_+offset, len ) == 0 ) )
1114   {
1115     if ( len > 0 && path[ len ] == 0 )
1116       return this;
1117     if ( len <= 0 || path[ len ] == '/' )
1118     {
1119       for ( Node *nd = child_; nd; nd = nd->next_ )
1120       {
1121 	Node *nn = nd->search( path, offset );
1122 	if ( nn ) return nn;
1123       }
1124       return 0;
1125     }
1126   }
1127   return 0;
1128 }
1129 
1130 // return the number of child nodes (groups)
nChildren()1131 int Fl_Preferences::Node::nChildren()
1132 {
1133   int cnt = 0;
1134   for ( Node *nd = child_; nd; nd = nd->next_ )
1135     cnt++;
1136   return cnt;
1137 }
1138 
1139 // return the n'th child node
child(int ix)1140 const char *Fl_Preferences::Node::child( int ix )
1141 {
1142   Node *nd;
1143   for ( nd = child_; nd; nd = nd->next_ )
1144   {
1145     if ( !ix-- ) break;
1146   }
1147   if ( nd && nd->path_ )
1148   {
1149     char *r = strrchr( nd->path_, '/' );
1150     return r ? r+1 : nd->path_ ;
1151   }
1152   return 0L ;
1153 }
1154 
1155 // remove myself from the list and delete me (and all children)
remove()1156 char Fl_Preferences::Node::remove()
1157 {
1158   Node *nd = 0, *np;
1159   if ( parent_ )
1160   {
1161     nd = parent_->child_; np = 0L;
1162     for ( ; nd; np = nd, nd = nd->next_ )
1163     {
1164       if ( nd == this )
1165       {
1166 	if ( np )
1167 	  np->next_ = nd->next_;
1168 	else
1169 	  parent_->child_ = nd->next_;
1170 	break;
1171       }
1172     }
1173     parent_->dirty_ = 1;
1174   }
1175   delete this;
1176   return ( nd != 0 );
1177 }
1178 
1179 
1180 //
1181 // End of "$Id: Fl_Preferences.cxx 6015 2008-01-09 21:23:51Z matt $".
1182 //
1183