]> git.saurik.com Git - wxWidgets.git/blob - src/common/fileconf.cpp
wxFileConfig now has it's own header, the config file name may be given to
[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
546 wxFileConfig::LineList *wxFileConfig::LineListInsert(const wxString& str,
547 LineList *pLine)
548 {
549 if ( pLine == NULL )
550 return LineListAppend(str);
551
552 LineList *pNewLine = new LineList(str, pLine->Next());
553 pLine->SetNext(pNewLine);
554
555 return pNewLine;
556 }
557
558 bool wxFileConfig::LineListIsEmpty()
559 {
560 return m_linesHead == NULL;
561 }
562
563 // ============================================================================
564 // wxFileConfig::ConfigGroup
565 // ============================================================================
566
567 // ----------------------------------------------------------------------------
568 // ctor/dtor
569 // ----------------------------------------------------------------------------
570
571 // ctor
572 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup *pParent,
573 const wxString& strName,
574 wxFileConfig *pConfig)
575 : m_strName(strName)
576 {
577 m_pConfig = pConfig;
578 m_pParent = pParent;
579 m_pLine = NULL;
580 m_bDirty = FALSE;
581
582 m_nLastEntry =
583 m_nLastGroup = NOT_FOUND;
584 }
585
586 // dtor deletes all children
587 wxFileConfig::ConfigGroup::~ConfigGroup()
588 {
589 // entries
590 uint n, nCount = m_aEntries.Count();
591 for ( n = 0; n < nCount; n++ )
592 delete m_aEntries[n];
593
594 // subgroups
595 nCount = m_aSubgroups.Count();
596 for ( n = 0; n < nCount; n++ )
597 delete m_aSubgroups[n];
598 }
599
600 // ----------------------------------------------------------------------------
601 // line
602 // ----------------------------------------------------------------------------
603
604 void wxFileConfig::ConfigGroup::SetLine(LineList *pLine)
605 {
606 wxASSERT( m_pLine == NULL ); // shouldn't be called twice
607
608 m_pLine = pLine;
609 }
610
611 // return the line which contains "[our name]"
612 wxFileConfig::LineList *wxFileConfig::ConfigGroup::GetGroupLine()
613 {
614 if ( m_pLine == NULL ) {
615 // this group wasn't present in local config file, add it now
616 if ( Parent() != NULL ) {
617 wxString strFullName;
618 strFullName << "[" << GetFullName().c_str() + 1 << "]"; // +1: no '/'
619 m_pLine = m_pConfig->LineListInsert(strFullName,
620 Parent()->GetLastGroupLine());
621 }
622 else {
623 // we're the root group, yet we were not in the local file => there were
624 // only comments and blank lines in config file or nothing at all
625 // we return NULL, so that LineListInsert() will do Append()
626 }
627 }
628
629 return m_pLine;
630 }
631
632 // return the last line belonging to the subgroups of this group
633 // (after which we can add a new subgroup)
634 wxFileConfig::LineList *wxFileConfig::ConfigGroup::GetLastGroupLine()
635 {
636 // if we have any subgroups, our last line is the last line of the last
637 // subgroup
638 if ( m_nLastGroup != NOT_FOUND ) {
639 return m_aSubgroups[m_nLastGroup]->GetLastGroupLine();
640 }
641
642 // if we have any entries, our last line is the last entry
643 if ( m_nLastEntry != NOT_FOUND ) {
644 return m_aEntries[m_nLastEntry]->GetLine();
645 }
646
647 // nothing at all: last line is the first one
648 return GetGroupLine();
649 }
650
651 // return the last line belonging to the entries of this group
652 // (after which we can add a new entry)
653 wxFileConfig::LineList *wxFileConfig::ConfigGroup::GetLastEntryLine()
654 {
655 if ( m_nLastEntry != NOT_FOUND )
656 return m_aEntries[m_nLastEntry]->GetLine();
657 else
658 return GetGroupLine();
659 }
660
661 // ----------------------------------------------------------------------------
662 // group name
663 // ----------------------------------------------------------------------------
664
665 wxString wxFileConfig::ConfigGroup::GetFullName() const
666 {
667 if ( Parent() )
668 return Parent()->GetFullName() + APPCONF_PATH_SEPARATOR + Name();
669 else
670 return "";
671 }
672
673 // ----------------------------------------------------------------------------
674 // find an item
675 // ----------------------------------------------------------------------------
676
677 wxFileConfig::ConfigEntry *
678 wxFileConfig::ConfigGroup::FindEntry(const char *szName) const
679 {
680 uint nCount = m_aEntries.Count();
681 for ( uint n = 0; n < nCount; n++ ) {
682 if ( m_aEntries[n]->Name().IsSameAs(szName, APPCONF_CASE_SENSITIVE) )
683 return m_aEntries[n];
684 }
685
686 return NULL;
687 }
688
689 wxFileConfig::ConfigGroup *
690 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName) const
691 {
692 uint nCount = m_aSubgroups.Count();
693 for ( uint n = 0; n < nCount; n++ ) {
694 if ( m_aSubgroups[n]->Name().IsSameAs(szName, APPCONF_CASE_SENSITIVE) )
695 return m_aSubgroups[n];
696 }
697
698 return NULL;
699 }
700
701 // ----------------------------------------------------------------------------
702 // create a new item
703 // ----------------------------------------------------------------------------
704
705 // create a new entry and add it to the current group
706 wxFileConfig::ConfigEntry *
707 wxFileConfig::ConfigGroup::AddEntry(const wxString& strName, int nLine)
708 {
709 wxASSERT( FindEntry(strName) == NULL );
710
711 ConfigEntry *pEntry = new ConfigEntry(this, strName, nLine);
712 m_aEntries.Add(pEntry);
713
714 return pEntry;
715 }
716
717 // create a new group and add it to the current group
718 wxFileConfig::ConfigGroup *
719 wxFileConfig::ConfigGroup::AddSubgroup(const wxString& strName)
720 {
721 wxASSERT( FindSubgroup(strName) == NULL );
722
723 ConfigGroup *pGroup = new ConfigGroup(this, strName, m_pConfig);
724 m_aSubgroups.Add(pGroup);
725
726 return pGroup;
727 }
728
729 // ----------------------------------------------------------------------------
730 // delete an item
731 // ----------------------------------------------------------------------------
732
733 bool wxFileConfig::ConfigGroup::DeleteSubgroup(const char *szName)
734 {
735 uint n, nCount = m_aSubgroups.Count();
736 for ( n = 0; n < nCount; n++ ) {
737 if ( m_aSubgroups[n]->Name().IsSameAs(szName, APPCONF_CASE_SENSITIVE) )
738 break;
739 }
740
741 if ( n == nCount )
742 return FALSE;
743
744 delete m_aSubgroups[n];
745 m_aSubgroups.Remove(n);
746 return TRUE;
747 }
748
749 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName)
750 {
751 uint n, nCount = m_aEntries.Count();
752 for ( n = 0; n < nCount; n++ ) {
753 if ( m_aEntries[n]->Name().IsSameAs(szName, APPCONF_CASE_SENSITIVE) )
754 break;
755 }
756
757 if ( n == nCount )
758 return FALSE;
759
760 delete m_aEntries[n];
761 m_aEntries.Remove(n);
762 return TRUE;
763 }
764
765 // ----------------------------------------------------------------------------
766 //
767 // ----------------------------------------------------------------------------
768 void wxFileConfig::ConfigGroup::SetDirty()
769 {
770 m_bDirty = TRUE;
771 if ( Parent() != NULL ) // propagate upwards
772 Parent()->SetDirty();
773 }
774
775 // ============================================================================
776 // wxFileConfig::ConfigEntry
777 // ============================================================================
778
779 // ----------------------------------------------------------------------------
780 // ctor
781 // ----------------------------------------------------------------------------
782 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup *pParent,
783 const wxString& strName,
784 int nLine)
785 : m_strName(strName)
786 {
787 m_pParent = pParent;
788 m_nLine = nLine;
789 m_pLine = NULL;
790
791 m_bDirty = FALSE;
792
793 m_bImmutable = strName[0] == APPCONF_IMMUTABLE_PREFIX;
794 if ( m_bImmutable )
795 m_strName.erase(0, 1); // remove first character
796 }
797
798 // ----------------------------------------------------------------------------
799 // set value
800 // ----------------------------------------------------------------------------
801
802 void wxFileConfig::ConfigEntry::SetLine(LineList *pLine)
803 {
804 if ( m_pLine != NULL ) {
805 wxLogWarning("Entry '%s' appears more than once in group '%s'",
806 Name().c_str(), m_pParent->GetFullName().c_str());
807 }
808
809 m_pLine = pLine;
810 }
811
812 // second parameter is FALSE if we read the value from file and prevents the
813 // entry from being marked as 'dirty'
814 void wxFileConfig::ConfigEntry::SetValue(const wxString& strValue, bool bUser)
815 {
816 if ( bUser && IsImmutable() ) {
817 wxLogWarning("Attempt to change immutable key '%s' ignored.",
818 Name().c_str());
819 return;
820 }
821
822 // do nothing if it's the same value
823 if ( strValue == m_strValue )
824 return;
825
826 m_strValue = strValue;
827
828 if ( bUser ) {
829 wxString strVal = FilterOut(strValue);
830 wxString strLine;
831 strLine << m_strName << " = " << strVal;
832
833 if ( m_pLine != NULL ) {
834 // entry was read from the local config file, just modify the line
835 m_pLine->SetText(strLine);
836 }
837 else {
838 // add a new line to the file
839 wxASSERT( m_nLine == NOT_FOUND ); // consistency check
840
841 Group()->Config()->LineListInsert(strLine, Group()->GetLastEntryLine());
842 }
843
844 SetDirty();
845 }
846 }
847
848 void wxFileConfig::ConfigEntry::SetDirty()
849 {
850 m_bDirty = TRUE;
851 Group()->SetDirty();
852 }
853
854 // ============================================================================
855 // global functions
856 // ============================================================================
857
858 // undo FilterOut
859 wxString FilterIn(const wxString& str)
860 {
861 wxString strResult;
862
863 bool bQuoted = !str.IsEmpty() && str[0] == '"';
864
865 for ( uint n = bQuoted ? 1 : 0; n < str.Len(); n++ ) {
866 if ( str[n] == '\\' ) {
867 switch ( str[++n] ) {
868 case 'n':
869 strResult += '\n';
870 break;
871
872 case 't':
873 strResult += '\t';
874 break;
875
876 case '\\':
877 strResult += '\\';
878 break;
879
880 case '"':
881 strResult += '"';
882 break;
883 }
884 }
885 else {
886 if ( str[n] != '"' || !bQuoted )
887 strResult += str[n];
888 else if ( n != str.Len() - 1 )
889 wxLogWarning("unexpected \" at position %d in '%s'.", n, str.c_str());
890 //else: it's the last quote of a quoted string, ok
891 }
892 }
893
894 return strResult;
895 }
896
897 // quote the string before writing it to file
898 wxString FilterOut(const wxString& str)
899 {
900 wxString strResult;
901
902 // quoting is necessary to preserve spaces in the beginning of the string
903 bool bQuote = isspace(str[0]) || str[0] == '"';
904
905 if ( bQuote )
906 strResult += '"';
907
908 char c;
909 for ( uint n = 0; n < str.Len(); n++ ) {
910 switch ( str[n] ) {
911 case '\n':
912 c = 'n';
913 break;
914
915 case '\t':
916 c = 't';
917 break;
918
919 case '\\':
920 c = '\\';
921 break;
922
923 case '"':
924 if ( bQuote )
925 c = '"';
926 //else: fall through
927
928 default:
929 strResult += str[n];
930 continue; // nothing special to do
931 }
932
933 // we get here only for special characters
934 strResult << '\\' << c;
935 }
936
937 if ( bQuote )
938 strResult += '"';
939
940 return strResult;
941 }