]> git.saurik.com Git - wxWidgets.git/blob - src/common/fileconf.cpp
Various documentation changes, makefile 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 // __WINDOWS__ is defined for MS Windows compilation
44 #if defined(__WINDOWS__) && !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 return;
330
331 if ( strPath[0] == APPCONF_PATH_SEPARATOR ) {
332 // absolute path
333 SplitPath(aParts, strPath);
334 }
335 else {
336 // relative path, combine with current one
337 wxString strFullPath = m_strPath;
338 strFullPath << APPCONF_PATH_SEPARATOR << strPath;
339 SplitPath(aParts, strFullPath);
340 }
341
342 // change current group
343 uint n;
344 m_pCurrentGroup = m_pRootGroup;
345 for ( n = 0; n < aParts.Count(); n++ ) {
346 ConfigGroup *pNextGroup = m_pCurrentGroup->FindSubgroup(aParts[n]);
347 if ( pNextGroup == NULL )
348 pNextGroup = m_pCurrentGroup->AddSubgroup(aParts[n]);
349 m_pCurrentGroup = pNextGroup;
350 }
351
352 // recombine path parts in one variable
353 m_strPath.Empty();
354 for ( n = 0; n < aParts.Count(); n++ ) {
355 m_strPath << APPCONF_PATH_SEPARATOR << aParts[n];
356 }
357 }
358
359 // ----------------------------------------------------------------------------
360 // enumeration
361 // ----------------------------------------------------------------------------
362
363 bool wxFileConfig::GetFirstGroup(wxString& str, long& lIndex)
364 {
365 lIndex = 0;
366 return GetNextGroup(str, lIndex);
367 }
368
369 bool wxFileConfig::GetNextGroup (wxString& str, long& lIndex)
370 {
371 if ( uint(lIndex) < m_pCurrentGroup->Groups().Count() ) {
372 str = m_pCurrentGroup->Groups()[lIndex++]->Name();
373 return TRUE;
374 }
375 else
376 return FALSE;
377 }
378
379 bool wxFileConfig::GetFirstEntry(wxString& str, long& lIndex)
380 {
381 lIndex = 0;
382 return GetNextEntry(str, lIndex);
383 }
384
385 bool wxFileConfig::GetNextEntry (wxString& str, long& lIndex)
386 {
387 if ( uint(lIndex) < m_pCurrentGroup->Entries().Count() ) {
388 str = m_pCurrentGroup->Entries()[lIndex++]->Name();
389 return TRUE;
390 }
391 else
392 return FALSE;
393 }
394
395 // ----------------------------------------------------------------------------
396 // read/write values
397 // ----------------------------------------------------------------------------
398
399 const char *wxFileConfig::Read(const char *szKey,
400 const char *szDefault) const
401 {
402 PathChanger path(this, szKey);
403
404 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
405 return pEntry == NULL ? szDefault : pEntry->Value().c_str();
406 }
407
408 bool wxFileConfig::Read(wxString *pstr,
409 const char *szKey,
410 const char *szDefault) const
411 {
412 PathChanger path(this, szKey);
413
414 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
415 if (pEntry == NULL) {
416 *pstr = szDefault;
417 return FALSE;
418 }
419 else {
420 *pstr = pEntry->Value();
421 return TRUE;
422 }
423 }
424
425 bool wxFileConfig::Read(long *pl, const char *szKey, long lDefault) const
426 {
427 wxString str;
428 if ( Read(&str, szKey) ) {
429 *pl = atol(str);
430 return TRUE;
431 }
432 else {
433 *pl = lDefault;
434 return FALSE;
435 }
436 }
437
438 bool wxFileConfig::Write(const char *szKey, const char *szValue)
439 {
440 PathChanger path(this, szKey);
441
442 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
443 if ( pEntry == NULL )
444 pEntry = m_pCurrentGroup->AddEntry(path.Name());
445 pEntry->SetValue(szValue);
446
447 return TRUE;
448 }
449
450 bool wxFileConfig::Write(const char *szKey, long lValue)
451 {
452 // ltoa() is not ANSI :-(
453 char szBuf[40]; // should be good for sizeof(long) <= 16 (128 bits)
454 sprintf(szBuf, "%ld", lValue);
455 return Write(szKey, szBuf);
456 }
457
458 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
459 {
460 if ( LineListIsEmpty() || !m_pRootGroup->IsDirty() )
461 return TRUE;
462
463 wxTempFile file(m_strLocalFile);
464
465 if ( !file.IsOpened() ) {
466 wxLogError("Can't open user configuration file.");
467 return FALSE;
468 }
469
470 // write all strings to file
471 for ( LineList *p = m_linesHead; p != NULL; p = p->Next() ) {
472 if ( !file.Write(p->Text() + wxTextFile::GetEOL()) ) {
473 wxLogError("Can't write user configuration file.");
474 return FALSE;
475 }
476 }
477
478 return file.Commit();
479 }
480
481 // ----------------------------------------------------------------------------
482 // delete groups/entries
483 // ----------------------------------------------------------------------------
484
485 bool wxFileConfig::DeleteEntry(const char *szKey, bool bGroupIfEmptyAlso)
486 {
487 PathChanger path(this, szKey);
488
489 if ( !m_pCurrentGroup->DeleteEntry(path.Name()) )
490 return FALSE;
491
492 if ( bGroupIfEmptyAlso && m_pCurrentGroup->IsEmpty() ) {
493 if ( m_pCurrentGroup != m_pRootGroup ) {
494 ConfigGroup *pGroup = m_pCurrentGroup;
495 SetPath(".."); // changes m_pCurrentGroup!
496 m_pCurrentGroup->DeleteSubgroup(pGroup->Name());
497 }
498 //else: never delete the root group
499 }
500
501 return TRUE;
502 }
503
504 bool wxFileConfig::DeleteGroup(const char *szKey)
505 {
506 PathChanger path(this, szKey);
507
508 return m_pCurrentGroup->DeleteSubgroup(path.Name());
509 }
510
511 bool wxFileConfig::DeleteAll()
512 {
513 const char *szFile = m_strLocalFile;
514 delete m_pRootGroup;
515 Init();
516
517 if ( remove(szFile) == -1 )
518 wxLogSysError("Can't delete user configuration file '%s'", szFile);
519
520 szFile = m_strGlobalFile;
521 if ( remove(szFile) )
522 wxLogSysError("Can't delete system configuration file '%s'", szFile);
523
524 return TRUE;
525 }
526
527 // ----------------------------------------------------------------------------
528 // linked list functions
529 // ----------------------------------------------------------------------------
530
531 // append a new line to the end of the list
532 wxFileConfig::LineList *wxFileConfig::LineListAppend(const wxString& str)
533 {
534 LineList *pLine = new LineList(str);
535
536 if ( m_linesTail == NULL ) {
537 // list is empty
538 m_linesHead = pLine;
539 }
540 else {
541 // adjust pointers
542 m_linesTail->SetNext(pLine);
543 }
544
545 m_linesTail = pLine;
546 return m_linesTail;
547 }
548
549 // insert a new line after the given one or in the very beginning if !pLine
550 wxFileConfig::LineList *wxFileConfig::LineListInsert(const wxString& str,
551 LineList *pLine)
552 {
553 if ( pLine == m_linesTail )
554 return LineListAppend(str);
555
556 LineList *pNewLine;
557
558 if ( pLine == NULL ) {
559 pNewLine = new LineList(str, m_linesHead);
560 m_linesHead = pNewLine;
561 }
562 else {
563 pNewLine = new LineList(str, pLine->Next());
564 pLine->SetNext(pNewLine);
565 }
566
567 return pNewLine;
568 }
569
570 bool wxFileConfig::LineListIsEmpty()
571 {
572 return m_linesHead == NULL;
573 }
574
575 // ============================================================================
576 // wxFileConfig::ConfigGroup
577 // ============================================================================
578
579 // ----------------------------------------------------------------------------
580 // ctor/dtor
581 // ----------------------------------------------------------------------------
582
583 // ctor
584 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup *pParent,
585 const wxString& strName,
586 wxFileConfig *pConfig)
587 : m_strName(strName)
588 {
589 m_pConfig = pConfig;
590 m_pParent = pParent;
591 m_pLine = NULL;
592 m_bDirty = FALSE;
593 m_pLastEntry = NULL;
594 m_pLastGroup = NULL;
595 }
596
597 // dtor deletes all children
598 wxFileConfig::ConfigGroup::~ConfigGroup()
599 {
600 // entries
601 uint n, nCount = m_aEntries.Count();
602 for ( n = 0; n < nCount; n++ )
603 delete m_aEntries[n];
604
605 // subgroups
606 nCount = m_aSubgroups.Count();
607 for ( n = 0; n < nCount; n++ )
608 delete m_aSubgroups[n];
609 }
610
611 // ----------------------------------------------------------------------------
612 // line
613 // ----------------------------------------------------------------------------
614
615 void wxFileConfig::ConfigGroup::SetLine(LineList *pLine)
616 {
617 wxASSERT( m_pLine == NULL ); // shouldn't be called twice
618
619 m_pLine = pLine;
620 }
621
622 // return the line which contains "[our name]"
623 wxFileConfig::LineList *wxFileConfig::ConfigGroup::GetGroupLine()
624 {
625 if ( m_pLine == NULL ) {
626 // this group wasn't present in local config file, add it now
627 if ( Parent() != NULL ) {
628 wxString strFullName;
629 strFullName << "[" << GetFullName().c_str() + 1 << "]"; // +1: no '/'
630 m_pLine = m_pConfig->LineListInsert(strFullName,
631 Parent()->GetLastGroupLine());
632 Parent()->SetLastGroup(this);
633 }
634 else {
635 // we return NULL, so that LineListInsert() will insert us in the
636 // very beginning
637 }
638 }
639
640 return m_pLine;
641 }
642
643 // return the last line belonging to the subgroups of this group
644 // (after which we can add a new subgroup)
645 wxFileConfig::LineList *wxFileConfig::ConfigGroup::GetLastGroupLine()
646 {
647 // if we have any subgroups, our last line is the last line of the last
648 // subgroup
649 if ( m_pLastGroup != NULL )
650 return m_pLastGroup->GetLastGroupLine();
651
652 // if we have any entries, our last line is the last entry
653 if ( m_pLastEntry != NULL )
654 return m_pLastEntry->GetLine();
655
656 // nothing at all: last line is the first one
657 return GetGroupLine();
658 }
659
660 // return the last line belonging to the entries of this group
661 // (after which we can add a new entry)
662 wxFileConfig::LineList *wxFileConfig::ConfigGroup::GetLastEntryLine()
663 {
664 if ( m_pLastEntry != NULL ) {
665 wxFileConfig::LineList *pLine = m_pLastEntry->GetLine();
666
667 wxASSERT( pLine != NULL ); // last entry must have !NULL associated line
668 return pLine;
669 }
670
671 // no entrues: insert after the group header
672 return GetGroupLine();
673 }
674
675 // ----------------------------------------------------------------------------
676 // group name
677 // ----------------------------------------------------------------------------
678
679 wxString wxFileConfig::ConfigGroup::GetFullName() const
680 {
681 if ( Parent() )
682 return Parent()->GetFullName() + APPCONF_PATH_SEPARATOR + Name();
683 else
684 return "";
685 }
686
687 // ----------------------------------------------------------------------------
688 // find an item
689 // ----------------------------------------------------------------------------
690
691 wxFileConfig::ConfigEntry *
692 wxFileConfig::ConfigGroup::FindEntry(const char *szName) const
693 {
694 uint nCount = m_aEntries.Count();
695 for ( uint n = 0; n < nCount; n++ ) {
696 if ( m_aEntries[n]->Name().IsSameAs(szName, APPCONF_CASE_SENSITIVE) )
697 return m_aEntries[n];
698 }
699
700 return NULL;
701 }
702
703 wxFileConfig::ConfigGroup *
704 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName) const
705 {
706 uint nCount = m_aSubgroups.Count();
707 for ( uint n = 0; n < nCount; n++ ) {
708 if ( m_aSubgroups[n]->Name().IsSameAs(szName, APPCONF_CASE_SENSITIVE) )
709 return m_aSubgroups[n];
710 }
711
712 return NULL;
713 }
714
715 // ----------------------------------------------------------------------------
716 // create a new item
717 // ----------------------------------------------------------------------------
718
719 // create a new entry and add it to the current group
720 wxFileConfig::ConfigEntry *
721 wxFileConfig::ConfigGroup::AddEntry(const wxString& strName, int nLine)
722 {
723 wxASSERT( FindEntry(strName) == NULL );
724
725 ConfigEntry *pEntry = new ConfigEntry(this, strName, nLine);
726 m_aEntries.Add(pEntry);
727
728 return pEntry;
729 }
730
731 // create a new group and add it to the current group
732 wxFileConfig::ConfigGroup *
733 wxFileConfig::ConfigGroup::AddSubgroup(const wxString& strName)
734 {
735 wxASSERT( FindSubgroup(strName) == NULL );
736
737 ConfigGroup *pGroup = new ConfigGroup(this, strName, m_pConfig);
738 m_aSubgroups.Add(pGroup);
739
740 return pGroup;
741 }
742
743 // ----------------------------------------------------------------------------
744 // delete an item
745 // ----------------------------------------------------------------------------
746
747 bool wxFileConfig::ConfigGroup::DeleteSubgroup(const char *szName)
748 {
749 uint n, nCount = m_aSubgroups.Count();
750 for ( n = 0; n < nCount; n++ ) {
751 if ( m_aSubgroups[n]->Name().IsSameAs(szName, APPCONF_CASE_SENSITIVE) )
752 break;
753 }
754
755 if ( n == nCount )
756 return FALSE;
757
758 delete m_aSubgroups[n];
759 m_aSubgroups.Remove(n);
760 return TRUE;
761 }
762
763 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName)
764 {
765 uint n, nCount = m_aEntries.Count();
766 for ( n = 0; n < nCount; n++ ) {
767 if ( m_aEntries[n]->Name().IsSameAs(szName, APPCONF_CASE_SENSITIVE) )
768 break;
769 }
770
771 if ( n == nCount )
772 return FALSE;
773
774 delete m_aEntries[n];
775 m_aEntries.Remove(n);
776 return TRUE;
777 }
778
779 // ----------------------------------------------------------------------------
780 //
781 // ----------------------------------------------------------------------------
782 void wxFileConfig::ConfigGroup::SetDirty()
783 {
784 m_bDirty = TRUE;
785 if ( Parent() != NULL ) // propagate upwards
786 Parent()->SetDirty();
787 }
788
789 // ============================================================================
790 // wxFileConfig::ConfigEntry
791 // ============================================================================
792
793 // ----------------------------------------------------------------------------
794 // ctor
795 // ----------------------------------------------------------------------------
796 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup *pParent,
797 const wxString& strName,
798 int nLine)
799 : m_strName(strName)
800 {
801 m_pParent = pParent;
802 m_nLine = nLine;
803 m_pLine = NULL;
804
805 m_bDirty = FALSE;
806
807 m_bImmutable = strName[0] == APPCONF_IMMUTABLE_PREFIX;
808 if ( m_bImmutable )
809 m_strName.erase(0, 1); // remove first character
810 }
811
812 // ----------------------------------------------------------------------------
813 // set value
814 // ----------------------------------------------------------------------------
815
816 void wxFileConfig::ConfigEntry::SetLine(LineList *pLine)
817 {
818 if ( m_pLine != NULL ) {
819 wxLogWarning("Entry '%s' appears more than once in group '%s'",
820 Name().c_str(), m_pParent->GetFullName().c_str());
821 }
822
823 m_pLine = pLine;
824 Group()->SetLastEntry(this);
825 }
826
827 // second parameter is FALSE if we read the value from file and prevents the
828 // entry from being marked as 'dirty'
829 void wxFileConfig::ConfigEntry::SetValue(const wxString& strValue, bool bUser)
830 {
831 if ( bUser && IsImmutable() ) {
832 wxLogWarning("Attempt to change immutable key '%s' ignored.",
833 Name().c_str());
834 return;
835 }
836
837 // do nothing if it's the same value
838 if ( strValue == m_strValue )
839 return;
840
841 m_strValue = strValue;
842
843 if ( bUser ) {
844 wxString strVal = FilterOut(strValue);
845 wxString strLine;
846 strLine << m_strName << " = " << strVal;
847
848 if ( m_pLine != NULL ) {
849 // entry was read from the local config file, just modify the line
850 m_pLine->SetText(strLine);
851 }
852 else {
853 // add a new line to the file
854 wxASSERT( m_nLine == NOT_FOUND ); // consistency check
855
856 m_pLine = Group()->Config()->LineListInsert(strLine,
857 Group()->GetLastEntryLine());
858 Group()->SetLastEntry(this);
859 }
860
861 SetDirty();
862 }
863 }
864
865 void wxFileConfig::ConfigEntry::SetDirty()
866 {
867 m_bDirty = TRUE;
868 Group()->SetDirty();
869 }
870
871 // ============================================================================
872 // global functions
873 // ============================================================================
874
875 // undo FilterOut
876 wxString FilterIn(const wxString& str)
877 {
878 wxString strResult;
879
880 bool bQuoted = !str.IsEmpty() && str[0] == '"';
881
882 for ( uint n = bQuoted ? 1 : 0; n < str.Len(); n++ ) {
883 if ( str[n] == '\\' ) {
884 switch ( str[++n] ) {
885 case 'n':
886 strResult += '\n';
887 break;
888
889 case 't':
890 strResult += '\t';
891 break;
892
893 case '\\':
894 strResult += '\\';
895 break;
896
897 case '"':
898 strResult += '"';
899 break;
900 }
901 }
902 else {
903 if ( str[n] != '"' || !bQuoted )
904 strResult += str[n];
905 else if ( n != str.Len() - 1 )
906 wxLogWarning("unexpected \" at position %d in '%s'.", n, str.c_str());
907 //else: it's the last quote of a quoted string, ok
908 }
909 }
910
911 return strResult;
912 }
913
914 // quote the string before writing it to file
915 wxString FilterOut(const wxString& str)
916 {
917 wxString strResult;
918
919 // quoting is necessary to preserve spaces in the beginning of the string
920 bool bQuote = isspace(str[0]) || str[0] == '"';
921
922 if ( bQuote )
923 strResult += '"';
924
925 char c;
926 for ( uint n = 0; n < str.Len(); n++ ) {
927 switch ( str[n] ) {
928 case '\n':
929 c = 'n';
930 break;
931
932 case '\t':
933 c = 't';
934 break;
935
936 case '\\':
937 c = '\\';
938 break;
939
940 case '"':
941 if ( bQuote )
942 c = '"';
943 //else: fall through
944
945 default:
946 strResult += str[n];
947 continue; // nothing special to do
948 }
949
950 // we get here only for special characters
951 strResult << '\\' << c;
952 }
953
954 if ( bQuote )
955 strResult += '"';
956
957 return strResult;
958 }