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