1 /********************************************************************************
2 * *
3 * S e t t i n g s C l a s s *
4 * *
5 *********************************************************************************
6 * Copyright (C) 1998,2006 by Jeroen van der Zijp. All Rights Reserved. *
7 *********************************************************************************
8 * This library is free software; you can redistribute it and/or *
9 * modify it under the terms of the GNU Lesser General Public *
10 * License as published by the Free Software Foundation; either *
11 * version 2.1 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 * Lesser General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU Lesser General Public *
19 * License along with this library; if not, write to the Free Software *
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
21 *********************************************************************************
22 * $Id: FXSettings.cpp,v 1.54.2.1 2008/07/28 18:24:09 fox Exp $ *
23 ********************************************************************************/
24 #include "xincs.h"
25 #include "fxver.h"
26 #include "fxdefs.h"
27 #include "fxascii.h"
28 #include "FXHash.h"
29 #include "FXStream.h"
30 #include "FXString.h"
31 #include "FXStringDict.h"
32 #include "FXFile.h"
33 #include "FXSettings.h"
34
35 /*
36 Notes:
37
38 - Format for settings database file:
39
40 [Section Key]
41 EntryKey=string-with-no-spaces
42 EntryKey="string\nwith a\nnewline in it\n"
43 EntryKey=" string with leading and trailing spaces and \"embedded\" in it "
44 EntryKey=string with no leading or trailing spaces
45
46 - EntryKey may is of the form "ali baba", "ali-baba", "ali_baba", or "ali.baba".
47
48 - Leading/trailing spaces are NOT part of the EntryKey.
49
50 - FXSectionDict should go; FXSettings should simply derive from FXDict.
51
52 - Escape sequences now allow octal (\377) as well as hex (\xff) codes.
53
54 - EntryKey format should be like values.
55
56 - Extensive error checking in unparseFile() to ensure no settings data is
57 lost when disk is full.
58
59 */
60
61 #define MAXBUFFER 2000
62 #define MAXNAME 200
63 #define MAXVALUE 2000
64
65 using namespace FX;
66
67 /*******************************************************************************/
68
69 namespace FX {
70
71 // Object implementation
72 FXIMPLEMENT(FXSettings,FXDict,NULL,0)
73
74
75 // Construct settings database
FXSettings()76 FXSettings::FXSettings(){
77 modified=false;
78 }
79
80
81 // Construct copy of existing database
FXSettings(const FXSettings & orig)82 FXSettings::FXSettings(const FXSettings& orig):FXDict(orig){
83 register FXint i;
84 modified=orig.modified;
85 for(i=0; i<orig.total; i++){
86 if(0<=dict[i].hash){
87 dict[i].data=new FXStringDict(*((FXStringDict*)orig.dict[i].data));
88 }
89 }
90 }
91
92
93 // Assignment operator
operator =(const FXSettings & orig)94 FXSettings& FXSettings::operator=(const FXSettings& orig){
95 register FXint i;
96 if(&orig!=this){
97 FXDict::operator=(orig);
98 for(i=0; i<orig.total; i++){
99 if(0<=orig.dict[i].hash){
100 dict[i].data=new FXStringDict(*((FXStringDict*)orig.dict[i].data));
101 }
102 }
103 }
104 return *this;
105 }
106
107
108 // Create data
createData(const void *)109 void *FXSettings::createData(const void*){
110 return new FXStringDict;
111 }
112
113
114 // Delete data
deleteData(void * ptr)115 void FXSettings::deleteData(void* ptr){
116 delete ((FXStringDict*)ptr);
117 }
118
119
120 // Read string
readString(FXFile & file,FXchar * buffer,FXint & bol,FXint & eol,FXint & end)121 static bool readString(FXFile& file,FXchar *buffer,FXint& bol,FXint& eol,FXint& end){
122 register FXint n;
123 do{
124 if(eol>=end){
125 if(bol<end){ memmove(buffer,buffer+bol,end-bol); }
126 end=end-bol;
127 bol=0;
128 eol=end;
129 n=file.readBlock(buffer+end,MAXBUFFER-end);
130 if(n<0) return false;
131 end+=n;
132 }
133 }
134 while(eol<end && buffer[eol++]!='\n');
135 return bol<eol;
136 }
137
138
139 // Parse filename
parseFile(const FXString & filename,bool mark)140 bool FXSettings::parseFile(const FXString& filename,bool mark){
141 FXFile file(filename,FXIO::Reading);
142 if(file.isOpen()){
143 FXchar line[MAXBUFFER];
144 FXint bol,eol,end,section,name,value,p,lineno;
145 FXStringDict *group=NULL;
146
147 lineno=bol=eol=end=0;
148
149 // Read lines
150 while(readString(file,line,bol,eol,end)){
151 lineno++;
152
153 // Skip leading spaces
154 while(bol<eol && Ascii::isBlank(line[bol])) bol++;
155
156 // Skip comment lines and empty lines
157 if(bol>=eol || line[bol]=='#' || line[bol]==';' || line[bol]=='\n' || line[bol]=='\r') goto next;
158
159 // Parse section name
160 if(line[bol]=='['){
161
162 // Scan section name
163 for(section=++bol; bol<eol && line[bol]!=']' && !Ascii::isControl(line[bol]); bol++);
164
165 // Check errors
166 if(bol>=eol || line[bol]!=']'){ fxwarning("%s:%d: illegal section name.\n",filename.text(),lineno); goto next; }
167
168 // Terminate name
169 line[bol]='\0';
170
171 // Add new section dict
172 group=insert(line+section);
173 }
174
175 // Parse name-value pair
176 else{
177
178 // Should have a group
179 if(!group){ fxwarning("%s:%d: settings entry should follow a section.\n",filename.text(),lineno); goto next; }
180
181 // Scan key name
182 for(name=bol; bol<eol && line[bol]!='=' && !Ascii::isControl(line[bol]); bol++);
183
184 // Check errors
185 if(bol>=eol || line[bol]!='='){ fxwarning("%s:%d: expected '=' to follow key.\n",filename.text(),lineno); goto next; }
186
187 // Remove trailing spaces after name
188 for(p=bol; name<p && Ascii::isBlank(line[p-1]); p--);
189
190 // Terminate name
191 line[p]='\0';
192
193 // Skip leading spaces
194 for(bol++; bol<eol && Ascii::isBlank(line[bol]); bol++);
195
196 // Scan value
197 for(value=bol; bol<eol && !Ascii::isControl(line[bol]); bol++);
198
199 // Remove trailing spaces after value
200 for(p=bol; value<p && Ascii::isBlank(line[p-1]); p--);
201
202 // Terminate value
203 line[p]='\0';
204
205 // Add entry to current section
206 group->replace(line+name,dequote(line+value),mark);
207 }
208 next: bol=eol;
209 }
210
211 // Done
212 return true;
213 }
214 return false;
215 }
216
217
218 // Write string
writeString(FXFile & file,const FXchar * string)219 static bool writeString(FXFile& file,const FXchar* string){
220 register FXint len=strlen(string);
221 return file.writeBlock(string,len)==len;
222 }
223
224
225 // Unparse registry file
unparseFile(const FXString & filename)226 bool FXSettings::unparseFile(const FXString& filename){
227 FXFile file(filename,FXIO::Writing);
228 FXchar line[MAXVALUE];
229 if(file.isOpen()){
230
231 // Loop over all sections
232 for(FXint s=first(); s<size(); s=next(s)){
233
234 // Get group
235 FXStringDict* group=data(s);
236 bool sec=false;
237
238 // Loop over all entries
239 for(FXint e=group->first(); e<group->size(); e=group->next(e)){
240
241 // Is key-value pair marked?
242 if(group->mark(e)){
243
244 // Write section name if not written yet
245 if(!sec){
246 if(!writeString(file,"[")) goto x;
247 if(!writeString(file,key(s))) goto x;
248 if(!writeString(file,"]" ENDLINE)) goto x;
249 sec=true;
250 }
251
252 // Write marked key-value pairs only
253 if(!writeString(file,group->key(e))) goto x;
254 if(!writeString(file,"=")) goto x;
255 if(!writeString(file,enquote(line,group->data(e)))) goto x;
256 if(!writeString(file,ENDLINE)) goto x;
257 }
258 }
259
260 // Blank line after end
261 if(sec){
262 if(!writeString(file,ENDLINE)) goto x;
263 }
264 }
265 return true;
266 }
267 x:return false;
268 }
269
270
271 // Dequote a value, in situ
dequote(FXchar * text) const272 FXchar* FXSettings::dequote(FXchar* text) const {
273 register FXchar *result=text;
274 register FXchar *ptr=text;
275 register FXuint v;
276 if(*text=='"'){
277 text++;
278 while((v=*text++)!='\0' && v!='\n' && v!='"'){
279 if(v=='\\'){
280 v=*text++;
281 switch(v){
282 case 'n':
283 v='\n';
284 break;
285 case 'r':
286 v='\r';
287 break;
288 case 'b':
289 v='\b';
290 break;
291 case 'v':
292 v='\v';
293 break;
294 case 'a':
295 v='\a';
296 break;
297 case 'f':
298 v='\f';
299 break;
300 case 't':
301 v='\t';
302 break;
303 case '\\':
304 v='\\';
305 break;
306 case '"':
307 v='"';
308 break;
309 case '\'':
310 v='\'';
311 break;
312 case 'x':
313 v='x';
314 if(Ascii::isHexDigit(*text)){
315 v=Ascii::digitValue(*text++);
316 if(Ascii::isHexDigit(*text)){
317 v=(v<<4)+Ascii::digitValue(*text++);
318 }
319 }
320 break;
321 case '0':
322 case '1':
323 case '2':
324 case '3':
325 case '4':
326 case '5':
327 case '6':
328 case '7':
329 v=v-'0';
330 if('0'<=*text && *text<='7'){
331 v=(v<<3)+*text++-'0';
332 if('0'<=*text && *text<='7'){
333 v=(v<<3)+*text++-'0';
334 }
335 }
336 break;
337 }
338 }
339 *ptr++=v;
340 }
341 *ptr='\0';
342 }
343 return result;
344 }
345
346
347 // Check if quotes are needed
needquotes(const FXchar * text)348 static bool needquotes(const FXchar* text){
349 register const FXchar *ptr=text;
350 register FXuchar c;
351 while((c=*ptr++)!='\0'){
352 if(0x7f<=c || c<0x20 || c=='"' || c=='\'' || c=='\\' || (c==' ' && (ptr==(text+1) || *ptr=='\0'))) return true;
353 }
354 return false;
355 }
356
357
358 // Enquote a value
enquote(FXchar * result,const FXchar * text)359 FXchar* FXSettings::enquote(FXchar* result,const FXchar* text){
360 register FXchar *end=result+MAXVALUE-6;
361 register FXchar *ptr=result;
362 register FXuchar c;
363 if(needquotes(text)){
364 *ptr++='"';
365 while((c=*text++)!='\0' && ptr<end){
366 switch(c){
367 case '\n':
368 *ptr++='\\';
369 *ptr++='n';
370 break;
371 case '\r':
372 *ptr++='\\';
373 *ptr++='r';
374 break;
375 case '\b':
376 *ptr++='\\';
377 *ptr++='b';
378 break;
379 case '\v':
380 *ptr++='\\';
381 *ptr++='v';
382 break;
383 case '\a':
384 *ptr++='\\';
385 *ptr++='a';
386 break;
387 case '\f':
388 *ptr++='\\';
389 *ptr++='f';
390 break;
391 case '\t':
392 *ptr++='\\';
393 *ptr++='t';
394 break;
395 case '\\':
396 *ptr++='\\';
397 *ptr++='\\';
398 break;
399 case '"':
400 *ptr++='\\';
401 *ptr++='"';
402 break;
403 case '\'':
404 *ptr++='\\';
405 *ptr++='\'';
406 break;
407 default:
408 if(c<0x20 || 0x7f<c){
409 *ptr++='\\';
410 *ptr++='x';
411 *ptr++=FXString::HEX[c>>4];
412 *ptr++=FXString::HEX[c&15];
413 }
414 else{
415 *ptr++=c;
416 }
417 break;
418 }
419 }
420 *ptr++='"';
421 }
422 else{
423 while((c=*text++)!='\0' && ptr<end){
424 *ptr++=c;
425 }
426 }
427 *ptr='\0';
428 return result;
429 }
430
431
432 // Furnish our own version if we have to
433 #ifndef HAVE_VSSCANF
434 extern "C" int vsscanf(const char* str, const char* format, va_list arg_ptr);
435 #endif
436
437
438 // Read a formatted registry entry
readFormatEntry(const FXchar * section,const FXchar * key,const FXchar * fmt,...)439 FXint FXSettings::readFormatEntry(const FXchar *section,const FXchar *key,const FXchar *fmt,...){
440 if(!section || !section[0]){ fxerror("FXSettings::readFormatEntry: bad section argument.\n"); }
441 if(!key || !key[0]){ fxerror("FXSettings::readFormatEntry: bad key argument.\n"); }
442 if(!fmt){ fxerror("FXSettings::readFormatEntry: bad fmt argument.\n"); }
443 FXStringDict *group=find(section);
444 va_list args;
445 va_start(args,fmt);
446 FXint result=0;
447 if(group){
448 const char *value=group->find(key);
449 if(value){
450 result=vsscanf((char*)value,fmt,args); // Cast needed for HP-UX 11, which has wrong prototype for vsscanf
451 }
452 }
453 va_end(args);
454 return result;
455 }
456
457
458 // Read a string-valued registry entry
readStringEntry(const FXchar * section,const FXchar * key,const FXchar * def)459 const FXchar *FXSettings::readStringEntry(const FXchar *section,const FXchar *key,const FXchar *def){
460 if(!section || !section[0]){ fxerror("FXSettings::readStringEntry: bad section argument.\n"); }
461 if(!key || !key[0]){ fxerror("FXSettings::readStringEntry: bad key argument.\n"); }
462 FXStringDict *group=find(section);
463 if(group){
464 const char *value=group->find(key);
465 if(value) return value;
466 }
467 return def;
468 }
469
470
471 // Read a int-valued registry entry
readIntEntry(const FXchar * section,const FXchar * key,FXint def)472 FXint FXSettings::readIntEntry(const FXchar *section,const FXchar *key,FXint def){
473 if(!section || !section[0]){ fxerror("FXSettings::readIntEntry: bad section argument.\n"); }
474 if(!key || !key[0]){ fxerror("FXSettings::readIntEntry: bad key argument.\n"); }
475 FXStringDict *group=find(section);
476 if(group){
477 const char *value=group->find(key);
478 if(value){
479 FXint ivalue;
480 if(value[0]=='0' && (value[1]=='x' || value[1]=='X')){
481 if(sscanf(value+2,"%x",&ivalue)) return ivalue;
482 }
483 else{
484 if(sscanf(value,"%d",&ivalue)==1) return ivalue;
485 }
486 }
487 }
488 return def;
489 }
490
491
492 // Read a unsigned int-valued registry entry
readUnsignedEntry(const FXchar * section,const FXchar * key,FXuint def)493 FXuint FXSettings::readUnsignedEntry(const FXchar *section,const FXchar *key,FXuint def){
494 if(!section || !section[0]){ fxerror("FXSettings::readUnsignedEntry: bad section argument.\n"); }
495 if(!key || !key[0]){ fxerror("FXSettings::readUnsignedEntry: bad key argument.\n"); }
496 FXStringDict *group=find(section);
497 if(group){
498 const char *value=group->find(key);
499 if(value){
500 FXuint ivalue;
501 if(value[0]=='0' && (value[1]=='x' || value[1]=='X')){
502 if(sscanf(value+2,"%x",&ivalue)) return ivalue;
503 }
504 else{
505 if(sscanf(value,"%u",&ivalue)==1) return ivalue;
506 }
507 }
508 }
509 return def;
510 }
511
512
513 // Read a double-valued registry entry
readRealEntry(const FXchar * section,const FXchar * key,FXdouble def)514 FXdouble FXSettings::readRealEntry(const FXchar *section,const FXchar *key,FXdouble def){
515 if(!section || !section[0]){ fxerror("FXSettings::readRealEntry: bad section argument.\n"); }
516 if(!key || !key[0]){ fxerror("FXSettings::readRealEntry: bad key argument.\n"); }
517 FXStringDict *group=find(section);
518 if(group){
519 const char *value=group->find(key);
520 if(value){
521 FXdouble dvalue;
522 if(sscanf(value,"%lf",&dvalue)==1) return dvalue;
523 }
524 }
525 return def;
526 }
527
528
529 // Read a color registry entry
readColorEntry(const FXchar * section,const FXchar * key,FXColor def)530 FXColor FXSettings::readColorEntry(const FXchar *section,const FXchar *key,FXColor def){
531 if(!section || !section[0]){ fxerror("FXSettings::readColorEntry: bad section argument.\n"); }
532 if(!key || !key[0]){ fxerror("FXSettings::readColorEntry: bad key argument.\n"); }
533 FXStringDict *group=find(section);
534 if(group){
535 const char *value=group->find(key);
536 if(value){
537 return fxcolorfromname(value);
538 }
539 }
540 return def;
541 }
542
543
544 // Read a boolean registry entry
readBoolEntry(const FXchar * section,const FXchar * key,FXbool def)545 FXbool FXSettings::readBoolEntry(const FXchar *section,const FXchar *key,FXbool def){
546 if(!section || !section[0]){ fxerror("FXSettings::readBoolEntry: bad section argument.\n"); }
547 if(!key || !key[0]){ fxerror("FXSettings::readBoolEntry: bad key argument.\n"); }
548 FXStringDict *group=find(section);
549 if(group){
550 const char *value=group->find(key);
551 if(value){
552 if(comparecase(value,"true")==0) return TRUE;
553 else if(comparecase(value,"false")==0) return FALSE;
554 else if(comparecase(value,"yes")==0) return TRUE;
555 else if(comparecase(value,"no")==0) return FALSE;
556 else if(comparecase(value,"on")==0) return TRUE;
557 else if(comparecase(value,"off")==0) return FALSE;
558 else if(comparecase(value,"1")==0) return TRUE;
559 else if(comparecase(value,"0")==0) return FALSE;
560 else if(comparecase(value,"maybe")==0) return MAYBE;
561 }
562 }
563 return def;
564 }
565
566
567 // Write a formatted registry entry
writeFormatEntry(const FXchar * section,const FXchar * key,const FXchar * fmt,...)568 FXint FXSettings::writeFormatEntry(const FXchar *section,const FXchar *key,const FXchar *fmt,...){
569 if(!section || !section[0]){ fxerror("FXSettings::writeFormatEntry: bad section argument.\n"); }
570 if(!key || !key[0]){ fxerror("FXSettings::writeFormatEntry: bad key argument.\n"); }
571 if(!fmt){ fxerror("FXSettings::writeFormatEntry: bad fmt argument.\n"); }
572 FXStringDict *group=insert(section);
573 va_list args;
574 va_start(args,fmt);
575 FXint result=0;
576 if(group){
577 FXchar buffer[2048];
578 #if defined(WIN32) || defined(HAVE_VSNPRINTF)
579 result=vsnprintf(buffer,sizeof(buffer),fmt,args);
580 #else
581 result=vsprintf(buffer,fmt,args);
582 #endif
583 group->replace(key,buffer,TRUE);
584 modified=true;
585 }
586 va_end(args);
587 return result;
588 }
589
590
591 // Write a string-valued registry entry
writeStringEntry(const FXchar * section,const FXchar * key,const FXchar * val)592 bool FXSettings::writeStringEntry(const FXchar *section,const FXchar *key,const FXchar *val){
593 if(!section || !section[0]){ fxerror("FXSettings::writeStringEntry: bad section argument.\n"); }
594 if(!key || !key[0]){ fxerror("FXSettings::writeStringEntry: bad key argument.\n"); }
595 FXStringDict *group=insert(section);
596 if(group){
597 group->replace(key,val,true);
598 modified=true;
599 return true;
600 }
601 return false;
602 }
603
604
605 // Write a int-valued registry entry
writeIntEntry(const FXchar * section,const FXchar * key,FXint val)606 bool FXSettings::writeIntEntry(const FXchar *section,const FXchar *key,FXint val){
607 if(!section || !section[0]){ fxerror("FXSettings::writeIntEntry: bad section argument.\n"); }
608 if(!key || !key[0]){ fxerror("FXSettings::writeIntEntry: bad key argument.\n"); }
609 FXStringDict *group=insert(section);
610 if(group){
611 FXchar buffer[32];
612 sprintf(buffer,"%d",val);
613 group->replace(key,buffer,true);
614 modified=true;
615 return true;
616 }
617 return false;
618 }
619
620
621 // Write a unsigned int-valued registry entry
writeUnsignedEntry(const FXchar * section,const FXchar * key,FXuint val)622 bool FXSettings::writeUnsignedEntry(const FXchar *section,const FXchar *key,FXuint val){
623 if(!section || !section[0]){ fxerror("FXSettings::writeUnsignedEntry: bad section argument.\n"); }
624 if(!key || !key[0]){ fxerror("FXSettings::writeUnsignedEntry: bad key argument.\n"); }
625 FXStringDict *group=insert(section);
626 if(group){
627 FXchar buffer[32];
628 sprintf(buffer,"%u",val);
629 group->replace(key,buffer,TRUE);
630 modified=true;
631 return true;
632 }
633 return false;
634 }
635
636
637 // Write a double-valued registry entry
writeRealEntry(const FXchar * section,const FXchar * key,FXdouble val)638 bool FXSettings::writeRealEntry(const FXchar *section,const FXchar *key,FXdouble val){
639 if(!section || !section[0]){ fxerror("FXSettings::writeRealEntry: bad section argument.\n"); }
640 if(!key || !key[0]){ fxerror("FXSettings::writeRealEntry: bad key argument.\n"); }
641 FXStringDict *group=insert(section);
642 if(group){
643 FXchar buffer[64];
644 sprintf(buffer,"%.16g",val);
645 group->replace(key,buffer,TRUE);
646 modified=true;
647 return true;
648 }
649 return false;
650 }
651
652
653 // Write a color registry entry
writeColorEntry(const FXchar * section,const FXchar * key,FXColor val)654 bool FXSettings::writeColorEntry(const FXchar *section,const FXchar *key,FXColor val){
655 if(!section || !section[0]){ fxerror("FXSettings::writeColorEntry: bad section argument.\n"); }
656 if(!key || !key[0]){ fxerror("FXSettings::writeColorEntry: bad key argument.\n"); }
657 FXStringDict *group=insert(section);
658 if(group){
659 FXchar buffer[64];
660 group->replace(key,fxnamefromcolor(buffer,val),TRUE);
661 modified=true;
662 return true;
663 }
664 return false;
665 }
666
667
668 // Write a boolean registry entry
writeBoolEntry(const FXchar * section,const FXchar * key,FXbool val)669 bool FXSettings::writeBoolEntry(const FXchar *section,const FXchar *key,FXbool val){
670 if(!section || !section[0]){ fxerror("FXSettings::writeBoolEntry: bad section argument.\n"); }
671 if(!key || !key[0]){ fxerror("FXSettings::writeBoolEntry: bad key argument.\n"); }
672 FXStringDict *group=insert(section);
673 if(group){
674 group->replace(key,(val==FALSE) ? "false" : (val==TRUE) ? "true" : "maybe",TRUE);
675 modified=true;
676 return true;
677 }
678 return false;
679 }
680
681
682 // Delete a registry entry
deleteEntry(const FXchar * section,const FXchar * key)683 bool FXSettings::deleteEntry(const FXchar *section,const FXchar *key){
684 if(!section || !section[0]){ fxerror("FXSettings::deleteEntry: bad section argument.\n"); }
685 if(!key || !key[0]){ fxerror("FXSettings::deleteEntry: bad key argument.\n"); }
686 FXStringDict *group=insert(section);
687 if(group){
688 group->remove(key);
689 modified=true;
690 return true;
691 }
692 return false;
693 }
694
695
696 // Delete section
deleteSection(const FXchar * section)697 bool FXSettings::deleteSection(const FXchar *section){
698 if(!section || !section[0]){ fxerror("FXSettings::deleteSection: bad section argument.\n"); }
699 remove(section);
700 modified=true;
701 return true;
702 }
703
704
705 // Clear all sections
clear()706 bool FXSettings::clear(){
707 FXDict::clear();
708 modified=true;
709 return true;
710 }
711
712
713 // See if section exists
existingSection(const FXchar * section)714 bool FXSettings::existingSection(const FXchar *section){
715 if(!section || !section[0]){ fxerror("FXSettings::existingSection: bad section argument.\n"); }
716 return find(section)!=NULL;
717 }
718
719
720 // See if entry exists
existingEntry(const FXchar * section,const FXchar * key)721 bool FXSettings::existingEntry(const FXchar *section,const FXchar *key){
722 if(!section || !section[0]){ fxerror("FXSettings::existingEntry: bad section argument.\n"); }
723 if(!key || !key[0]){ fxerror("FXSettings::existingEntry: bad key argument.\n"); }
724 FXStringDict *group=find(section);
725 return group && group->find(key)!=NULL;
726 }
727
728
729 // Clean up
~FXSettings()730 FXSettings::~FXSettings(){
731 clear();
732 }
733
734 }
735