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