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