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