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 // ----------------------------------------------------------------------------
23 // ----------------------------------------------------------------------------
24 #include "wx/wxprec.h"
31 #include <wx/string.h>
36 #include <wx/dynarray.h>
39 #include <wx/textfile.h>
40 #include <wx/config.h>
41 #include <wx/fileconf.h>
44 // _WINDOWS_ is defined when windows.h is included,
45 // __WXMSW__ is defined for MS Windows compilation
46 #if defined(__WXMSW__) && !defined(_WINDOWS_)
53 // ----------------------------------------------------------------------------
55 // ----------------------------------------------------------------------------
56 #define CONST_CAST ((wxFileConfig *)this)->
58 // ----------------------------------------------------------------------------
60 // ----------------------------------------------------------------------------
65 // ----------------------------------------------------------------------------
66 // global functions declarations
67 // ----------------------------------------------------------------------------
69 // is 'c' a valid character in group name?
70 // NB: wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR must be valid chars,
71 // but _not_ ']' (group name delimiter)
72 inline bool IsValid(char c
) { return isalnum(c
) || strchr("@_/-!.*%", c
); }
74 // compare functions for sorting the arrays
75 static int CompareEntries(wxFileConfig::ConfigEntry
*p1
,
76 wxFileConfig::ConfigEntry
*p2
);
77 static int CompareGroups(wxFileConfig::ConfigGroup
*p1
,
78 wxFileConfig::ConfigGroup
*p2
);
81 static wxString
FilterIn(const wxString
& str
);
82 static wxString
FilterOut(const wxString
& str
);
84 // ============================================================================
86 // ============================================================================
88 // ----------------------------------------------------------------------------
90 // ----------------------------------------------------------------------------
91 wxString
wxFileConfig::GetGlobalDir()
97 #elif defined(__WXSTUBS__)
98 wxASSERT_MSG( FALSE
, "TODO" ) ;
100 char szWinDir
[MAX_PATH
];
101 ::GetWindowsDirectory(szWinDir
, MAX_PATH
);
105 #endif // Unix/Windows
110 wxString
wxFileConfig::GetLocalDir()
114 wxGetHomeDir(&strDir
);
119 wxString
wxFileConfig::GetGlobalFileName(const char *szFile
)
121 wxString str
= GetGlobalDir();
124 if ( strchr(szFile
, '.') == NULL
)
134 wxString
wxFileConfig::GetLocalFileName(const char *szFile
)
136 wxString str
= GetLocalDir();
145 if ( strchr(szFile
, '.') == NULL
)
152 // ----------------------------------------------------------------------------
154 // ----------------------------------------------------------------------------
156 void wxFileConfig::Init()
159 m_pRootGroup
= new ConfigGroup(NULL
, "", this);
164 // it's not an error if (one of the) file(s) doesn't exist
166 // parse the global file
167 if ( !m_strGlobalFile
.IsEmpty() && wxFile::Exists(m_strGlobalFile
) ) {
168 wxTextFile
fileGlobal(m_strGlobalFile
);
170 if ( fileGlobal
.Open() ) {
171 Parse(fileGlobal
, FALSE
/* global */);
175 wxLogWarning(_("can't open global configuration file '%s'."),
176 m_strGlobalFile
.c_str());
179 // parse the local file
180 if ( !m_strLocalFile
.IsEmpty() && wxFile::Exists(m_strLocalFile
) ) {
181 wxTextFile
fileLocal(m_strLocalFile
);
182 if ( fileLocal
.Open() ) {
183 Parse(fileLocal
, TRUE
/* local */);
187 wxLogWarning(_("can't open user configuration file '%s'."),
188 m_strLocalFile
.c_str());
192 // constructor supports creation of wxFileConfig objects of any type
193 wxFileConfig::wxFileConfig(const wxString
& appName
, const wxString
& vendorName
,
194 const wxString
& strLocal
, const wxString
& strGlobal
,
196 : wxConfigBase(appName
, vendorName
, strLocal
, strGlobal
, style
),
197 m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
199 // Make up an application name if not supplied
200 if (appName
.IsEmpty() && wxTheApp
)
202 SetAppName(wxTheApp
->GetAppName());
205 // Make up names for files if empty
206 if ( m_strLocalFile
.IsEmpty() && (style
& wxCONFIG_USE_LOCAL_FILE
) )
208 m_strLocalFile
= GetLocalFileName(GetAppName());
211 if ( m_strGlobalFile
.IsEmpty() && (style
& wxCONFIG_USE_GLOBAL_FILE
) )
213 m_strGlobalFile
= GetGlobalFileName(GetAppName());
216 // Check if styles are not supplied, but filenames are, in which case
217 // add the correct styles.
218 if ( !m_strLocalFile
.IsEmpty() )
219 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE
);
221 if ( !m_strGlobalFile
.IsEmpty() )
222 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE
);
224 // if the path is not absolute, prepend the standard directory to it
225 if ( !m_strLocalFile
.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile
) )
227 wxString strLocal
= m_strLocalFile
;
228 m_strLocalFile
= GetLocalDir();
229 m_strLocalFile
<< strLocal
;
232 if ( !m_strGlobalFile
.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile
) )
234 wxString strGlobal
= m_strGlobalFile
;
235 m_strGlobalFile
= GetGlobalDir();
236 m_strGlobalFile
<< strGlobal
;
242 void wxFileConfig::CleanUp()
246 LineList
*pCur
= m_linesHead
;
247 while ( pCur
!= NULL
) {
248 LineList
*pNext
= pCur
->Next();
254 wxFileConfig::~wxFileConfig()
261 // ----------------------------------------------------------------------------
262 // parse a config file
263 // ----------------------------------------------------------------------------
265 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
271 size_t nLineCount
= file
.GetLineCount();
272 for ( size_t n
= 0; n
< nLineCount
; n
++ ) {
275 // add the line to linked list
277 LineListAppend(strLine
);
279 // skip leading spaces
280 for ( pStart
= strLine
; isspace(*pStart
); pStart
++ )
283 // skip blank/comment lines
284 if ( *pStart
== '\0'|| *pStart
== ';' || *pStart
== '#' )
287 if ( *pStart
== '[' ) { // a new group
290 while ( *++pEnd
!= ']' ) {
291 if ( !IsValid(*pEnd
) && *pEnd
!= ' ' ) // allow spaces in group names
295 if ( *pEnd
!= ']' ) {
296 wxLogError(_("file '%s': unexpected character %c at line %d."),
297 file
.GetName(), *pEnd
, n
+ 1);
298 continue; // skip this line
301 // group name here is always considered as abs path
304 strGroup
<< wxCONFIG_PATH_SEPARATOR
<< wxString(pStart
, pEnd
- pStart
);
306 // will create it if doesn't yet exist
310 m_pCurrentGroup
->SetLine(m_linesTail
);
312 // check that there is nothing except comments left on this line
314 while ( *++pEnd
!= '\0' && bCont
) {
323 // ignore whitespace ('\n' impossible here)
327 wxLogWarning(_("file '%s', line %d: '%s' "
328 "ignored after group header."),
329 file
.GetName(), n
+ 1, pEnd
);
335 const char *pEnd
= pStart
;
336 while ( IsValid(*pEnd
) )
339 wxString
strKey(pStart
, pEnd
);
342 while ( isspace(*pEnd
) )
345 if ( *pEnd
++ != '=' ) {
346 wxLogError(_("file '%s', line %d: '=' expected."),
347 file
.GetName(), n
+ 1);
350 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
352 if ( pEntry
== NULL
) {
354 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
357 pEntry
->SetLine(m_linesTail
);
360 if ( bLocal
&& pEntry
->IsImmutable() ) {
361 // immutable keys can't be changed by user
362 wxLogWarning(_("file '%s', line %d: value for "
363 "immutable key '%s' ignored."),
364 file
.GetName(), n
+ 1, strKey
.c_str());
367 // the condition below catches the cases (a) and (b) but not (c):
368 // (a) global key found second time in global file
369 // (b) key found second (or more) time in local file
370 // (c) key from global file now found in local one
371 // which is exactly what we want.
372 else if ( !bLocal
|| pEntry
->IsLocal() ) {
373 wxLogWarning(_("file '%s', line %d: key '%s' was first "
374 "found at line %d."),
375 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
378 pEntry
->SetLine(m_linesTail
);
383 while ( isspace(*pEnd
) )
386 pEntry
->SetValue(FilterIn(pEnd
), FALSE
/* read from file */);
392 // ----------------------------------------------------------------------------
394 // ----------------------------------------------------------------------------
396 void wxFileConfig::SetRootPath()
399 m_pCurrentGroup
= m_pRootGroup
;
402 void wxFileConfig::SetPath(const wxString
& strPath
)
404 wxArrayString aParts
;
406 if ( strPath
.IsEmpty() ) {
411 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
413 wxSplitPath(aParts
, strPath
);
416 // relative path, combine with current one
417 wxString strFullPath
= m_strPath
;
418 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
419 wxSplitPath(aParts
, strFullPath
);
422 // change current group
424 m_pCurrentGroup
= m_pRootGroup
;
425 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
426 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
427 if ( pNextGroup
== NULL
)
428 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
429 m_pCurrentGroup
= pNextGroup
;
432 // recombine path parts in one variable
434 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
435 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
439 // ----------------------------------------------------------------------------
441 // ----------------------------------------------------------------------------
443 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
446 return GetNextGroup(str
, lIndex
);
449 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
451 if ( size_t(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
452 str
= m_pCurrentGroup
->Groups()[lIndex
++]->Name();
459 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
462 return GetNextEntry(str
, lIndex
);
465 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
467 if ( size_t(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
468 str
= m_pCurrentGroup
->Entries()[lIndex
++]->Name();
475 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
477 size_t n
= m_pCurrentGroup
->Entries().Count();
479 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
480 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
481 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
482 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
483 n
+= GetNumberOfEntries(TRUE
);
484 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
491 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
493 size_t n
= m_pCurrentGroup
->Groups().Count();
495 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
496 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
497 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
498 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
499 n
+= GetNumberOfGroups(TRUE
);
500 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
507 // ----------------------------------------------------------------------------
508 // tests for existence
509 // ----------------------------------------------------------------------------
511 bool wxFileConfig::HasGroup(const wxString
& strName
) const
513 wxConfigPathChanger
path(this, strName
);
515 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
516 return pGroup
!= NULL
;
519 bool wxFileConfig::HasEntry(const wxString
& strName
) const
521 wxConfigPathChanger
path(this, strName
);
523 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
524 return pEntry
!= NULL
;
527 // ----------------------------------------------------------------------------
529 // ----------------------------------------------------------------------------
531 bool wxFileConfig::Read(const wxString
& key
,
532 wxString
* pStr
) const
534 wxConfigPathChanger
path(this, key
);
536 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
537 if (pEntry
== NULL
) {
541 *pStr
= ExpandEnvVars(pEntry
->Value());
546 bool wxFileConfig::Read(const wxString
& key
,
547 wxString
* pStr
, const wxString
& defVal
) const
549 wxConfigPathChanger
path(this, key
);
551 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
552 if (pEntry
== NULL
) {
553 if( IsRecordingDefaults() )
554 ((wxFileConfig
*)this)->Write(key
,defVal
);
555 *pStr
= ExpandEnvVars(defVal
);
559 *pStr
= ExpandEnvVars(pEntry
->Value());
564 bool wxFileConfig::Read(const wxString
& key
, long *pl
) const
567 if ( Read(key
, & str
) ) {
576 bool wxFileConfig::Write(const wxString
& key
, const wxString
& szValue
)
578 wxConfigPathChanger
path(this, key
);
580 wxString strName
= path
.Name();
581 if ( strName
.IsEmpty() ) {
582 // setting the value of a group is an error
583 wxASSERT_MSG( IsEmpty(szValue
), _("can't set value of a group!") );
585 // ... except if it's empty in which case it's a way to force it's creation
586 m_pCurrentGroup
->SetDirty();
588 // this will add a line for this group if it didn't have it before
589 (void)m_pCurrentGroup
->GetGroupLine();
594 // check that the name is reasonable
595 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
596 wxLogError(_("Entry name can't start with '%c'."),
597 wxCONFIG_IMMUTABLE_PREFIX
);
601 for ( const char *pc
= strName
; *pc
!= '\0'; pc
++ ) {
602 if ( !IsValid(*pc
) ) {
603 wxLogError(_("Character '%c' is invalid in a config entry name."),
609 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
610 if ( pEntry
== NULL
)
611 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
613 pEntry
->SetValue(szValue
);
619 bool wxFileConfig::Write(const wxString
& key
, long lValue
)
621 // ltoa() is not ANSI :-(
622 char szBuf
[40]; // should be good for sizeof(long) <= 16 (128 bits)
623 sprintf(szBuf
, "%ld", lValue
);
624 return Write(key
, szBuf
);
627 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
629 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() )
632 wxTempFile
file(m_strLocalFile
);
634 if ( !file
.IsOpened() ) {
635 wxLogError(_("can't open user configuration file."));
639 // write all strings to file
640 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
641 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
642 wxLogError(_("can't write user configuration file."));
647 return file
.Commit();
650 // ----------------------------------------------------------------------------
651 // delete groups/entries
652 // ----------------------------------------------------------------------------
654 bool wxFileConfig::DeleteEntry(const wxString
& key
, bool bGroupIfEmptyAlso
)
656 wxConfigPathChanger
path(this, key
);
658 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
661 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
662 if ( m_pCurrentGroup
!= m_pRootGroup
) {
663 ConfigGroup
*pGroup
= m_pCurrentGroup
;
664 SetPath(".."); // changes m_pCurrentGroup!
665 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
667 //else: never delete the root group
673 bool wxFileConfig::DeleteGroup(const wxString
& key
)
675 wxConfigPathChanger
path(this, key
);
677 return m_pCurrentGroup
->DeleteSubgroupByName(path
.Name());
680 bool wxFileConfig::DeleteAll()
684 const char *szFile
= m_strLocalFile
;
686 if ( remove(szFile
) == -1 )
687 wxLogSysError(_("can't delete user configuration file '%s'"), szFile
);
689 m_strLocalFile
= m_strGlobalFile
= "";
695 // ----------------------------------------------------------------------------
696 // linked list functions
697 // ----------------------------------------------------------------------------
699 // append a new line to the end of the list
700 wxFileConfig::LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
702 LineList
*pLine
= new LineList(str
);
704 if ( m_linesTail
== NULL
) {
710 m_linesTail
->SetNext(pLine
);
711 pLine
->SetPrev(m_linesTail
);
718 // insert a new line after the given one or in the very beginning if !pLine
719 wxFileConfig::LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
722 if ( pLine
== m_linesTail
)
723 return LineListAppend(str
);
725 LineList
*pNewLine
= new LineList(str
);
726 if ( pLine
== NULL
) {
727 // prepend to the list
728 pNewLine
->SetNext(m_linesHead
);
729 m_linesHead
->SetPrev(pNewLine
);
730 m_linesHead
= pNewLine
;
733 // insert before pLine
734 LineList
*pNext
= pLine
->Next();
735 pNewLine
->SetNext(pNext
);
736 pNewLine
->SetPrev(pLine
);
737 pNext
->SetPrev(pNewLine
);
738 pLine
->SetNext(pNewLine
);
744 void wxFileConfig::LineListRemove(LineList
*pLine
)
746 LineList
*pPrev
= pLine
->Prev(),
747 *pNext
= pLine
->Next();
753 pPrev
->SetNext(pNext
);
759 pNext
->SetPrev(pPrev
);
764 bool wxFileConfig::LineListIsEmpty()
766 return m_linesHead
== NULL
;
769 // ============================================================================
770 // wxFileConfig::ConfigGroup
771 // ============================================================================
773 // ----------------------------------------------------------------------------
775 // ----------------------------------------------------------------------------
778 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup
*pParent
,
779 const wxString
& strName
,
780 wxFileConfig
*pConfig
)
781 : m_aEntries(CompareEntries
),
782 m_aSubgroups(CompareGroups
),
794 // dtor deletes all children
795 wxFileConfig::ConfigGroup::~ConfigGroup()
798 size_t n
, nCount
= m_aEntries
.Count();
799 for ( n
= 0; n
< nCount
; n
++ )
800 delete m_aEntries
[n
];
803 nCount
= m_aSubgroups
.Count();
804 for ( n
= 0; n
< nCount
; n
++ )
805 delete m_aSubgroups
[n
];
808 // ----------------------------------------------------------------------------
810 // ----------------------------------------------------------------------------
812 void wxFileConfig::ConfigGroup::SetLine(LineList
*pLine
)
814 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
820 This is a bit complicated, so let me explain it in details. All lines that
821 were read from the local file (the only one we will ever modify) are stored
822 in a (doubly) linked list. Our problem is to know at which position in this
823 list should we insert the new entries/subgroups. To solve it we keep three
824 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
826 m_pLine points to the line containing "[group_name]"
827 m_pLastEntry points to the last entry of this group in the local file.
828 m_pLastGroup subgroup
830 Initially, they're NULL all three. When the group (an entry/subgroup) is read
831 from the local file, the corresponding variable is set. However, if the group
832 was read from the global file and then modified or created by the application
833 these variables are still NULL and we need to create the corresponding lines.
834 See the following functions (and comments preceding them) for the details of
837 Also, when our last entry/group are deleted we need to find the new last
838 element - the code in DeleteEntry/Subgroup does this by backtracking the list
839 of lines until it either founds an entry/subgroup (and this is the new last
840 element) or the m_pLine of the group, in which case there are no more entries
841 (or subgroups) left and m_pLast<element> becomes NULL.
843 NB: This last problem could be avoided for entries if we added new entries
844 immediately after m_pLine, but in this case the entries would appear
845 backwards in the config file (OTOH, it's not that important) and as we
846 would still need to do it for the subgroups the code wouldn't have been
847 significantly less complicated.
850 // Return the line which contains "[our name]". If we're still not in the list,
851 // add our line to it immediately after the last line of our parent group if we
852 // have it or in the very beginning if we're the root group.
853 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetGroupLine()
855 if ( m_pLine
== NULL
) {
856 ConfigGroup
*pParent
= Parent();
858 // this group wasn't present in local config file, add it now
859 if ( pParent
!= NULL
) {
860 wxString strFullName
;
861 strFullName
<< "[" << GetFullName().c_str() + 1 << "]"; // +1: no '/'
862 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
863 pParent
->GetLastGroupLine());
864 pParent
->SetLastGroup(this); // we're surely after all the others
867 // we return NULL, so that LineListInsert() will insert us in the
875 // Return the last line belonging to the subgroups of this group (after which
876 // we can add a new subgroup), if we don't have any subgroups or entries our
877 // last line is the group line (m_pLine) itself.
878 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastGroupLine()
880 // if we have any subgroups, our last line is the last line of the last
882 if ( m_pLastGroup
!= NULL
) {
883 wxFileConfig::LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
885 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
889 // no subgroups, so the last line is the line of thelast entry (if any)
890 return GetLastEntryLine();
893 // return the last line belonging to the entries of this group (after which
894 // we can add a new entry), if we don't have any entries we will add the new
895 // one immediately after the group line itself.
896 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastEntryLine()
898 if ( m_pLastEntry
!= NULL
) {
899 wxFileConfig::LineList
*pLine
= m_pLastEntry
->GetLine();
901 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
905 // no entries: insert after the group header
906 return GetGroupLine();
909 // ----------------------------------------------------------------------------
911 // ----------------------------------------------------------------------------
913 wxString
wxFileConfig::ConfigGroup::GetFullName() const
916 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
921 // ----------------------------------------------------------------------------
923 // ----------------------------------------------------------------------------
925 // use binary search because the array is sorted
926 wxFileConfig::ConfigEntry
*
927 wxFileConfig::ConfigGroup::FindEntry(const char *szName
) const
931 hi
= m_aEntries
.Count();
933 wxFileConfig::ConfigEntry
*pEntry
;
937 pEntry
= m_aEntries
[i
];
939 #if wxCONFIG_CASE_SENSITIVE
940 res
= strcmp(pEntry
->Name(), szName
);
942 res
= Stricmp(pEntry
->Name(), szName
);
956 wxFileConfig::ConfigGroup
*
957 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName
) const
961 hi
= m_aSubgroups
.Count();
963 wxFileConfig::ConfigGroup
*pGroup
;
967 pGroup
= m_aSubgroups
[i
];
969 #if wxCONFIG_CASE_SENSITIVE
970 res
= strcmp(pGroup
->Name(), szName
);
972 res
= Stricmp(pGroup
->Name(), szName
);
986 // ----------------------------------------------------------------------------
988 // ----------------------------------------------------------------------------
990 // create a new entry and add it to the current group
991 wxFileConfig::ConfigEntry
*
992 wxFileConfig::ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
994 wxASSERT( FindEntry(strName
) == NULL
);
996 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
997 m_aEntries
.Add(pEntry
);
1002 // create a new group and add it to the current group
1003 wxFileConfig::ConfigGroup
*
1004 wxFileConfig::ConfigGroup::AddSubgroup(const wxString
& strName
)
1006 wxASSERT( FindSubgroup(strName
) == NULL
);
1008 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
1009 m_aSubgroups
.Add(pGroup
);
1014 // ----------------------------------------------------------------------------
1016 // ----------------------------------------------------------------------------
1019 The delete operations are _very_ slow if we delete the last item of this
1020 group (see comments before GetXXXLineXXX functions for more details),
1021 so it's much better to start with the first entry/group if we want to
1022 delete several of them.
1025 bool wxFileConfig::ConfigGroup::DeleteSubgroupByName(const char *szName
)
1027 return DeleteSubgroup(FindSubgroup(szName
));
1030 // doesn't delete the subgroup itself, but does remove references to it from
1031 // all other data structures (and normally the returned pointer should be
1032 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1033 bool wxFileConfig::ConfigGroup::DeleteSubgroup(ConfigGroup
*pGroup
)
1035 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1037 // delete all entries
1038 size_t nCount
= pGroup
->m_aEntries
.Count();
1039 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1040 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1041 if ( pLine
!= NULL
)
1042 m_pConfig
->LineListRemove(pLine
);
1045 // and subgroups of this sungroup
1046 nCount
= pGroup
->m_aSubgroups
.Count();
1047 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1048 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[nGroup
]);
1051 LineList
*pLine
= pGroup
->m_pLine
;
1052 if ( pLine
!= NULL
) {
1053 // notice that we may do this test inside the previous "if" because the
1054 // last entry's line is surely !NULL
1055 if ( pGroup
== m_pLastGroup
) {
1056 // our last entry is being deleted - find the last one which stays
1057 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1059 // go back until we find a subgroup or reach the group's line
1060 ConfigGroup
*pNewLast
= NULL
;
1061 size_t n
, nSubgroups
= m_aSubgroups
.Count();
1063 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1064 // is it our subgroup?
1065 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1066 // do _not_ call GetGroupLine! we don't want to add it to the local
1067 // file if it's not already there
1068 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1069 pNewLast
= m_aSubgroups
[n
];
1072 if ( pNewLast
!= NULL
) // found?
1076 if ( pl
== m_pLine
) {
1077 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1079 // we've reached the group line without finding any subgroups
1080 m_pLastGroup
= NULL
;
1083 m_pLastGroup
= pNewLast
;
1086 m_pConfig
->LineListRemove(pLine
);
1091 m_aSubgroups
.Remove(pGroup
);
1097 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName
)
1099 ConfigEntry
*pEntry
= FindEntry(szName
);
1100 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1102 LineList
*pLine
= pEntry
->GetLine();
1103 if ( pLine
!= NULL
) {
1104 // notice that we may do this test inside the previous "if" because the
1105 // last entry's line is surely !NULL
1106 if ( pEntry
== m_pLastEntry
) {
1107 // our last entry is being deleted - find the last one which stays
1108 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1110 // go back until we find another entry or reach the group's line
1111 ConfigEntry
*pNewLast
= NULL
;
1112 size_t n
, nEntries
= m_aEntries
.Count();
1114 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1115 // is it our subgroup?
1116 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1117 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1118 pNewLast
= m_aEntries
[n
];
1121 if ( pNewLast
!= NULL
) // found?
1125 if ( pl
== m_pLine
) {
1126 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1128 // we've reached the group line without finding any subgroups
1129 m_pLastEntry
= NULL
;
1132 m_pLastEntry
= pNewLast
;
1135 m_pConfig
->LineListRemove(pLine
);
1138 // we must be written back for the changes to be saved
1141 m_aEntries
.Remove(pEntry
);
1147 // ----------------------------------------------------------------------------
1149 // ----------------------------------------------------------------------------
1150 void wxFileConfig::ConfigGroup::SetDirty()
1153 if ( Parent() != NULL
) // propagate upwards
1154 Parent()->SetDirty();
1157 // ============================================================================
1158 // wxFileConfig::ConfigEntry
1159 // ============================================================================
1161 // ----------------------------------------------------------------------------
1163 // ----------------------------------------------------------------------------
1164 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup
*pParent
,
1165 const wxString
& strName
,
1167 : m_strName(strName
)
1169 wxASSERT( !strName
.IsEmpty() );
1171 m_pParent
= pParent
;
1177 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1179 m_strName
.erase(0, 1); // remove first character
1182 // ----------------------------------------------------------------------------
1184 // ----------------------------------------------------------------------------
1186 void wxFileConfig::ConfigEntry::SetLine(LineList
*pLine
)
1188 if ( m_pLine
!= NULL
) {
1189 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1190 Name().c_str(), m_pParent
->GetFullName().c_str());
1194 Group()->SetLastEntry(this);
1197 // second parameter is FALSE if we read the value from file and prevents the
1198 // entry from being marked as 'dirty'
1199 void wxFileConfig::ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1201 if ( bUser
&& IsImmutable() ) {
1202 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1207 // do nothing if it's the same value
1208 if ( strValue
== m_strValue
)
1211 m_strValue
= strValue
;
1214 wxString strVal
= FilterOut(strValue
);
1216 strLine
<< m_strName
<< " = " << strVal
;
1218 if ( m_pLine
!= NULL
) {
1219 // entry was read from the local config file, just modify the line
1220 m_pLine
->SetText(strLine
);
1223 // add a new line to the file
1224 wxASSERT( m_nLine
== NOT_FOUND
); // consistency check
1226 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1227 Group()->GetLastEntryLine());
1228 Group()->SetLastEntry(this);
1235 void wxFileConfig::ConfigEntry::SetDirty()
1238 Group()->SetDirty();
1241 // ============================================================================
1243 // ============================================================================
1245 // ----------------------------------------------------------------------------
1246 // compare functions for array sorting
1247 // ----------------------------------------------------------------------------
1249 int CompareEntries(wxFileConfig::ConfigEntry
*p1
,
1250 wxFileConfig::ConfigEntry
*p2
)
1252 #if wxCONFIG_CASE_SENSITIVE
1253 return strcmp(p1
->Name(), p2
->Name());
1255 return Stricmp(p1
->Name(), p2
->Name());
1259 int CompareGroups(wxFileConfig::ConfigGroup
*p1
,
1260 wxFileConfig::ConfigGroup
*p2
)
1262 #if wxCONFIG_CASE_SENSITIVE
1263 return strcmp(p1
->Name(), p2
->Name());
1265 return Stricmp(p1
->Name(), p2
->Name());
1269 // ----------------------------------------------------------------------------
1271 // ----------------------------------------------------------------------------
1274 wxString
FilterIn(const wxString
& str
)
1277 strResult
.Alloc(str
.Len());
1279 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1281 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1282 if ( str
[n
] == '\\' ) {
1283 switch ( str
[++n
] ) {
1306 if ( str
[n
] != '"' || !bQuoted
)
1307 strResult
+= str
[n
];
1308 else if ( n
!= str
.Len() - 1 ) {
1309 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1312 //else: it's the last quote of a quoted string, ok
1319 // quote the string before writing it to file
1320 wxString
FilterOut(const wxString
& str
)
1326 strResult
.Alloc(str
.Len());
1328 // quoting is necessary to preserve spaces in the beginning of the string
1329 bool bQuote
= isspace(str
[0]) || str
[0] == '"';
1335 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
1358 //else: fall through
1361 strResult
+= str
[n
];
1362 continue; // nothing special to do
1365 // we get here only for special characters
1366 strResult
<< '\\' << c
;