]> git.saurik.com Git - wxWidgets.git/blob - src/common/fileconf.cpp
fixed GetGlobalFilename()
[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 #ifdef __GNUG__
14 #pragma implementation "fileconf.h"
15 #endif
16
17 // ============================================================================
18 // declarations
19 // ============================================================================
20
21 // ----------------------------------------------------------------------------
22 // headers
23 // ----------------------------------------------------------------------------
24 #include "wx/wxprec.h"
25
26 #ifdef __BORLANDC__
27 #pragma hdrstop
28 #endif //__BORLANDC__
29
30 #ifndef WX_PRECOMP
31 #include <wx/string.h>
32 #include <wx/intl.h>
33 #endif //WX_PRECOMP
34
35 #include <wx/dynarray.h>
36 #include <wx/file.h>
37 #include <wx/log.h>
38 #include <wx/textfile.h>
39 #include <wx/config.h>
40 #include <wx/fileconf.h>
41
42 // _WINDOWS_ is defined when windows.h is included,
43 // __WXMSW__ is defined for MS Windows compilation
44 #if defined(__WXMSW__) && !defined(_WINDOWS_)
45 #include <windows.h>
46 #endif //windows.h
47
48 #include <stdlib.h>
49 #include <ctype.h>
50
51 // ----------------------------------------------------------------------------
52 // macros
53 // ----------------------------------------------------------------------------
54 #define CONST_CAST ((wxFileConfig *)this)->
55
56 // ----------------------------------------------------------------------------
57 // global functions declarations
58 // ----------------------------------------------------------------------------
59
60 // is 'c' a valid character in group name?
61 // NB: wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR must be valid chars,
62 // but _not_ ']' (group name delimiter)
63 inline bool IsValid(char c) { return isalnum(c) || strchr("@_/-!.*%", c); }
64
65 // compare functions for sorting the arrays
66 static int CompareEntries(wxFileConfig::ConfigEntry *p1,
67 wxFileConfig::ConfigEntry *p2);
68 static int CompareGroups(wxFileConfig::ConfigGroup *p1,
69 wxFileConfig::ConfigGroup *p2);
70
71 // filter strings
72 static wxString FilterIn(const wxString& str);
73 static wxString FilterOut(const wxString& str);
74
75 // ============================================================================
76 // implementation
77 // ============================================================================
78
79 // ----------------------------------------------------------------------------
80 // static functions
81 // ----------------------------------------------------------------------------
82 wxString wxFileConfig::GetGlobalDir()
83 {
84 wxString strDir;
85
86 #ifdef __UNIX__
87 strDir = "/etc/";
88 #else // Windows
89 #ifndef _MAX_PATH
90 #define _MAX_PATH 512
91 #endif
92
93 char szWinDir[_MAX_PATH];
94 ::GetWindowsDirectory(szWinDir, _MAX_PATH);
95
96 strDir = szWinDir;
97 strDir << '\\';
98 #endif // Unix/Windows
99
100 return strDir;
101 }
102
103 wxString wxFileConfig::GetLocalDir()
104 {
105 wxString strDir;
106
107 #ifdef __UNIX__
108 const char *szHome = getenv("HOME");
109 if ( szHome == NULL ) {
110 // we're homeless...
111 wxLogWarning(_("can't find user's HOME, using current directory."));
112 strDir = ".";
113 }
114 else
115 strDir = szHome;
116 strDir << '/'; // a double slash is no problem, a missin one yes
117 #else // Windows
118 #ifdef __WIN32__
119 const char *szHome = getenv("HOMEDRIVE");
120 if ( szHome != NULL )
121 strDir << szHome;
122 szHome = getenv("HOMEPATH");
123 if ( szHome != NULL )
124 strDir << szHome;
125 #else // Win16
126 // Win16 has no idea about home, so use the current directory instead
127 strDir = ".\\";
128 #endif // WIN16/32
129 #endif // UNIX/Win
130
131 return strDir;
132 }
133
134 wxString wxFileConfig::GetGlobalFileName(const char *szFile)
135 {
136 wxString str = GetGlobalDir();
137 str << szFile;
138
139 if ( strchr(szFile, '.') == NULL )
140 #ifdef __UNIX__
141 str << ".conf";
142 #else // Windows
143 str << ".ini";
144 #endif // UNIX/Win
145
146 return str;
147 }
148
149 wxString wxFileConfig::GetLocalFileName(const char *szFile)
150 {
151 wxString str = GetLocalDir();
152
153 #ifdef __UNIX__
154 str << '.';
155 #endif
156
157 str << szFile;
158
159 #ifdef __WXMSW__
160 if ( strchr(szFile, '.') == NULL )
161 str << ".ini";
162 #endif
163
164 return str;
165 }
166
167 // ----------------------------------------------------------------------------
168 // ctor
169 // ----------------------------------------------------------------------------
170
171 void wxFileConfig::Init()
172 {
173 m_pCurrentGroup =
174 m_pRootGroup = new ConfigGroup(NULL, "", this);
175
176 m_linesHead =
177 m_linesTail = NULL;
178
179 // it's not an error if (one of the) file(s) doesn't exist
180
181 // parse the global file
182 if ( !m_strGlobalFile.IsEmpty() && wxFile::Exists(m_strGlobalFile) ) {
183 wxTextFile fileGlobal(m_strGlobalFile);
184
185 if ( fileGlobal.Open() ) {
186 Parse(fileGlobal, FALSE /* global */);
187 SetRootPath();
188 }
189 else
190 wxLogWarning(_("can't open global configuration file '%s'."),
191 m_strGlobalFile.c_str());
192 }
193
194 // parse the local file
195 if ( !m_strLocalFile.IsEmpty() && wxFile::Exists(m_strLocalFile) ) {
196 wxTextFile fileLocal(m_strLocalFile);
197 if ( fileLocal.Open() ) {
198 Parse(fileLocal, TRUE /* local */);
199 SetRootPath();
200 }
201 else
202 wxLogWarning(_("can't open user configuration file '%s'."),
203 m_strLocalFile.c_str());
204 }
205 }
206
207 wxFileConfig::wxFileConfig(const char *szAppName, bool bLocalOnly)
208 {
209 wxASSERT( !IsEmpty(szAppName) ); // invent a name for your application!
210
211 m_strLocalFile = GetLocalFileName(szAppName);
212 if ( !bLocalOnly )
213 m_strGlobalFile = GetGlobalFileName(szAppName);
214 //else: it's going to be empty and we won't use the global file
215
216 Init();
217 }
218
219 wxFileConfig::wxFileConfig(const wxString& strLocal, const wxString& strGlobal)
220 : m_strLocalFile(strLocal), m_strGlobalFile(strGlobal)
221 {
222 // if the path is not absolute, prepend the standard directory to it
223
224 if ( !strLocal.IsEmpty() && !wxIsPathSeparator(strLocal[0u]) )
225 {
226 m_strLocalFile = GetLocalDir();
227 m_strLocalFile << strLocal;
228 }
229
230 if ( !strGlobal.IsEmpty() && !wxIsPathSeparator(strGlobal[0u]) )
231 {
232 m_strGlobalFile = GetGlobalDir();
233 m_strGlobalFile << strGlobal;
234 }
235 Init();
236 }
237
238 void wxFileConfig::CleanUp()
239 {
240 delete m_pRootGroup;
241
242 LineList *pCur = m_linesHead;
243 while ( pCur != NULL ) {
244 LineList *pNext = pCur->Next();
245 delete pCur;
246 pCur = pNext;
247 }
248 }
249
250 wxFileConfig::~wxFileConfig()
251 {
252 Flush();
253
254 CleanUp();
255 }
256
257 // ----------------------------------------------------------------------------
258 // parse a config file
259 // ----------------------------------------------------------------------------
260
261 void wxFileConfig::Parse(wxTextFile& file, bool bLocal)
262 {
263 const char *pStart;
264 const char *pEnd;
265 wxString strLine;
266
267 uint nLineCount = file.GetLineCount();
268 for ( uint n = 0; n < nLineCount; n++ ) {
269 strLine = file[n];
270
271 // add the line to linked list
272 if ( bLocal )
273 LineListAppend(strLine);
274
275 // skip leading spaces
276 for ( pStart = strLine; isspace(*pStart); pStart++ )
277 ;
278
279 // skip blank/comment lines
280 if ( *pStart == '\0'|| *pStart == ';' || *pStart == '#' )
281 continue;
282
283 if ( *pStart == '[' ) { // a new group
284 pEnd = pStart;
285
286 while ( *++pEnd != ']' ) {
287 if ( !IsValid(*pEnd) && *pEnd != ' ' ) // allow spaces in group names
288 break;
289 }
290
291 if ( *pEnd != ']' ) {
292 wxLogError(_("file '%s': unexpected character %c at line %d."),
293 file.GetName(), *pEnd, n + 1);
294 continue; // skip this line
295 }
296
297 // group name here is always considered as abs path
298 wxString strGroup;
299 pStart++;
300 strGroup << wxCONFIG_PATH_SEPARATOR << wxString(pStart, pEnd - pStart);
301
302 // will create it if doesn't yet exist
303 SetPath(strGroup);
304
305 if ( bLocal )
306 m_pCurrentGroup->SetLine(m_linesTail);
307
308 // check that there is nothing except comments left on this line
309 bool bCont = TRUE;
310 while ( *++pEnd != '\0' && bCont ) {
311 switch ( *pEnd ) {
312 case '#':
313 case ';':
314 bCont = FALSE;
315 break;
316
317 case ' ':
318 case '\t':
319 // ignore whitespace ('\n' impossible here)
320 break;
321
322 default:
323 wxLogWarning(_("file '%s', line %d: '%s' "
324 "ignored after group header."),
325 file.GetName(), n + 1, pEnd);
326 bCont = FALSE;
327 }
328 }
329 }
330 else { // a key
331 const char *pEnd = pStart;
332 while ( IsValid(*pEnd) )
333 pEnd++;
334
335 wxString strKey(pStart, pEnd);
336
337 // skip whitespace
338 while ( isspace(*pEnd) )
339 pEnd++;
340
341 if ( *pEnd++ != '=' ) {
342 wxLogError(_("file '%s', line %d: '=' expected."),
343 file.GetName(), n + 1);
344 }
345 else {
346 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strKey);
347
348 if ( pEntry == NULL ) {
349 // new entry
350 pEntry = m_pCurrentGroup->AddEntry(strKey, n);
351
352 if ( bLocal )
353 pEntry->SetLine(m_linesTail);
354 }
355 else {
356 if ( bLocal && pEntry->IsImmutable() ) {
357 // immutable keys can't be changed by user
358 wxLogWarning(_("file '%s', line %d: value for "
359 "immutable key '%s' ignored."),
360 file.GetName(), n + 1, strKey.c_str());
361 continue;
362 }
363 // the condition below catches the cases (a) and (b) but not (c):
364 // (a) global key found second time in global file
365 // (b) key found second (or more) time in local file
366 // (c) key from global file now found in local one
367 // which is exactly what we want.
368 else if ( !bLocal || pEntry->IsLocal() ) {
369 wxLogWarning(_("file '%s', line %d: key '%s' was first "
370 "found at line %d."),
371 file.GetName(), n + 1, strKey.c_str(), pEntry->Line());
372
373 if ( bLocal )
374 pEntry->SetLine(m_linesTail);
375 }
376 }
377
378 // skip whitespace
379 while ( isspace(*pEnd) )
380 pEnd++;
381
382 pEntry->SetValue(FilterIn(pEnd), FALSE /* read from file */);
383 }
384 }
385 }
386 }
387
388 // ----------------------------------------------------------------------------
389 // set/retrieve path
390 // ----------------------------------------------------------------------------
391
392 void wxFileConfig::SetRootPath()
393 {
394 m_strPath.Empty();
395 m_pCurrentGroup = m_pRootGroup;
396 }
397
398 void wxFileConfig::SetPath(const wxString& strPath)
399 {
400 wxArrayString aParts;
401
402 if ( strPath.IsEmpty() ) {
403 SetRootPath();
404 return;
405 }
406
407 if ( strPath[0] == wxCONFIG_PATH_SEPARATOR ) {
408 // absolute path
409 wxSplitPath(aParts, strPath);
410 }
411 else {
412 // relative path, combine with current one
413 wxString strFullPath = m_strPath;
414 strFullPath << wxCONFIG_PATH_SEPARATOR << strPath;
415 wxSplitPath(aParts, strFullPath);
416 }
417
418 // change current group
419 uint n;
420 m_pCurrentGroup = m_pRootGroup;
421 for ( n = 0; n < aParts.Count(); n++ ) {
422 ConfigGroup *pNextGroup = m_pCurrentGroup->FindSubgroup(aParts[n]);
423 if ( pNextGroup == NULL )
424 pNextGroup = m_pCurrentGroup->AddSubgroup(aParts[n]);
425 m_pCurrentGroup = pNextGroup;
426 }
427
428 // recombine path parts in one variable
429 m_strPath.Empty();
430 for ( n = 0; n < aParts.Count(); n++ ) {
431 m_strPath << wxCONFIG_PATH_SEPARATOR << aParts[n];
432 }
433 }
434
435 // ----------------------------------------------------------------------------
436 // enumeration
437 // ----------------------------------------------------------------------------
438
439 bool wxFileConfig::GetFirstGroup(wxString& str, long& lIndex) const
440 {
441 lIndex = 0;
442 return GetNextGroup(str, lIndex);
443 }
444
445 bool wxFileConfig::GetNextGroup (wxString& str, long& lIndex) const
446 {
447 if ( uint(lIndex) < m_pCurrentGroup->Groups().Count() ) {
448 str = m_pCurrentGroup->Groups()[lIndex++]->Name();
449 return TRUE;
450 }
451 else
452 return FALSE;
453 }
454
455 bool wxFileConfig::GetFirstEntry(wxString& str, long& lIndex) const
456 {
457 lIndex = 0;
458 return GetNextEntry(str, lIndex);
459 }
460
461 bool wxFileConfig::GetNextEntry (wxString& str, long& lIndex) const
462 {
463 if ( uint(lIndex) < m_pCurrentGroup->Entries().Count() ) {
464 str = m_pCurrentGroup->Entries()[lIndex++]->Name();
465 return TRUE;
466 }
467 else
468 return FALSE;
469 }
470
471 uint wxFileConfig::GetNumberOfEntries(bool bRecursive) const
472 {
473 uint n = m_pCurrentGroup->Entries().Count();
474 if ( bRecursive ) {
475 ConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
476 uint nSubgroups = m_pCurrentGroup->Groups().Count();
477 for ( uint nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
478 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
479 n += GetNumberOfEntries(TRUE);
480 CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
481 }
482 }
483
484 return n;
485 }
486
487 uint wxFileConfig::GetNumberOfGroups(bool bRecursive) const
488 {
489 uint n = m_pCurrentGroup->Groups().Count();
490 if ( bRecursive ) {
491 ConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
492 uint nSubgroups = m_pCurrentGroup->Groups().Count();
493 for ( uint nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
494 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
495 n += GetNumberOfGroups(TRUE);
496 CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
497 }
498 }
499
500 return n;
501 }
502
503 // ----------------------------------------------------------------------------
504 // tests for existence
505 // ----------------------------------------------------------------------------
506
507 bool wxFileConfig::HasGroup(const wxString& strName) const
508 {
509 PathChanger path(this, strName);
510
511 ConfigGroup *pGroup = m_pCurrentGroup->FindSubgroup(path.Name());
512 return pGroup != NULL;
513 }
514
515 bool wxFileConfig::HasEntry(const wxString& strName) const
516 {
517 PathChanger path(this, strName);
518
519 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
520 return pEntry != NULL;
521 }
522
523 // ----------------------------------------------------------------------------
524 // read/write values
525 // ----------------------------------------------------------------------------
526
527 bool wxFileConfig::Read(wxString *pstr,
528 const char *szKey,
529 const char *szDefault) const
530 {
531 PathChanger path(this, szKey);
532
533 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
534 if (pEntry == NULL) {
535 *pstr = ExpandEnvVars(szDefault);
536 return FALSE;
537 }
538 else {
539 *pstr = ExpandEnvVars(pEntry->Value());
540 return TRUE;
541 }
542 }
543
544 const char *wxFileConfig::Read(const char *szKey,
545 const char *szDefault) const
546 {
547 static wxString s_str;
548 Read(&s_str, szKey, szDefault);
549
550 return s_str.c_str();
551 }
552
553 bool wxFileConfig::Read(long *pl, const char *szKey, long lDefault) const
554 {
555 wxString str;
556 if ( Read(&str, szKey) ) {
557 *pl = atol(str);
558 return TRUE;
559 }
560 else {
561 *pl = lDefault;
562 return FALSE;
563 }
564 }
565
566 bool wxFileConfig::Write(const char *szKey, const char *szValue)
567 {
568 PathChanger path(this, szKey);
569
570 wxString strName = path.Name();
571 if ( strName.IsEmpty() ) {
572 // setting the value of a group is an error
573 wxASSERT_MSG( IsEmpty(szValue), "can't set value of a group!" );
574
575 // ... except if it's empty in which case it's a way to force it's creation
576 m_pCurrentGroup->SetDirty();
577
578 // this will add a line for this group if it didn't have it before
579 (void)m_pCurrentGroup->GetGroupLine();
580 }
581 else {
582 // writing an entry
583
584 // check that the name is reasonable
585 if ( strName[0u] == wxCONFIG_IMMUTABLE_PREFIX ) {
586 wxLogError(_("Entry name can't start with '%c'."),
587 wxCONFIG_IMMUTABLE_PREFIX);
588 return FALSE;
589 }
590
591 for ( const char *pc = strName; *pc != '\0'; pc++ ) {
592 if ( !IsValid(*pc) ) {
593 wxLogError(_("Character '%c' is invalid in a config entry name."),
594 *pc);
595 return FALSE;
596 }
597 }
598
599 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strName);
600 if ( pEntry == NULL )
601 pEntry = m_pCurrentGroup->AddEntry(strName);
602
603 pEntry->SetValue(szValue);
604 }
605
606 return TRUE;
607 }
608
609 bool wxFileConfig::Write(const char *szKey, long lValue)
610 {
611 // ltoa() is not ANSI :-(
612 char szBuf[40]; // should be good for sizeof(long) <= 16 (128 bits)
613 sprintf(szBuf, "%ld", lValue);
614 return Write(szKey, szBuf);
615 }
616
617 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
618 {
619 if ( LineListIsEmpty() || !m_pRootGroup->IsDirty() )
620 return TRUE;
621
622 wxTempFile file(m_strLocalFile);
623
624 if ( !file.IsOpened() ) {
625 wxLogError(_("can't open user configuration file."));
626 return FALSE;
627 }
628
629 // write all strings to file
630 for ( LineList *p = m_linesHead; p != NULL; p = p->Next() ) {
631 if ( !file.Write(p->Text() + wxTextFile::GetEOL()) ) {
632 wxLogError(_("can't write user configuration file."));
633 return FALSE;
634 }
635 }
636
637 return file.Commit();
638 }
639
640 // ----------------------------------------------------------------------------
641 // delete groups/entries
642 // ----------------------------------------------------------------------------
643
644 bool wxFileConfig::DeleteEntry(const char *szKey, bool bGroupIfEmptyAlso)
645 {
646 PathChanger path(this, szKey);
647
648 if ( !m_pCurrentGroup->DeleteEntry(path.Name()) )
649 return FALSE;
650
651 if ( bGroupIfEmptyAlso && m_pCurrentGroup->IsEmpty() ) {
652 if ( m_pCurrentGroup != m_pRootGroup ) {
653 ConfigGroup *pGroup = m_pCurrentGroup;
654 SetPath(".."); // changes m_pCurrentGroup!
655 m_pCurrentGroup->DeleteSubgroupByName(pGroup->Name());
656 }
657 //else: never delete the root group
658 }
659
660 return TRUE;
661 }
662
663 bool wxFileConfig::DeleteGroup(const char *szKey)
664 {
665 PathChanger path(this, szKey);
666
667 return m_pCurrentGroup->DeleteSubgroupByName(path.Name());
668 }
669
670 bool wxFileConfig::DeleteAll()
671 {
672 CleanUp();
673
674 m_strLocalFile = m_strGlobalFile = "";
675 Init();
676
677 const char *szFile = m_strLocalFile;
678
679 if ( remove(szFile) == -1 )
680 wxLogSysError(_("can't delete user configuration file '%s'"), szFile);
681
682 szFile = m_strGlobalFile;
683 if ( remove(szFile) )
684 wxLogSysError(_("can't delete system configuration file '%s'"), szFile);
685
686 return TRUE;
687 }
688
689 // ----------------------------------------------------------------------------
690 // linked list functions
691 // ----------------------------------------------------------------------------
692
693 // append a new line to the end of the list
694 wxFileConfig::LineList *wxFileConfig::LineListAppend(const wxString& str)
695 {
696 LineList *pLine = new LineList(str);
697
698 if ( m_linesTail == NULL ) {
699 // list is empty
700 m_linesHead = pLine;
701 }
702 else {
703 // adjust pointers
704 m_linesTail->SetNext(pLine);
705 pLine->SetPrev(m_linesTail);
706 }
707
708 m_linesTail = pLine;
709 return m_linesTail;
710 }
711
712 // insert a new line after the given one or in the very beginning if !pLine
713 wxFileConfig::LineList *wxFileConfig::LineListInsert(const wxString& str,
714 LineList *pLine)
715 {
716 if ( pLine == m_linesTail )
717 return LineListAppend(str);
718
719 LineList *pNewLine = new LineList(str);
720 if ( pLine == NULL ) {
721 // prepend to the list
722 pNewLine->SetNext(m_linesHead);
723 m_linesHead->SetPrev(pNewLine);
724 m_linesHead = pNewLine;
725 }
726 else {
727 // insert before pLine
728 LineList *pNext = pLine->Next();
729 pNewLine->SetNext(pNext);
730 pNewLine->SetPrev(pLine);
731 pNext->SetPrev(pNewLine);
732 pLine->SetNext(pNewLine);
733 }
734
735 return pNewLine;
736 }
737
738 void wxFileConfig::LineListRemove(LineList *pLine)
739 {
740 LineList *pPrev = pLine->Prev(),
741 *pNext = pLine->Next();
742
743 // first entry?
744 if ( pPrev == NULL )
745 m_linesHead = pNext;
746 else
747 pPrev->SetNext(pNext);
748
749 // last entry?
750 if ( pNext == NULL )
751 m_linesTail = pPrev;
752 else
753 pNext->SetPrev(pPrev);
754
755 delete pLine;
756 }
757
758 bool wxFileConfig::LineListIsEmpty()
759 {
760 return m_linesHead == NULL;
761 }
762
763 // ============================================================================
764 // wxFileConfig::ConfigGroup
765 // ============================================================================
766
767 // ----------------------------------------------------------------------------
768 // ctor/dtor
769 // ----------------------------------------------------------------------------
770
771 // ctor
772 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup *pParent,
773 const wxString& strName,
774 wxFileConfig *pConfig)
775 : m_aEntries(CompareEntries),
776 m_aSubgroups(CompareGroups),
777 m_strName(strName)
778 {
779 m_pConfig = pConfig;
780 m_pParent = pParent;
781 m_bDirty = FALSE;
782 m_pLine = NULL;
783
784 m_pLastEntry = NULL;
785 m_pLastGroup = NULL;
786 }
787
788 // dtor deletes all children
789 wxFileConfig::ConfigGroup::~ConfigGroup()
790 {
791 // entries
792 uint n, nCount = m_aEntries.Count();
793 for ( n = 0; n < nCount; n++ )
794 delete m_aEntries[n];
795
796 // subgroups
797 nCount = m_aSubgroups.Count();
798 for ( n = 0; n < nCount; n++ )
799 delete m_aSubgroups[n];
800 }
801
802 // ----------------------------------------------------------------------------
803 // line
804 // ----------------------------------------------------------------------------
805
806 void wxFileConfig::ConfigGroup::SetLine(LineList *pLine)
807 {
808 wxASSERT( m_pLine == NULL ); // shouldn't be called twice
809
810 m_pLine = pLine;
811 }
812
813 /*
814 This is a bit complicated, so let me explain it in details. All lines that
815 were read from the local file (the only one we will ever modify) are stored
816 in a (doubly) linked list. Our problem is to know at which position in this
817 list should we insert the new entries/subgroups. To solve it we keep three
818 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
819
820 m_pLine points to the line containing "[group_name]"
821 m_pLastEntry points to the last entry of this group in the local file.
822 m_pLastGroup subgroup
823
824 Initially, they're NULL all three. When the group (an entry/subgroup) is read
825 from the local file, the corresponding variable is set. However, if the group
826 was read from the global file and then modified or created by the application
827 these variables are still NULL and we need to create the corresponding lines.
828 See the following functions (and comments preceding them) for the details of
829 how we do it.
830
831 Also, when our last entry/group are deleted we need to find the new last
832 element - the code in DeleteEntry/Subgroup does this by backtracking the list
833 of lines until it either founds an entry/subgroup (and this is the new last
834 element) or the m_pLine of the group, in which case there are no more entries
835 (or subgroups) left and m_pLast<element> becomes NULL.
836
837 NB: This last problem could be avoided for entries if we added new entries
838 immediately after m_pLine, but in this case the entries would appear
839 backwards in the config file (OTOH, it's not that important) and as we
840 would still need to do it for the subgroups the code wouldn't have been
841 significantly less complicated.
842 */
843
844 // Return the line which contains "[our name]". If we're still not in the list,
845 // add our line to it immediately after the last line of our parent group if we
846 // have it or in the very beginning if we're the root group.
847 wxFileConfig::LineList *wxFileConfig::ConfigGroup::GetGroupLine()
848 {
849 if ( m_pLine == NULL ) {
850 ConfigGroup *pParent = Parent();
851
852 // this group wasn't present in local config file, add it now
853 if ( pParent != NULL ) {
854 wxString strFullName;
855 strFullName << "[" << GetFullName().c_str() + 1 << "]"; // +1: no '/'
856 m_pLine = m_pConfig->LineListInsert(strFullName,
857 pParent->GetLastGroupLine());
858 pParent->SetLastGroup(this); // we're surely after all the others
859 }
860 else {
861 // we return NULL, so that LineListInsert() will insert us in the
862 // very beginning
863 }
864 }
865
866 return m_pLine;
867 }
868
869 // Return the last line belonging to the subgroups of this group (after which
870 // we can add a new subgroup), if we don't have any subgroups or entries our
871 // last line is the group line (m_pLine) itself.
872 wxFileConfig::LineList *wxFileConfig::ConfigGroup::GetLastGroupLine()
873 {
874 // if we have any subgroups, our last line is the last line of the last
875 // subgroup
876 if ( m_pLastGroup != NULL ) {
877 wxFileConfig::LineList *pLine = m_pLastGroup->GetLastGroupLine();
878
879 wxASSERT( pLine != NULL ); // last group must have !NULL associated line
880 return pLine;
881 }
882
883 // no subgroups, so the last line is the line of thelast entry (if any)
884 return GetLastEntryLine();
885 }
886
887 // return the last line belonging to the entries of this group (after which
888 // we can add a new entry), if we don't have any entries we will add the new
889 // one immediately after the group line itself.
890 wxFileConfig::LineList *wxFileConfig::ConfigGroup::GetLastEntryLine()
891 {
892 if ( m_pLastEntry != NULL ) {
893 wxFileConfig::LineList *pLine = m_pLastEntry->GetLine();
894
895 wxASSERT( pLine != NULL ); // last entry must have !NULL associated line
896 return pLine;
897 }
898
899 // no entries: insert after the group header
900 return GetGroupLine();
901 }
902
903 // ----------------------------------------------------------------------------
904 // group name
905 // ----------------------------------------------------------------------------
906
907 wxString wxFileConfig::ConfigGroup::GetFullName() const
908 {
909 if ( Parent() )
910 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR + Name();
911 else
912 return "";
913 }
914
915 // ----------------------------------------------------------------------------
916 // find an item
917 // ----------------------------------------------------------------------------
918
919 // use binary search because the array is sorted
920 wxFileConfig::ConfigEntry *
921 wxFileConfig::ConfigGroup::FindEntry(const char *szName) const
922 {
923 uint i,
924 lo = 0,
925 hi = m_aEntries.Count();
926 int res;
927 wxFileConfig::ConfigEntry *pEntry;
928
929 while ( lo < hi ) {
930 i = (lo + hi)/2;
931 pEntry = m_aEntries[i];
932
933 #if wxCONFIG_CASE_SENSITIVE
934 res = strcmp(pEntry->Name(), szName);
935 #else
936 res = Stricmp(pEntry->Name(), szName);
937 #endif
938
939 if ( res > 0 )
940 hi = i;
941 else if ( res < 0 )
942 lo = i + 1;
943 else
944 return pEntry;
945 }
946
947 return NULL;
948 }
949
950 wxFileConfig::ConfigGroup *
951 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName) const
952 {
953 uint i,
954 lo = 0,
955 hi = m_aSubgroups.Count();
956 int res;
957 wxFileConfig::ConfigGroup *pGroup;
958
959 while ( lo < hi ) {
960 i = (lo + hi)/2;
961 pGroup = m_aSubgroups[i];
962
963 #if wxCONFIG_CASE_SENSITIVE
964 res = strcmp(pGroup->Name(), szName);
965 #else
966 res = Stricmp(pGroup->Name(), szName);
967 #endif
968
969 if ( res > 0 )
970 hi = i;
971 else if ( res < 0 )
972 lo = i + 1;
973 else
974 return pGroup;
975 }
976
977 return NULL;
978 }
979
980 // ----------------------------------------------------------------------------
981 // create a new item
982 // ----------------------------------------------------------------------------
983
984 // create a new entry and add it to the current group
985 wxFileConfig::ConfigEntry *
986 wxFileConfig::ConfigGroup::AddEntry(const wxString& strName, int nLine)
987 {
988 wxASSERT( FindEntry(strName) == NULL );
989
990 ConfigEntry *pEntry = new ConfigEntry(this, strName, nLine);
991 m_aEntries.Add(pEntry);
992
993 return pEntry;
994 }
995
996 // create a new group and add it to the current group
997 wxFileConfig::ConfigGroup *
998 wxFileConfig::ConfigGroup::AddSubgroup(const wxString& strName)
999 {
1000 wxASSERT( FindSubgroup(strName) == NULL );
1001
1002 ConfigGroup *pGroup = new ConfigGroup(this, strName, m_pConfig);
1003 m_aSubgroups.Add(pGroup);
1004
1005 return pGroup;
1006 }
1007
1008 // ----------------------------------------------------------------------------
1009 // delete an item
1010 // ----------------------------------------------------------------------------
1011
1012 /*
1013 The delete operations are _very_ slow if we delete the last item of this
1014 group (see comments before GetXXXLineXXX functions for more details),
1015 so it's much better to start with the first entry/group if we want to
1016 delete several of them.
1017 */
1018
1019 bool wxFileConfig::ConfigGroup::DeleteSubgroupByName(const char *szName)
1020 {
1021 return DeleteSubgroup(FindSubgroup(szName));
1022 }
1023
1024 // doesn't delete the subgroup itself, but does remove references to it from
1025 // all other data structures (and normally the returned pointer should be
1026 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1027 bool wxFileConfig::ConfigGroup::DeleteSubgroup(ConfigGroup *pGroup)
1028 {
1029 wxCHECK( pGroup != NULL, FALSE ); // deleting non existing group?
1030
1031 // delete all entries
1032 uint nCount = pGroup->m_aEntries.Count();
1033 for ( uint nEntry = 0; nEntry < nCount; nEntry++ ) {
1034 LineList *pLine = pGroup->m_aEntries[nEntry]->GetLine();
1035 if ( pLine != NULL )
1036 m_pConfig->LineListRemove(pLine);
1037 }
1038
1039 // and subgroups of this sungroup
1040 nCount = pGroup->m_aSubgroups.Count();
1041 for ( uint nGroup = 0; nGroup < nCount; nGroup++ ) {
1042 pGroup->DeleteSubgroup(pGroup->m_aSubgroups[nGroup]);
1043 }
1044
1045 LineList *pLine = pGroup->m_pLine;
1046 if ( pLine != NULL ) {
1047 // notice that we may do this test inside the previous "if" because the
1048 // last entry's line is surely !NULL
1049 if ( pGroup == m_pLastGroup ) {
1050 // our last entry is being deleted - find the last one which stays
1051 wxASSERT( m_pLine != NULL ); // we have a subgroup with !NULL pLine...
1052
1053 // go back until we find a subgroup or reach the group's line
1054 ConfigGroup *pNewLast = NULL;
1055 uint n, nSubgroups = m_aSubgroups.Count();
1056 LineList *pl;
1057 for ( pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() ) {
1058 // is it our subgroup?
1059 for ( n = 0; (pNewLast == NULL) && (n < nSubgroups); n++ ) {
1060 // do _not_ call GetGroupLine! we don't want to add it to the local
1061 // file if it's not already there
1062 if ( m_aSubgroups[n]->m_pLine == m_pLine )
1063 pNewLast = m_aSubgroups[n];
1064 }
1065
1066 if ( pNewLast != NULL ) // found?
1067 break;
1068 }
1069
1070 if ( pl == m_pLine ) {
1071 wxASSERT( !pNewLast ); // how comes it has the same line as we?
1072
1073 // we've reached the group line without finding any subgroups
1074 m_pLastGroup = NULL;
1075 }
1076 else
1077 m_pLastGroup = pNewLast;
1078 }
1079
1080 m_pConfig->LineListRemove(pLine);
1081 }
1082
1083 SetDirty();
1084
1085 m_aSubgroups.Remove(pGroup);
1086 delete pGroup;
1087
1088 return TRUE;
1089 }
1090
1091 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName)
1092 {
1093 ConfigEntry *pEntry = FindEntry(szName);
1094 wxCHECK( pEntry != NULL, FALSE ); // deleting non existing item?
1095
1096 LineList *pLine = pEntry->GetLine();
1097 if ( pLine != NULL ) {
1098 // notice that we may do this test inside the previous "if" because the
1099 // last entry's line is surely !NULL
1100 if ( pEntry == m_pLastEntry ) {
1101 // our last entry is being deleted - find the last one which stays
1102 wxASSERT( m_pLine != NULL ); // if we have an entry with !NULL pLine...
1103
1104 // go back until we find another entry or reach the group's line
1105 ConfigEntry *pNewLast = NULL;
1106 uint n, nEntries = m_aEntries.Count();
1107 LineList *pl;
1108 for ( pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() ) {
1109 // is it our subgroup?
1110 for ( n = 0; (pNewLast == NULL) && (n < nEntries); n++ ) {
1111 if ( m_aEntries[n]->GetLine() == m_pLine )
1112 pNewLast = m_aEntries[n];
1113 }
1114
1115 if ( pNewLast != NULL ) // found?
1116 break;
1117 }
1118
1119 if ( pl == m_pLine ) {
1120 wxASSERT( !pNewLast ); // how comes it has the same line as we?
1121
1122 // we've reached the group line without finding any subgroups
1123 m_pLastEntry = NULL;
1124 }
1125 else
1126 m_pLastEntry = pNewLast;
1127 }
1128
1129 m_pConfig->LineListRemove(pLine);
1130 }
1131
1132 // we must be written back for the changes to be saved
1133 SetDirty();
1134
1135 m_aEntries.Remove(pEntry);
1136 delete pEntry;
1137
1138 return TRUE;
1139 }
1140
1141 // ----------------------------------------------------------------------------
1142 //
1143 // ----------------------------------------------------------------------------
1144 void wxFileConfig::ConfigGroup::SetDirty()
1145 {
1146 m_bDirty = TRUE;
1147 if ( Parent() != NULL ) // propagate upwards
1148 Parent()->SetDirty();
1149 }
1150
1151 // ============================================================================
1152 // wxFileConfig::ConfigEntry
1153 // ============================================================================
1154
1155 // ----------------------------------------------------------------------------
1156 // ctor
1157 // ----------------------------------------------------------------------------
1158 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup *pParent,
1159 const wxString& strName,
1160 int nLine)
1161 : m_strName(strName)
1162 {
1163 wxASSERT( !strName.IsEmpty() );
1164
1165 m_pParent = pParent;
1166 m_nLine = nLine;
1167 m_pLine = NULL;
1168
1169 m_bDirty = FALSE;
1170
1171 m_bImmutable = strName[0] == wxCONFIG_IMMUTABLE_PREFIX;
1172 if ( m_bImmutable )
1173 m_strName.erase(0, 1); // remove first character
1174 }
1175
1176 // ----------------------------------------------------------------------------
1177 // set value
1178 // ----------------------------------------------------------------------------
1179
1180 void wxFileConfig::ConfigEntry::SetLine(LineList *pLine)
1181 {
1182 if ( m_pLine != NULL ) {
1183 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1184 Name().c_str(), m_pParent->GetFullName().c_str());
1185 }
1186
1187 m_pLine = pLine;
1188 Group()->SetLastEntry(this);
1189 }
1190
1191 // second parameter is FALSE if we read the value from file and prevents the
1192 // entry from being marked as 'dirty'
1193 void wxFileConfig::ConfigEntry::SetValue(const wxString& strValue, bool bUser)
1194 {
1195 if ( bUser && IsImmutable() ) {
1196 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1197 Name().c_str());
1198 return;
1199 }
1200
1201 // do nothing if it's the same value
1202 if ( strValue == m_strValue )
1203 return;
1204
1205 m_strValue = strValue;
1206
1207 if ( bUser ) {
1208 wxString strVal = FilterOut(strValue);
1209 wxString strLine;
1210 strLine << m_strName << " = " << strVal;
1211
1212 if ( m_pLine != NULL ) {
1213 // entry was read from the local config file, just modify the line
1214 m_pLine->SetText(strLine);
1215 }
1216 else {
1217 // add a new line to the file
1218 wxASSERT( m_nLine == NOT_FOUND ); // consistency check
1219
1220 m_pLine = Group()->Config()->LineListInsert(strLine,
1221 Group()->GetLastEntryLine());
1222 Group()->SetLastEntry(this);
1223 }
1224
1225 SetDirty();
1226 }
1227 }
1228
1229 void wxFileConfig::ConfigEntry::SetDirty()
1230 {
1231 m_bDirty = TRUE;
1232 Group()->SetDirty();
1233 }
1234
1235 // ============================================================================
1236 // global functions
1237 // ============================================================================
1238
1239 // ----------------------------------------------------------------------------
1240 // compare functions for array sorting
1241 // ----------------------------------------------------------------------------
1242
1243 int CompareEntries(wxFileConfig::ConfigEntry *p1,
1244 wxFileConfig::ConfigEntry *p2)
1245 {
1246 #if wxCONFIG_CASE_SENSITIVE
1247 return strcmp(p1->Name(), p2->Name());
1248 #else
1249 return Stricmp(p1->Name(), p2->Name());
1250 #endif
1251 }
1252
1253 int CompareGroups(wxFileConfig::ConfigGroup *p1,
1254 wxFileConfig::ConfigGroup *p2)
1255 {
1256 #if wxCONFIG_CASE_SENSITIVE
1257 return strcmp(p1->Name(), p2->Name());
1258 #else
1259 return Stricmp(p1->Name(), p2->Name());
1260 #endif
1261 }
1262
1263 // ----------------------------------------------------------------------------
1264 // filter functions
1265 // ----------------------------------------------------------------------------
1266
1267 // undo FilterOut
1268 wxString FilterIn(const wxString& str)
1269 {
1270 wxString strResult;
1271 strResult.Alloc(str.Len());
1272
1273 bool bQuoted = !str.IsEmpty() && str[0] == '"';
1274
1275 for ( uint n = bQuoted ? 1 : 0; n < str.Len(); n++ ) {
1276 if ( str[n] == '\\' ) {
1277 switch ( str[++n] ) {
1278 case 'n':
1279 strResult += '\n';
1280 break;
1281
1282 case 'r':
1283 strResult += '\r';
1284 break;
1285
1286 case 't':
1287 strResult += '\t';
1288 break;
1289
1290 case '\\':
1291 strResult += '\\';
1292 break;
1293
1294 case '"':
1295 strResult += '"';
1296 break;
1297 }
1298 }
1299 else {
1300 if ( str[n] != '"' || !bQuoted )
1301 strResult += str[n];
1302 else if ( n != str.Len() - 1 ) {
1303 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1304 n, str.c_str());
1305 }
1306 //else: it's the last quote of a quoted string, ok
1307 }
1308 }
1309
1310 return strResult;
1311 }
1312
1313 // quote the string before writing it to file
1314 wxString FilterOut(const wxString& str)
1315 {
1316 wxString strResult;
1317 strResult.Alloc(str.Len());
1318
1319 // quoting is necessary to preserve spaces in the beginning of the string
1320 bool bQuote = isspace(str[0]) || str[0] == '"';
1321
1322 if ( bQuote )
1323 strResult += '"';
1324
1325 char c;
1326 for ( uint n = 0; n < str.Len(); n++ ) {
1327 switch ( str[n] ) {
1328 case '\n':
1329 c = 'n';
1330 break;
1331
1332 case '\r':
1333 c = 'r';
1334 break;
1335
1336 case '\t':
1337 c = 't';
1338 break;
1339
1340 case '\\':
1341 c = '\\';
1342 break;
1343
1344 case '"':
1345 if ( bQuote )
1346 c = '"';
1347 //else: fall through
1348
1349 default:
1350 strResult += str[n];
1351 continue; // nothing special to do
1352 }
1353
1354 // we get here only for special characters
1355 strResult << '\\' << c;
1356 }
1357
1358 if ( bQuote )
1359 strResult += '"';
1360
1361 return strResult;
1362 }