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