]> git.saurik.com Git - wxWidgets.git/blob - src/common/fileconf.cpp
Add support for dynamic event tables
[wxWidgets.git] / src / common / fileconf.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: fileconf.cpp
3 // Purpose: implementation of wxFileConfig derivation of wxConfig
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 07.04.98 (adapted from appconf.cpp)
7 // RCS-ID: $Id$
8 // Copyright: (c) 1997 Karsten Ballüder & Vadim Zeitlin
9 // Ballueder@usa.net <zeitlin@dptmaths.ens-cachan.fr>
10 // Licence: wxWindows license
11 ///////////////////////////////////////////////////////////////////////////////
12
13 // ============================================================================
14 // declarations
15 // ============================================================================
16
17 // ----------------------------------------------------------------------------
18 // headers
19 // ----------------------------------------------------------------------------
20 #include "wx/wxprec.h"
21
22 #ifdef __BORLANDC__
23 #pragma hdrstop
24 #endif //__BORLANDC__
25
26 #ifndef WX_PRECOMP
27 #include <wx/string.h>
28 #include <wx/intl.h>
29 #endif //WX_PRECOMP
30
31 #include <wx/dynarray.h>
32 #include <wx/file.h>
33 #include <wx/log.h>
34 #include <wx/textfile.h>
35 #include <wx/config.h>
36
37 #ifdef __WINDOWS__
38 #include <windows.h>
39 #endif
40
41 #include <stdlib.h>
42 #include <ctype.h>
43
44 // ----------------------------------------------------------------------------
45 // global functions declarations
46 // ----------------------------------------------------------------------------
47
48 // is 'c' a valid character in group name?
49 // NB: APPCONF_IMMUTABLE_PREFIX and APPCONF_PATH_SEPARATOR must be valid chars,
50 // but _not_ ']' (group name delimiter)
51 inline bool IsValid(char c) { return isalnum(c) || strchr("_/-!.*%", c); }
52
53 // get the system wide and user configuration file full path
54 static const char *GetGlobalFileName(const char *szFile);
55 static const char *GetLocalFileName(const char *szFile);
56
57 // split path into parts removing '..' in progress
58 static void SplitPath(wxArrayString& aParts, const char *sz);
59
60 // filter strings
61 static wxString FilterIn(const wxString& str);
62 static wxString FilterOut(const wxString& str);
63
64 // ----------------------------------------------------------------------------
65 // wxFileConfig
66 // ----------------------------------------------------------------------------
67
68 /*
69 FileConfig derives from BaseConfig and implements file based config class,
70 i.e. it uses ASCII disk files to store the information. These files are
71 alternatively called INI, .conf or .rc in the documentation. They are
72 organized in groups or sections, which can nest (i.e. a group contains
73 subgroups, which contain their own subgroups &c). Each group has some
74 number of entries, which are "key = value" pairs. More precisely, the format
75 is:
76
77 # comments are allowed after either ';' or '#' (Win/UNIX standard)
78
79 # blank lines (as above) are ignored
80
81 # global entries are members of special (no name) top group
82 written_for = wxWindows
83 platform = Linux
84
85 # the start of the group 'Foo'
86 [Foo] # may put comments like this also
87 # following 3 lines are entries
88 key = value
89 another_key = " strings with spaces in the beginning should be quoted, \
90 otherwise the spaces are lost"
91 last_key = but you don't have to put " normally (nor quote them, like here)
92
93 # subgroup of the group 'Foo'
94 # (order is not important, only the name is: separator is '/', as in paths)
95 [Foo/Bar]
96 # entries prefixed with "!" are immutable, i.e. can't be changed if they are
97 # set in the system-wide config file
98 !special_key = value
99 bar_entry = whatever
100
101 [Foo/Bar/Fubar] # depth is (theoretically :-) unlimited
102 # may have the same name as key in another section
103 bar_entry = whatever not
104
105 You have {read/write/delete}Entry functions (guess what they do) and also
106 setCurrentPath to select current group. enum{Subgroups/Entries} allow you
107 to get all entries in the config file (in the current group). Finally,
108 flush() writes immediately all changed entries to disk (otherwise it would
109 be done automatically in dtor)
110
111 FileConfig manages not less than 2 config files for each program: global
112 and local (or system and user if you prefer). Entries are read from both of
113 them and the local entries override the global ones unless the latter is
114 immutable (prefixed with '!') in which case a warning message is generated
115 and local value is ignored. Of course, the changes are always written to local
116 file only.
117 */
118
119 class wxFileConfig : public wxConfig
120 {
121 public:
122 // ctor & dtor
123 // the config file is searched in the following locations
124 // global local
125 // Unix /etc/file.ext ~/.file
126 // Win %windir%\file.ext %USERPROFILE%\file.ext
127 //
128 // where file is the basename of strFile, ext is it's extension
129 // or .conf (Unix) or .ini (Win) if it has none
130 wxFileConfig(const wxString& strFile, bool bLocalOnly = FALSE);
131 // dtor will save unsaved data
132 virtual ~wxFileConfig();
133
134 // implement inherited pure virtual functions
135 virtual void SetPath(const wxString& strPath);
136 virtual const wxString& GetPath() const { return m_strPath; }
137
138 virtual bool GetFirstGroup(wxString& str, long& lIndex);
139 virtual bool GetNextGroup (wxString& str, long& lIndex);
140 virtual bool GetFirstEntry(wxString& str, long& lIndex);
141 virtual bool GetNextEntry (wxString& str, long& lIndex);
142
143 virtual const char *Read(const char *szKey, const char *szDefault = 0) const;
144 virtual long Read(const char *szKey, long lDefault) const;
145 virtual bool Write(const char *szKey, const char *szValue);
146 virtual bool Write(const char *szKey, long Value);
147 virtual bool Flush(bool bCurrentOnly = FALSE);
148
149 virtual bool DeleteEntry(const char *szKey, bool bGroupIfEmptyAlso);
150 virtual bool DeleteGroup(const char *szKey);
151 virtual bool DeleteAll();
152
153 public:
154 // fwd decl
155 class ConfigGroup;
156 class ConfigEntry;
157
158 // we store all lines of the local config file as a linked list in memory
159 class LineList
160 {
161 public:
162 // ctor
163 LineList(const wxString& str, LineList *pNext = NULL) : m_strLine(str)
164 { SetNext(pNext); }
165
166 //
167 LineList *Next() const { return m_pNext; }
168 void SetNext(LineList *pNext) { m_pNext = pNext; }
169
170 //
171 void SetText(const wxString& str) { m_strLine = str; }
172 const wxString& Text() const { return m_strLine; }
173
174 private:
175 wxString m_strLine; // line contents
176 LineList *m_pNext; // next node
177 };
178
179 // functions to work with this list
180 LineList *LineListAppend(const wxString& str);
181 LineList *LineListInsert(const wxString& str,
182 LineList *pLine); // NULL => Append()
183 bool LineListIsEmpty();
184
185 private:
186 // put the object in the initial state
187 void Init();
188
189 // parse the whole file
190 void Parse(wxTextFile& file, bool bLocal);
191
192 // the same as SetPath("/")
193 void SetRootPath();
194
195 // member variables
196 // ----------------
197 LineList *m_linesHead, // head of the linked list
198 *m_linesTail; // tail
199
200 wxString m_strFile; // file name passed to ctor
201 wxString m_strPath; // current path (not '/' terminated)
202
203 ConfigGroup *m_pRootGroup, // the top (unnamed) group
204 *m_pCurrentGroup; // the current group
205
206 // a handy little class which changes current path to the path of given entry
207 // and restores it in dtor: so if you declare a local variable of this type,
208 // you work in the entry directory and the path is automatically restored
209 // when function returns
210 class PathChanger
211 {
212 public:
213 // ctor/dtor do path changing/restorin
214 PathChanger(const wxFileConfig *pContainer, const wxString& strEntry);
215 ~PathChanger();
216
217 // get the key name
218 const wxString& Name() const { return m_strName; }
219
220 private:
221 wxFileConfig *m_pContainer; // object we live in
222 wxString m_strName, // name of entry (i.e. name only)
223 m_strOldPath; // saved path
224 bool m_bChanged; // was the path changed?
225 };
226
227 //protected: --- if FileConfig::ConfigEntry is not public, functions in
228 // ConfigGroup such as Find/AddEntry can't return "ConfigEntry *"
229 public:
230 WX_DEFINE_ARRAY(ConfigEntry *, ArrayEntries);
231 WX_DEFINE_ARRAY(ConfigGroup *, ArrayGroups);
232
233 class ConfigEntry
234 {
235 private:
236 ConfigGroup *m_pParent; // group that contains us
237 wxString m_strName, // entry name
238 m_strValue; // value
239 bool m_bDirty, // changed since last read?
240 m_bImmutable; // can be overriden locally?
241 int m_nLine; // used if m_pLine == NULL only
242 LineList *m_pLine; // pointer to our line in the linked list
243 // or NULL if it was found in global file
244
245 public:
246 ConfigEntry(ConfigGroup *pParent, const wxString& strName, int nLine);
247
248 // simple accessors
249 const wxString& Name() const { return m_strName; }
250 const wxString& Value() const { return m_strValue; }
251 ConfigGroup *Group() const { return m_pParent; }
252 bool IsDirty() const { return m_bDirty; }
253 bool IsImmutable() const { return m_bImmutable; }
254 bool IsLocal() const { return m_pLine != 0; }
255 int Line() const { return m_nLine; }
256 LineList *GetLine() const { return m_pLine; }
257
258 // modify entry attributes
259 void SetValue(const wxString& strValue, bool bUser = TRUE);
260 void SetDirty();
261 void SetLine(LineList *pLine);
262 };
263
264 protected:
265 class ConfigGroup
266 {
267 private:
268 wxFileConfig *m_pConfig; // config object we belong to
269 ConfigGroup *m_pParent; // parent group (NULL for root group)
270 ArrayEntries m_aEntries; // entries in this group
271 ArrayGroups m_aSubgroups; // subgroups
272 wxString m_strName; // group's name
273 bool m_bDirty; // if FALSE => all subgroups are not dirty
274 LineList *m_pLine; // pointer to our line in the linked list
275 int m_nLastEntry, // last here means "last added"
276 m_nLastGroup; //
277
278 public:
279 // ctor
280 ConfigGroup(ConfigGroup *pParent, const wxString& strName, wxFileConfig *);
281
282 // dtor deletes all entries and subgroups also
283 ~ConfigGroup();
284
285 // simple accessors
286 const wxString& Name() const { return m_strName; }
287 ConfigGroup *Parent() const { return m_pParent; }
288 wxFileConfig *Config() const { return m_pConfig; }
289 bool IsDirty() const { return m_bDirty; }
290
291 bool IsEmpty() const { return Entries().IsEmpty() && Groups().IsEmpty(); }
292 const ArrayEntries& Entries() const { return m_aEntries; }
293 const ArrayGroups& Groups() const { return m_aSubgroups; }
294
295 // find entry/subgroup (NULL if not found)
296 ConfigGroup *FindSubgroup(const char *szName) const;
297 ConfigEntry *FindEntry (const char *szName) const;
298
299 // delete entry/subgroup, return FALSE if doesn't exist
300 bool DeleteSubgroup(const char *szName);
301 bool DeleteEntry(const char *szName);
302
303 // create new entry/subgroup returning pointer to newly created element
304 ConfigGroup *AddSubgroup(const wxString& strName);
305 ConfigEntry *AddEntry (const wxString& strName, int nLine = NOT_FOUND);
306
307 // will also recursively set parent's dirty flag
308 void SetDirty();
309 void SetLine(LineList *pLine);
310
311 wxString GetFullName() const;
312
313 // get the last line belonging to an entry/subgroup of this group
314 LineList *GetGroupLine();
315 LineList *GetLastEntryLine();
316 LineList *GetLastGroupLine();
317 };
318 };
319
320 // ============================================================================
321 // implementation
322 // ============================================================================
323
324 // ----------------------------------------------------------------------------
325 // ctor
326 // ----------------------------------------------------------------------------
327
328 void wxFileConfig::Init()
329 {
330 m_pCurrentGroup =
331 m_pRootGroup = new ConfigGroup(NULL, "", this);
332
333 m_linesHead =
334 m_linesTail = NULL;
335
336 m_bExpandEnvVars = TRUE;
337
338 m_strPath.Empty();
339 }
340
341 wxFileConfig::wxFileConfig(const wxString& strFile, bool bLocalOnly)
342 : m_strFile(strFile)
343 {
344 Init();
345
346 const char *szFile;
347
348 // it's not an error if (one of the) file(s) doesn't exist
349
350 // parse the global file
351 if ( !bLocalOnly ) {
352 szFile = GetGlobalFileName(strFile);
353 if ( wxFile::Exists(szFile) ) {
354 wxTextFile fileGlobal(szFile);
355
356 if ( fileGlobal.Open() ) {
357 Parse(fileGlobal, FALSE /* global */);
358 SetRootPath();
359 }
360 else
361 wxLogWarning("Can't open global configuration file.");
362 }
363 }
364
365 // parse the local file
366 szFile = GetLocalFileName(strFile);
367 if ( wxFile::Exists(szFile) ) {
368 wxTextFile fileLocal(szFile);
369 if ( fileLocal.Open() ) {
370 Parse(fileLocal, TRUE /* local */);
371 SetRootPath();
372 }
373 else
374 wxLogWarning("Can't open user configuration file.");
375 }
376 }
377
378 wxFileConfig::~wxFileConfig()
379 {
380 Flush();
381 delete m_pRootGroup;
382 }
383
384 // ----------------------------------------------------------------------------
385 // parse a config file
386 // ----------------------------------------------------------------------------
387
388 void wxFileConfig::Parse(wxTextFile& file, bool bLocal)
389 {
390 const char *pStart;
391 const char *pEnd;
392
393 for ( uint n = 0; n < file.GetLineCount(); n++ ) {
394 // add the line to linked list
395 if ( bLocal )
396 LineListAppend(file[n]);
397
398 // skip leading spaces
399 for ( pStart = file[n]; isspace(*pStart); pStart++ )
400 ;
401
402 // skip blank/comment lines
403 if ( *pStart == '\0'|| *pStart == ';' || *pStart == '#' )
404 continue;
405
406 if ( *pStart == '[' ) { // a new group
407 pEnd = pStart;
408
409 while ( *++pEnd != ']' ) {
410 if ( !IsValid(*pEnd) && *pEnd != ' ' ) // allow spaces in group names
411 break;
412 }
413
414 if ( *pEnd != ']' ) {
415 wxLogError("file '%s': unexpected character at line %d (missing ']'?)",
416 file.GetName(), n + 1);
417 continue; // skip this line
418 }
419
420 // group name here is always considered as abs path
421 wxString strGroup;
422 pStart++;
423 strGroup << APPCONF_PATH_SEPARATOR << wxString(pStart, pEnd - pStart);
424
425 // will create it if doesn't yet exist
426 SetPath(strGroup);
427
428 if ( bLocal )
429 m_pCurrentGroup->SetLine(m_linesTail);
430
431 // check that there is nothing except comments left on this line
432 bool bCont = TRUE;
433 while ( *++pEnd != '\0' && bCont ) {
434 switch ( *pEnd ) {
435 case '#':
436 case ';':
437 bCont = FALSE;
438 break;
439
440 case ' ':
441 case '\t':
442 // ignore whitespace ('\n' impossible here)
443 break;
444
445 default:
446 wxLogWarning("file '%s', line %d: '%s' ignored after group header.",
447 file.GetName(), n + 1, pEnd);
448 bCont = FALSE;
449 }
450 }
451 }
452 else { // a key
453 const char *pEnd = pStart;
454 while ( IsValid(*pEnd) )
455 pEnd++;
456
457 wxString strKey(pStart, pEnd);
458
459 // skip whitespace
460 while ( isspace(*pEnd) )
461 pEnd++;
462
463 if ( *pEnd++ != '=' ) {
464 wxLogError("file '%s', line %d: '=' expected.", file.GetName(), n + 1);
465 }
466 else {
467 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strKey);
468
469 if ( pEntry == NULL ) {
470 // new entry
471 pEntry = m_pCurrentGroup->AddEntry(strKey, n);
472
473 if ( bLocal )
474 pEntry->SetLine(m_linesTail);
475 }
476 else {
477 if ( bLocal && pEntry->IsImmutable() ) {
478 // immutable keys can't be changed by user
479 wxLogWarning("file '%s', line %d: value for immutable key '%s' ignored.",
480 file.GetName(), n + 1, strKey.c_str());
481 continue;
482 }
483 // the condition below catches the cases (a) and (b) but not (c):
484 // (a) global key found second time in global file
485 // (b) key found second (or more) time in local file
486 // (c) key from global file now found in local one
487 // which is exactly what we want.
488 else if ( !bLocal || pEntry->IsLocal() ) {
489 wxLogWarning("file '%s', line %d: key '%s' was first found at line %d.",
490 file.GetName(), n + 1, strKey.c_str(), pEntry->Line());
491
492 if ( bLocal )
493 pEntry->SetLine(m_linesTail);
494 }
495 }
496
497 // skip whitespace
498 while ( isspace(*pEnd) )
499 pEnd++;
500
501 wxString strValue;
502 if (m_bExpandEnvVars)
503 strValue = ExpandEnvVars(FilterIn(pEnd));
504 else
505 strValue = FilterIn(pEnd);
506 pEntry->SetValue(strValue, FALSE);
507 }
508 }
509 }
510 }
511
512 // ----------------------------------------------------------------------------
513 // set/retrieve path
514 // ----------------------------------------------------------------------------
515
516 void wxFileConfig::SetRootPath()
517 {
518 m_strPath.Empty();
519 m_pCurrentGroup = m_pRootGroup;
520 }
521
522 void wxFileConfig::SetPath(const wxString& strPath)
523 {
524 wxArrayString aParts;
525
526 if ( strPath.IsEmpty() )
527 return;
528
529 if ( strPath[0] == APPCONF_PATH_SEPARATOR ) {
530 // absolute path
531 SplitPath(aParts, strPath);
532 }
533 else {
534 // relative path, combine with current one
535 wxString strFullPath = m_strPath;
536 strFullPath << APPCONF_PATH_SEPARATOR << strPath;
537 SplitPath(aParts, strFullPath);
538 }
539
540 // change current group
541 uint n;
542 m_pCurrentGroup = m_pRootGroup;
543 for ( n = 0; n < aParts.Count(); n++ ) {
544 ConfigGroup *pNextGroup = m_pCurrentGroup->FindSubgroup(aParts[n]);
545 if ( pNextGroup == NULL )
546 pNextGroup = m_pCurrentGroup->AddSubgroup(aParts[n]);
547 m_pCurrentGroup = pNextGroup;
548 }
549
550 // recombine path parts in one variable
551 m_strPath.Empty();
552 for ( n = 0; n < aParts.Count(); n++ ) {
553 m_strPath << APPCONF_PATH_SEPARATOR << aParts[n];
554 }
555 }
556
557 // ----------------------------------------------------------------------------
558 // enumeration
559 // ----------------------------------------------------------------------------
560
561 bool wxFileConfig::GetFirstGroup(wxString& str, long& lIndex)
562 {
563 lIndex = 0;
564 return GetNextGroup(str, lIndex);
565 }
566
567 bool wxFileConfig::GetNextGroup (wxString& str, long& lIndex)
568 {
569 if ( uint(lIndex) < m_pCurrentGroup->Groups().Count() ) {
570 str = m_pCurrentGroup->Groups()[lIndex++]->Name();
571 return TRUE;
572 }
573 else
574 return FALSE;
575 }
576
577 bool wxFileConfig::GetFirstEntry(wxString& str, long& lIndex)
578 {
579 lIndex = 0;
580 return GetNextEntry(str, lIndex);
581 }
582
583 bool wxFileConfig::GetNextEntry (wxString& str, long& lIndex)
584 {
585 if ( uint(lIndex) < m_pCurrentGroup->Entries().Count() ) {
586 str = m_pCurrentGroup->Entries()[lIndex++]->Name();
587 return TRUE;
588 }
589 else
590 return FALSE;
591 }
592
593 // ----------------------------------------------------------------------------
594 // read/write values
595 // ----------------------------------------------------------------------------
596
597 const char *wxFileConfig::Read(const char *szKey, const char *szDefault) const
598 {
599 PathChanger path(this, szKey);
600
601 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
602 if (pEntry == NULL)
603 return szDefault;
604 else
605 return pEntry->Value();
606 // return pEntry == NULL ? szDefault : pEntry->Value();
607 }
608
609 long wxFileConfig::Read(const char *szKey, long lDefault) const
610 {
611 const char *pc = Read(szKey);
612 return pc == NULL ? lDefault : atol(pc);
613 }
614
615 bool wxFileConfig::Write(const char *szKey, const char *szValue)
616 {
617 PathChanger path(this, szKey);
618
619 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
620 if ( pEntry == NULL )
621 pEntry = m_pCurrentGroup->AddEntry(path.Name());
622 pEntry->SetValue(szValue);
623
624 return TRUE;
625 }
626
627 bool wxFileConfig::Write(const char *szKey, long lValue)
628 {
629 // ltoa() is not ANSI :-(
630 char szBuf[40]; // should be good for sizeof(long) <= 16 (128 bits)
631 sprintf(szBuf, "%ld", lValue);
632 return Write(szKey, szBuf);
633 }
634
635 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
636 {
637 if ( LineListIsEmpty() || !m_pRootGroup->IsDirty() )
638 return TRUE;
639
640 wxTempFile file(GetLocalFileName(m_strFile));
641
642 if ( !file.IsOpened() ) {
643 wxLogError("Can't open user configuration file.");
644 return FALSE;
645 }
646
647 // write all strings to file
648 for ( LineList *p = m_linesHead; p != NULL; p = p->Next() ) {
649 if ( !file.Write(p->Text() + wxTextFile::GetEOL()) ) {
650 wxLogError("Can't write user configuration file.");
651 return FALSE;
652 }
653 }
654
655 return file.Commit();
656 }
657
658 // ----------------------------------------------------------------------------
659 // delete groups/entries
660 // ----------------------------------------------------------------------------
661
662 bool wxFileConfig::DeleteEntry(const char *szKey, bool bGroupIfEmptyAlso)
663 {
664 PathChanger path(this, szKey);
665
666 if ( !m_pCurrentGroup->DeleteEntry(path.Name()) )
667 return FALSE;
668
669 if ( bGroupIfEmptyAlso && m_pCurrentGroup->IsEmpty() ) {
670 if ( m_pCurrentGroup != m_pRootGroup ) {
671 ConfigGroup *pGroup = m_pCurrentGroup;
672 SetPath(".."); // changes m_pCurrentGroup!
673 m_pCurrentGroup->DeleteSubgroup(pGroup->Name());
674 }
675 //else: never delete the root group
676 }
677
678 return TRUE;
679 }
680
681 bool wxFileConfig::DeleteGroup(const char *szKey)
682 {
683 PathChanger path(this, szKey);
684
685 return m_pCurrentGroup->DeleteSubgroup(path.Name());
686 }
687
688 bool wxFileConfig::DeleteAll()
689 {
690 const char *szFile = GetLocalFileName(m_strFile);
691 delete m_pRootGroup;
692 Init();
693
694 if ( remove(szFile) == -1 )
695 wxLogSysError("Can't delete user configuration file '%s'", szFile);
696
697 szFile = GetGlobalFileName(m_strFile);
698 if ( remove(szFile) )
699 wxLogSysError("Can't delete system configuration file '%s'", szFile);
700
701 return TRUE;
702 }
703
704 // ----------------------------------------------------------------------------
705 // linked list functions
706 // ----------------------------------------------------------------------------
707
708 // append a new line to the end of the list
709 wxFileConfig::LineList *wxFileConfig::LineListAppend(const wxString& str)
710 {
711 LineList *pLine = new LineList(str);
712
713 if ( m_linesTail == NULL ) {
714 // list is empty
715 m_linesHead = pLine;
716 }
717 else {
718 // adjust pointers
719 m_linesTail->SetNext(pLine);
720 }
721
722 m_linesTail = pLine;
723 return m_linesTail;
724 }
725
726 // insert a new line after the given one
727 wxFileConfig::LineList *wxFileConfig::LineListInsert(const wxString& str,
728 LineList *pLine)
729 {
730 if ( pLine == NULL )
731 return LineListAppend(str);
732
733 LineList *pNewLine = new LineList(str, pLine->Next());
734 pLine->SetNext(pNewLine);
735
736 return pNewLine;
737 }
738
739 bool wxFileConfig::LineListIsEmpty()
740 {
741 return m_linesHead == NULL;
742 }
743
744 // ============================================================================
745 // wxFileConfig::ConfigGroup
746 // ============================================================================
747
748 // ----------------------------------------------------------------------------
749 // ctor/dtor
750 // ----------------------------------------------------------------------------
751
752 // ctor
753 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup *pParent,
754 const wxString& strName,
755 wxFileConfig *pConfig)
756 : m_strName(strName)
757 {
758 m_pConfig = pConfig;
759 m_pParent = pParent;
760 m_pLine = NULL;
761 m_bDirty = FALSE;
762
763 m_nLastEntry =
764 m_nLastGroup = NOT_FOUND;
765 }
766
767 // dtor deletes all children
768 wxFileConfig::ConfigGroup::~ConfigGroup()
769 {
770 // entries
771 uint n, nCount = m_aEntries.Count();
772 for ( n = 0; n < nCount; n++ )
773 delete m_aEntries[n];
774
775 // subgroups
776 nCount = m_aSubgroups.Count();
777 for ( n = 0; n < nCount; n++ )
778 delete m_aSubgroups[n];
779 }
780
781 // ----------------------------------------------------------------------------
782 // line
783 // ----------------------------------------------------------------------------
784
785 void wxFileConfig::ConfigGroup::SetLine(LineList *pLine)
786 {
787 wxASSERT( m_pLine == NULL ); // shouldn't be called twice
788
789 m_pLine = pLine;
790 }
791
792 // return the line which contains "[our name]"
793 wxFileConfig::LineList *wxFileConfig::ConfigGroup::GetGroupLine()
794 {
795 if ( m_pLine == NULL ) {
796 // this group wasn't present in local config file, add it now
797 if ( Parent() != NULL ) {
798 wxString strFullName;
799 strFullName << "[" << GetFullName().c_str() + 1 << "]"; // +1: no '/'
800 m_pLine = m_pConfig->LineListInsert(strFullName,
801 Parent()->GetLastGroupLine());
802 }
803 else {
804 // we're the root group, yet we were not in the local file => there were
805 // only comments and blank lines in config file or nothing at all
806 // we return NULL, so that LineListInsert() will do Append()
807 }
808 }
809
810 return m_pLine;
811 }
812
813 // return the last line belonging to the subgroups of this group
814 // (after which we can add a new subgroup)
815 wxFileConfig::LineList *wxFileConfig::ConfigGroup::GetLastGroupLine()
816 {
817 // if we have any subgroups, our last line is the last line of the last
818 // subgroup
819 if ( m_nLastGroup != NOT_FOUND ) {
820 return m_aSubgroups[m_nLastGroup]->GetLastGroupLine();
821 }
822
823 // if we have any entries, our last line is the last entry
824 if ( m_nLastEntry != NOT_FOUND ) {
825 return m_aEntries[m_nLastEntry]->GetLine();
826 }
827
828 // nothing at all: last line is the first one
829 return GetGroupLine();
830 }
831
832 // return the last line belonging to the entries of this group
833 // (after which we can add a new entry)
834 wxFileConfig::LineList *wxFileConfig::ConfigGroup::GetLastEntryLine()
835 {
836 if ( m_nLastEntry != NOT_FOUND )
837 return m_aEntries[m_nLastEntry]->GetLine();
838 else
839 return GetGroupLine();
840 }
841
842 // ----------------------------------------------------------------------------
843 // group name
844 // ----------------------------------------------------------------------------
845
846 wxString wxFileConfig::ConfigGroup::GetFullName() const
847 {
848 if ( Parent() )
849 return Parent()->GetFullName() + APPCONF_PATH_SEPARATOR + Name();
850 else
851 return "";
852 }
853
854 // ----------------------------------------------------------------------------
855 // find an item
856 // ----------------------------------------------------------------------------
857
858 wxFileConfig::ConfigEntry *
859 wxFileConfig::ConfigGroup::FindEntry(const char *szName) const
860 {
861 uint nCount = m_aEntries.Count();
862 for ( uint n = 0; n < nCount; n++ ) {
863 if ( m_aEntries[n]->Name().IsSameAs(szName, APPCONF_CASE_SENSITIVE) )
864 return m_aEntries[n];
865 }
866
867 return NULL;
868 }
869
870 wxFileConfig::ConfigGroup *
871 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName) const
872 {
873 uint nCount = m_aSubgroups.Count();
874 for ( uint n = 0; n < nCount; n++ ) {
875 if ( m_aSubgroups[n]->Name().IsSameAs(szName, APPCONF_CASE_SENSITIVE) )
876 return m_aSubgroups[n];
877 }
878
879 return NULL;
880 }
881
882 // ----------------------------------------------------------------------------
883 // create a new item
884 // ----------------------------------------------------------------------------
885
886 // create a new entry and add it to the current group
887 wxFileConfig::ConfigEntry *
888 wxFileConfig::ConfigGroup::AddEntry(const wxString& strName, int nLine)
889 {
890 wxASSERT( FindEntry(strName) == NULL );
891
892 ConfigEntry *pEntry = new ConfigEntry(this, strName, nLine);
893 m_aEntries.Add(pEntry);
894
895 return pEntry;
896 }
897
898 // create a new group and add it to the current group
899 wxFileConfig::ConfigGroup *
900 wxFileConfig::ConfigGroup::AddSubgroup(const wxString& strName)
901 {
902 wxASSERT( FindSubgroup(strName) == NULL );
903
904 ConfigGroup *pGroup = new ConfigGroup(this, strName, m_pConfig);
905 m_aSubgroups.Add(pGroup);
906
907 return pGroup;
908 }
909
910 // ----------------------------------------------------------------------------
911 // delete an item
912 // ----------------------------------------------------------------------------
913
914 bool wxFileConfig::ConfigGroup::DeleteSubgroup(const char *szName)
915 {
916 uint n, nCount = m_aSubgroups.Count();
917 for ( n = 0; n < nCount; n++ ) {
918 if ( m_aSubgroups[n]->Name().IsSameAs(szName, APPCONF_CASE_SENSITIVE) )
919 break;
920 }
921
922 if ( n == nCount )
923 return FALSE;
924
925 delete m_aSubgroups[n];
926 m_aSubgroups.Remove(n);
927 return TRUE;
928 }
929
930 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName)
931 {
932 uint n, nCount = m_aEntries.Count();
933 for ( n = 0; n < nCount; n++ ) {
934 if ( m_aEntries[n]->Name().IsSameAs(szName, APPCONF_CASE_SENSITIVE) )
935 break;
936 }
937
938 if ( n == nCount )
939 return FALSE;
940
941 delete m_aEntries[n];
942 m_aEntries.Remove(n);
943 return TRUE;
944 }
945
946 // ----------------------------------------------------------------------------
947 //
948 // ----------------------------------------------------------------------------
949 void wxFileConfig::ConfigGroup::SetDirty()
950 {
951 m_bDirty = TRUE;
952 if ( Parent() != NULL ) // propagate upwards
953 Parent()->SetDirty();
954 }
955
956 // ============================================================================
957 // wxFileConfig::ConfigEntry
958 // ============================================================================
959
960 // ----------------------------------------------------------------------------
961 // ctor
962 // ----------------------------------------------------------------------------
963 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup *pParent,
964 const wxString& strName,
965 int nLine)
966 : m_strName(strName)
967 {
968 m_pParent = pParent;
969 m_nLine = nLine;
970 m_pLine = NULL;
971
972 m_bDirty = FALSE;
973
974 m_bImmutable = strName[0] == APPCONF_IMMUTABLE_PREFIX;
975 if ( m_bImmutable )
976 m_strName.erase(0, 1); // remove first character
977 }
978
979 // ----------------------------------------------------------------------------
980 // set value
981 // ----------------------------------------------------------------------------
982
983 void wxFileConfig::ConfigEntry::SetLine(LineList *pLine)
984 {
985 wxASSERT( m_pLine == NULL );
986
987 m_pLine = pLine;
988 }
989
990 // second parameter is FALSE if we read the value from file and prevents the
991 // entry from being marked as 'dirty'
992 void wxFileConfig::ConfigEntry::SetValue(const wxString& strValue, bool bUser)
993 {
994 if ( bUser && IsImmutable() ) {
995 wxLogWarning("Attempt to change immutable key '%s' ignored.",
996 Name().c_str());
997 return;
998 }
999
1000 // do nothing if it's the same value
1001 if ( strValue == m_strValue )
1002 return;
1003
1004 m_strValue = strValue;
1005
1006 if ( bUser ) {
1007 wxString strVal = FilterOut(strValue);
1008 wxString strLine;
1009 strLine << m_strName << " = " << strVal;
1010
1011 if ( m_pLine != NULL ) {
1012 // entry was read from the local config file, just modify the line
1013 m_pLine->SetText(strLine);
1014 }
1015 else {
1016 // add a new line to the file
1017 wxASSERT( m_nLine == NOT_FOUND ); // consistency check
1018
1019 Group()->Config()->LineListInsert(strLine, Group()->GetLastEntryLine());
1020 }
1021
1022 SetDirty();
1023 }
1024 }
1025
1026 void wxFileConfig::ConfigEntry::SetDirty()
1027 {
1028 m_bDirty = TRUE;
1029 Group()->SetDirty();
1030 }
1031
1032 // ============================================================================
1033 // wxFileConfig::PathChanger
1034 // ============================================================================
1035
1036 wxFileConfig::PathChanger::PathChanger(const wxFileConfig *pContainer,
1037 const wxString& strEntry)
1038 {
1039 m_pContainer = (wxFileConfig *)pContainer;
1040 wxString strPath = strEntry.Before(APPCONF_PATH_SEPARATOR);
1041 if ( !strPath.IsEmpty() ) {
1042 // do change the path
1043 m_bChanged = TRUE;
1044 m_strName = strEntry.Right(APPCONF_PATH_SEPARATOR);
1045 m_strOldPath = m_pContainer->GetPath();
1046 m_strOldPath += APPCONF_PATH_SEPARATOR;
1047 m_pContainer->SetPath(strPath);
1048 }
1049 else {
1050 // it's a name only, without path - nothing to do
1051 m_bChanged = FALSE;
1052 m_strName = strEntry;
1053 }
1054 }
1055
1056 wxFileConfig::PathChanger::~PathChanger()
1057 {
1058 // only restore path if it was changed
1059 if ( m_bChanged ) {
1060 m_pContainer->SetPath(m_strOldPath);
1061 }
1062 }
1063
1064 // ============================================================================
1065 // global functions
1066 // ============================================================================
1067
1068 const char *GetGlobalFileName(const char *szFile)
1069 {
1070 static wxString s_str;
1071 s_str.Empty();
1072
1073 bool bNoExt = strchr(szFile, '.') == NULL;
1074
1075 #ifdef __UNIX__
1076 s_str << "/etc/" << szFile;
1077 if ( bNoExt )
1078 s_str << ".conf";
1079 #else // Windows
1080 #ifndef _MAX_PATH
1081 #define _MAX_PATH 512
1082 #endif
1083 char szWinDir[_MAX_PATH];
1084 ::GetWindowsDirectory(szWinDir, _MAX_PATH);
1085 s_str << szWinDir << "\\" << szFile;
1086 if ( bNoExt )
1087 s_str << ".INI";
1088 #endif // UNIX/Win
1089
1090 return s_str.c_str();
1091 }
1092
1093 const char *GetLocalFileName(const char *szFile)
1094 {
1095 static wxString s_str;
1096 s_str.Empty();
1097
1098 #ifdef __UNIX__
1099 const char *szHome = getenv("HOME");
1100 if ( szHome == NULL ) {
1101 // we're homeless...
1102 wxLogWarning("can't find user's HOME, using current directory.");
1103 szHome = ".";
1104 }
1105 s_str << szHome << "/." << szFile;
1106 #else // Windows
1107 #ifdef __WIN32__
1108 const char *szHome = getenv("HOMEDRIVE");
1109 if ( szHome == NULL )
1110 szHome = "";
1111 s_str << szHome;
1112 szHome = getenv("HOMEPATH");
1113 s_str << ( szHome == NULL ? "." : szHome ) << szFile;
1114 if ( strchr(szFile, '.') == NULL )
1115 s_str << ".INI";
1116 #else // Win16
1117 // Win16 has no idea about home, so use the current directory instead
1118 s_str << ".\\" << szFile;
1119 #endif // WIN16/32
1120 #endif // UNIX/Win
1121
1122 return s_str.c_str();
1123 }
1124
1125 void SplitPath(wxArrayString& aParts, const char *sz)
1126 {
1127 aParts.Empty();
1128
1129 wxString strCurrent;
1130 const char *pc = sz;
1131 for ( ;; ) {
1132 if ( *pc == '\0' || *pc == APPCONF_PATH_SEPARATOR ) {
1133 if ( strCurrent == "." ) {
1134 // ignore
1135 }
1136 else if ( strCurrent == ".." ) {
1137 // go up one level
1138 if ( aParts.IsEmpty() )
1139 wxLogWarning("'%s' has extra '..', ignored.", sz);
1140 else
1141 aParts.Remove(aParts.Count() - 1);
1142 }
1143 else if ( !strCurrent.IsEmpty() ) {
1144 aParts.Add(strCurrent);
1145 strCurrent.Empty();
1146 }
1147 //else:
1148 // could log an error here, but we prefer to ignore extra '/'
1149
1150 if ( *pc == '\0' )
1151 return;
1152 }
1153 else
1154 strCurrent += *pc;
1155
1156 pc++;
1157 }
1158 }
1159
1160 // undo FilterOut
1161 wxString FilterIn(const wxString& str)
1162 {
1163 wxString strResult;
1164
1165 bool bQuoted = str[0] == '"';
1166
1167 for ( uint n = bQuoted ? 1 : 0; n < str.Len(); n++ ) {
1168 if ( str[n] == '\\' ) {
1169 switch ( str[++n] ) {
1170 case 'n':
1171 strResult += '\n';
1172 break;
1173
1174 case 't':
1175 strResult += '\t';
1176 break;
1177
1178 case '\\':
1179 strResult += '\\';
1180 break;
1181
1182 case '"':
1183 strResult += '"';
1184 break;
1185 }
1186 }
1187 else {
1188 if ( str[n] != '"' || !bQuoted )
1189 strResult += str[n];
1190 else if ( n != str.Len() - 1 )
1191 wxLogWarning("unexpected \" at position %d in '%s'.", n, str.c_str());
1192 //else: it's the last quote of a quoted string, ok
1193 }
1194 }
1195
1196 return strResult;
1197 }
1198
1199 // quote the string before writing it to file
1200 wxString FilterOut(const wxString& str)
1201 {
1202 wxString strResult;
1203
1204 // quoting is necessary to preserve spaces in the beginning of the string
1205 bool bQuote = isspace(str[0]) || str[0] == '"';
1206
1207 if ( bQuote )
1208 strResult += '"';
1209
1210 char c;
1211 for ( uint n = 0; n < str.Len(); n++ ) {
1212 switch ( str[n] ) {
1213 case '\n':
1214 c = 'n';
1215 break;
1216
1217 case '\t':
1218 c = 't';
1219 break;
1220
1221 case '\\':
1222 c = '\\';
1223 break;
1224
1225 case '"':
1226 if ( bQuote )
1227 c = '"';
1228 //else: fall through
1229
1230 default:
1231 strResult += str[n];
1232 continue; // nothing special to do
1233 }
1234
1235 // we get here only for special characters
1236 strResult << '\\' << c;
1237 }
1238
1239 if ( bQuote )
1240 strResult += '"';
1241
1242 return strResult;
1243 }
1244
1245 wxConfig *CreateFileConfig(const wxString& strFile, bool bLocalOnly)
1246 {
1247 return new wxFileConfig(strFile, bLocalOnly);
1248 }