]> git.saurik.com Git - wxWidgets.git/blob - src/common/fileconf.cpp
wxTextStream now interprets 1,1 as 1.1 (European formating).
[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 // headers
19 // ----------------------------------------------------------------------------
20
21 #include "wx/wxprec.h"
22
23 #ifdef __BORLANDC__
24 #pragma hdrstop
25 #endif //__BORLANDC__
26
27 #if wxUSE_CONFIG
28
29 #ifndef WX_PRECOMP
30 #include "wx/string.h"
31 #include "wx/intl.h"
32 #endif //WX_PRECOMP
33
34 #include "wx/app.h"
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 #include "wx/utils.h" // for wxGetHomeDir
43
44 // _WINDOWS_ is defined when windows.h is included,
45 // __WXMSW__ is defined for MS Windows compilation
46 #if defined(__WXMSW__) && !defined(_WINDOWS_)
47 #include <windows.h>
48 #endif //windows.h
49 #if defined(__WXPM__)
50 #define INCL_DOS
51 #include <os2.h>
52 #define LINKAGEMODE _Optlink
53 #else
54 #define LINKAGEMODE
55 #endif
56
57 #include <stdlib.h>
58 #include <ctype.h>
59
60 // ----------------------------------------------------------------------------
61 // macros
62 // ----------------------------------------------------------------------------
63 #define CONST_CAST ((wxFileConfig *)this)->
64
65 // ----------------------------------------------------------------------------
66 // constants
67 // ----------------------------------------------------------------------------
68 #ifndef MAX_PATH
69 #define MAX_PATH 512
70 #endif
71
72 // ----------------------------------------------------------------------------
73 // global functions declarations
74 // ----------------------------------------------------------------------------
75
76 // compare functions for sorting the arrays
77 static int LINKAGEMODE CompareEntries(ConfigEntry *p1, ConfigEntry *p2);
78 static int LINKAGEMODE CompareGroups(ConfigGroup *p1, ConfigGroup *p2);
79
80 // filter strings
81 static wxString FilterInValue(const wxString& str);
82 static wxString FilterOutValue(const wxString& str);
83
84 static wxString FilterInEntryName(const wxString& str);
85 static wxString FilterOutEntryName(const wxString& str);
86
87 // get the name to use in wxFileConfig ctor
88 static wxString GetAppName(const wxString& appname);
89
90 // ============================================================================
91 // implementation
92 // ============================================================================
93
94 // ----------------------------------------------------------------------------
95 // static functions
96 // ----------------------------------------------------------------------------
97 wxString wxFileConfig::GetGlobalDir()
98 {
99 wxString strDir;
100
101 #ifdef __VMS__ // Note if __VMS is defined __UNIX is also defined
102 strDir = wxT("sys$manager:");
103 #elif defined( __UNIX__ )
104 strDir = wxT("/etc/");
105 #elif defined(__WXPM__)
106 ULONG aulSysInfo[QSV_MAX] = {0};
107 UINT drive;
108 APIRET rc;
109
110 rc = DosQuerySysInfo( 1L, QSV_MAX, (PVOID)aulSysInfo, sizeof(ULONG)*QSV_MAX);
111 if (rc == 0)
112 {
113 drive = aulSysInfo[QSV_BOOT_DRIVE - 1];
114 switch(drive)
115 {
116 case 1:
117 strDir = "A:\\OS2\\";
118 break;
119 case 2:
120 strDir = "B:\\OS2\\";
121 break;
122 case 3:
123 strDir = "C:\\OS2\\";
124 break;
125 case 4:
126 strDir = "D:\\OS2\\";
127 break;
128 case 5:
129 strDir = "E:\\OS2\\";
130 break;
131 case 6:
132 strDir = "F:\\OS2\\";
133 break;
134 case 7:
135 strDir = "G:\\OS2\\";
136 break;
137 case 8:
138 strDir = "H:\\OS2\\";
139 break;
140 case 9:
141 strDir = "I:\\OS2\\";
142 break;
143 case 10:
144 strDir = "J:\\OS2\\";
145 break;
146 case 11:
147 strDir = "K:\\OS2\\";
148 break;
149 case 12:
150 strDir = "L:\\OS2\\";
151 break;
152 case 13:
153 strDir = "M:\\OS2\\";
154 break;
155 case 14:
156 strDir = "N:\\OS2\\";
157 break;
158 case 15:
159 strDir = "O:\\OS2\\";
160 break;
161 case 16:
162 strDir = "P:\\OS2\\";
163 break;
164 case 17:
165 strDir = "Q:\\OS2\\";
166 break;
167 case 18:
168 strDir = "R:\\OS2\\";
169 break;
170 case 19:
171 strDir = "S:\\OS2\\";
172 break;
173 case 20:
174 strDir = "T:\\OS2\\";
175 break;
176 case 21:
177 strDir = "U:\\OS2\\";
178 break;
179 case 22:
180 strDir = "V:\\OS2\\";
181 break;
182 case 23:
183 strDir = "W:\\OS2\\";
184 break;
185 case 24:
186 strDir = "X:\\OS2\\";
187 break;
188 case 25:
189 strDir = "Y:\\OS2\\";
190 break;
191 case 26:
192 strDir = "Z:\\OS2\\";
193 break;
194 }
195 }
196 #elif defined(__WXSTUBS__)
197 wxASSERT_MSG( FALSE, wxT("TODO") ) ;
198 #elif defined(__WXMAC__)
199 {
200 short vRefNum ;
201 long dirID ;
202
203 if ( FindFolder( (short) kOnSystemDisk, kPreferencesFolderType, kDontCreateFolder, &vRefNum, &dirID) == noErr)
204 {
205 FSSpec file ;
206 if ( FSMakeFSSpec( vRefNum , dirID , "\p" , &file ) == noErr )
207 {
208 strDir = wxMacFSSpec2UnixFilename( &file ) + "/" ;
209 }
210 }
211 }
212 #else // Windows
213 wxChar szWinDir[MAX_PATH];
214 ::GetWindowsDirectory(szWinDir, MAX_PATH);
215
216 strDir = szWinDir;
217 strDir << wxT('\\');
218 #endif // Unix/Windows
219
220 return strDir;
221 }
222
223 wxString wxFileConfig::GetLocalDir()
224 {
225 wxString strDir;
226
227 #ifndef __WXMAC__
228 wxGetHomeDir(&strDir);
229
230 #ifndef __VMS__
231 # ifdef __UNIX__
232 if (strDir.Last() != wxT('/')) strDir << wxT('/');
233 #else
234 if (strDir.Last() != wxT('\\')) strDir << wxT('\\');
235 #endif
236 #endif
237 #else
238 // no local dir concept on mac
239 return GetGlobalDir() ;
240 #endif
241
242 return strDir;
243 }
244
245 wxString wxFileConfig::GetGlobalFileName(const wxChar *szFile)
246 {
247 wxString str = GetGlobalDir();
248 str << szFile;
249
250 if ( wxStrchr(szFile, wxT('.')) == NULL )
251 #ifdef __UNIX__
252 str << wxT(".conf");
253 #elif defined( __WXMAC__ )
254 str << " Preferences";
255 #else // Windows
256 str << wxT(".ini");
257 #endif // UNIX/Win
258
259 return str;
260 }
261
262 wxString wxFileConfig::GetLocalFileName(const wxChar *szFile)
263 {
264 #ifdef __VMS__ // On VMS I saw the problem that the home directory was appended
265 // twice for the configuration file. Does that also happen for other
266 // platforms?
267 wxString str = wxT( ' ' );
268 #else
269 wxString str = GetLocalDir();
270 #endif
271
272 #ifdef __UNIX__
273 str << wxT('.');
274 #endif
275
276 str << szFile;
277
278 #ifdef __WXMSW__
279 if ( wxStrchr(szFile, wxT('.')) == NULL )
280 str << wxT(".ini");
281 #endif
282
283
284 #ifdef __WXMAC__
285 str << " Preferences";
286 #endif
287 return str;
288 }
289
290 // ----------------------------------------------------------------------------
291 // ctor
292 // ----------------------------------------------------------------------------
293
294 void wxFileConfig::Init()
295 {
296 m_pCurrentGroup =
297 m_pRootGroup = new ConfigGroup(NULL, "", this);
298
299 m_linesHead =
300 m_linesTail = NULL;
301
302 // it's not an error if (one of the) file(s) doesn't exist
303
304 // parse the global file
305 if ( !m_strGlobalFile.IsEmpty() && wxFile::Exists(m_strGlobalFile) ) {
306 wxTextFile fileGlobal(m_strGlobalFile);
307
308 if ( fileGlobal.Open() ) {
309 Parse(fileGlobal, FALSE /* global */);
310 SetRootPath();
311 }
312 else
313 wxLogWarning(_("can't open global configuration file '%s'."),
314 m_strGlobalFile.c_str());
315 }
316
317 // parse the local file
318 if ( !m_strLocalFile.IsEmpty() && wxFile::Exists(m_strLocalFile) ) {
319 wxTextFile fileLocal(m_strLocalFile);
320 if ( fileLocal.Open() ) {
321 Parse(fileLocal, TRUE /* local */);
322 SetRootPath();
323 }
324 else
325 wxLogWarning(_("can't open user configuration file '%s'."),
326 m_strLocalFile.c_str());
327 }
328 }
329
330 // constructor supports creation of wxFileConfig objects of any type
331 wxFileConfig::wxFileConfig(const wxString& appName, const wxString& vendorName,
332 const wxString& strLocal, const wxString& strGlobal,
333 long style)
334 : wxConfigBase(::GetAppName(appName), vendorName,
335 strLocal, strGlobal,
336 style),
337 m_strLocalFile(strLocal), m_strGlobalFile(strGlobal)
338 {
339 // Make up names for files if empty
340 if ( m_strLocalFile.IsEmpty() && (style & wxCONFIG_USE_LOCAL_FILE) )
341 {
342 m_strLocalFile = GetLocalFileName(GetAppName());
343 }
344
345 if ( m_strGlobalFile.IsEmpty() && (style & wxCONFIG_USE_GLOBAL_FILE) )
346 {
347 m_strGlobalFile = GetGlobalFileName(GetAppName());
348 }
349
350 // Check if styles are not supplied, but filenames are, in which case
351 // add the correct styles.
352 if ( !m_strLocalFile.IsEmpty() )
353 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE);
354
355 if ( !m_strGlobalFile.IsEmpty() )
356 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE);
357
358 // if the path is not absolute, prepend the standard directory to it
359 // UNLESS wxCONFIG_USE_RELATIVE_PATH style is set
360 if ( !(style & wxCONFIG_USE_RELATIVE_PATH) )
361 {
362 if ( !m_strLocalFile.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile) )
363 {
364 wxString strLocal = m_strLocalFile;
365 m_strLocalFile = GetLocalDir();
366 m_strLocalFile << strLocal;
367 }
368
369 if ( !m_strGlobalFile.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile) )
370 {
371 wxString strGlobal = m_strGlobalFile;
372 m_strGlobalFile = GetGlobalDir();
373 m_strGlobalFile << strGlobal;
374 }
375 }
376
377 Init();
378 }
379
380 void wxFileConfig::CleanUp()
381 {
382 delete m_pRootGroup;
383
384 LineList *pCur = m_linesHead;
385 while ( pCur != NULL ) {
386 LineList *pNext = pCur->Next();
387 delete pCur;
388 pCur = pNext;
389 }
390 }
391
392 wxFileConfig::~wxFileConfig()
393 {
394 Flush();
395
396 CleanUp();
397 }
398
399 // ----------------------------------------------------------------------------
400 // parse a config file
401 // ----------------------------------------------------------------------------
402
403 void wxFileConfig::Parse(wxTextFile& file, bool bLocal)
404 {
405 const wxChar *pStart;
406 const wxChar *pEnd;
407 wxString strLine;
408
409 size_t nLineCount = file.GetLineCount();
410 for ( size_t n = 0; n < nLineCount; n++ ) {
411 strLine = file[n];
412
413 // add the line to linked list
414 if ( bLocal )
415 LineListAppend(strLine);
416
417 // skip leading spaces
418 for ( pStart = strLine; wxIsspace(*pStart); pStart++ )
419 ;
420
421 // skip blank/comment lines
422 if ( *pStart == wxT('\0')|| *pStart == wxT(';') || *pStart == wxT('#') )
423 continue;
424
425 if ( *pStart == wxT('[') ) { // a new group
426 pEnd = pStart;
427
428 while ( *++pEnd != wxT(']') ) {
429 if ( *pEnd == wxT('\n') || *pEnd == wxT('\0') )
430 break;
431 }
432
433 if ( *pEnd != wxT(']') ) {
434 wxLogError(_("file '%s': unexpected character %c at line %d."),
435 file.GetName(), *pEnd, n + 1);
436 continue; // skip this line
437 }
438
439 // group name here is always considered as abs path
440 wxString strGroup;
441 pStart++;
442 strGroup << wxCONFIG_PATH_SEPARATOR
443 << FilterInEntryName(wxString(pStart, pEnd - pStart));
444
445 // will create it if doesn't yet exist
446 SetPath(strGroup);
447
448 if ( bLocal )
449 m_pCurrentGroup->SetLine(m_linesTail);
450
451 // check that there is nothing except comments left on this line
452 bool bCont = TRUE;
453 while ( *++pEnd != wxT('\0') && bCont ) {
454 switch ( *pEnd ) {
455 case wxT('#'):
456 case wxT(';'):
457 bCont = FALSE;
458 break;
459
460 case wxT(' '):
461 case wxT('\t'):
462 // ignore whitespace ('\n' impossible here)
463 break;
464
465 default:
466 wxLogWarning(_("file '%s', line %d: '%s' "
467 "ignored after group header."),
468 file.GetName(), n + 1, pEnd);
469 bCont = FALSE;
470 }
471 }
472 }
473 else { // a key
474 const wxChar *pEnd = pStart;
475 while ( *pEnd != wxT('=') && !wxIsspace(*pEnd) ) {
476 if ( *pEnd == wxT('\\') ) {
477 // next character may be space or not - still take it because it's
478 // quoted
479 pEnd++;
480 }
481
482 pEnd++;
483 }
484
485 wxString strKey(FilterInEntryName(wxString(pStart, pEnd)));
486
487 // skip whitespace
488 while ( isspace(*pEnd) )
489 pEnd++;
490
491 if ( *pEnd++ != wxT('=') ) {
492 wxLogError(_("file '%s', line %d: '=' expected."),
493 file.GetName(), n + 1);
494 }
495 else {
496 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strKey);
497
498 if ( pEntry == NULL ) {
499 // new entry
500 pEntry = m_pCurrentGroup->AddEntry(strKey, n);
501
502 if ( bLocal )
503 pEntry->SetLine(m_linesTail);
504 }
505 else {
506 if ( bLocal && pEntry->IsImmutable() ) {
507 // immutable keys can't be changed by user
508 wxLogWarning(_("file '%s', line %d: value for "
509 "immutable key '%s' ignored."),
510 file.GetName(), n + 1, strKey.c_str());
511 continue;
512 }
513 // the condition below catches the cases (a) and (b) but not (c):
514 // (a) global key found second time in global file
515 // (b) key found second (or more) time in local file
516 // (c) key from global file now found in local one
517 // which is exactly what we want.
518 else if ( !bLocal || pEntry->IsLocal() ) {
519 wxLogWarning(_("file '%s', line %d: key '%s' was first "
520 "found at line %d."),
521 file.GetName(), n + 1, strKey.c_str(), pEntry->Line());
522
523 if ( bLocal )
524 pEntry->SetLine(m_linesTail);
525 }
526 }
527
528 // skip whitespace
529 while ( wxIsspace(*pEnd) )
530 pEnd++;
531
532 pEntry->SetValue(FilterInValue(pEnd), FALSE /* read from file */);
533 }
534 }
535 }
536 }
537
538 // ----------------------------------------------------------------------------
539 // set/retrieve path
540 // ----------------------------------------------------------------------------
541
542 void wxFileConfig::SetRootPath()
543 {
544 m_strPath.Empty();
545 m_pCurrentGroup = m_pRootGroup;
546 }
547
548 void wxFileConfig::SetPath(const wxString& strPath)
549 {
550 wxArrayString aParts;
551
552 if ( strPath.IsEmpty() ) {
553 SetRootPath();
554 return;
555 }
556
557 if ( strPath[0] == wxCONFIG_PATH_SEPARATOR ) {
558 // absolute path
559 wxSplitPath(aParts, strPath);
560 }
561 else {
562 // relative path, combine with current one
563 wxString strFullPath = m_strPath;
564 strFullPath << wxCONFIG_PATH_SEPARATOR << strPath;
565 wxSplitPath(aParts, strFullPath);
566 }
567
568 // change current group
569 size_t n;
570 m_pCurrentGroup = m_pRootGroup;
571 for ( n = 0; n < aParts.Count(); n++ ) {
572 ConfigGroup *pNextGroup = m_pCurrentGroup->FindSubgroup(aParts[n]);
573 if ( pNextGroup == NULL )
574 pNextGroup = m_pCurrentGroup->AddSubgroup(aParts[n]);
575 m_pCurrentGroup = pNextGroup;
576 }
577
578 // recombine path parts in one variable
579 m_strPath.Empty();
580 for ( n = 0; n < aParts.Count(); n++ ) {
581 m_strPath << wxCONFIG_PATH_SEPARATOR << aParts[n];
582 }
583 }
584
585 // ----------------------------------------------------------------------------
586 // enumeration
587 // ----------------------------------------------------------------------------
588
589 bool wxFileConfig::GetFirstGroup(wxString& str, long& lIndex) const
590 {
591 lIndex = 0;
592 return GetNextGroup(str, lIndex);
593 }
594
595 bool wxFileConfig::GetNextGroup (wxString& str, long& lIndex) const
596 {
597 if ( size_t(lIndex) < m_pCurrentGroup->Groups().Count() ) {
598 str = m_pCurrentGroup->Groups()[lIndex++]->Name();
599 return TRUE;
600 }
601 else
602 return FALSE;
603 }
604
605 bool wxFileConfig::GetFirstEntry(wxString& str, long& lIndex) const
606 {
607 lIndex = 0;
608 return GetNextEntry(str, lIndex);
609 }
610
611 bool wxFileConfig::GetNextEntry (wxString& str, long& lIndex) const
612 {
613 if ( size_t(lIndex) < m_pCurrentGroup->Entries().Count() ) {
614 str = m_pCurrentGroup->Entries()[lIndex++]->Name();
615 return TRUE;
616 }
617 else
618 return FALSE;
619 }
620
621 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive) const
622 {
623 size_t n = m_pCurrentGroup->Entries().Count();
624 if ( bRecursive ) {
625 ConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
626 size_t nSubgroups = m_pCurrentGroup->Groups().Count();
627 for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
628 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
629 n += GetNumberOfEntries(TRUE);
630 CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
631 }
632 }
633
634 return n;
635 }
636
637 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive) const
638 {
639 size_t n = m_pCurrentGroup->Groups().Count();
640 if ( bRecursive ) {
641 ConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
642 size_t nSubgroups = m_pCurrentGroup->Groups().Count();
643 for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
644 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
645 n += GetNumberOfGroups(TRUE);
646 CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
647 }
648 }
649
650 return n;
651 }
652
653 // ----------------------------------------------------------------------------
654 // tests for existence
655 // ----------------------------------------------------------------------------
656
657 bool wxFileConfig::HasGroup(const wxString& strName) const
658 {
659 wxConfigPathChanger path(this, strName);
660
661 ConfigGroup *pGroup = m_pCurrentGroup->FindSubgroup(path.Name());
662 return pGroup != NULL;
663 }
664
665 bool wxFileConfig::HasEntry(const wxString& strName) const
666 {
667 wxConfigPathChanger path(this, strName);
668
669 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
670 return pEntry != NULL;
671 }
672
673 // ----------------------------------------------------------------------------
674 // read/write values
675 // ----------------------------------------------------------------------------
676
677 bool wxFileConfig::Read(const wxString& key,
678 wxString* pStr) const
679 {
680 wxConfigPathChanger path(this, key);
681
682 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
683 if (pEntry == NULL) {
684 return FALSE;
685 }
686 else {
687 *pStr = ExpandEnvVars(pEntry->Value());
688 return TRUE;
689 }
690 }
691
692 bool wxFileConfig::Read(const wxString& key,
693 wxString* pStr, const wxString& defVal) const
694 {
695 wxConfigPathChanger path(this, key);
696
697 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
698 if (pEntry == NULL) {
699 if( IsRecordingDefaults() )
700 ((wxFileConfig *)this)->Write(key,defVal);
701 *pStr = ExpandEnvVars(defVal);
702 return FALSE;
703 }
704 else {
705 *pStr = ExpandEnvVars(pEntry->Value());
706 return TRUE;
707 }
708 }
709
710 bool wxFileConfig::Read(const wxString& key, long *pl) const
711 {
712 wxString str;
713 if ( Read(key, & str) ) {
714 *pl = wxAtol(str);
715 return TRUE;
716 }
717 else {
718 return FALSE;
719 }
720 }
721
722 bool wxFileConfig::Write(const wxString& key, const wxString& szValue)
723 {
724 wxConfigPathChanger path(this, key);
725
726 wxString strName = path.Name();
727 if ( strName.IsEmpty() ) {
728 // setting the value of a group is an error
729 wxASSERT_MSG( wxIsEmpty(szValue), wxT("can't set value of a group!") );
730
731 // ... except if it's empty in which case it's a way to force it's creation
732 m_pCurrentGroup->SetDirty();
733
734 // this will add a line for this group if it didn't have it before
735 (void)m_pCurrentGroup->GetGroupLine();
736 }
737 else {
738 // writing an entry
739
740 // check that the name is reasonable
741 if ( strName[0u] == wxCONFIG_IMMUTABLE_PREFIX ) {
742 wxLogError(_("Config entry name cannot start with '%c'."),
743 wxCONFIG_IMMUTABLE_PREFIX);
744 return FALSE;
745 }
746
747 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strName);
748 if ( pEntry == NULL )
749 pEntry = m_pCurrentGroup->AddEntry(strName);
750
751 pEntry->SetValue(szValue);
752 }
753
754 return TRUE;
755 }
756
757 bool wxFileConfig::Write(const wxString& key, long lValue)
758 {
759 // ltoa() is not ANSI :-(
760 wxString buf;
761 buf.Printf(wxT("%ld"), lValue);
762 return Write(key, buf);
763 }
764
765 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
766 {
767 if ( LineListIsEmpty() || !m_pRootGroup->IsDirty() )
768 return TRUE;
769
770 wxTempFile file(m_strLocalFile);
771
772 if ( !file.IsOpened() ) {
773 wxLogError(_("can't open user configuration file."));
774 return FALSE;
775 }
776
777 // write all strings to file
778 for ( LineList *p = m_linesHead; p != NULL; p = p->Next() ) {
779 if ( !file.Write(p->Text() + wxTextFile::GetEOL()) ) {
780 wxLogError(_("can't write user configuration file."));
781 return FALSE;
782 }
783 }
784
785 #ifndef __WXMAC__
786 return file.Commit();
787 #else
788 bool ret = file.Commit();
789 if ( ret )
790 {
791 FSSpec spec ;
792
793 wxUnixFilename2FSSpec( m_strLocalFile , &spec ) ;
794 FInfo finfo ;
795 if ( FSpGetFInfo( &spec , &finfo ) == noErr )
796 {
797 finfo.fdType = 'TEXT' ;
798 finfo.fdCreator = 'ttxt' ;
799 FSpSetFInfo( &spec , &finfo ) ;
800 }
801 }
802 return ret ;
803 #endif
804 }
805
806 // ----------------------------------------------------------------------------
807 // renaming groups/entries
808 // ----------------------------------------------------------------------------
809
810 bool wxFileConfig::RenameEntry(const wxString& oldName,
811 const wxString& newName)
812 {
813 // check that the entry exists
814 ConfigEntry *oldEntry = m_pCurrentGroup->FindEntry(oldName);
815 if ( !oldEntry )
816 return FALSE;
817
818 // check that the new entry doesn't already exist
819 if ( m_pCurrentGroup->FindEntry(newName) )
820 return FALSE;
821
822 // delete the old entry, create the new one
823 wxString value = oldEntry->Value();
824 if ( !m_pCurrentGroup->DeleteEntry(oldName) )
825 return FALSE;
826
827 ConfigEntry *newEntry = m_pCurrentGroup->AddEntry(newName);
828 newEntry->SetValue(value);
829
830 return TRUE;
831 }
832
833 bool wxFileConfig::RenameGroup(const wxString& oldName,
834 const wxString& newName)
835 {
836 // check that the group exists
837 ConfigGroup *group = m_pCurrentGroup->FindSubgroup(oldName);
838 if ( !group )
839 return FALSE;
840
841 // check that the new group doesn't already exist
842 if ( m_pCurrentGroup->FindSubgroup(newName) )
843 return FALSE;
844
845 group->Rename(newName);
846
847 return TRUE;
848 }
849
850 // ----------------------------------------------------------------------------
851 // delete groups/entries
852 // ----------------------------------------------------------------------------
853
854 bool wxFileConfig::DeleteEntry(const wxString& key, bool bGroupIfEmptyAlso)
855 {
856 wxConfigPathChanger path(this, key);
857
858 if ( !m_pCurrentGroup->DeleteEntry(path.Name()) )
859 return FALSE;
860
861 if ( bGroupIfEmptyAlso && m_pCurrentGroup->IsEmpty() ) {
862 if ( m_pCurrentGroup != m_pRootGroup ) {
863 ConfigGroup *pGroup = m_pCurrentGroup;
864 SetPath(wxT("..")); // changes m_pCurrentGroup!
865 m_pCurrentGroup->DeleteSubgroupByName(pGroup->Name());
866 }
867 //else: never delete the root group
868 }
869
870 return TRUE;
871 }
872
873 bool wxFileConfig::DeleteGroup(const wxString& key)
874 {
875 wxConfigPathChanger path(this, key);
876
877 return m_pCurrentGroup->DeleteSubgroupByName(path.Name());
878 }
879
880 bool wxFileConfig::DeleteAll()
881 {
882 CleanUp();
883
884 if ( remove(m_strLocalFile.fn_str()) == -1 )
885 wxLogSysError(_("can't delete user configuration file '%s'"), m_strLocalFile.c_str());
886
887 m_strLocalFile = m_strGlobalFile = wxT("");
888 Init();
889
890 return TRUE;
891 }
892
893 // ----------------------------------------------------------------------------
894 // linked list functions
895 // ----------------------------------------------------------------------------
896
897 // append a new line to the end of the list
898 LineList *wxFileConfig::LineListAppend(const wxString& str)
899 {
900 LineList *pLine = new LineList(str);
901
902 if ( m_linesTail == NULL ) {
903 // list is empty
904 m_linesHead = pLine;
905 }
906 else {
907 // adjust pointers
908 m_linesTail->SetNext(pLine);
909 pLine->SetPrev(m_linesTail);
910 }
911
912 m_linesTail = pLine;
913 return m_linesTail;
914 }
915
916 // insert a new line after the given one or in the very beginning if !pLine
917 LineList *wxFileConfig::LineListInsert(const wxString& str,
918 LineList *pLine)
919 {
920 if ( pLine == m_linesTail )
921 return LineListAppend(str);
922
923 LineList *pNewLine = new LineList(str);
924 if ( pLine == NULL ) {
925 // prepend to the list
926 pNewLine->SetNext(m_linesHead);
927 m_linesHead->SetPrev(pNewLine);
928 m_linesHead = pNewLine;
929 }
930 else {
931 // insert before pLine
932 LineList *pNext = pLine->Next();
933 pNewLine->SetNext(pNext);
934 pNewLine->SetPrev(pLine);
935 pNext->SetPrev(pNewLine);
936 pLine->SetNext(pNewLine);
937 }
938
939 return pNewLine;
940 }
941
942 void wxFileConfig::LineListRemove(LineList *pLine)
943 {
944 LineList *pPrev = pLine->Prev(),
945 *pNext = pLine->Next();
946
947 // first entry?
948 if ( pPrev == NULL )
949 m_linesHead = pNext;
950 else
951 pPrev->SetNext(pNext);
952
953 // last entry?
954 if ( pNext == NULL )
955 m_linesTail = pPrev;
956 else
957 pNext->SetPrev(pPrev);
958
959 delete pLine;
960 }
961
962 bool wxFileConfig::LineListIsEmpty()
963 {
964 return m_linesHead == NULL;
965 }
966
967 // ============================================================================
968 // wxFileConfig::ConfigGroup
969 // ============================================================================
970
971 // ----------------------------------------------------------------------------
972 // ctor/dtor
973 // ----------------------------------------------------------------------------
974
975 // ctor
976 ConfigGroup::ConfigGroup(ConfigGroup *pParent,
977 const wxString& strName,
978 wxFileConfig *pConfig)
979 : m_aEntries(CompareEntries),
980 m_aSubgroups(CompareGroups),
981 m_strName(strName)
982 {
983 m_pConfig = pConfig;
984 m_pParent = pParent;
985 m_bDirty = FALSE;
986 m_pLine = NULL;
987
988 m_pLastEntry = NULL;
989 m_pLastGroup = NULL;
990 }
991
992 // dtor deletes all children
993 ConfigGroup::~ConfigGroup()
994 {
995 // entries
996 size_t n, nCount = m_aEntries.Count();
997 for ( n = 0; n < nCount; n++ )
998 delete m_aEntries[n];
999
1000 // subgroups
1001 nCount = m_aSubgroups.Count();
1002 for ( n = 0; n < nCount; n++ )
1003 delete m_aSubgroups[n];
1004 }
1005
1006 // ----------------------------------------------------------------------------
1007 // line
1008 // ----------------------------------------------------------------------------
1009
1010 void ConfigGroup::SetLine(LineList *pLine)
1011 {
1012 wxASSERT( m_pLine == NULL ); // shouldn't be called twice
1013
1014 m_pLine = pLine;
1015 }
1016
1017 /*
1018 This is a bit complicated, so let me explain it in details. All lines that
1019 were read from the local file (the only one we will ever modify) are stored
1020 in a (doubly) linked list. Our problem is to know at which position in this
1021 list should we insert the new entries/subgroups. To solve it we keep three
1022 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
1023
1024 m_pLine points to the line containing "[group_name]"
1025 m_pLastEntry points to the last entry of this group in the local file.
1026 m_pLastGroup subgroup
1027
1028 Initially, they're NULL all three. When the group (an entry/subgroup) is read
1029 from the local file, the corresponding variable is set. However, if the group
1030 was read from the global file and then modified or created by the application
1031 these variables are still NULL and we need to create the corresponding lines.
1032 See the following functions (and comments preceding them) for the details of
1033 how we do it.
1034
1035 Also, when our last entry/group are deleted we need to find the new last
1036 element - the code in DeleteEntry/Subgroup does this by backtracking the list
1037 of lines until it either founds an entry/subgroup (and this is the new last
1038 element) or the m_pLine of the group, in which case there are no more entries
1039 (or subgroups) left and m_pLast<element> becomes NULL.
1040
1041 NB: This last problem could be avoided for entries if we added new entries
1042 immediately after m_pLine, but in this case the entries would appear
1043 backwards in the config file (OTOH, it's not that important) and as we
1044 would still need to do it for the subgroups the code wouldn't have been
1045 significantly less complicated.
1046 */
1047
1048 // Return the line which contains "[our name]". If we're still not in the list,
1049 // add our line to it immediately after the last line of our parent group if we
1050 // have it or in the very beginning if we're the root group.
1051 LineList *ConfigGroup::GetGroupLine()
1052 {
1053 if ( m_pLine == NULL ) {
1054 ConfigGroup *pParent = Parent();
1055
1056 // this group wasn't present in local config file, add it now
1057 if ( pParent != NULL ) {
1058 wxString strFullName;
1059 strFullName << wxT("[")
1060 // +1: no '/'
1061 << FilterOutEntryName(GetFullName().c_str() + 1)
1062 << wxT("]");
1063 m_pLine = m_pConfig->LineListInsert(strFullName,
1064 pParent->GetLastGroupLine());
1065 pParent->SetLastGroup(this); // we're surely after all the others
1066 }
1067 else {
1068 // we return NULL, so that LineListInsert() will insert us in the
1069 // very beginning
1070 }
1071 }
1072
1073 return m_pLine;
1074 }
1075
1076 // Return the last line belonging to the subgroups of this group (after which
1077 // we can add a new subgroup), if we don't have any subgroups or entries our
1078 // last line is the group line (m_pLine) itself.
1079 LineList *ConfigGroup::GetLastGroupLine()
1080 {
1081 // if we have any subgroups, our last line is the last line of the last
1082 // subgroup
1083 if ( m_pLastGroup != NULL ) {
1084 LineList *pLine = m_pLastGroup->GetLastGroupLine();
1085
1086 wxASSERT( pLine != NULL ); // last group must have !NULL associated line
1087 return pLine;
1088 }
1089
1090 // no subgroups, so the last line is the line of thelast entry (if any)
1091 return GetLastEntryLine();
1092 }
1093
1094 // return the last line belonging to the entries of this group (after which
1095 // we can add a new entry), if we don't have any entries we will add the new
1096 // one immediately after the group line itself.
1097 LineList *ConfigGroup::GetLastEntryLine()
1098 {
1099 if ( m_pLastEntry != NULL ) {
1100 LineList *pLine = m_pLastEntry->GetLine();
1101
1102 wxASSERT( pLine != NULL ); // last entry must have !NULL associated line
1103 return pLine;
1104 }
1105
1106 // no entries: insert after the group header
1107 return GetGroupLine();
1108 }
1109
1110 // ----------------------------------------------------------------------------
1111 // group name
1112 // ----------------------------------------------------------------------------
1113
1114 void ConfigGroup::Rename(const wxString& newName)
1115 {
1116 m_strName = newName;
1117
1118 LineList *line = GetGroupLine();
1119 wxString strFullName;
1120 strFullName << wxT("[") << (GetFullName().c_str() + 1) << wxT("]"); // +1: no '/'
1121 line->SetText(strFullName);
1122
1123 SetDirty();
1124 }
1125
1126 wxString ConfigGroup::GetFullName() const
1127 {
1128 if ( Parent() )
1129 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR + Name();
1130 else
1131 return wxT("");
1132 }
1133
1134 // ----------------------------------------------------------------------------
1135 // find an item
1136 // ----------------------------------------------------------------------------
1137
1138 // use binary search because the array is sorted
1139 ConfigEntry *
1140 ConfigGroup::FindEntry(const wxChar *szName) const
1141 {
1142 size_t i,
1143 lo = 0,
1144 hi = m_aEntries.Count();
1145 int res;
1146 ConfigEntry *pEntry;
1147
1148 while ( lo < hi ) {
1149 i = (lo + hi)/2;
1150 pEntry = m_aEntries[i];
1151
1152 #if wxCONFIG_CASE_SENSITIVE
1153 res = wxStrcmp(pEntry->Name(), szName);
1154 #else
1155 res = wxStricmp(pEntry->Name(), szName);
1156 #endif
1157
1158 if ( res > 0 )
1159 hi = i;
1160 else if ( res < 0 )
1161 lo = i + 1;
1162 else
1163 return pEntry;
1164 }
1165
1166 return NULL;
1167 }
1168
1169 ConfigGroup *
1170 ConfigGroup::FindSubgroup(const wxChar *szName) const
1171 {
1172 size_t i,
1173 lo = 0,
1174 hi = m_aSubgroups.Count();
1175 int res;
1176 ConfigGroup *pGroup;
1177
1178 while ( lo < hi ) {
1179 i = (lo + hi)/2;
1180 pGroup = m_aSubgroups[i];
1181
1182 #if wxCONFIG_CASE_SENSITIVE
1183 res = wxStrcmp(pGroup->Name(), szName);
1184 #else
1185 res = wxStricmp(pGroup->Name(), szName);
1186 #endif
1187
1188 if ( res > 0 )
1189 hi = i;
1190 else if ( res < 0 )
1191 lo = i + 1;
1192 else
1193 return pGroup;
1194 }
1195
1196 return NULL;
1197 }
1198
1199 // ----------------------------------------------------------------------------
1200 // create a new item
1201 // ----------------------------------------------------------------------------
1202
1203 // create a new entry and add it to the current group
1204 ConfigEntry *
1205 ConfigGroup::AddEntry(const wxString& strName, int nLine)
1206 {
1207 wxASSERT( FindEntry(strName) == NULL );
1208
1209 ConfigEntry *pEntry = new ConfigEntry(this, strName, nLine);
1210 m_aEntries.Add(pEntry);
1211
1212 return pEntry;
1213 }
1214
1215 // create a new group and add it to the current group
1216 ConfigGroup *
1217 ConfigGroup::AddSubgroup(const wxString& strName)
1218 {
1219 wxASSERT( FindSubgroup(strName) == NULL );
1220
1221 ConfigGroup *pGroup = new ConfigGroup(this, strName, m_pConfig);
1222 m_aSubgroups.Add(pGroup);
1223
1224 return pGroup;
1225 }
1226
1227 // ----------------------------------------------------------------------------
1228 // delete an item
1229 // ----------------------------------------------------------------------------
1230
1231 /*
1232 The delete operations are _very_ slow if we delete the last item of this
1233 group (see comments before GetXXXLineXXX functions for more details),
1234 so it's much better to start with the first entry/group if we want to
1235 delete several of them.
1236 */
1237
1238 bool ConfigGroup::DeleteSubgroupByName(const wxChar *szName)
1239 {
1240 return DeleteSubgroup(FindSubgroup(szName));
1241 }
1242
1243 // doesn't delete the subgroup itself, but does remove references to it from
1244 // all other data structures (and normally the returned pointer should be
1245 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1246 bool ConfigGroup::DeleteSubgroup(ConfigGroup *pGroup)
1247 {
1248 wxCHECK( pGroup != NULL, FALSE ); // deleting non existing group?
1249
1250 // delete all entries
1251 size_t nCount = pGroup->m_aEntries.Count();
1252 for ( size_t nEntry = 0; nEntry < nCount; nEntry++ ) {
1253 LineList *pLine = pGroup->m_aEntries[nEntry]->GetLine();
1254 if ( pLine != NULL )
1255 m_pConfig->LineListRemove(pLine);
1256 }
1257
1258 // and subgroups of this sungroup
1259 nCount = pGroup->m_aSubgroups.Count();
1260 for ( size_t nGroup = 0; nGroup < nCount; nGroup++ ) {
1261 pGroup->DeleteSubgroup(pGroup->m_aSubgroups[0]);
1262 }
1263
1264 LineList *pLine = pGroup->m_pLine;
1265 if ( pLine != NULL ) {
1266 // notice that we may do this test inside the previous "if" because the
1267 // last entry's line is surely !NULL
1268 if ( pGroup == m_pLastGroup ) {
1269 // our last entry is being deleted - find the last one which stays
1270 wxASSERT( m_pLine != NULL ); // we have a subgroup with !NULL pLine...
1271
1272 // go back until we find a subgroup or reach the group's line
1273 ConfigGroup *pNewLast = NULL;
1274 size_t n, nSubgroups = m_aSubgroups.Count();
1275 LineList *pl;
1276 for ( pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() ) {
1277 // is it our subgroup?
1278 for ( n = 0; (pNewLast == NULL) && (n < nSubgroups); n++ ) {
1279 // do _not_ call GetGroupLine! we don't want to add it to the local
1280 // file if it's not already there
1281 if ( m_aSubgroups[n]->m_pLine == m_pLine )
1282 pNewLast = m_aSubgroups[n];
1283 }
1284
1285 if ( pNewLast != NULL ) // found?
1286 break;
1287 }
1288
1289 if ( pl == m_pLine ) {
1290 wxASSERT( !pNewLast ); // how comes it has the same line as we?
1291
1292 // we've reached the group line without finding any subgroups
1293 m_pLastGroup = NULL;
1294 }
1295 else
1296 m_pLastGroup = pNewLast;
1297 }
1298
1299 m_pConfig->LineListRemove(pLine);
1300 }
1301
1302 SetDirty();
1303
1304 m_aSubgroups.Remove(pGroup);
1305 delete pGroup;
1306
1307 return TRUE;
1308 }
1309
1310 bool ConfigGroup::DeleteEntry(const wxChar *szName)
1311 {
1312 ConfigEntry *pEntry = FindEntry(szName);
1313 wxCHECK( pEntry != NULL, FALSE ); // deleting non existing item?
1314
1315 LineList *pLine = pEntry->GetLine();
1316 if ( pLine != NULL ) {
1317 // notice that we may do this test inside the previous "if" because the
1318 // last entry's line is surely !NULL
1319 if ( pEntry == m_pLastEntry ) {
1320 // our last entry is being deleted - find the last one which stays
1321 wxASSERT( m_pLine != NULL ); // if we have an entry with !NULL pLine...
1322
1323 // go back until we find another entry or reach the group's line
1324 ConfigEntry *pNewLast = NULL;
1325 size_t n, nEntries = m_aEntries.Count();
1326 LineList *pl;
1327 for ( pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() ) {
1328 // is it our subgroup?
1329 for ( n = 0; (pNewLast == NULL) && (n < nEntries); n++ ) {
1330 if ( m_aEntries[n]->GetLine() == m_pLine )
1331 pNewLast = m_aEntries[n];
1332 }
1333
1334 if ( pNewLast != NULL ) // found?
1335 break;
1336 }
1337
1338 if ( pl == m_pLine ) {
1339 wxASSERT( !pNewLast ); // how comes it has the same line as we?
1340
1341 // we've reached the group line without finding any subgroups
1342 m_pLastEntry = NULL;
1343 }
1344 else
1345 m_pLastEntry = pNewLast;
1346 }
1347
1348 m_pConfig->LineListRemove(pLine);
1349 }
1350
1351 // we must be written back for the changes to be saved
1352 SetDirty();
1353
1354 m_aEntries.Remove(pEntry);
1355 delete pEntry;
1356
1357 return TRUE;
1358 }
1359
1360 // ----------------------------------------------------------------------------
1361 //
1362 // ----------------------------------------------------------------------------
1363 void ConfigGroup::SetDirty()
1364 {
1365 m_bDirty = TRUE;
1366 if ( Parent() != NULL ) // propagate upwards
1367 Parent()->SetDirty();
1368 }
1369
1370 // ============================================================================
1371 // wxFileConfig::ConfigEntry
1372 // ============================================================================
1373
1374 // ----------------------------------------------------------------------------
1375 // ctor
1376 // ----------------------------------------------------------------------------
1377 ConfigEntry::ConfigEntry(ConfigGroup *pParent,
1378 const wxString& strName,
1379 int nLine)
1380 : m_strName(strName)
1381 {
1382 wxASSERT( !strName.IsEmpty() );
1383
1384 m_pParent = pParent;
1385 m_nLine = nLine;
1386 m_pLine = NULL;
1387
1388 m_bDirty = FALSE;
1389
1390 m_bImmutable = strName[0] == wxCONFIG_IMMUTABLE_PREFIX;
1391 if ( m_bImmutable )
1392 m_strName.erase(0, 1); // remove first character
1393 }
1394
1395 // ----------------------------------------------------------------------------
1396 // set value
1397 // ----------------------------------------------------------------------------
1398
1399 void ConfigEntry::SetLine(LineList *pLine)
1400 {
1401 if ( m_pLine != NULL ) {
1402 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1403 Name().c_str(), m_pParent->GetFullName().c_str());
1404 }
1405
1406 m_pLine = pLine;
1407 Group()->SetLastEntry(this);
1408 }
1409
1410 // second parameter is FALSE if we read the value from file and prevents the
1411 // entry from being marked as 'dirty'
1412 void ConfigEntry::SetValue(const wxString& strValue, bool bUser)
1413 {
1414 if ( bUser && IsImmutable() ) {
1415 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1416 Name().c_str());
1417 return;
1418 }
1419
1420 // do nothing if it's the same value
1421 if ( strValue == m_strValue )
1422 return;
1423
1424 m_strValue = strValue;
1425
1426 if ( bUser ) {
1427 wxString strVal = FilterOutValue(strValue);
1428 wxString strLine;
1429 strLine << FilterOutEntryName(m_strName) << wxT('=') << strVal;
1430
1431 if ( m_pLine != NULL ) {
1432 // entry was read from the local config file, just modify the line
1433 m_pLine->SetText(strLine);
1434 }
1435 else {
1436 // add a new line to the file
1437 wxASSERT( m_nLine == wxNOT_FOUND ); // consistency check
1438
1439 m_pLine = Group()->Config()->LineListInsert(strLine,
1440 Group()->GetLastEntryLine());
1441 Group()->SetLastEntry(this);
1442 }
1443
1444 SetDirty();
1445 }
1446 }
1447
1448 void ConfigEntry::SetDirty()
1449 {
1450 m_bDirty = TRUE;
1451 Group()->SetDirty();
1452 }
1453
1454 // ============================================================================
1455 // global functions
1456 // ============================================================================
1457
1458 // ----------------------------------------------------------------------------
1459 // compare functions for array sorting
1460 // ----------------------------------------------------------------------------
1461
1462 int CompareEntries(ConfigEntry *p1,
1463 ConfigEntry *p2)
1464 {
1465 #if wxCONFIG_CASE_SENSITIVE
1466 return wxStrcmp(p1->Name(), p2->Name());
1467 #else
1468 return wxStricmp(p1->Name(), p2->Name());
1469 #endif
1470 }
1471
1472 int CompareGroups(ConfigGroup *p1,
1473 ConfigGroup *p2)
1474 {
1475 #if wxCONFIG_CASE_SENSITIVE
1476 return wxStrcmp(p1->Name(), p2->Name());
1477 #else
1478 return wxStricmp(p1->Name(), p2->Name());
1479 #endif
1480 }
1481
1482 // ----------------------------------------------------------------------------
1483 // filter functions
1484 // ----------------------------------------------------------------------------
1485
1486 // undo FilterOutValue
1487 static wxString FilterInValue(const wxString& str)
1488 {
1489 wxString strResult;
1490 strResult.Alloc(str.Len());
1491
1492 bool bQuoted = !str.IsEmpty() && str[0] == '"';
1493
1494 for ( size_t n = bQuoted ? 1 : 0; n < str.Len(); n++ ) {
1495 if ( str[n] == wxT('\\') ) {
1496 switch ( str[++n] ) {
1497 case wxT('n'):
1498 strResult += wxT('\n');
1499 break;
1500
1501 case wxT('r'):
1502 strResult += wxT('\r');
1503 break;
1504
1505 case wxT('t'):
1506 strResult += wxT('\t');
1507 break;
1508
1509 case wxT('\\'):
1510 strResult += wxT('\\');
1511 break;
1512
1513 case wxT('"'):
1514 strResult += wxT('"');
1515 break;
1516 }
1517 }
1518 else {
1519 if ( str[n] != wxT('"') || !bQuoted )
1520 strResult += str[n];
1521 else if ( n != str.Len() - 1 ) {
1522 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1523 n, str.c_str());
1524 }
1525 //else: it's the last quote of a quoted string, ok
1526 }
1527 }
1528
1529 return strResult;
1530 }
1531
1532 // quote the string before writing it to file
1533 static wxString FilterOutValue(const wxString& str)
1534 {
1535 if ( !str )
1536 return str;
1537
1538 wxString strResult;
1539 strResult.Alloc(str.Len());
1540
1541 // quoting is necessary to preserve spaces in the beginning of the string
1542 bool bQuote = wxIsspace(str[0]) || str[0] == wxT('"');
1543
1544 if ( bQuote )
1545 strResult += wxT('"');
1546
1547 wxChar c;
1548 for ( size_t n = 0; n < str.Len(); n++ ) {
1549 switch ( str[n] ) {
1550 case wxT('\n'):
1551 c = wxT('n');
1552 break;
1553
1554 case wxT('\r'):
1555 c = wxT('r');
1556 break;
1557
1558 case wxT('\t'):
1559 c = wxT('t');
1560 break;
1561
1562 case wxT('\\'):
1563 c = wxT('\\');
1564 break;
1565
1566 case wxT('"'):
1567 if ( bQuote ) {
1568 c = wxT('"');
1569 break;
1570 }
1571 //else: fall through
1572
1573 default:
1574 strResult += str[n];
1575 continue; // nothing special to do
1576 }
1577
1578 // we get here only for special characters
1579 strResult << wxT('\\') << c;
1580 }
1581
1582 if ( bQuote )
1583 strResult += wxT('"');
1584
1585 return strResult;
1586 }
1587
1588 // undo FilterOutEntryName
1589 static wxString FilterInEntryName(const wxString& str)
1590 {
1591 wxString strResult;
1592 strResult.Alloc(str.Len());
1593
1594 for ( const wxChar *pc = str.c_str(); *pc != '\0'; pc++ ) {
1595 if ( *pc == wxT('\\') )
1596 pc++;
1597
1598 strResult += *pc;
1599 }
1600
1601 return strResult;
1602 }
1603
1604 // sanitize entry or group name: insert '\\' before any special characters
1605 static wxString FilterOutEntryName(const wxString& str)
1606 {
1607 wxString strResult;
1608 strResult.Alloc(str.Len());
1609
1610 for ( const wxChar *pc = str.c_str(); *pc != wxT('\0'); pc++ ) {
1611 wxChar c = *pc;
1612
1613 // we explicitly allow some of "safe" chars and 8bit ASCII characters
1614 // which will probably never have special meaning
1615 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
1616 // should *not* be quoted
1617 if ( !wxIsalnum(c) && !wxStrchr(wxT("@_/-!.*%"), c) && ((c & 0x80) == 0) )
1618 strResult += wxT('\\');
1619
1620 strResult += c;
1621 }
1622
1623 return strResult;
1624 }
1625
1626 // we can't put ?: in the ctor initializer list because it confuses some
1627 // broken compilers (Borland C++)
1628 static wxString GetAppName(const wxString& appName)
1629 {
1630 if ( !appName && wxTheApp )
1631 return wxTheApp->GetAppName();
1632 else
1633 return appName;
1634 }
1635
1636 #endif // wxUSE_CONFIG
1637