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