1 ///////////////////////////////////////////////////////////////////////////////
3 // Purpose: implementation of wxFileConfig derivation of wxConfig
4 // Author: Vadim Zeitlin
6 // Created: 07.04.98 (adapted from appconf.cpp)
8 // Copyright: (c) 1997 Karsten Ballüder & Vadim Zeitlin
9 // Ballueder@usa.net <zeitlin@dptmaths.ens-cachan.fr>
10 // Licence: wxWindows license
11 ///////////////////////////////////////////////////////////////////////////////
14 #pragma implementation "fileconf.h"
17 // ----------------------------------------------------------------------------
19 // ----------------------------------------------------------------------------
21 #include "wx/wxprec.h"
30 #include "wx/string.h"
35 #include "wx/dynarray.h"
38 #include "wx/textfile.h"
39 #include "wx/config.h"
40 #include "wx/fileconf.h"
42 #include "wx/utils.h" // for wxGetHomeDir
44 // _WINDOWS_ is defined when windows.h is included,
45 // __WXMSW__ is defined for MS Windows compilation
46 #if defined(__WXMSW__) && !defined(_WINDOWS_)
57 // headers needed for umask()
59 #include <sys/types.h>
63 // ----------------------------------------------------------------------------
65 // ----------------------------------------------------------------------------
66 #define CONST_CAST ((wxFileConfig *)this)->
68 // ----------------------------------------------------------------------------
70 // ----------------------------------------------------------------------------
76 // ----------------------------------------------------------------------------
77 // global functions declarations
78 // ----------------------------------------------------------------------------
80 // compare functions for sorting the arrays
81 static int LINKAGEMODE
CompareEntries(wxFileConfigEntry
*p1
, wxFileConfigEntry
*p2
);
82 static int LINKAGEMODE
CompareGroups(wxFileConfigGroup
*p1
, wxFileConfigGroup
*p2
);
85 static wxString
FilterInValue(const wxString
& str
);
86 static wxString
FilterOutValue(const wxString
& str
);
88 static wxString
FilterInEntryName(const wxString
& str
);
89 static wxString
FilterOutEntryName(const wxString
& str
);
91 // get the name to use in wxFileConfig ctor
92 static wxString
GetAppName(const wxString
& appname
);
94 // ============================================================================
96 // ============================================================================
98 // ----------------------------------------------------------------------------
99 // "template" array types
100 // ----------------------------------------------------------------------------
102 WX_DEFINE_SORTED_EXPORTED_ARRAY(wxFileConfigEntry
*, ArrayEntries
);
103 WX_DEFINE_SORTED_EXPORTED_ARRAY(wxFileConfigGroup
*, ArrayGroups
);
105 // ----------------------------------------------------------------------------
106 // wxFileConfigLineList
107 // ----------------------------------------------------------------------------
109 // we store all lines of the local config file as a linked list in memory
110 class wxFileConfigLineList
113 void SetNext(wxFileConfigLineList
*pNext
) { m_pNext
= pNext
; }
114 void SetPrev(wxFileConfigLineList
*pPrev
) { m_pPrev
= pPrev
; }
117 wxFileConfigLineList(const wxString
& str
,
118 wxFileConfigLineList
*pNext
= NULL
) : m_strLine(str
)
119 { SetNext(pNext
); SetPrev(NULL
); }
121 // next/prev nodes in the linked list
122 wxFileConfigLineList
*Next() const { return m_pNext
; }
123 wxFileConfigLineList
*Prev() const { return m_pPrev
; }
125 // get/change lines text
126 void SetText(const wxString
& str
) { m_strLine
= str
; }
127 const wxString
& Text() const { return m_strLine
; }
130 wxString m_strLine
; // line contents
131 wxFileConfigLineList
*m_pNext
, // next node
132 *m_pPrev
; // previous one
135 // ----------------------------------------------------------------------------
136 // wxFileConfigEntry: a name/value pair
137 // ----------------------------------------------------------------------------
139 class wxFileConfigEntry
142 wxFileConfigGroup
*m_pParent
; // group that contains us
144 wxString m_strName
, // entry name
146 bool m_bDirty
:1, // changed since last read?
147 m_bImmutable
:1, // can be overriden locally?
148 m_bHasValue
:1; // set after first call to SetValue()
150 int m_nLine
; // used if m_pLine == NULL only
152 // pointer to our line in the linked list or NULL if it was found in global
153 // file (which we don't modify)
154 wxFileConfigLineList
*m_pLine
;
157 wxFileConfigEntry(wxFileConfigGroup
*pParent
,
158 const wxString
& strName
, int nLine
);
161 const wxString
& Name() const { return m_strName
; }
162 const wxString
& Value() const { return m_strValue
; }
163 wxFileConfigGroup
*Group() const { return m_pParent
; }
164 bool IsDirty() const { return m_bDirty
; }
165 bool IsImmutable() const { return m_bImmutable
; }
166 bool IsLocal() const { return m_pLine
!= 0; }
167 int Line() const { return m_nLine
; }
168 wxFileConfigLineList
*
169 GetLine() const { return m_pLine
; }
171 // modify entry attributes
172 void SetValue(const wxString
& strValue
, bool bUser
= TRUE
);
174 void SetLine(wxFileConfigLineList
*pLine
);
177 // ----------------------------------------------------------------------------
178 // wxFileConfigGroup: container of entries and other groups
179 // ----------------------------------------------------------------------------
181 class wxFileConfigGroup
184 wxFileConfig
*m_pConfig
; // config object we belong to
185 wxFileConfigGroup
*m_pParent
; // parent group (NULL for root group)
186 ArrayEntries m_aEntries
; // entries in this group
187 ArrayGroups m_aSubgroups
; // subgroups
188 wxString m_strName
; // group's name
189 bool m_bDirty
; // if FALSE => all subgroups are not dirty
190 wxFileConfigLineList
*m_pLine
; // pointer to our line in the linked list
191 wxFileConfigEntry
*m_pLastEntry
; // last entry/subgroup of this group in the
192 wxFileConfigGroup
*m_pLastGroup
; // local file (we insert new ones after it)
194 // DeleteSubgroupByName helper
195 bool DeleteSubgroup(wxFileConfigGroup
*pGroup
);
199 wxFileConfigGroup(wxFileConfigGroup
*pParent
, const wxString
& strName
, wxFileConfig
*);
201 // dtor deletes all entries and subgroups also
202 ~wxFileConfigGroup();
205 const wxString
& Name() const { return m_strName
; }
206 wxFileConfigGroup
*Parent() const { return m_pParent
; }
207 wxFileConfig
*Config() const { return m_pConfig
; }
208 bool IsDirty() const { return m_bDirty
; }
210 const ArrayEntries
& Entries() const { return m_aEntries
; }
211 const ArrayGroups
& Groups() const { return m_aSubgroups
; }
212 bool IsEmpty() const { return Entries().IsEmpty() && Groups().IsEmpty(); }
214 // find entry/subgroup (NULL if not found)
215 wxFileConfigGroup
*FindSubgroup(const wxChar
*szName
) const;
216 wxFileConfigEntry
*FindEntry (const wxChar
*szName
) const;
218 // delete entry/subgroup, return FALSE if doesn't exist
219 bool DeleteSubgroupByName(const wxChar
*szName
);
220 bool DeleteEntry(const wxChar
*szName
);
222 // create new entry/subgroup returning pointer to newly created element
223 wxFileConfigGroup
*AddSubgroup(const wxString
& strName
);
224 wxFileConfigEntry
*AddEntry (const wxString
& strName
, int nLine
= wxNOT_FOUND
);
226 // will also recursively set parent's dirty flag
228 void SetLine(wxFileConfigLineList
*pLine
);
230 // rename: no checks are done to ensure that the name is unique!
231 void Rename(const wxString
& newName
);
234 wxString
GetFullName() const;
236 // get the last line belonging to an entry/subgroup of this group
237 wxFileConfigLineList
*GetGroupLine(); // line which contains [group]
238 wxFileConfigLineList
*GetLastEntryLine(); // after which our subgroups start
239 wxFileConfigLineList
*GetLastGroupLine(); // after which the next group starts
241 // called by entries/subgroups when they're created/deleted
242 void SetLastEntry(wxFileConfigEntry
*pEntry
) { m_pLastEntry
= pEntry
; }
243 void SetLastGroup(wxFileConfigGroup
*pGroup
) { m_pLastGroup
= pGroup
; }
246 // ============================================================================
248 // ============================================================================
250 // ----------------------------------------------------------------------------
252 // ----------------------------------------------------------------------------
253 wxString
wxFileConfig::GetGlobalDir()
257 #ifdef __VMS__ // Note if __VMS is defined __UNIX is also defined
258 strDir
= wxT("sys$manager:");
259 #elif defined(__WXMAC__)
260 strDir
= wxMacFindFolder( (short) kOnSystemDisk
, kPreferencesFolderType
, kDontCreateFolder
) ;
261 #elif defined( __UNIX__ )
262 strDir
= wxT("/etc/");
263 #elif defined(__WXPM__)
264 ULONG aulSysInfo
[QSV_MAX
] = {0};
268 rc
= DosQuerySysInfo( 1L, QSV_MAX
, (PVOID
)aulSysInfo
, sizeof(ULONG
)*QSV_MAX
);
271 drive
= aulSysInfo
[QSV_BOOT_DRIVE
- 1];
275 strDir
= "A:\\OS2\\";
278 strDir
= "B:\\OS2\\";
281 strDir
= "C:\\OS2\\";
284 strDir
= "D:\\OS2\\";
287 strDir
= "E:\\OS2\\";
290 strDir
= "F:\\OS2\\";
293 strDir
= "G:\\OS2\\";
296 strDir
= "H:\\OS2\\";
299 strDir
= "I:\\OS2\\";
302 strDir
= "J:\\OS2\\";
305 strDir
= "K:\\OS2\\";
308 strDir
= "L:\\OS2\\";
311 strDir
= "M:\\OS2\\";
314 strDir
= "N:\\OS2\\";
317 strDir
= "O:\\OS2\\";
320 strDir
= "P:\\OS2\\";
323 strDir
= "Q:\\OS2\\";
326 strDir
= "R:\\OS2\\";
329 strDir
= "S:\\OS2\\";
332 strDir
= "T:\\OS2\\";
335 strDir
= "U:\\OS2\\";
338 strDir
= "V:\\OS2\\";
341 strDir
= "W:\\OS2\\";
344 strDir
= "X:\\OS2\\";
347 strDir
= "Y:\\OS2\\";
350 strDir
= "Z:\\OS2\\";
354 #elif defined(__WXSTUBS__)
355 wxASSERT_MSG( FALSE
, wxT("TODO") ) ;
357 wxChar szWinDir
[MAX_PATH
];
358 ::GetWindowsDirectory(szWinDir
, MAX_PATH
);
362 #endif // Unix/Windows
367 wxString
wxFileConfig::GetLocalDir()
371 #if defined(__WXMAC__)
372 // no local dir concept on Mac OS 9
373 return GetGlobalDir() ;
375 wxGetHomeDir(&strDir
);
379 if (strDir
.Last() != wxT(']'))
381 if (strDir
.Last() != wxT('/')) strDir
<< wxT('/');
383 if (strDir
.Last() != wxT('\\')) strDir
<< wxT('\\');
390 wxString
wxFileConfig::GetGlobalFileName(const wxChar
*szFile
)
392 wxString str
= GetGlobalDir();
395 if ( wxStrchr(szFile
, wxT('.')) == NULL
)
396 #if defined( __WXMAC__ )
397 str
<< " Preferences";
398 #elif defined( __UNIX__ )
407 wxString
wxFileConfig::GetLocalFileName(const wxChar
*szFile
)
409 #ifdef __VMS__ // On VMS I saw the problem that the home directory was appended
410 // twice for the configuration file. Does that also happen for other
412 wxString str
= wxT( '.' );
414 wxString str
= GetLocalDir();
417 #if defined( __UNIX__ ) && !defined( __VMS ) && !defined( __WXMAC__ )
424 if ( wxStrchr(szFile
, wxT('.')) == NULL
)
429 str
<< " Preferences";
434 // ----------------------------------------------------------------------------
436 // ----------------------------------------------------------------------------
438 void wxFileConfig::Init()
441 m_pRootGroup
= new wxFileConfigGroup(NULL
, "", this);
446 // it's not an error if (one of the) file(s) doesn't exist
448 // parse the global file
449 if ( !m_strGlobalFile
.IsEmpty() && wxFile::Exists(m_strGlobalFile
) ) {
450 wxTextFile
fileGlobal(m_strGlobalFile
);
452 if ( fileGlobal
.Open() ) {
453 Parse(fileGlobal
, FALSE
/* global */);
457 wxLogWarning(_("can't open global configuration file '%s'."),
458 m_strGlobalFile
.c_str());
461 // parse the local file
462 if ( !m_strLocalFile
.IsEmpty() && wxFile::Exists(m_strLocalFile
) ) {
463 wxTextFile
fileLocal(m_strLocalFile
);
464 if ( fileLocal
.Open() ) {
465 Parse(fileLocal
, TRUE
/* local */);
469 wxLogWarning(_("can't open user configuration file '%s'."),
470 m_strLocalFile
.c_str());
474 // constructor supports creation of wxFileConfig objects of any type
475 wxFileConfig::wxFileConfig(const wxString
& appName
, const wxString
& vendorName
,
476 const wxString
& strLocal
, const wxString
& strGlobal
,
478 : wxConfigBase(::GetAppName(appName
), vendorName
,
481 m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
483 // Make up names for files if empty
484 if ( m_strLocalFile
.IsEmpty() && (style
& wxCONFIG_USE_LOCAL_FILE
) )
486 m_strLocalFile
= GetLocalFileName(GetAppName());
489 if ( m_strGlobalFile
.IsEmpty() && (style
& wxCONFIG_USE_GLOBAL_FILE
) )
491 m_strGlobalFile
= GetGlobalFileName(GetAppName());
494 // Check if styles are not supplied, but filenames are, in which case
495 // add the correct styles.
496 if ( !m_strLocalFile
.IsEmpty() )
497 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE
);
499 if ( !m_strGlobalFile
.IsEmpty() )
500 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE
);
502 // if the path is not absolute, prepend the standard directory to it
503 // UNLESS wxCONFIG_USE_RELATIVE_PATH style is set
504 if ( !(style
& wxCONFIG_USE_RELATIVE_PATH
) )
506 if ( !m_strLocalFile
.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile
) )
508 wxString strLocal
= m_strLocalFile
;
509 m_strLocalFile
= GetLocalDir();
510 m_strLocalFile
<< strLocal
;
513 if ( !m_strGlobalFile
.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile
) )
515 wxString strGlobal
= m_strGlobalFile
;
516 m_strGlobalFile
= GetGlobalDir();
517 m_strGlobalFile
<< strGlobal
;
526 void wxFileConfig::CleanUp()
530 wxFileConfigLineList
*pCur
= m_linesHead
;
531 while ( pCur
!= NULL
) {
532 wxFileConfigLineList
*pNext
= pCur
->Next();
538 wxFileConfig::~wxFileConfig()
545 // ----------------------------------------------------------------------------
546 // parse a config file
547 // ----------------------------------------------------------------------------
549 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
551 const wxChar
*pStart
;
555 size_t nLineCount
= file
.GetLineCount();
556 for ( size_t n
= 0; n
< nLineCount
; n
++ ) {
559 // add the line to linked list
561 LineListAppend(strLine
);
563 // skip leading spaces
564 for ( pStart
= strLine
; wxIsspace(*pStart
); pStart
++ )
567 // skip blank/comment lines
568 if ( *pStart
== wxT('\0')|| *pStart
== wxT(';') || *pStart
== wxT('#') )
571 if ( *pStart
== wxT('[') ) { // a new group
574 while ( *++pEnd
!= wxT(']') ) {
575 if ( *pEnd
== wxT('\\') ) {
576 // the next char is escaped, so skip it even if it is ']'
580 if ( *pEnd
== wxT('\n') || *pEnd
== wxT('\0') ) {
581 // we reached the end of line, break out of the loop
586 if ( *pEnd
!= wxT(']') ) {
587 wxLogError(_("file '%s': unexpected character %c at line %d."),
588 file
.GetName(), *pEnd
, n
+ 1);
589 continue; // skip this line
592 // group name here is always considered as abs path
595 strGroup
<< wxCONFIG_PATH_SEPARATOR
596 << FilterInEntryName(wxString(pStart
, pEnd
- pStart
));
598 // will create it if doesn't yet exist
602 m_pCurrentGroup
->SetLine(m_linesTail
);
604 // check that there is nothing except comments left on this line
606 while ( *++pEnd
!= wxT('\0') && bCont
) {
615 // ignore whitespace ('\n' impossible here)
619 wxLogWarning(_("file '%s', line %d: '%s' ignored after group header."),
620 file
.GetName(), n
+ 1, pEnd
);
626 const wxChar
*pEnd
= pStart
;
627 while ( *pEnd
&& *pEnd
!= wxT('=') && !wxIsspace(*pEnd
) ) {
628 if ( *pEnd
== wxT('\\') ) {
629 // next character may be space or not - still take it because it's
630 // quoted (unless there is nothing)
633 // the error message will be given below anyhow
641 wxString
strKey(FilterInEntryName(wxString(pStart
, pEnd
)));
644 while ( wxIsspace(*pEnd
) )
647 if ( *pEnd
++ != wxT('=') ) {
648 wxLogError(_("file '%s', line %d: '=' expected."),
649 file
.GetName(), n
+ 1);
652 wxFileConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
654 if ( pEntry
== NULL
) {
656 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
659 pEntry
->SetLine(m_linesTail
);
662 if ( bLocal
&& pEntry
->IsImmutable() ) {
663 // immutable keys can't be changed by user
664 wxLogWarning(_("file '%s', line %d: value for immutable key '%s' ignored."),
665 file
.GetName(), n
+ 1, strKey
.c_str());
668 // the condition below catches the cases (a) and (b) but not (c):
669 // (a) global key found second time in global file
670 // (b) key found second (or more) time in local file
671 // (c) key from global file now found in local one
672 // which is exactly what we want.
673 else if ( !bLocal
|| pEntry
->IsLocal() ) {
674 wxLogWarning(_("file '%s', line %d: key '%s' was first found at line %d."),
675 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
678 pEntry
->SetLine(m_linesTail
);
683 while ( wxIsspace(*pEnd
) )
686 pEntry
->SetValue(FilterInValue(pEnd
), FALSE
/* read from file */);
692 // ----------------------------------------------------------------------------
694 // ----------------------------------------------------------------------------
696 void wxFileConfig::SetRootPath()
699 m_pCurrentGroup
= m_pRootGroup
;
702 void wxFileConfig::SetPath(const wxString
& strPath
)
704 wxArrayString aParts
;
706 if ( strPath
.IsEmpty() ) {
711 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
713 wxSplitPath(aParts
, strPath
);
716 // relative path, combine with current one
717 wxString strFullPath
= m_strPath
;
718 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
719 wxSplitPath(aParts
, strFullPath
);
722 // change current group
724 m_pCurrentGroup
= m_pRootGroup
;
725 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
726 wxFileConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
727 if ( pNextGroup
== NULL
)
728 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
729 m_pCurrentGroup
= pNextGroup
;
732 // recombine path parts in one variable
734 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
735 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
739 // ----------------------------------------------------------------------------
741 // ----------------------------------------------------------------------------
743 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
746 return GetNextGroup(str
, lIndex
);
749 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
751 if ( size_t(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
752 str
= m_pCurrentGroup
->Groups()[(size_t)lIndex
++]->Name();
759 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
762 return GetNextEntry(str
, lIndex
);
765 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
767 if ( size_t(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
768 str
= m_pCurrentGroup
->Entries()[(size_t)lIndex
++]->Name();
775 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
777 size_t n
= m_pCurrentGroup
->Entries().Count();
779 wxFileConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
780 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
781 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
782 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
783 n
+= GetNumberOfEntries(TRUE
);
784 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
791 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
793 size_t n
= m_pCurrentGroup
->Groups().Count();
795 wxFileConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
796 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
797 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
798 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
799 n
+= GetNumberOfGroups(TRUE
);
800 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
807 // ----------------------------------------------------------------------------
808 // tests for existence
809 // ----------------------------------------------------------------------------
811 bool wxFileConfig::HasGroup(const wxString
& strName
) const
813 wxConfigPathChanger
path(this, strName
);
815 wxFileConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
816 return pGroup
!= NULL
;
819 bool wxFileConfig::HasEntry(const wxString
& strName
) const
821 wxConfigPathChanger
path(this, strName
);
823 wxFileConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
824 return pEntry
!= NULL
;
827 // ----------------------------------------------------------------------------
829 // ----------------------------------------------------------------------------
831 bool wxFileConfig::Read(const wxString
& key
,
832 wxString
* pStr
) const
834 wxConfigPathChanger
path(this, key
);
836 wxFileConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
837 if (pEntry
== NULL
) {
841 *pStr
= ExpandEnvVars(pEntry
->Value());
845 bool wxFileConfig::Read(const wxString
& key
,
846 wxString
* pStr
, const wxString
& defVal
) const
848 wxConfigPathChanger
path(this, key
);
850 wxFileConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
852 if (pEntry
== NULL
) {
853 if( IsRecordingDefaults() )
854 ((wxFileConfig
*)this)->Write(key
,defVal
);
855 *pStr
= ExpandEnvVars(defVal
);
859 *pStr
= ExpandEnvVars(pEntry
->Value());
866 bool wxFileConfig::Read(const wxString
& key
, long *pl
) const
869 if ( !Read(key
, & str
) )
878 bool wxFileConfig::Write(const wxString
& key
, const wxString
& szValue
)
880 wxConfigPathChanger
path(this, key
);
882 wxString strName
= path
.Name();
883 if ( strName
.IsEmpty() ) {
884 // setting the value of a group is an error
885 wxASSERT_MSG( wxIsEmpty(szValue
), wxT("can't set value of a group!") );
887 // ... except if it's empty in which case it's a way to force it's creation
888 m_pCurrentGroup
->SetDirty();
890 // this will add a line for this group if it didn't have it before
891 (void)m_pCurrentGroup
->GetGroupLine();
896 // check that the name is reasonable
897 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
898 wxLogError(_("Config entry name cannot start with '%c'."),
899 wxCONFIG_IMMUTABLE_PREFIX
);
903 wxFileConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
904 if ( pEntry
== NULL
)
905 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
907 pEntry
->SetValue(szValue
);
913 bool wxFileConfig::Write(const wxString
& key
, long lValue
)
915 // ltoa() is not ANSI :-(
917 buf
.Printf(wxT("%ld"), lValue
);
918 return Write(key
, buf
);
921 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
923 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() || !m_strLocalFile
)
927 // set the umask if needed
931 umaskOld
= umask((mode_t
)m_umask
);
935 wxTempFile
file(m_strLocalFile
);
937 if ( !file
.IsOpened() ) {
938 wxLogError(_("can't open user configuration file."));
942 // write all strings to file
943 for ( wxFileConfigLineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
944 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
945 wxLogError(_("can't write user configuration file."));
950 bool ret
= file
.Commit();
952 #if defined(__WXMAC__)
957 wxMacFilename2FSSpec( m_strLocalFile
, &spec
) ;
959 if ( FSpGetFInfo( &spec
, &finfo
) == noErr
)
961 finfo
.fdType
= 'TEXT' ;
962 finfo
.fdCreator
= 'ttxt' ;
963 FSpSetFInfo( &spec
, &finfo
) ;
969 // restore the old umask if we changed it
972 (void)umask(umaskOld
);
979 // ----------------------------------------------------------------------------
980 // renaming groups/entries
981 // ----------------------------------------------------------------------------
983 bool wxFileConfig::RenameEntry(const wxString
& oldName
,
984 const wxString
& newName
)
986 // check that the entry exists
987 wxFileConfigEntry
*oldEntry
= m_pCurrentGroup
->FindEntry(oldName
);
991 // check that the new entry doesn't already exist
992 if ( m_pCurrentGroup
->FindEntry(newName
) )
995 // delete the old entry, create the new one
996 wxString value
= oldEntry
->Value();
997 if ( !m_pCurrentGroup
->DeleteEntry(oldName
) )
1000 wxFileConfigEntry
*newEntry
= m_pCurrentGroup
->AddEntry(newName
);
1001 newEntry
->SetValue(value
);
1006 bool wxFileConfig::RenameGroup(const wxString
& oldName
,
1007 const wxString
& newName
)
1009 // check that the group exists
1010 wxFileConfigGroup
*group
= m_pCurrentGroup
->FindSubgroup(oldName
);
1014 // check that the new group doesn't already exist
1015 if ( m_pCurrentGroup
->FindSubgroup(newName
) )
1018 group
->Rename(newName
);
1023 // ----------------------------------------------------------------------------
1024 // delete groups/entries
1025 // ----------------------------------------------------------------------------
1027 bool wxFileConfig::DeleteEntry(const wxString
& key
, bool bGroupIfEmptyAlso
)
1029 wxConfigPathChanger
path(this, key
);
1031 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
1034 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
1035 if ( m_pCurrentGroup
!= m_pRootGroup
) {
1036 wxFileConfigGroup
*pGroup
= m_pCurrentGroup
;
1037 SetPath(wxT("..")); // changes m_pCurrentGroup!
1038 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
1040 //else: never delete the root group
1046 bool wxFileConfig::DeleteGroup(const wxString
& key
)
1048 wxConfigPathChanger
path(this, key
);
1050 return m_pCurrentGroup
->DeleteSubgroupByName(path
.Name());
1053 bool wxFileConfig::DeleteAll()
1057 if ( wxRemove(m_strLocalFile
) == -1 )
1058 wxLogSysError(_("can't delete user configuration file '%s'"), m_strLocalFile
.c_str());
1060 m_strLocalFile
= m_strGlobalFile
= wxT("");
1066 // ----------------------------------------------------------------------------
1067 // linked list functions
1068 // ----------------------------------------------------------------------------
1070 // append a new line to the end of the list
1071 wxFileConfigLineList
*wxFileConfig::LineListAppend(const wxString
& str
)
1073 wxFileConfigLineList
*pLine
= new wxFileConfigLineList(str
);
1075 if ( m_linesTail
== NULL
) {
1077 m_linesHead
= pLine
;
1081 m_linesTail
->SetNext(pLine
);
1082 pLine
->SetPrev(m_linesTail
);
1085 m_linesTail
= pLine
;
1089 // insert a new line after the given one or in the very beginning if !pLine
1090 wxFileConfigLineList
*wxFileConfig::LineListInsert(const wxString
& str
,
1091 wxFileConfigLineList
*pLine
)
1093 if ( pLine
== m_linesTail
)
1094 return LineListAppend(str
);
1096 wxFileConfigLineList
*pNewLine
= new wxFileConfigLineList(str
);
1097 if ( pLine
== NULL
) {
1098 // prepend to the list
1099 pNewLine
->SetNext(m_linesHead
);
1100 m_linesHead
->SetPrev(pNewLine
);
1101 m_linesHead
= pNewLine
;
1104 // insert before pLine
1105 wxFileConfigLineList
*pNext
= pLine
->Next();
1106 pNewLine
->SetNext(pNext
);
1107 pNewLine
->SetPrev(pLine
);
1108 pNext
->SetPrev(pNewLine
);
1109 pLine
->SetNext(pNewLine
);
1115 void wxFileConfig::LineListRemove(wxFileConfigLineList
*pLine
)
1117 wxFileConfigLineList
*pPrev
= pLine
->Prev(),
1118 *pNext
= pLine
->Next();
1121 if ( pPrev
== NULL
)
1122 m_linesHead
= pNext
;
1124 pPrev
->SetNext(pNext
);
1127 if ( pNext
== NULL
)
1128 m_linesTail
= pPrev
;
1130 pNext
->SetPrev(pPrev
);
1135 bool wxFileConfig::LineListIsEmpty()
1137 return m_linesHead
== NULL
;
1140 // ============================================================================
1141 // wxFileConfig::wxFileConfigGroup
1142 // ============================================================================
1144 // ----------------------------------------------------------------------------
1146 // ----------------------------------------------------------------------------
1149 wxFileConfigGroup::wxFileConfigGroup(wxFileConfigGroup
*pParent
,
1150 const wxString
& strName
,
1151 wxFileConfig
*pConfig
)
1152 : m_aEntries(CompareEntries
),
1153 m_aSubgroups(CompareGroups
),
1156 m_pConfig
= pConfig
;
1157 m_pParent
= pParent
;
1161 m_pLastEntry
= NULL
;
1162 m_pLastGroup
= NULL
;
1165 // dtor deletes all children
1166 wxFileConfigGroup::~wxFileConfigGroup()
1169 size_t n
, nCount
= m_aEntries
.Count();
1170 for ( n
= 0; n
< nCount
; n
++ )
1171 delete m_aEntries
[n
];
1174 nCount
= m_aSubgroups
.Count();
1175 for ( n
= 0; n
< nCount
; n
++ )
1176 delete m_aSubgroups
[n
];
1179 // ----------------------------------------------------------------------------
1181 // ----------------------------------------------------------------------------
1183 void wxFileConfigGroup::SetLine(wxFileConfigLineList
*pLine
)
1185 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
1191 This is a bit complicated, so let me explain it in details. All lines that
1192 were read from the local file (the only one we will ever modify) are stored
1193 in a (doubly) linked list. Our problem is to know at which position in this
1194 list should we insert the new entries/subgroups. To solve it we keep three
1195 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
1197 m_pLine points to the line containing "[group_name]"
1198 m_pLastEntry points to the last entry of this group in the local file.
1199 m_pLastGroup subgroup
1201 Initially, they're NULL all three. When the group (an entry/subgroup) is read
1202 from the local file, the corresponding variable is set. However, if the group
1203 was read from the global file and then modified or created by the application
1204 these variables are still NULL and we need to create the corresponding lines.
1205 See the following functions (and comments preceding them) for the details of
1208 Also, when our last entry/group are deleted we need to find the new last
1209 element - the code in DeleteEntry/Subgroup does this by backtracking the list
1210 of lines until it either founds an entry/subgroup (and this is the new last
1211 element) or the m_pLine of the group, in which case there are no more entries
1212 (or subgroups) left and m_pLast<element> becomes NULL.
1214 NB: This last problem could be avoided for entries if we added new entries
1215 immediately after m_pLine, but in this case the entries would appear
1216 backwards in the config file (OTOH, it's not that important) and as we
1217 would still need to do it for the subgroups the code wouldn't have been
1218 significantly less complicated.
1221 // Return the line which contains "[our name]". If we're still not in the list,
1222 // add our line to it immediately after the last line of our parent group if we
1223 // have it or in the very beginning if we're the root group.
1224 wxFileConfigLineList
*wxFileConfigGroup::GetGroupLine()
1226 if ( m_pLine
== NULL
) {
1227 wxFileConfigGroup
*pParent
= Parent();
1229 // this group wasn't present in local config file, add it now
1230 if ( pParent
!= NULL
) {
1231 wxString strFullName
;
1232 strFullName
<< wxT("[")
1234 << FilterOutEntryName(GetFullName().c_str() + 1)
1236 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
1237 pParent
->GetLastGroupLine());
1238 pParent
->SetLastGroup(this); // we're surely after all the others
1241 // we return NULL, so that LineListInsert() will insert us in the
1249 // Return the last line belonging to the subgroups of this group (after which
1250 // we can add a new subgroup), if we don't have any subgroups or entries our
1251 // last line is the group line (m_pLine) itself.
1252 wxFileConfigLineList
*wxFileConfigGroup::GetLastGroupLine()
1254 // if we have any subgroups, our last line is the last line of the last
1256 if ( m_pLastGroup
!= NULL
) {
1257 wxFileConfigLineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
1259 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
1263 // no subgroups, so the last line is the line of thelast entry (if any)
1264 return GetLastEntryLine();
1267 // return the last line belonging to the entries of this group (after which
1268 // we can add a new entry), if we don't have any entries we will add the new
1269 // one immediately after the group line itself.
1270 wxFileConfigLineList
*wxFileConfigGroup::GetLastEntryLine()
1272 if ( m_pLastEntry
!= NULL
) {
1273 wxFileConfigLineList
*pLine
= m_pLastEntry
->GetLine();
1275 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
1279 // no entries: insert after the group header
1280 return GetGroupLine();
1283 // ----------------------------------------------------------------------------
1285 // ----------------------------------------------------------------------------
1287 void wxFileConfigGroup::Rename(const wxString
& newName
)
1289 m_strName
= newName
;
1291 wxFileConfigLineList
*line
= GetGroupLine();
1292 wxString strFullName
;
1293 strFullName
<< wxT("[") << (GetFullName().c_str() + 1) << wxT("]"); // +1: no '/'
1294 line
->SetText(strFullName
);
1299 wxString
wxFileConfigGroup::GetFullName() const
1302 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
1307 // ----------------------------------------------------------------------------
1309 // ----------------------------------------------------------------------------
1311 // use binary search because the array is sorted
1313 wxFileConfigGroup::FindEntry(const wxChar
*szName
) const
1317 hi
= m_aEntries
.Count();
1319 wxFileConfigEntry
*pEntry
;
1323 pEntry
= m_aEntries
[i
];
1325 #if wxCONFIG_CASE_SENSITIVE
1326 res
= wxStrcmp(pEntry
->Name(), szName
);
1328 res
= wxStricmp(pEntry
->Name(), szName
);
1343 wxFileConfigGroup::FindSubgroup(const wxChar
*szName
) const
1347 hi
= m_aSubgroups
.Count();
1349 wxFileConfigGroup
*pGroup
;
1353 pGroup
= m_aSubgroups
[i
];
1355 #if wxCONFIG_CASE_SENSITIVE
1356 res
= wxStrcmp(pGroup
->Name(), szName
);
1358 res
= wxStricmp(pGroup
->Name(), szName
);
1372 // ----------------------------------------------------------------------------
1373 // create a new item
1374 // ----------------------------------------------------------------------------
1376 // create a new entry and add it to the current group
1378 wxFileConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
1380 wxASSERT( FindEntry(strName
) == NULL
);
1382 wxFileConfigEntry
*pEntry
= new wxFileConfigEntry(this, strName
, nLine
);
1383 m_aEntries
.Add(pEntry
);
1388 // create a new group and add it to the current group
1390 wxFileConfigGroup::AddSubgroup(const wxString
& strName
)
1392 wxASSERT( FindSubgroup(strName
) == NULL
);
1394 wxFileConfigGroup
*pGroup
= new wxFileConfigGroup(this, strName
, m_pConfig
);
1395 m_aSubgroups
.Add(pGroup
);
1400 // ----------------------------------------------------------------------------
1402 // ----------------------------------------------------------------------------
1405 The delete operations are _very_ slow if we delete the last item of this
1406 group (see comments before GetXXXLineXXX functions for more details),
1407 so it's much better to start with the first entry/group if we want to
1408 delete several of them.
1411 bool wxFileConfigGroup::DeleteSubgroupByName(const wxChar
*szName
)
1413 return DeleteSubgroup(FindSubgroup(szName
));
1416 // doesn't delete the subgroup itself, but does remove references to it from
1417 // all other data structures (and normally the returned pointer should be
1418 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1419 bool wxFileConfigGroup::DeleteSubgroup(wxFileConfigGroup
*pGroup
)
1421 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1423 // delete all entries
1424 size_t nCount
= pGroup
->m_aEntries
.Count();
1425 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1426 wxFileConfigLineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1427 if ( pLine
!= NULL
)
1428 m_pConfig
->LineListRemove(pLine
);
1431 // and subgroups of this sungroup
1432 nCount
= pGroup
->m_aSubgroups
.Count();
1433 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1434 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[0]);
1437 wxFileConfigLineList
*pLine
= pGroup
->m_pLine
;
1438 if ( pLine
!= NULL
) {
1439 // notice that we may do this test inside the previous "if" because the
1440 // last entry's line is surely !NULL
1441 if ( pGroup
== m_pLastGroup
) {
1442 // our last entry is being deleted - find the last one which stays
1443 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1445 // go back until we find a subgroup or reach the group's line
1446 wxFileConfigGroup
*pNewLast
= NULL
;
1447 size_t n
, nSubgroups
= m_aSubgroups
.Count();
1448 wxFileConfigLineList
*pl
;
1449 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1450 // is it our subgroup?
1451 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1452 // do _not_ call GetGroupLine! we don't want to add it to the local
1453 // file if it's not already there
1454 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1455 pNewLast
= m_aSubgroups
[n
];
1458 if ( pNewLast
!= NULL
) // found?
1462 if ( pl
== m_pLine
) {
1463 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1465 // we've reached the group line without finding any subgroups
1466 m_pLastGroup
= NULL
;
1469 m_pLastGroup
= pNewLast
;
1472 m_pConfig
->LineListRemove(pLine
);
1477 m_aSubgroups
.Remove(pGroup
);
1483 bool wxFileConfigGroup::DeleteEntry(const wxChar
*szName
)
1485 wxFileConfigEntry
*pEntry
= FindEntry(szName
);
1486 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1488 wxFileConfigLineList
*pLine
= pEntry
->GetLine();
1489 if ( pLine
!= NULL
) {
1490 // notice that we may do this test inside the previous "if" because the
1491 // last entry's line is surely !NULL
1492 if ( pEntry
== m_pLastEntry
) {
1493 // our last entry is being deleted - find the last one which stays
1494 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1496 // go back until we find another entry or reach the group's line
1497 wxFileConfigEntry
*pNewLast
= NULL
;
1498 size_t n
, nEntries
= m_aEntries
.Count();
1499 wxFileConfigLineList
*pl
;
1500 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1501 // is it our subgroup?
1502 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1503 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1504 pNewLast
= m_aEntries
[n
];
1507 if ( pNewLast
!= NULL
) // found?
1511 if ( pl
== m_pLine
) {
1512 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1514 // we've reached the group line without finding any subgroups
1515 m_pLastEntry
= NULL
;
1518 m_pLastEntry
= pNewLast
;
1521 m_pConfig
->LineListRemove(pLine
);
1524 // we must be written back for the changes to be saved
1527 m_aEntries
.Remove(pEntry
);
1533 // ----------------------------------------------------------------------------
1535 // ----------------------------------------------------------------------------
1536 void wxFileConfigGroup::SetDirty()
1539 if ( Parent() != NULL
) // propagate upwards
1540 Parent()->SetDirty();
1543 // ============================================================================
1544 // wxFileConfig::wxFileConfigEntry
1545 // ============================================================================
1547 // ----------------------------------------------------------------------------
1549 // ----------------------------------------------------------------------------
1550 wxFileConfigEntry::wxFileConfigEntry(wxFileConfigGroup
*pParent
,
1551 const wxString
& strName
,
1553 : m_strName(strName
)
1555 wxASSERT( !strName
.IsEmpty() );
1557 m_pParent
= pParent
;
1562 m_bHasValue
= FALSE
;
1564 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1566 m_strName
.erase(0, 1); // remove first character
1569 // ----------------------------------------------------------------------------
1571 // ----------------------------------------------------------------------------
1573 void wxFileConfigEntry::SetLine(wxFileConfigLineList
*pLine
)
1575 if ( m_pLine
!= NULL
) {
1576 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1577 Name().c_str(), m_pParent
->GetFullName().c_str());
1581 Group()->SetLastEntry(this);
1584 // second parameter is FALSE if we read the value from file and prevents the
1585 // entry from being marked as 'dirty'
1586 void wxFileConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1588 if ( bUser
&& IsImmutable() ) {
1589 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1594 // do nothing if it's the same value: but don't test for it if m_bHasValue
1595 // hadn't been set yet or we'd never write empty values to the file
1596 if ( m_bHasValue
&& strValue
== m_strValue
)
1600 m_strValue
= strValue
;
1603 wxString strVal
= FilterOutValue(strValue
);
1605 strLine
<< FilterOutEntryName(m_strName
) << wxT('=') << strVal
;
1607 if ( m_pLine
!= NULL
) {
1608 // entry was read from the local config file, just modify the line
1609 m_pLine
->SetText(strLine
);
1612 // add a new line to the file
1613 wxASSERT( m_nLine
== wxNOT_FOUND
); // consistency check
1615 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1616 Group()->GetLastEntryLine());
1617 Group()->SetLastEntry(this);
1624 void wxFileConfigEntry::SetDirty()
1627 Group()->SetDirty();
1630 // ============================================================================
1632 // ============================================================================
1634 // ----------------------------------------------------------------------------
1635 // compare functions for array sorting
1636 // ----------------------------------------------------------------------------
1638 int CompareEntries(wxFileConfigEntry
*p1
, wxFileConfigEntry
*p2
)
1640 #if wxCONFIG_CASE_SENSITIVE
1641 return wxStrcmp(p1
->Name(), p2
->Name());
1643 return wxStricmp(p1
->Name(), p2
->Name());
1647 int CompareGroups(wxFileConfigGroup
*p1
, wxFileConfigGroup
*p2
)
1649 #if wxCONFIG_CASE_SENSITIVE
1650 return wxStrcmp(p1
->Name(), p2
->Name());
1652 return wxStricmp(p1
->Name(), p2
->Name());
1656 // ----------------------------------------------------------------------------
1658 // ----------------------------------------------------------------------------
1660 // undo FilterOutValue
1661 static wxString
FilterInValue(const wxString
& str
)
1664 strResult
.Alloc(str
.Len());
1666 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1668 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1669 if ( str
[n
] == wxT('\\') ) {
1670 switch ( str
[++n
] ) {
1672 strResult
+= wxT('\n');
1676 strResult
+= wxT('\r');
1680 strResult
+= wxT('\t');
1684 strResult
+= wxT('\\');
1688 strResult
+= wxT('"');
1693 if ( str
[n
] != wxT('"') || !bQuoted
)
1694 strResult
+= str
[n
];
1695 else if ( n
!= str
.Len() - 1 ) {
1696 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1699 //else: it's the last quote of a quoted string, ok
1706 // quote the string before writing it to file
1707 static wxString
FilterOutValue(const wxString
& str
)
1713 strResult
.Alloc(str
.Len());
1715 // quoting is necessary to preserve spaces in the beginning of the string
1716 bool bQuote
= wxIsspace(str
[0]) || str
[0] == wxT('"');
1719 strResult
+= wxT('"');
1722 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
1745 //else: fall through
1748 strResult
+= str
[n
];
1749 continue; // nothing special to do
1752 // we get here only for special characters
1753 strResult
<< wxT('\\') << c
;
1757 strResult
+= wxT('"');
1762 // undo FilterOutEntryName
1763 static wxString
FilterInEntryName(const wxString
& str
)
1766 strResult
.Alloc(str
.Len());
1768 for ( const wxChar
*pc
= str
.c_str(); *pc
!= '\0'; pc
++ ) {
1769 if ( *pc
== wxT('\\') )
1778 // sanitize entry or group name: insert '\\' before any special characters
1779 static wxString
FilterOutEntryName(const wxString
& str
)
1782 strResult
.Alloc(str
.Len());
1784 for ( const wxChar
*pc
= str
.c_str(); *pc
!= wxT('\0'); pc
++ ) {
1787 // we explicitly allow some of "safe" chars and 8bit ASCII characters
1788 // which will probably never have special meaning
1789 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
1790 // should *not* be quoted
1791 if ( !wxIsalnum(c
) && !wxStrchr(wxT("@_/-!.*%"), c
) && ((c
& 0x80) == 0) )
1792 strResult
+= wxT('\\');
1800 // we can't put ?: in the ctor initializer list because it confuses some
1801 // broken compilers (Borland C++)
1802 static wxString
GetAppName(const wxString
& appName
)
1804 if ( !appName
&& wxTheApp
)
1805 return wxTheApp
->GetAppName();
1810 #endif // wxUSE_CONFIG