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