]> git.saurik.com Git - wxWidgets.git/blob - src/common/fileconf.cpp
wxFrame: OnSize() has a SEGV if wxFrame has no child. Fixed.
[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 // global functions declarations
53 // ----------------------------------------------------------------------------
54
55 // is 'c' a valid character in group name?
56 // NB: APPCONF_IMMUTABLE_PREFIX and APPCONF_PATH_SEPARATOR must be valid chars,
57 // but _not_ ']' (group name delimiter)
58 inline bool IsValid(char c) { return isalnum(c) || strchr("@_/-!.*%", c); }
59
60 // compare functions for sorting the arrays
61 static int CompareEntries(wxFileConfig::ConfigEntry *p1,
62 wxFileConfig::ConfigEntry *p2);
63 static int CompareGroups(wxFileConfig::ConfigGroup *p1,
64 wxFileConfig::ConfigGroup *p2);
65
66 // filter strings
67 static wxString FilterIn(const wxString& str);
68 static wxString FilterOut(const wxString& str);
69
70 // ============================================================================
71 // implementation
72 // ============================================================================
73
74 // ----------------------------------------------------------------------------
75 // static functions
76 // ----------------------------------------------------------------------------
77 wxString wxFileConfig::GetGlobalFileName(const char *szFile)
78 {
79 wxString str;
80
81 bool bNoExt = strchr(szFile, '.') == NULL;
82
83 #ifdef __UNIX__
84 str << "/etc/" << szFile;
85 if ( bNoExt )
86 str << ".conf";
87 #else // Windows
88 #ifndef _MAX_PATH
89 #define _MAX_PATH 512
90 #endif
91
92 char szWinDir[_MAX_PATH];
93 ::GetWindowsDirectory(szWinDir, _MAX_PATH);
94 str << szWinDir << "\\" << szFile;
95 if ( bNoExt )
96 str << ".ini";
97 #endif // UNIX/Win
98
99 return str;
100 }
101
102 wxString wxFileConfig::GetLocalFileName(const char *szFile)
103 {
104 wxString str;
105
106 #ifdef __UNIX__
107 const char *szHome = getenv("HOME");
108 if ( szHome == NULL ) {
109 // we're homeless...
110 wxLogWarning(_("can't find user's HOME, using current directory."));
111 szHome = ".";
112 }
113 str << szHome << "/." << szFile;
114 #else // Windows
115 #ifdef __WIN32__
116 const char *szHome = getenv("HOMEDRIVE");
117 if ( szHome != NULL )
118 str << szHome;
119 szHome = getenv("HOMEPATH");
120 if ( szHome != NULL )
121 str << szHome;
122 str << szFile;
123 if ( strchr(szFile, '.') == NULL )
124 str << ".ini";
125 #else // Win16
126 // Win16 has no idea about home, so use the current directory instead
127 str << ".\\" << szFile;
128 #endif // WIN16/32
129 #endif // UNIX/Win
130
131 return str;
132 }
133
134 // ----------------------------------------------------------------------------
135 // ctor
136 // ----------------------------------------------------------------------------
137
138 void wxFileConfig::Init()
139 {
140 m_pCurrentGroup =
141 m_pRootGroup = new ConfigGroup(NULL, "", this);
142
143 m_linesHead =
144 m_linesTail = NULL;
145
146 m_bExpandEnvVars = TRUE;
147
148 m_strPath.Empty();
149 }
150
151 wxFileConfig::wxFileConfig(const wxString& strLocal, const wxString& strGlobal)
152 : m_strLocalFile(strLocal), m_strGlobalFile(strGlobal)
153 {
154 Init();
155
156 // it's not an error if (one of the) file(s) doesn't exist
157
158 // parse the global file
159 if ( !strGlobal.IsEmpty() ) {
160 if ( wxFile::Exists(strGlobal) ) {
161 wxTextFile fileGlobal(strGlobal);
162
163 if ( fileGlobal.Open() ) {
164 Parse(fileGlobal, FALSE /* global */);
165 SetRootPath();
166 }
167 else
168 wxLogWarning(_("can't open global configuration file '%s'."),
169 strGlobal.c_str());
170 }
171 }
172
173 // parse the local file
174 if ( wxFile::Exists(strLocal) ) {
175 wxTextFile fileLocal(strLocal);
176 if ( fileLocal.Open() ) {
177 Parse(fileLocal, TRUE /* local */);
178 SetRootPath();
179 }
180 else
181 wxLogWarning(_("can't open user configuration file '%s'."),
182 strLocal.c_str());
183 }
184 }
185
186 wxFileConfig::~wxFileConfig()
187 {
188 Flush();
189 delete m_pRootGroup;
190
191 LineList *pCur = m_linesHead;
192 while ( pCur != NULL ) {
193 LineList *pNext = pCur->Next();
194 delete pCur;
195 pCur = pNext;
196 }
197 }
198
199 // ----------------------------------------------------------------------------
200 // parse a config file
201 // ----------------------------------------------------------------------------
202
203 void wxFileConfig::Parse(wxTextFile& file, bool bLocal)
204 {
205 const char *pStart;
206 const char *pEnd;
207
208 for ( uint n = 0; n < file.GetLineCount(); n++ ) {
209 // add the line to linked list
210 if ( bLocal )
211 LineListAppend(file[n]);
212
213 // skip leading spaces
214 for ( pStart = file[n]; isspace(*pStart); pStart++ )
215 ;
216
217 // skip blank/comment lines
218 if ( *pStart == '\0'|| *pStart == ';' || *pStart == '#' )
219 continue;
220
221 if ( *pStart == '[' ) { // a new group
222 pEnd = pStart;
223
224 while ( *++pEnd != ']' ) {
225 if ( !IsValid(*pEnd) && *pEnd != ' ' ) // allow spaces in group names
226 break;
227 }
228
229 if ( *pEnd != ']' ) {
230 wxLogError(_("file '%s': unexpected character %c at line %d."),
231 file.GetName(), *pEnd, n + 1);
232 continue; // skip this line
233 }
234
235 // group name here is always considered as abs path
236 wxString strGroup;
237 pStart++;
238 strGroup << APPCONF_PATH_SEPARATOR << wxString(pStart, pEnd - pStart);
239
240 // will create it if doesn't yet exist
241 SetPath(strGroup);
242
243 if ( bLocal )
244 m_pCurrentGroup->SetLine(m_linesTail);
245
246 // check that there is nothing except comments left on this line
247 bool bCont = TRUE;
248 while ( *++pEnd != '\0' && bCont ) {
249 switch ( *pEnd ) {
250 case '#':
251 case ';':
252 bCont = FALSE;
253 break;
254
255 case ' ':
256 case '\t':
257 // ignore whitespace ('\n' impossible here)
258 break;
259
260 default:
261 wxLogWarning(_("file '%s', line %d: '%s' "
262 "ignored after group header."),
263 file.GetName(), n + 1, pEnd);
264 bCont = FALSE;
265 }
266 }
267 }
268 else { // a key
269 const char *pEnd = pStart;
270 while ( IsValid(*pEnd) )
271 pEnd++;
272
273 wxString strKey(pStart, pEnd);
274
275 // skip whitespace
276 while ( isspace(*pEnd) )
277 pEnd++;
278
279 if ( *pEnd++ != '=' ) {
280 wxLogError(_("file '%s', line %d: '=' expected."),
281 file.GetName(), n + 1);
282 }
283 else {
284 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strKey);
285
286 if ( pEntry == NULL ) {
287 // new entry
288 pEntry = m_pCurrentGroup->AddEntry(strKey, n);
289
290 if ( bLocal )
291 pEntry->SetLine(m_linesTail);
292 }
293 else {
294 if ( bLocal && pEntry->IsImmutable() ) {
295 // immutable keys can't be changed by user
296 wxLogWarning(_("file '%s', line %d: value for "
297 "immutable key '%s' ignored."),
298 file.GetName(), n + 1, strKey.c_str());
299 continue;
300 }
301 // the condition below catches the cases (a) and (b) but not (c):
302 // (a) global key found second time in global file
303 // (b) key found second (or more) time in local file
304 // (c) key from global file now found in local one
305 // which is exactly what we want.
306 else if ( !bLocal || pEntry->IsLocal() ) {
307 wxLogWarning(_("file '%s', line %d: key '%s' was first "
308 "found at line %d."),
309 file.GetName(), n + 1, strKey.c_str(), pEntry->Line());
310
311 if ( bLocal )
312 pEntry->SetLine(m_linesTail);
313 }
314 }
315
316 // skip whitespace
317 while ( isspace(*pEnd) )
318 pEnd++;
319
320 wxString strValue;
321 if (m_bExpandEnvVars)
322 strValue = ExpandEnvVars(FilterIn(pEnd));
323 else
324 strValue = FilterIn(pEnd);
325 pEntry->SetValue(strValue, FALSE);
326 }
327 }
328 }
329 }
330
331 // ----------------------------------------------------------------------------
332 // set/retrieve path
333 // ----------------------------------------------------------------------------
334
335 void wxFileConfig::SetRootPath()
336 {
337 m_strPath.Empty();
338 m_pCurrentGroup = m_pRootGroup;
339 }
340
341 void wxFileConfig::SetPath(const wxString& strPath)
342 {
343 wxArrayString aParts;
344
345 if ( strPath.IsEmpty() ) {
346 SetRootPath();
347 return;
348 }
349
350 if ( strPath[0] == APPCONF_PATH_SEPARATOR ) {
351 // absolute path
352 SplitPath(aParts, strPath);
353 }
354 else {
355 // relative path, combine with current one
356 wxString strFullPath = m_strPath;
357 strFullPath << APPCONF_PATH_SEPARATOR << strPath;
358 SplitPath(aParts, strFullPath);
359 }
360
361 // change current group
362 uint n;
363 m_pCurrentGroup = m_pRootGroup;
364 for ( n = 0; n < aParts.Count(); n++ ) {
365 ConfigGroup *pNextGroup = m_pCurrentGroup->FindSubgroup(aParts[n]);
366 if ( pNextGroup == NULL )
367 pNextGroup = m_pCurrentGroup->AddSubgroup(aParts[n]);
368 m_pCurrentGroup = pNextGroup;
369 }
370
371 // recombine path parts in one variable
372 m_strPath.Empty();
373 for ( n = 0; n < aParts.Count(); n++ ) {
374 m_strPath << APPCONF_PATH_SEPARATOR << aParts[n];
375 }
376 }
377
378 // ----------------------------------------------------------------------------
379 // enumeration
380 // ----------------------------------------------------------------------------
381
382 bool wxFileConfig::GetFirstGroup(wxString& str, long& lIndex)
383 {
384 lIndex = 0;
385 return GetNextGroup(str, lIndex);
386 }
387
388 bool wxFileConfig::GetNextGroup (wxString& str, long& lIndex)
389 {
390 if ( uint(lIndex) < m_pCurrentGroup->Groups().Count() ) {
391 str = m_pCurrentGroup->Groups()[lIndex++]->Name();
392 return TRUE;
393 }
394 else
395 return FALSE;
396 }
397
398 bool wxFileConfig::GetFirstEntry(wxString& str, long& lIndex)
399 {
400 lIndex = 0;
401 return GetNextEntry(str, lIndex);
402 }
403
404 bool wxFileConfig::GetNextEntry (wxString& str, long& lIndex)
405 {
406 if ( uint(lIndex) < m_pCurrentGroup->Entries().Count() ) {
407 str = m_pCurrentGroup->Entries()[lIndex++]->Name();
408 return TRUE;
409 }
410 else
411 return FALSE;
412 }
413
414 // ----------------------------------------------------------------------------
415 // tests for existence
416 // ----------------------------------------------------------------------------
417
418 bool wxFileConfig::HasGroup(const wxString& strName) const
419 {
420 PathChanger path(this, strName);
421
422 ConfigGroup *pGroup = m_pCurrentGroup->FindSubgroup(path.Name());
423 return pGroup != NULL;
424 }
425
426 bool wxFileConfig::HasEntry(const wxString& strName) const
427 {
428 PathChanger path(this, strName);
429
430 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
431 return pEntry != NULL;
432 }
433
434 // ----------------------------------------------------------------------------
435 // read/write values
436 // ----------------------------------------------------------------------------
437
438 const char *wxFileConfig::Read(const char *szKey,
439 const char *szDefault) const
440 {
441 PathChanger path(this, szKey);
442
443 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
444 return pEntry == NULL ? szDefault : pEntry->Value().c_str();
445 }
446
447 bool wxFileConfig::Read(wxString *pstr,
448 const char *szKey,
449 const char *szDefault) const
450 {
451 PathChanger path(this, szKey);
452
453 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
454 if (pEntry == NULL) {
455 *pstr = szDefault;
456 return FALSE;
457 }
458 else {
459 *pstr = pEntry->Value();
460 return TRUE;
461 }
462 }
463
464 bool wxFileConfig::Read(long *pl, const char *szKey, long lDefault) const
465 {
466 wxString str;
467 if ( Read(&str, szKey) ) {
468 *pl = atol(str);
469 return TRUE;
470 }
471 else {
472 *pl = lDefault;
473 return FALSE;
474 }
475 }
476
477 bool wxFileConfig::Write(const char *szKey, const char *szValue)
478 {
479 PathChanger path(this, szKey);
480
481 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
482 if ( pEntry == NULL )
483 pEntry = m_pCurrentGroup->AddEntry(path.Name());
484 pEntry->SetValue(szValue);
485
486 return TRUE;
487 }
488
489 bool wxFileConfig::Write(const char *szKey, long lValue)
490 {
491 // ltoa() is not ANSI :-(
492 char szBuf[40]; // should be good for sizeof(long) <= 16 (128 bits)
493 sprintf(szBuf, "%ld", lValue);
494 return Write(szKey, szBuf);
495 }
496
497 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
498 {
499 if ( LineListIsEmpty() || !m_pRootGroup->IsDirty() )
500 return TRUE;
501
502 wxTempFile file(m_strLocalFile);
503
504 if ( !file.IsOpened() ) {
505 wxLogError(_("can't open user configuration file."));
506 return FALSE;
507 }
508
509 // write all strings to file
510 for ( LineList *p = m_linesHead; p != NULL; p = p->Next() ) {
511 if ( !file.Write(p->Text() + wxTextFile::GetEOL()) ) {
512 wxLogError(_("can't write user configuration file."));
513 return FALSE;
514 }
515 }
516
517 return file.Commit();
518 }
519
520 // ----------------------------------------------------------------------------
521 // delete groups/entries
522 // ----------------------------------------------------------------------------
523
524 bool wxFileConfig::DeleteEntry(const char *szKey, bool bGroupIfEmptyAlso)
525 {
526 PathChanger path(this, szKey);
527
528 if ( !m_pCurrentGroup->DeleteEntry(path.Name()) )
529 return FALSE;
530
531 if ( bGroupIfEmptyAlso && m_pCurrentGroup->IsEmpty() ) {
532 if ( m_pCurrentGroup != m_pRootGroup ) {
533 ConfigGroup *pGroup = m_pCurrentGroup;
534 SetPath(".."); // changes m_pCurrentGroup!
535 m_pCurrentGroup->DeleteSubgroup(pGroup->Name());
536 }
537 //else: never delete the root group
538 }
539
540 return TRUE;
541 }
542
543 bool wxFileConfig::DeleteGroup(const char *szKey)
544 {
545 PathChanger path(this, szKey);
546
547 return m_pCurrentGroup->DeleteSubgroup(path.Name());
548 }
549
550 bool wxFileConfig::DeleteAll()
551 {
552 const char *szFile = m_strLocalFile;
553 delete m_pRootGroup;
554 Init();
555
556 if ( remove(szFile) == -1 )
557 wxLogSysError(_("can't delete user configuration file '%s'"), szFile);
558
559 szFile = m_strGlobalFile;
560 if ( remove(szFile) )
561 wxLogSysError(_("can't delete system configuration file '%s'"), szFile);
562
563 return TRUE;
564 }
565
566 // ----------------------------------------------------------------------------
567 // linked list functions
568 // ----------------------------------------------------------------------------
569
570 // append a new line to the end of the list
571 wxFileConfig::LineList *wxFileConfig::LineListAppend(const wxString& str)
572 {
573 LineList *pLine = new LineList(str);
574
575 if ( m_linesTail == NULL ) {
576 // list is empty
577 m_linesHead = pLine;
578 }
579 else {
580 // adjust pointers
581 m_linesTail->SetNext(pLine);
582 pLine->SetPrev(m_linesTail);
583 }
584
585 m_linesTail = pLine;
586 return m_linesTail;
587 }
588
589 // insert a new line after the given one or in the very beginning if !pLine
590 wxFileConfig::LineList *wxFileConfig::LineListInsert(const wxString& str,
591 LineList *pLine)
592 {
593 if ( pLine == m_linesTail )
594 return LineListAppend(str);
595
596 LineList *pNewLine = new LineList(str);
597 if ( pLine == NULL ) {
598 // prepend to the list
599 pNewLine->SetNext(m_linesHead);
600 m_linesHead->SetPrev(pNewLine);
601 m_linesHead = pNewLine;
602 }
603 else {
604 // insert before pLine
605 LineList *pNext = pLine->Next();
606 pNewLine->SetNext(pNext);
607 pNewLine->SetPrev(pLine);
608 pNext->SetPrev(pNewLine);
609 pLine->SetNext(pNewLine);
610 }
611
612 return pNewLine;
613 }
614
615 void wxFileConfig::LineListRemove(LineList *pLine)
616 {
617 LineList *pPrev = pLine->Prev(),
618 *pNext = pLine->Next();
619 if ( pPrev == NULL ) {
620 // deleting the first entry
621 m_linesHead = pNext;
622 }
623 else {
624 // not the first entry
625 pPrev->SetNext(pNext);
626 }
627
628 pNext->SetPrev(pPrev);
629
630 delete pLine;
631 }
632
633 bool wxFileConfig::LineListIsEmpty()
634 {
635 return m_linesHead == NULL;
636 }
637
638 // ============================================================================
639 // wxFileConfig::ConfigGroup
640 // ============================================================================
641
642 // ----------------------------------------------------------------------------
643 // ctor/dtor
644 // ----------------------------------------------------------------------------
645
646 // ctor
647 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup *pParent,
648 const wxString& strName,
649 wxFileConfig *pConfig)
650 : m_aEntries(CompareEntries),
651 m_aSubgroups(CompareGroups),
652 m_strName(strName)
653 {
654 m_pConfig = pConfig;
655 m_pParent = pParent;
656 m_pLine = NULL;
657 m_bDirty = FALSE;
658 m_pLastEntry = NULL;
659 m_pLastGroup = NULL;
660 }
661
662 // dtor deletes all children
663 wxFileConfig::ConfigGroup::~ConfigGroup()
664 {
665 // entries
666 uint n, nCount = m_aEntries.Count();
667 for ( n = 0; n < nCount; n++ )
668 delete m_aEntries[n];
669
670 // subgroups
671 nCount = m_aSubgroups.Count();
672 for ( n = 0; n < nCount; n++ )
673 delete m_aSubgroups[n];
674 }
675
676 // ----------------------------------------------------------------------------
677 // line
678 // ----------------------------------------------------------------------------
679
680 void wxFileConfig::ConfigGroup::SetLine(LineList *pLine)
681 {
682 wxASSERT( m_pLine == NULL ); // shouldn't be called twice
683
684 m_pLine = pLine;
685 }
686
687 // return the line which contains "[our name]"
688 wxFileConfig::LineList *wxFileConfig::ConfigGroup::GetGroupLine()
689 {
690 if ( m_pLine == NULL ) {
691 // this group wasn't present in local config file, add it now
692 if ( Parent() != NULL ) {
693 wxString strFullName;
694 strFullName << "[" << GetFullName().c_str() + 1 << "]"; // +1: no '/'
695 m_pLine = m_pConfig->LineListInsert(strFullName,
696 Parent()->GetLastGroupLine());
697 Parent()->SetLastGroup(this);
698 }
699 else {
700 // we return NULL, so that LineListInsert() will insert us in the
701 // very beginning
702 }
703 }
704
705 return m_pLine;
706 }
707
708 // return the last line belonging to the subgroups of this group
709 // (after which we can add a new subgroup)
710 wxFileConfig::LineList *wxFileConfig::ConfigGroup::GetLastGroupLine()
711 {
712 // if we have any subgroups, our last line is the last line of the last
713 // subgroup
714 if ( m_pLastGroup != NULL )
715 return m_pLastGroup->GetLastGroupLine();
716
717 // if we have any entries, our last line is the last entry
718 if ( m_pLastEntry != NULL )
719 return m_pLastEntry->GetLine();
720
721 // nothing at all: last line is the first one
722 return GetGroupLine();
723 }
724
725 // return the last line belonging to the entries of this group
726 // (after which we can add a new entry)
727 wxFileConfig::LineList *wxFileConfig::ConfigGroup::GetLastEntryLine()
728 {
729 if ( m_pLastEntry != NULL ) {
730 wxFileConfig::LineList *pLine = m_pLastEntry->GetLine();
731
732 wxASSERT( pLine != NULL ); // last entry must have !NULL associated line
733 return pLine;
734 }
735
736 // no entrues: insert after the group header
737 return GetGroupLine();
738 }
739
740 // ----------------------------------------------------------------------------
741 // group name
742 // ----------------------------------------------------------------------------
743
744 wxString wxFileConfig::ConfigGroup::GetFullName() const
745 {
746 if ( Parent() )
747 return Parent()->GetFullName() + APPCONF_PATH_SEPARATOR + Name();
748 else
749 return "";
750 }
751
752 // ----------------------------------------------------------------------------
753 // find an item
754 // ----------------------------------------------------------------------------
755
756 // use binary search because the array is sorted
757 wxFileConfig::ConfigEntry *
758 wxFileConfig::ConfigGroup::FindEntry(const char *szName) const
759 {
760 uint i,
761 lo = 0,
762 hi = m_aEntries.Count();
763 int res;
764 wxFileConfig::ConfigEntry *pEntry;
765
766 while ( lo < hi ) {
767 i = (lo + hi)/2;
768 pEntry = m_aEntries[i];
769
770 #if APPCONF_CASE_SENSITIVE
771 res = strcmp(pEntry->Name(), szName);
772 #else
773 res = Stricmp(pEntry->Name(), szName);
774 #endif
775
776 if ( res < 0 )
777 hi = i;
778 else if ( res > 0 )
779 lo = i + 1;
780 else
781 return pEntry;
782 }
783
784 return NULL;
785 }
786
787 wxFileConfig::ConfigGroup *
788 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName) const
789 {
790 uint i,
791 lo = 0,
792 hi = m_aSubgroups.Count();
793 int res;
794 wxFileConfig::ConfigGroup *pGroup;
795
796 while ( lo < hi ) {
797 i = (lo + hi)/2;
798 pGroup = m_aSubgroups[i];
799
800 #if APPCONF_CASE_SENSITIVE
801 res = strcmp(pGroup->Name(), szName);
802 #else
803 res = Stricmp(pGroup->Name(), szName);
804 #endif
805
806 if ( res < 0 )
807 hi = i;
808 else if ( res > 0 )
809 lo = i + 1;
810 else
811 return pGroup;
812 }
813
814 return NULL;
815 }
816
817 // ----------------------------------------------------------------------------
818 // create a new item
819 // ----------------------------------------------------------------------------
820
821 // create a new entry and add it to the current group
822 wxFileConfig::ConfigEntry *
823 wxFileConfig::ConfigGroup::AddEntry(const wxString& strName, int nLine)
824 {
825 wxASSERT( FindEntry(strName) == NULL );
826
827 ConfigEntry *pEntry = new ConfigEntry(this, strName, nLine);
828 m_aEntries.Add(pEntry);
829
830 return pEntry;
831 }
832
833 // create a new group and add it to the current group
834 wxFileConfig::ConfigGroup *
835 wxFileConfig::ConfigGroup::AddSubgroup(const wxString& strName)
836 {
837 wxASSERT( FindSubgroup(strName) == NULL );
838
839 ConfigGroup *pGroup = new ConfigGroup(this, strName, m_pConfig);
840 m_aSubgroups.Add(pGroup);
841
842 return pGroup;
843 }
844
845 // ----------------------------------------------------------------------------
846 // delete an item
847 // ----------------------------------------------------------------------------
848
849 bool wxFileConfig::ConfigGroup::DeleteSubgroup(const char *szName)
850 {
851 uint n, nCount = m_aSubgroups.Count();
852 for ( n = 0; n < nCount; n++ ) {
853 if ( m_aSubgroups[n]->Name().IsSameAs(szName, APPCONF_CASE_SENSITIVE) )
854 break;
855 }
856
857 if ( n == nCount )
858 return FALSE;
859
860 nCount = m_aEntries.Count();
861 for ( n = 0; n < nCount; n++ ) {
862 LineList *pLine = m_aEntries[n]->GetLine();
863 if ( pLine != NULL )
864 m_pConfig->LineListRemove(pLine);
865 }
866
867 ConfigGroup *pGroup = m_aSubgroups[n];
868 LineList *pLine = pGroup->m_pLine;
869 if ( pLine != NULL )
870 m_pConfig->LineListRemove(pLine);
871 delete pGroup;
872
873 SetDirty();
874
875 m_aSubgroups.Remove(n);
876 return TRUE;
877 }
878
879 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName)
880 {
881 uint n, nCount = m_aEntries.Count();
882 for ( n = 0; n < nCount; n++ ) {
883 if ( m_aEntries[n]->Name().IsSameAs(szName, APPCONF_CASE_SENSITIVE) )
884 break;
885 }
886
887 if ( n == nCount )
888 return FALSE;
889
890 ConfigEntry *pEntry = m_aEntries[n];
891 LineList *pLine = pEntry->GetLine();
892 if ( pLine != NULL )
893 m_pConfig->LineListRemove(pLine);
894 delete pEntry;
895
896 SetDirty();
897
898 m_aEntries.Remove(n);
899 return TRUE;
900 }
901
902 // ----------------------------------------------------------------------------
903 //
904 // ----------------------------------------------------------------------------
905 void wxFileConfig::ConfigGroup::SetDirty()
906 {
907 m_bDirty = TRUE;
908 if ( Parent() != NULL ) // propagate upwards
909 Parent()->SetDirty();
910 }
911
912 // ============================================================================
913 // wxFileConfig::ConfigEntry
914 // ============================================================================
915
916 // ----------------------------------------------------------------------------
917 // ctor
918 // ----------------------------------------------------------------------------
919 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup *pParent,
920 const wxString& strName,
921 int nLine)
922 : m_strName(strName)
923 {
924 m_pParent = pParent;
925 m_nLine = nLine;
926 m_pLine = NULL;
927
928 m_bDirty = FALSE;
929
930 m_bImmutable = strName[0] == APPCONF_IMMUTABLE_PREFIX;
931 if ( m_bImmutable )
932 m_strName.erase(0, 1); // remove first character
933 }
934
935 // ----------------------------------------------------------------------------
936 // set value
937 // ----------------------------------------------------------------------------
938
939 void wxFileConfig::ConfigEntry::SetLine(LineList *pLine)
940 {
941 if ( m_pLine != NULL ) {
942 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
943 Name().c_str(), m_pParent->GetFullName().c_str());
944 }
945
946 m_pLine = pLine;
947 Group()->SetLastEntry(this);
948 }
949
950 // second parameter is FALSE if we read the value from file and prevents the
951 // entry from being marked as 'dirty'
952 void wxFileConfig::ConfigEntry::SetValue(const wxString& strValue, bool bUser)
953 {
954 if ( bUser && IsImmutable() ) {
955 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
956 Name().c_str());
957 return;
958 }
959
960 // do nothing if it's the same value
961 if ( strValue == m_strValue )
962 return;
963
964 m_strValue = strValue;
965
966 if ( bUser ) {
967 wxString strVal = FilterOut(strValue);
968 wxString strLine;
969 strLine << m_strName << " = " << strVal;
970
971 if ( m_pLine != NULL ) {
972 // entry was read from the local config file, just modify the line
973 m_pLine->SetText(strLine);
974 }
975 else {
976 // add a new line to the file
977 wxASSERT( m_nLine == NOT_FOUND ); // consistency check
978
979 m_pLine = Group()->Config()->LineListInsert(strLine,
980 Group()->GetLastEntryLine());
981 Group()->SetLastEntry(this);
982 }
983
984 SetDirty();
985 }
986 }
987
988 void wxFileConfig::ConfigEntry::SetDirty()
989 {
990 m_bDirty = TRUE;
991 Group()->SetDirty();
992 }
993
994 // ============================================================================
995 // global functions
996 // ============================================================================
997
998 // ----------------------------------------------------------------------------
999 // compare functions for array sorting
1000 // ----------------------------------------------------------------------------
1001
1002 int CompareEntries(wxFileConfig::ConfigEntry *p1,
1003 wxFileConfig::ConfigEntry *p2)
1004 {
1005 #if APPCONF_CASE_SENSITIVE
1006 return strcmp(p1->Name(), p2->Name());
1007 #else
1008 return Stricmp(p1->Name(), p2->Name());
1009 #endif
1010 }
1011
1012 int CompareGroups(wxFileConfig::ConfigGroup *p1,
1013 wxFileConfig::ConfigGroup *p2)
1014 {
1015 #if APPCONF_CASE_SENSITIVE
1016 return strcmp(p1->Name(), p2->Name());
1017 #else
1018 return Stricmp(p1->Name(), p2->Name());
1019 #endif
1020 }
1021
1022 // ----------------------------------------------------------------------------
1023 // filter functions
1024 // ----------------------------------------------------------------------------
1025
1026 // undo FilterOut
1027 wxString FilterIn(const wxString& str)
1028 {
1029 wxString strResult;
1030 strResult.Alloc(str.Len());
1031
1032 bool bQuoted = !str.IsEmpty() && str[0] == '"';
1033
1034 for ( uint n = bQuoted ? 1 : 0; n < str.Len(); n++ ) {
1035 if ( str[n] == '\\' ) {
1036 switch ( str[++n] ) {
1037 case 'n':
1038 strResult += '\n';
1039 break;
1040
1041 case 'r':
1042 strResult += '\r';
1043 break;
1044
1045 case 't':
1046 strResult += '\t';
1047 break;
1048
1049 case '\\':
1050 strResult += '\\';
1051 break;
1052
1053 case '"':
1054 strResult += '"';
1055 break;
1056 }
1057 }
1058 else {
1059 if ( str[n] != '"' || !bQuoted )
1060 strResult += str[n];
1061 else if ( n != str.Len() - 1 ) {
1062 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1063 n, str.c_str());
1064 }
1065 //else: it's the last quote of a quoted string, ok
1066 }
1067 }
1068
1069 return strResult;
1070 }
1071
1072 // quote the string before writing it to file
1073 wxString FilterOut(const wxString& str)
1074 {
1075 wxString strResult;
1076 strResult.Alloc(str.Len());
1077
1078 // quoting is necessary to preserve spaces in the beginning of the string
1079 bool bQuote = isspace(str[0]) || str[0] == '"';
1080
1081 if ( bQuote )
1082 strResult += '"';
1083
1084 char c;
1085 for ( uint n = 0; n < str.Len(); n++ ) {
1086 switch ( str[n] ) {
1087 case '\n':
1088 c = 'n';
1089 break;
1090
1091 case '\r':
1092 c = 'r';
1093 break;
1094
1095 case '\t':
1096 c = 't';
1097 break;
1098
1099 case '\\':
1100 c = '\\';
1101 break;
1102
1103 case '"':
1104 if ( bQuote )
1105 c = '"';
1106 //else: fall through
1107
1108 default:
1109 strResult += str[n];
1110 continue; // nothing special to do
1111 }
1112
1113 // we get here only for special characters
1114 strResult << '\\' << c;
1115 }
1116
1117 if ( bQuote )
1118 strResult += '"';
1119
1120 return strResult;
1121 }