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