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>
43 // _WINDOWS_ is defined when windows.h is included,
44 // __WXMSW__ is defined for MS Windows compilation
45 #if defined(__WXMSW__) && !defined(_WINDOWS_)
52 // ----------------------------------------------------------------------------
54 // ----------------------------------------------------------------------------
55 #define CONST_CAST ((wxFileConfig *)this)->
57 // ----------------------------------------------------------------------------
59 // ----------------------------------------------------------------------------
64 // ----------------------------------------------------------------------------
65 // global functions declarations
66 // ----------------------------------------------------------------------------
68 // is 'c' a valid character in group name?
69 // NB: wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR must be valid chars,
70 // but _not_ ']' (group name delimiter)
71 inline bool IsValid(char c
) { return isalnum(c
) || strchr("@_/-!.*%", c
); }
73 // compare functions for sorting the arrays
74 static int CompareEntries(wxFileConfig::ConfigEntry
*p1
,
75 wxFileConfig::ConfigEntry
*p2
);
76 static int CompareGroups(wxFileConfig::ConfigGroup
*p1
,
77 wxFileConfig::ConfigGroup
*p2
);
80 static wxString
FilterIn(const wxString
& str
);
81 static wxString
FilterOut(const wxString
& str
);
83 // ============================================================================
85 // ============================================================================
87 // ----------------------------------------------------------------------------
89 // ----------------------------------------------------------------------------
90 wxString
wxFileConfig::GetGlobalDir()
96 #elif defined(__WXSTUBS__)
97 wxASSERT_MSG( FALSE
, "TODO" ) ;
99 char szWinDir
[MAX_PATH
];
100 ::GetWindowsDirectory(szWinDir
, MAX_PATH
);
104 #endif // Unix/Windows
109 wxString
wxFileConfig::GetLocalDir()
113 wxGetHomeDir(&strDir
);
118 wxString
wxFileConfig::GetGlobalFileName(const char *szFile
)
120 wxString str
= GetGlobalDir();
123 if ( strchr(szFile
, '.') == NULL
)
133 wxString
wxFileConfig::GetLocalFileName(const char *szFile
)
135 wxString str
= GetLocalDir();
144 if ( strchr(szFile
, '.') == NULL
)
151 // ----------------------------------------------------------------------------
153 // ----------------------------------------------------------------------------
155 void wxFileConfig::Init()
158 m_pRootGroup
= new ConfigGroup(NULL
, "", this);
163 // it's not an error if (one of the) file(s) doesn't exist
165 // parse the global file
166 if ( !m_strGlobalFile
.IsEmpty() && wxFile::Exists(m_strGlobalFile
) ) {
167 wxTextFile
fileGlobal(m_strGlobalFile
);
169 if ( fileGlobal
.Open() ) {
170 Parse(fileGlobal
, FALSE
/* global */);
174 wxLogWarning(_("can't open global configuration file '%s'."),
175 m_strGlobalFile
.c_str());
178 // parse the local file
179 if ( !m_strLocalFile
.IsEmpty() && wxFile::Exists(m_strLocalFile
) ) {
180 wxTextFile
fileLocal(m_strLocalFile
);
181 if ( fileLocal
.Open() ) {
182 Parse(fileLocal
, TRUE
/* local */);
186 wxLogWarning(_("can't open user configuration file '%s'."),
187 m_strLocalFile
.c_str());
191 // constructor supports creation of wxFileConfig objects of any type
192 wxFileConfig::wxFileConfig(const wxString
& appName
, const wxString
& vendorName
,
193 const wxString
& strLocal
, const wxString
& strGlobal
,
195 : wxConfigBase(appName
, vendorName
, strLocal
, strGlobal
, style
),
196 m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
198 // Make up an application name if not supplied
199 if (appName
.IsEmpty() && wxTheApp
)
201 SetAppName(wxTheApp
->GetAppName());
204 // Make up names for files if empty
205 if ( m_strLocalFile
.IsEmpty() && (style
& wxCONFIG_USE_LOCAL_FILE
) )
207 m_strLocalFile
= GetLocalFileName(GetAppName());
210 if ( m_strGlobalFile
.IsEmpty() && (style
& wxCONFIG_USE_GLOBAL_FILE
) )
212 m_strGlobalFile
= GetGlobalFileName(GetAppName());
215 // Check if styles are not supplied, but filenames are, in which case
216 // add the correct styles.
217 if ( !m_strLocalFile
.IsEmpty() )
218 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE
);
220 if ( !m_strGlobalFile
.IsEmpty() )
221 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE
);
223 // if the path is not absolute, prepend the standard directory to it
224 if ( !m_strLocalFile
.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile
) )
226 wxString strLocal
= m_strLocalFile
;
227 m_strLocalFile
= GetLocalDir();
228 m_strLocalFile
<< strLocal
;
231 if ( !m_strGlobalFile
.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile
) )
233 wxString strGlobal
= m_strGlobalFile
;
234 m_strGlobalFile
= GetGlobalDir();
235 m_strGlobalFile
<< strGlobal
;
241 void wxFileConfig::CleanUp()
245 LineList
*pCur
= m_linesHead
;
246 while ( pCur
!= NULL
) {
247 LineList
*pNext
= pCur
->Next();
253 wxFileConfig::~wxFileConfig()
260 // ----------------------------------------------------------------------------
261 // parse a config file
262 // ----------------------------------------------------------------------------
264 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
270 size_t nLineCount
= file
.GetLineCount();
271 for ( size_t n
= 0; n
< nLineCount
; n
++ ) {
274 // add the line to linked list
276 LineListAppend(strLine
);
278 // skip leading spaces
279 for ( pStart
= strLine
; isspace(*pStart
); pStart
++ )
282 // skip blank/comment lines
283 if ( *pStart
== '\0'|| *pStart
== ';' || *pStart
== '#' )
286 if ( *pStart
== '[' ) { // a new group
289 while ( *++pEnd
!= ']' ) {
290 if ( !IsValid(*pEnd
) && *pEnd
!= ' ' ) // allow spaces in group names
294 if ( *pEnd
!= ']' ) {
295 wxLogError(_("file '%s': unexpected character %c at line %d."),
296 file
.GetName(), *pEnd
, n
+ 1);
297 continue; // skip this line
300 // group name here is always considered as abs path
303 strGroup
<< wxCONFIG_PATH_SEPARATOR
<< wxString(pStart
, pEnd
- pStart
);
305 // will create it if doesn't yet exist
309 m_pCurrentGroup
->SetLine(m_linesTail
);
311 // check that there is nothing except comments left on this line
313 while ( *++pEnd
!= '\0' && bCont
) {
322 // ignore whitespace ('\n' impossible here)
326 wxLogWarning(_("file '%s', line %d: '%s' "
327 "ignored after group header."),
328 file
.GetName(), n
+ 1, pEnd
);
334 const char *pEnd
= pStart
;
335 while ( IsValid(*pEnd
) )
338 wxString
strKey(pStart
, pEnd
);
341 while ( isspace(*pEnd
) )
344 if ( *pEnd
++ != '=' ) {
345 wxLogError(_("file '%s', line %d: '=' expected."),
346 file
.GetName(), n
+ 1);
349 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
351 if ( pEntry
== NULL
) {
353 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
356 pEntry
->SetLine(m_linesTail
);
359 if ( bLocal
&& pEntry
->IsImmutable() ) {
360 // immutable keys can't be changed by user
361 wxLogWarning(_("file '%s', line %d: value for "
362 "immutable key '%s' ignored."),
363 file
.GetName(), n
+ 1, strKey
.c_str());
366 // the condition below catches the cases (a) and (b) but not (c):
367 // (a) global key found second time in global file
368 // (b) key found second (or more) time in local file
369 // (c) key from global file now found in local one
370 // which is exactly what we want.
371 else if ( !bLocal
|| pEntry
->IsLocal() ) {
372 wxLogWarning(_("file '%s', line %d: key '%s' was first "
373 "found at line %d."),
374 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
377 pEntry
->SetLine(m_linesTail
);
382 while ( isspace(*pEnd
) )
385 pEntry
->SetValue(FilterIn(pEnd
), FALSE
/* read from file */);
391 // ----------------------------------------------------------------------------
393 // ----------------------------------------------------------------------------
395 void wxFileConfig::SetRootPath()
398 m_pCurrentGroup
= m_pRootGroup
;
401 void wxFileConfig::SetPath(const wxString
& strPath
)
403 wxArrayString aParts
;
405 if ( strPath
.IsEmpty() ) {
410 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
412 wxSplitPath(aParts
, strPath
);
415 // relative path, combine with current one
416 wxString strFullPath
= m_strPath
;
417 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
418 wxSplitPath(aParts
, strFullPath
);
421 // change current group
423 m_pCurrentGroup
= m_pRootGroup
;
424 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
425 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
426 if ( pNextGroup
== NULL
)
427 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
428 m_pCurrentGroup
= pNextGroup
;
431 // recombine path parts in one variable
433 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
434 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
438 // ----------------------------------------------------------------------------
440 // ----------------------------------------------------------------------------
442 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
445 return GetNextGroup(str
, lIndex
);
448 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
450 if ( size_t(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
451 str
= m_pCurrentGroup
->Groups()[lIndex
++]->Name();
458 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
461 return GetNextEntry(str
, lIndex
);
464 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
466 if ( size_t(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
467 str
= m_pCurrentGroup
->Entries()[lIndex
++]->Name();
474 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
476 size_t n
= m_pCurrentGroup
->Entries().Count();
478 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
479 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
480 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
481 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
482 n
+= GetNumberOfEntries(TRUE
);
483 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
490 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
492 size_t n
= m_pCurrentGroup
->Groups().Count();
494 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
495 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
496 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
497 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
498 n
+= GetNumberOfGroups(TRUE
);
499 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
506 // ----------------------------------------------------------------------------
507 // tests for existence
508 // ----------------------------------------------------------------------------
510 bool wxFileConfig::HasGroup(const wxString
& strName
) const
512 wxConfigPathChanger
path(this, strName
);
514 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
515 return pGroup
!= NULL
;
518 bool wxFileConfig::HasEntry(const wxString
& strName
) const
520 wxConfigPathChanger
path(this, strName
);
522 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
523 return pEntry
!= NULL
;
526 // ----------------------------------------------------------------------------
528 // ----------------------------------------------------------------------------
530 bool wxFileConfig::Read(const wxString
& key
,
531 wxString
* pStr
) const
533 wxConfigPathChanger
path(this, key
);
535 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
536 if (pEntry
== NULL
) {
540 *pStr
= ExpandEnvVars(pEntry
->Value());
545 bool wxFileConfig::Read(const wxString
& key
,
546 wxString
* pStr
, const wxString
& defVal
) const
548 wxConfigPathChanger
path(this, key
);
550 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
551 if (pEntry
== NULL
) {
552 if( IsRecordingDefaults() )
553 ((wxFileConfig
*)this)->Write(key
,defVal
);
554 *pStr
= ExpandEnvVars(defVal
);
558 *pStr
= ExpandEnvVars(pEntry
->Value());
563 bool wxFileConfig::Read(const wxString
& key
, long *pl
) const
566 if ( Read(key
, & str
) ) {
575 bool wxFileConfig::Write(const wxString
& key
, const wxString
& szValue
)
577 wxConfigPathChanger
path(this, key
);
579 wxString strName
= path
.Name();
580 if ( strName
.IsEmpty() ) {
581 // setting the value of a group is an error
582 wxASSERT_MSG( IsEmpty(szValue
), _("can't set value of a group!") );
584 // ... except if it's empty in which case it's a way to force it's creation
585 m_pCurrentGroup
->SetDirty();
587 // this will add a line for this group if it didn't have it before
588 (void)m_pCurrentGroup
->GetGroupLine();
593 // check that the name is reasonable
594 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
595 wxLogError(_("Entry name can't start with '%c'."),
596 wxCONFIG_IMMUTABLE_PREFIX
);
600 for ( const char *pc
= strName
; *pc
!= '\0'; pc
++ ) {
601 if ( !IsValid(*pc
) ) {
602 wxLogError(_("Character '%c' is invalid in a config entry name."),
608 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
609 if ( pEntry
== NULL
)
610 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
612 pEntry
->SetValue(szValue
);
618 bool wxFileConfig::Write(const wxString
& key
, long lValue
)
620 // ltoa() is not ANSI :-(
621 char szBuf
[40]; // should be good for sizeof(long) <= 16 (128 bits)
622 sprintf(szBuf
, "%ld", lValue
);
623 return Write(key
, szBuf
);
626 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
628 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() )
631 wxTempFile
file(m_strLocalFile
);
633 if ( !file
.IsOpened() ) {
634 wxLogError(_("can't open user configuration file."));
638 // write all strings to file
639 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
640 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
641 wxLogError(_("can't write user configuration file."));
646 return file
.Commit();
649 // ----------------------------------------------------------------------------
650 // delete groups/entries
651 // ----------------------------------------------------------------------------
653 bool wxFileConfig::DeleteEntry(const wxString
& key
, bool bGroupIfEmptyAlso
)
655 wxConfigPathChanger
path(this, key
);
657 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
660 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
661 if ( m_pCurrentGroup
!= m_pRootGroup
) {
662 ConfigGroup
*pGroup
= m_pCurrentGroup
;
663 SetPath(".."); // changes m_pCurrentGroup!
664 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
666 //else: never delete the root group
672 bool wxFileConfig::DeleteGroup(const wxString
& key
)
674 wxConfigPathChanger
path(this, key
);
676 return m_pCurrentGroup
->DeleteSubgroupByName(path
.Name());
679 bool wxFileConfig::DeleteAll()
683 const char *szFile
= m_strLocalFile
;
685 if ( remove(szFile
) == -1 )
686 wxLogSysError(_("can't delete user configuration file '%s'"), szFile
);
688 m_strLocalFile
= m_strGlobalFile
= "";
694 // ----------------------------------------------------------------------------
695 // linked list functions
696 // ----------------------------------------------------------------------------
698 // append a new line to the end of the list
699 wxFileConfig::LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
701 LineList
*pLine
= new LineList(str
);
703 if ( m_linesTail
== NULL
) {
709 m_linesTail
->SetNext(pLine
);
710 pLine
->SetPrev(m_linesTail
);
717 // insert a new line after the given one or in the very beginning if !pLine
718 wxFileConfig::LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
721 if ( pLine
== m_linesTail
)
722 return LineListAppend(str
);
724 LineList
*pNewLine
= new LineList(str
);
725 if ( pLine
== NULL
) {
726 // prepend to the list
727 pNewLine
->SetNext(m_linesHead
);
728 m_linesHead
->SetPrev(pNewLine
);
729 m_linesHead
= pNewLine
;
732 // insert before pLine
733 LineList
*pNext
= pLine
->Next();
734 pNewLine
->SetNext(pNext
);
735 pNewLine
->SetPrev(pLine
);
736 pNext
->SetPrev(pNewLine
);
737 pLine
->SetNext(pNewLine
);
743 void wxFileConfig::LineListRemove(LineList
*pLine
)
745 LineList
*pPrev
= pLine
->Prev(),
746 *pNext
= pLine
->Next();
752 pPrev
->SetNext(pNext
);
758 pNext
->SetPrev(pPrev
);
763 bool wxFileConfig::LineListIsEmpty()
765 return m_linesHead
== NULL
;
768 // ============================================================================
769 // wxFileConfig::ConfigGroup
770 // ============================================================================
772 // ----------------------------------------------------------------------------
774 // ----------------------------------------------------------------------------
777 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup
*pParent
,
778 const wxString
& strName
,
779 wxFileConfig
*pConfig
)
780 : m_aEntries(CompareEntries
),
781 m_aSubgroups(CompareGroups
),
793 // dtor deletes all children
794 wxFileConfig::ConfigGroup::~ConfigGroup()
797 size_t n
, nCount
= m_aEntries
.Count();
798 for ( n
= 0; n
< nCount
; n
++ )
799 delete m_aEntries
[n
];
802 nCount
= m_aSubgroups
.Count();
803 for ( n
= 0; n
< nCount
; n
++ )
804 delete m_aSubgroups
[n
];
807 // ----------------------------------------------------------------------------
809 // ----------------------------------------------------------------------------
811 void wxFileConfig::ConfigGroup::SetLine(LineList
*pLine
)
813 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
819 This is a bit complicated, so let me explain it in details. All lines that
820 were read from the local file (the only one we will ever modify) are stored
821 in a (doubly) linked list. Our problem is to know at which position in this
822 list should we insert the new entries/subgroups. To solve it we keep three
823 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
825 m_pLine points to the line containing "[group_name]"
826 m_pLastEntry points to the last entry of this group in the local file.
827 m_pLastGroup subgroup
829 Initially, they're NULL all three. When the group (an entry/subgroup) is read
830 from the local file, the corresponding variable is set. However, if the group
831 was read from the global file and then modified or created by the application
832 these variables are still NULL and we need to create the corresponding lines.
833 See the following functions (and comments preceding them) for the details of
836 Also, when our last entry/group are deleted we need to find the new last
837 element - the code in DeleteEntry/Subgroup does this by backtracking the list
838 of lines until it either founds an entry/subgroup (and this is the new last
839 element) or the m_pLine of the group, in which case there are no more entries
840 (or subgroups) left and m_pLast<element> becomes NULL.
842 NB: This last problem could be avoided for entries if we added new entries
843 immediately after m_pLine, but in this case the entries would appear
844 backwards in the config file (OTOH, it's not that important) and as we
845 would still need to do it for the subgroups the code wouldn't have been
846 significantly less complicated.
849 // Return the line which contains "[our name]". If we're still not in the list,
850 // add our line to it immediately after the last line of our parent group if we
851 // have it or in the very beginning if we're the root group.
852 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetGroupLine()
854 if ( m_pLine
== NULL
) {
855 ConfigGroup
*pParent
= Parent();
857 // this group wasn't present in local config file, add it now
858 if ( pParent
!= NULL
) {
859 wxString strFullName
;
860 strFullName
<< "[" << GetFullName().c_str() + 1 << "]"; // +1: no '/'
861 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
862 pParent
->GetLastGroupLine());
863 pParent
->SetLastGroup(this); // we're surely after all the others
866 // we return NULL, so that LineListInsert() will insert us in the
874 // Return the last line belonging to the subgroups of this group (after which
875 // we can add a new subgroup), if we don't have any subgroups or entries our
876 // last line is the group line (m_pLine) itself.
877 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastGroupLine()
879 // if we have any subgroups, our last line is the last line of the last
881 if ( m_pLastGroup
!= NULL
) {
882 wxFileConfig::LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
884 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
888 // no subgroups, so the last line is the line of thelast entry (if any)
889 return GetLastEntryLine();
892 // return the last line belonging to the entries of this group (after which
893 // we can add a new entry), if we don't have any entries we will add the new
894 // one immediately after the group line itself.
895 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastEntryLine()
897 if ( m_pLastEntry
!= NULL
) {
898 wxFileConfig::LineList
*pLine
= m_pLastEntry
->GetLine();
900 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
904 // no entries: insert after the group header
905 return GetGroupLine();
908 // ----------------------------------------------------------------------------
910 // ----------------------------------------------------------------------------
912 wxString
wxFileConfig::ConfigGroup::GetFullName() const
915 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
920 // ----------------------------------------------------------------------------
922 // ----------------------------------------------------------------------------
924 // use binary search because the array is sorted
925 wxFileConfig::ConfigEntry
*
926 wxFileConfig::ConfigGroup::FindEntry(const char *szName
) const
930 hi
= m_aEntries
.Count();
932 wxFileConfig::ConfigEntry
*pEntry
;
936 pEntry
= m_aEntries
[i
];
938 #if wxCONFIG_CASE_SENSITIVE
939 res
= strcmp(pEntry
->Name(), szName
);
941 res
= Stricmp(pEntry
->Name(), szName
);
955 wxFileConfig::ConfigGroup
*
956 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName
) const
960 hi
= m_aSubgroups
.Count();
962 wxFileConfig::ConfigGroup
*pGroup
;
966 pGroup
= m_aSubgroups
[i
];
968 #if wxCONFIG_CASE_SENSITIVE
969 res
= strcmp(pGroup
->Name(), szName
);
971 res
= Stricmp(pGroup
->Name(), szName
);
985 // ----------------------------------------------------------------------------
987 // ----------------------------------------------------------------------------
989 // create a new entry and add it to the current group
990 wxFileConfig::ConfigEntry
*
991 wxFileConfig::ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
993 wxASSERT( FindEntry(strName
) == NULL
);
995 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
996 m_aEntries
.Add(pEntry
);
1001 // create a new group and add it to the current group
1002 wxFileConfig::ConfigGroup
*
1003 wxFileConfig::ConfigGroup::AddSubgroup(const wxString
& strName
)
1005 wxASSERT( FindSubgroup(strName
) == NULL
);
1007 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
1008 m_aSubgroups
.Add(pGroup
);
1013 // ----------------------------------------------------------------------------
1015 // ----------------------------------------------------------------------------
1018 The delete operations are _very_ slow if we delete the last item of this
1019 group (see comments before GetXXXLineXXX functions for more details),
1020 so it's much better to start with the first entry/group if we want to
1021 delete several of them.
1024 bool wxFileConfig::ConfigGroup::DeleteSubgroupByName(const char *szName
)
1026 return DeleteSubgroup(FindSubgroup(szName
));
1029 // doesn't delete the subgroup itself, but does remove references to it from
1030 // all other data structures (and normally the returned pointer should be
1031 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1032 bool wxFileConfig::ConfigGroup::DeleteSubgroup(ConfigGroup
*pGroup
)
1034 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1036 // delete all entries
1037 size_t nCount
= pGroup
->m_aEntries
.Count();
1038 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1039 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1040 if ( pLine
!= NULL
)
1041 m_pConfig
->LineListRemove(pLine
);
1044 // and subgroups of this sungroup
1045 nCount
= pGroup
->m_aSubgroups
.Count();
1046 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1047 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[nGroup
]);
1050 LineList
*pLine
= pGroup
->m_pLine
;
1051 if ( pLine
!= NULL
) {
1052 // notice that we may do this test inside the previous "if" because the
1053 // last entry's line is surely !NULL
1054 if ( pGroup
== m_pLastGroup
) {
1055 // our last entry is being deleted - find the last one which stays
1056 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1058 // go back until we find a subgroup or reach the group's line
1059 ConfigGroup
*pNewLast
= NULL
;
1060 size_t n
, nSubgroups
= m_aSubgroups
.Count();
1062 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1063 // is it our subgroup?
1064 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1065 // do _not_ call GetGroupLine! we don't want to add it to the local
1066 // file if it's not already there
1067 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1068 pNewLast
= m_aSubgroups
[n
];
1071 if ( pNewLast
!= NULL
) // found?
1075 if ( pl
== m_pLine
) {
1076 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1078 // we've reached the group line without finding any subgroups
1079 m_pLastGroup
= NULL
;
1082 m_pLastGroup
= pNewLast
;
1085 m_pConfig
->LineListRemove(pLine
);
1090 m_aSubgroups
.Remove(pGroup
);
1096 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName
)
1098 ConfigEntry
*pEntry
= FindEntry(szName
);
1099 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1101 LineList
*pLine
= pEntry
->GetLine();
1102 if ( pLine
!= NULL
) {
1103 // notice that we may do this test inside the previous "if" because the
1104 // last entry's line is surely !NULL
1105 if ( pEntry
== m_pLastEntry
) {
1106 // our last entry is being deleted - find the last one which stays
1107 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1109 // go back until we find another entry or reach the group's line
1110 ConfigEntry
*pNewLast
= NULL
;
1111 size_t n
, nEntries
= m_aEntries
.Count();
1113 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1114 // is it our subgroup?
1115 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1116 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1117 pNewLast
= m_aEntries
[n
];
1120 if ( pNewLast
!= NULL
) // found?
1124 if ( pl
== m_pLine
) {
1125 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1127 // we've reached the group line without finding any subgroups
1128 m_pLastEntry
= NULL
;
1131 m_pLastEntry
= pNewLast
;
1134 m_pConfig
->LineListRemove(pLine
);
1137 // we must be written back for the changes to be saved
1140 m_aEntries
.Remove(pEntry
);
1146 // ----------------------------------------------------------------------------
1148 // ----------------------------------------------------------------------------
1149 void wxFileConfig::ConfigGroup::SetDirty()
1152 if ( Parent() != NULL
) // propagate upwards
1153 Parent()->SetDirty();
1156 // ============================================================================
1157 // wxFileConfig::ConfigEntry
1158 // ============================================================================
1160 // ----------------------------------------------------------------------------
1162 // ----------------------------------------------------------------------------
1163 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup
*pParent
,
1164 const wxString
& strName
,
1166 : m_strName(strName
)
1168 wxASSERT( !strName
.IsEmpty() );
1170 m_pParent
= pParent
;
1176 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1178 m_strName
.erase(0, 1); // remove first character
1181 // ----------------------------------------------------------------------------
1183 // ----------------------------------------------------------------------------
1185 void wxFileConfig::ConfigEntry::SetLine(LineList
*pLine
)
1187 if ( m_pLine
!= NULL
) {
1188 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1189 Name().c_str(), m_pParent
->GetFullName().c_str());
1193 Group()->SetLastEntry(this);
1196 // second parameter is FALSE if we read the value from file and prevents the
1197 // entry from being marked as 'dirty'
1198 void wxFileConfig::ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1200 if ( bUser
&& IsImmutable() ) {
1201 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1206 // do nothing if it's the same value
1207 if ( strValue
== m_strValue
)
1210 m_strValue
= strValue
;
1213 wxString strVal
= FilterOut(strValue
);
1215 strLine
<< m_strName
<< " = " << strVal
;
1217 if ( m_pLine
!= NULL
) {
1218 // entry was read from the local config file, just modify the line
1219 m_pLine
->SetText(strLine
);
1222 // add a new line to the file
1223 wxASSERT( m_nLine
== NOT_FOUND
); // consistency check
1225 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1226 Group()->GetLastEntryLine());
1227 Group()->SetLastEntry(this);
1234 void wxFileConfig::ConfigEntry::SetDirty()
1237 Group()->SetDirty();
1240 // ============================================================================
1242 // ============================================================================
1244 // ----------------------------------------------------------------------------
1245 // compare functions for array sorting
1246 // ----------------------------------------------------------------------------
1248 int CompareEntries(wxFileConfig::ConfigEntry
*p1
,
1249 wxFileConfig::ConfigEntry
*p2
)
1251 #if wxCONFIG_CASE_SENSITIVE
1252 return strcmp(p1
->Name(), p2
->Name());
1254 return Stricmp(p1
->Name(), p2
->Name());
1258 int CompareGroups(wxFileConfig::ConfigGroup
*p1
,
1259 wxFileConfig::ConfigGroup
*p2
)
1261 #if wxCONFIG_CASE_SENSITIVE
1262 return strcmp(p1
->Name(), p2
->Name());
1264 return Stricmp(p1
->Name(), p2
->Name());
1268 // ----------------------------------------------------------------------------
1270 // ----------------------------------------------------------------------------
1273 wxString
FilterIn(const wxString
& str
)
1276 strResult
.Alloc(str
.Len());
1278 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1280 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1281 if ( str
[n
] == '\\' ) {
1282 switch ( str
[++n
] ) {
1305 if ( str
[n
] != '"' || !bQuoted
)
1306 strResult
+= str
[n
];
1307 else if ( n
!= str
.Len() - 1 ) {
1308 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1311 //else: it's the last quote of a quoted string, ok
1318 // quote the string before writing it to file
1319 wxString
FilterOut(const wxString
& str
)
1325 strResult
.Alloc(str
.Len());
1327 // quoting is necessary to preserve spaces in the beginning of the string
1328 bool bQuote
= isspace(str
[0]) || str
[0] == '"';
1334 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
1357 //else: fall through
1360 strResult
+= str
[n
];
1361 continue; // nothing special to do
1364 // we get here only for special characters
1365 strResult
<< '\\' << c
;