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