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 #include "wx/utils.h" // for wxGetHomeDir
45 // _WINDOWS_ is defined when windows.h is included,
46 // __WXMSW__ is defined for MS Windows compilation
47 #if defined(__WXMSW__) && !defined(_WINDOWS_)
54 // ----------------------------------------------------------------------------
56 // ----------------------------------------------------------------------------
57 #define CONST_CAST ((wxFileConfig *)this)->
59 // ----------------------------------------------------------------------------
61 // ----------------------------------------------------------------------------
66 // ----------------------------------------------------------------------------
67 // global functions declarations
68 // ----------------------------------------------------------------------------
70 // is 'c' a valid character in group name?
71 // NB: wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR must be valid chars,
72 // but _not_ ']' (group name delimiter)
73 inline bool IsValid(char c
) { return isalnum(c
) || strchr("@_/-!.*%", c
); }
75 // compare functions for sorting the arrays
76 static int CompareEntries(wxFileConfig::ConfigEntry
*p1
,
77 wxFileConfig::ConfigEntry
*p2
);
78 static int CompareGroups(wxFileConfig::ConfigGroup
*p1
,
79 wxFileConfig::ConfigGroup
*p2
);
82 static wxString
FilterIn(const wxString
& str
);
83 static wxString
FilterOut(const wxString
& str
);
85 // ============================================================================
87 // ============================================================================
89 // ----------------------------------------------------------------------------
91 // ----------------------------------------------------------------------------
92 wxString
wxFileConfig::GetGlobalDir()
98 #elif defined(__WXSTUBS__)
99 wxASSERT_MSG( FALSE
, "TODO" ) ;
100 #elif defined(__WXMAC__)
101 wxASSERT_MSG( FALSE
, "TODO" ) ;
103 char szWinDir
[MAX_PATH
];
104 ::GetWindowsDirectory(szWinDir
, MAX_PATH
);
108 #endif // Unix/Windows
113 wxString
wxFileConfig::GetLocalDir()
117 wxGetHomeDir(&strDir
);
120 if (strDir
.Last() != '/') strDir
<< '/';
122 if (strDir
.Last() != '\\') strDir
<< '\\';
128 wxString
wxFileConfig::GetGlobalFileName(const char *szFile
)
130 wxString str
= GetGlobalDir();
133 if ( strchr(szFile
, '.') == NULL
)
143 wxString
wxFileConfig::GetLocalFileName(const char *szFile
)
145 wxString str
= GetLocalDir();
154 if ( strchr(szFile
, '.') == NULL
)
161 // ----------------------------------------------------------------------------
163 // ----------------------------------------------------------------------------
165 void wxFileConfig::Init()
168 m_pRootGroup
= new ConfigGroup(NULL
, "", this);
173 // it's not an error if (one of the) file(s) doesn't exist
175 // parse the global file
176 if ( !m_strGlobalFile
.IsEmpty() && wxFile::Exists(m_strGlobalFile
) ) {
177 wxTextFile
fileGlobal(m_strGlobalFile
);
179 if ( fileGlobal
.Open() ) {
180 Parse(fileGlobal
, FALSE
/* global */);
184 wxLogWarning(_("can't open global configuration file '%s'."),
185 m_strGlobalFile
.c_str());
188 // parse the local file
189 if ( !m_strLocalFile
.IsEmpty() && wxFile::Exists(m_strLocalFile
) ) {
190 wxTextFile
fileLocal(m_strLocalFile
);
191 if ( fileLocal
.Open() ) {
192 Parse(fileLocal
, TRUE
/* local */);
196 wxLogWarning(_("can't open user configuration file '%s'."),
197 m_strLocalFile
.c_str());
201 // constructor supports creation of wxFileConfig objects of any type
202 wxFileConfig::wxFileConfig(const wxString
& appName
, const wxString
& vendorName
,
203 const wxString
& strLocal
, const wxString
& strGlobal
,
205 : wxConfigBase(appName
, vendorName
, strLocal
, strGlobal
, style
),
206 m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
208 // Make up an application name if not supplied
209 if (appName
.IsEmpty() && wxTheApp
)
211 SetAppName(wxTheApp
->GetAppName());
214 // Make up names for files if empty
215 if ( m_strLocalFile
.IsEmpty() && (style
& wxCONFIG_USE_LOCAL_FILE
) )
217 m_strLocalFile
= GetLocalFileName(GetAppName());
220 if ( m_strGlobalFile
.IsEmpty() && (style
& wxCONFIG_USE_GLOBAL_FILE
) )
222 m_strGlobalFile
= GetGlobalFileName(GetAppName());
225 // Check if styles are not supplied, but filenames are, in which case
226 // add the correct styles.
227 if ( !m_strLocalFile
.IsEmpty() )
228 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE
);
230 if ( !m_strGlobalFile
.IsEmpty() )
231 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE
);
233 // if the path is not absolute, prepend the standard directory to it
234 if ( !m_strLocalFile
.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile
) )
236 wxString strLocal
= m_strLocalFile
;
237 m_strLocalFile
= GetLocalDir();
238 m_strLocalFile
<< strLocal
;
241 if ( !m_strGlobalFile
.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile
) )
243 wxString strGlobal
= m_strGlobalFile
;
244 m_strGlobalFile
= GetGlobalDir();
245 m_strGlobalFile
<< strGlobal
;
251 void wxFileConfig::CleanUp()
255 LineList
*pCur
= m_linesHead
;
256 while ( pCur
!= NULL
) {
257 LineList
*pNext
= pCur
->Next();
263 wxFileConfig::~wxFileConfig()
270 // ----------------------------------------------------------------------------
271 // parse a config file
272 // ----------------------------------------------------------------------------
274 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
280 size_t nLineCount
= file
.GetLineCount();
281 for ( size_t n
= 0; n
< nLineCount
; n
++ ) {
284 // add the line to linked list
286 LineListAppend(strLine
);
288 // skip leading spaces
289 for ( pStart
= strLine
; isspace(*pStart
); pStart
++ )
292 // skip blank/comment lines
293 if ( *pStart
== '\0'|| *pStart
== ';' || *pStart
== '#' )
296 if ( *pStart
== '[' ) { // a new group
299 while ( *++pEnd
!= ']' ) {
300 if ( !IsValid(*pEnd
) && *pEnd
!= ' ' ) // allow spaces in group names
304 if ( *pEnd
!= ']' ) {
305 wxLogError(_("file '%s': unexpected character %c at line %d."),
306 file
.GetName(), *pEnd
, n
+ 1);
307 continue; // skip this line
310 // group name here is always considered as abs path
313 strGroup
<< wxCONFIG_PATH_SEPARATOR
<< wxString(pStart
, pEnd
- pStart
);
315 // will create it if doesn't yet exist
319 m_pCurrentGroup
->SetLine(m_linesTail
);
321 // check that there is nothing except comments left on this line
323 while ( *++pEnd
!= '\0' && bCont
) {
332 // ignore whitespace ('\n' impossible here)
336 wxLogWarning(_("file '%s', line %d: '%s' "
337 "ignored after group header."),
338 file
.GetName(), n
+ 1, pEnd
);
344 const char *pEnd
= pStart
;
345 while ( IsValid(*pEnd
) )
348 wxString
strKey(pStart
, pEnd
);
351 while ( isspace(*pEnd
) )
354 if ( *pEnd
++ != '=' ) {
355 wxLogError(_("file '%s', line %d: '=' expected."),
356 file
.GetName(), n
+ 1);
359 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
361 if ( pEntry
== NULL
) {
363 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
366 pEntry
->SetLine(m_linesTail
);
369 if ( bLocal
&& pEntry
->IsImmutable() ) {
370 // immutable keys can't be changed by user
371 wxLogWarning(_("file '%s', line %d: value for "
372 "immutable key '%s' ignored."),
373 file
.GetName(), n
+ 1, strKey
.c_str());
376 // the condition below catches the cases (a) and (b) but not (c):
377 // (a) global key found second time in global file
378 // (b) key found second (or more) time in local file
379 // (c) key from global file now found in local one
380 // which is exactly what we want.
381 else if ( !bLocal
|| pEntry
->IsLocal() ) {
382 wxLogWarning(_("file '%s', line %d: key '%s' was first "
383 "found at line %d."),
384 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
387 pEntry
->SetLine(m_linesTail
);
392 while ( isspace(*pEnd
) )
395 pEntry
->SetValue(FilterIn(pEnd
), FALSE
/* read from file */);
401 // ----------------------------------------------------------------------------
403 // ----------------------------------------------------------------------------
405 void wxFileConfig::SetRootPath()
408 m_pCurrentGroup
= m_pRootGroup
;
411 void wxFileConfig::SetPath(const wxString
& strPath
)
413 wxArrayString aParts
;
415 if ( strPath
.IsEmpty() ) {
420 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
422 wxSplitPath(aParts
, strPath
);
425 // relative path, combine with current one
426 wxString strFullPath
= m_strPath
;
427 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
428 wxSplitPath(aParts
, strFullPath
);
431 // change current group
433 m_pCurrentGroup
= m_pRootGroup
;
434 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
435 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
436 if ( pNextGroup
== NULL
)
437 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
438 m_pCurrentGroup
= pNextGroup
;
441 // recombine path parts in one variable
443 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
444 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
448 // ----------------------------------------------------------------------------
450 // ----------------------------------------------------------------------------
452 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
455 return GetNextGroup(str
, lIndex
);
458 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
460 if ( size_t(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
461 str
= m_pCurrentGroup
->Groups()[lIndex
++]->Name();
468 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
471 return GetNextEntry(str
, lIndex
);
474 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
476 if ( size_t(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
477 str
= m_pCurrentGroup
->Entries()[lIndex
++]->Name();
484 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
486 size_t n
= m_pCurrentGroup
->Entries().Count();
488 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
489 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
490 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
491 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
492 n
+= GetNumberOfEntries(TRUE
);
493 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
500 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
502 size_t n
= m_pCurrentGroup
->Groups().Count();
504 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
505 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
506 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
507 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
508 n
+= GetNumberOfGroups(TRUE
);
509 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
516 // ----------------------------------------------------------------------------
517 // tests for existence
518 // ----------------------------------------------------------------------------
520 bool wxFileConfig::HasGroup(const wxString
& strName
) const
522 wxConfigPathChanger
path(this, strName
);
524 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
525 return pGroup
!= NULL
;
528 bool wxFileConfig::HasEntry(const wxString
& strName
) const
530 wxConfigPathChanger
path(this, strName
);
532 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
533 return pEntry
!= NULL
;
536 // ----------------------------------------------------------------------------
538 // ----------------------------------------------------------------------------
540 bool wxFileConfig::Read(const wxString
& key
,
541 wxString
* pStr
) const
543 wxConfigPathChanger
path(this, key
);
545 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
546 if (pEntry
== NULL
) {
550 *pStr
= ExpandEnvVars(pEntry
->Value());
555 bool wxFileConfig::Read(const wxString
& key
,
556 wxString
* pStr
, const wxString
& defVal
) const
558 wxConfigPathChanger
path(this, key
);
560 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
561 if (pEntry
== NULL
) {
562 if( IsRecordingDefaults() )
563 ((wxFileConfig
*)this)->Write(key
,defVal
);
564 *pStr
= ExpandEnvVars(defVal
);
568 *pStr
= ExpandEnvVars(pEntry
->Value());
573 bool wxFileConfig::Read(const wxString
& key
, long *pl
) const
576 if ( Read(key
, & str
) ) {
585 bool wxFileConfig::Write(const wxString
& key
, const wxString
& szValue
)
587 wxConfigPathChanger
path(this, key
);
589 wxString strName
= path
.Name();
590 if ( strName
.IsEmpty() ) {
591 // setting the value of a group is an error
592 wxASSERT_MSG( IsEmpty(szValue
), _("can't set value of a group!") );
594 // ... except if it's empty in which case it's a way to force it's creation
595 m_pCurrentGroup
->SetDirty();
597 // this will add a line for this group if it didn't have it before
598 (void)m_pCurrentGroup
->GetGroupLine();
603 // check that the name is reasonable
604 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
605 wxLogError(_("Entry name can't start with '%c'."),
606 wxCONFIG_IMMUTABLE_PREFIX
);
610 for ( const char *pc
= strName
; *pc
!= '\0'; pc
++ ) {
611 if ( !IsValid(*pc
) ) {
612 wxLogError(_("Character '%c' is invalid in a config entry name."),
618 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
619 if ( pEntry
== NULL
)
620 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
622 pEntry
->SetValue(szValue
);
628 bool wxFileConfig::Write(const wxString
& key
, long lValue
)
630 // ltoa() is not ANSI :-(
632 buf
.Printf("%ld", lValue
);
633 return Write(key
, buf
);
636 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
638 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() )
641 wxTempFile
file(m_strLocalFile
);
643 if ( !file
.IsOpened() ) {
644 wxLogError(_("can't open user configuration file."));
648 // write all strings to file
649 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
650 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
651 wxLogError(_("can't write user configuration file."));
656 return file
.Commit();
659 // ----------------------------------------------------------------------------
660 // delete groups/entries
661 // ----------------------------------------------------------------------------
663 bool wxFileConfig::DeleteEntry(const wxString
& key
, bool bGroupIfEmptyAlso
)
665 wxConfigPathChanger
path(this, key
);
667 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
670 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
671 if ( m_pCurrentGroup
!= m_pRootGroup
) {
672 ConfigGroup
*pGroup
= m_pCurrentGroup
;
673 SetPath(".."); // changes m_pCurrentGroup!
674 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
676 //else: never delete the root group
682 bool wxFileConfig::DeleteGroup(const wxString
& key
)
684 wxConfigPathChanger
path(this, key
);
686 return m_pCurrentGroup
->DeleteSubgroupByName(path
.Name());
689 bool wxFileConfig::DeleteAll()
693 const char *szFile
= m_strLocalFile
;
695 if ( remove(szFile
) == -1 )
696 wxLogSysError(_("can't delete user configuration file '%s'"), szFile
);
698 m_strLocalFile
= m_strGlobalFile
= "";
704 // ----------------------------------------------------------------------------
705 // linked list functions
706 // ----------------------------------------------------------------------------
708 // append a new line to the end of the list
709 wxFileConfig::LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
711 LineList
*pLine
= new LineList(str
);
713 if ( m_linesTail
== NULL
) {
719 m_linesTail
->SetNext(pLine
);
720 pLine
->SetPrev(m_linesTail
);
727 // insert a new line after the given one or in the very beginning if !pLine
728 wxFileConfig::LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
731 if ( pLine
== m_linesTail
)
732 return LineListAppend(str
);
734 LineList
*pNewLine
= new LineList(str
);
735 if ( pLine
== NULL
) {
736 // prepend to the list
737 pNewLine
->SetNext(m_linesHead
);
738 m_linesHead
->SetPrev(pNewLine
);
739 m_linesHead
= pNewLine
;
742 // insert before pLine
743 LineList
*pNext
= pLine
->Next();
744 pNewLine
->SetNext(pNext
);
745 pNewLine
->SetPrev(pLine
);
746 pNext
->SetPrev(pNewLine
);
747 pLine
->SetNext(pNewLine
);
753 void wxFileConfig::LineListRemove(LineList
*pLine
)
755 LineList
*pPrev
= pLine
->Prev(),
756 *pNext
= pLine
->Next();
762 pPrev
->SetNext(pNext
);
768 pNext
->SetPrev(pPrev
);
773 bool wxFileConfig::LineListIsEmpty()
775 return m_linesHead
== NULL
;
778 // ============================================================================
779 // wxFileConfig::ConfigGroup
780 // ============================================================================
782 // ----------------------------------------------------------------------------
784 // ----------------------------------------------------------------------------
787 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup
*pParent
,
788 const wxString
& strName
,
789 wxFileConfig
*pConfig
)
790 : m_aEntries(CompareEntries
),
791 m_aSubgroups(CompareGroups
),
803 // dtor deletes all children
804 wxFileConfig::ConfigGroup::~ConfigGroup()
807 size_t n
, nCount
= m_aEntries
.Count();
808 for ( n
= 0; n
< nCount
; n
++ )
809 delete m_aEntries
[n
];
812 nCount
= m_aSubgroups
.Count();
813 for ( n
= 0; n
< nCount
; n
++ )
814 delete m_aSubgroups
[n
];
817 // ----------------------------------------------------------------------------
819 // ----------------------------------------------------------------------------
821 void wxFileConfig::ConfigGroup::SetLine(LineList
*pLine
)
823 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
829 This is a bit complicated, so let me explain it in details. All lines that
830 were read from the local file (the only one we will ever modify) are stored
831 in a (doubly) linked list. Our problem is to know at which position in this
832 list should we insert the new entries/subgroups. To solve it we keep three
833 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
835 m_pLine points to the line containing "[group_name]"
836 m_pLastEntry points to the last entry of this group in the local file.
837 m_pLastGroup subgroup
839 Initially, they're NULL all three. When the group (an entry/subgroup) is read
840 from the local file, the corresponding variable is set. However, if the group
841 was read from the global file and then modified or created by the application
842 these variables are still NULL and we need to create the corresponding lines.
843 See the following functions (and comments preceding them) for the details of
846 Also, when our last entry/group are deleted we need to find the new last
847 element - the code in DeleteEntry/Subgroup does this by backtracking the list
848 of lines until it either founds an entry/subgroup (and this is the new last
849 element) or the m_pLine of the group, in which case there are no more entries
850 (or subgroups) left and m_pLast<element> becomes NULL.
852 NB: This last problem could be avoided for entries if we added new entries
853 immediately after m_pLine, but in this case the entries would appear
854 backwards in the config file (OTOH, it's not that important) and as we
855 would still need to do it for the subgroups the code wouldn't have been
856 significantly less complicated.
859 // Return the line which contains "[our name]". If we're still not in the list,
860 // add our line to it immediately after the last line of our parent group if we
861 // have it or in the very beginning if we're the root group.
862 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetGroupLine()
864 if ( m_pLine
== NULL
) {
865 ConfigGroup
*pParent
= Parent();
867 // this group wasn't present in local config file, add it now
868 if ( pParent
!= NULL
) {
869 wxString strFullName
;
870 strFullName
<< "[" << (GetFullName().c_str() + 1) << "]"; // +1: no '/'
871 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
872 pParent
->GetLastGroupLine());
873 pParent
->SetLastGroup(this); // we're surely after all the others
876 // we return NULL, so that LineListInsert() will insert us in the
884 // Return the last line belonging to the subgroups of this group (after which
885 // we can add a new subgroup), if we don't have any subgroups or entries our
886 // last line is the group line (m_pLine) itself.
887 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastGroupLine()
889 // if we have any subgroups, our last line is the last line of the last
891 if ( m_pLastGroup
!= NULL
) {
892 wxFileConfig::LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
894 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
898 // no subgroups, so the last line is the line of thelast entry (if any)
899 return GetLastEntryLine();
902 // return the last line belonging to the entries of this group (after which
903 // we can add a new entry), if we don't have any entries we will add the new
904 // one immediately after the group line itself.
905 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastEntryLine()
907 if ( m_pLastEntry
!= NULL
) {
908 wxFileConfig::LineList
*pLine
= m_pLastEntry
->GetLine();
910 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
914 // no entries: insert after the group header
915 return GetGroupLine();
918 // ----------------------------------------------------------------------------
920 // ----------------------------------------------------------------------------
922 wxString
wxFileConfig::ConfigGroup::GetFullName() const
925 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
930 // ----------------------------------------------------------------------------
932 // ----------------------------------------------------------------------------
934 // use binary search because the array is sorted
935 wxFileConfig::ConfigEntry
*
936 wxFileConfig::ConfigGroup::FindEntry(const char *szName
) const
940 hi
= m_aEntries
.Count();
942 wxFileConfig::ConfigEntry
*pEntry
;
946 pEntry
= m_aEntries
[i
];
948 #if wxCONFIG_CASE_SENSITIVE
949 res
= strcmp(pEntry
->Name(), szName
);
951 res
= Stricmp(pEntry
->Name(), szName
);
965 wxFileConfig::ConfigGroup
*
966 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName
) const
970 hi
= m_aSubgroups
.Count();
972 wxFileConfig::ConfigGroup
*pGroup
;
976 pGroup
= m_aSubgroups
[i
];
978 #if wxCONFIG_CASE_SENSITIVE
979 res
= strcmp(pGroup
->Name(), szName
);
981 res
= Stricmp(pGroup
->Name(), szName
);
995 // ----------------------------------------------------------------------------
997 // ----------------------------------------------------------------------------
999 // create a new entry and add it to the current group
1000 wxFileConfig::ConfigEntry
*
1001 wxFileConfig::ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
1003 wxASSERT( FindEntry(strName
) == NULL
);
1005 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
1006 m_aEntries
.Add(pEntry
);
1011 // create a new group and add it to the current group
1012 wxFileConfig::ConfigGroup
*
1013 wxFileConfig::ConfigGroup::AddSubgroup(const wxString
& strName
)
1015 wxASSERT( FindSubgroup(strName
) == NULL
);
1017 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
1018 m_aSubgroups
.Add(pGroup
);
1023 // ----------------------------------------------------------------------------
1025 // ----------------------------------------------------------------------------
1028 The delete operations are _very_ slow if we delete the last item of this
1029 group (see comments before GetXXXLineXXX functions for more details),
1030 so it's much better to start with the first entry/group if we want to
1031 delete several of them.
1034 bool wxFileConfig::ConfigGroup::DeleteSubgroupByName(const char *szName
)
1036 return DeleteSubgroup(FindSubgroup(szName
));
1039 // doesn't delete the subgroup itself, but does remove references to it from
1040 // all other data structures (and normally the returned pointer should be
1041 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1042 bool wxFileConfig::ConfigGroup::DeleteSubgroup(ConfigGroup
*pGroup
)
1044 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1046 // delete all entries
1047 size_t nCount
= pGroup
->m_aEntries
.Count();
1048 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1049 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1050 if ( pLine
!= NULL
)
1051 m_pConfig
->LineListRemove(pLine
);
1054 // and subgroups of this sungroup
1055 nCount
= pGroup
->m_aSubgroups
.Count();
1056 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1057 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[nGroup
]);
1060 LineList
*pLine
= pGroup
->m_pLine
;
1061 if ( pLine
!= NULL
) {
1062 // notice that we may do this test inside the previous "if" because the
1063 // last entry's line is surely !NULL
1064 if ( pGroup
== m_pLastGroup
) {
1065 // our last entry is being deleted - find the last one which stays
1066 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1068 // go back until we find a subgroup or reach the group's line
1069 ConfigGroup
*pNewLast
= NULL
;
1070 size_t n
, nSubgroups
= m_aSubgroups
.Count();
1072 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1073 // is it our subgroup?
1074 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1075 // do _not_ call GetGroupLine! we don't want to add it to the local
1076 // file if it's not already there
1077 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1078 pNewLast
= m_aSubgroups
[n
];
1081 if ( pNewLast
!= NULL
) // found?
1085 if ( pl
== m_pLine
) {
1086 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1088 // we've reached the group line without finding any subgroups
1089 m_pLastGroup
= NULL
;
1092 m_pLastGroup
= pNewLast
;
1095 m_pConfig
->LineListRemove(pLine
);
1100 m_aSubgroups
.Remove(pGroup
);
1106 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName
)
1108 ConfigEntry
*pEntry
= FindEntry(szName
);
1109 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1111 LineList
*pLine
= pEntry
->GetLine();
1112 if ( pLine
!= NULL
) {
1113 // notice that we may do this test inside the previous "if" because the
1114 // last entry's line is surely !NULL
1115 if ( pEntry
== m_pLastEntry
) {
1116 // our last entry is being deleted - find the last one which stays
1117 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1119 // go back until we find another entry or reach the group's line
1120 ConfigEntry
*pNewLast
= NULL
;
1121 size_t n
, nEntries
= m_aEntries
.Count();
1123 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1124 // is it our subgroup?
1125 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1126 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1127 pNewLast
= m_aEntries
[n
];
1130 if ( pNewLast
!= NULL
) // found?
1134 if ( pl
== m_pLine
) {
1135 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1137 // we've reached the group line without finding any subgroups
1138 m_pLastEntry
= NULL
;
1141 m_pLastEntry
= pNewLast
;
1144 m_pConfig
->LineListRemove(pLine
);
1147 // we must be written back for the changes to be saved
1150 m_aEntries
.Remove(pEntry
);
1156 // ----------------------------------------------------------------------------
1158 // ----------------------------------------------------------------------------
1159 void wxFileConfig::ConfigGroup::SetDirty()
1162 if ( Parent() != NULL
) // propagate upwards
1163 Parent()->SetDirty();
1166 // ============================================================================
1167 // wxFileConfig::ConfigEntry
1168 // ============================================================================
1170 // ----------------------------------------------------------------------------
1172 // ----------------------------------------------------------------------------
1173 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup
*pParent
,
1174 const wxString
& strName
,
1176 : m_strName(strName
)
1178 wxASSERT( !strName
.IsEmpty() );
1180 m_pParent
= pParent
;
1186 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1188 m_strName
.erase(0, 1); // remove first character
1191 // ----------------------------------------------------------------------------
1193 // ----------------------------------------------------------------------------
1195 void wxFileConfig::ConfigEntry::SetLine(LineList
*pLine
)
1197 if ( m_pLine
!= NULL
) {
1198 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1199 Name().c_str(), m_pParent
->GetFullName().c_str());
1203 Group()->SetLastEntry(this);
1206 // second parameter is FALSE if we read the value from file and prevents the
1207 // entry from being marked as 'dirty'
1208 void wxFileConfig::ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1210 if ( bUser
&& IsImmutable() ) {
1211 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1216 // do nothing if it's the same value
1217 if ( strValue
== m_strValue
)
1220 m_strValue
= strValue
;
1223 wxString strVal
= FilterOut(strValue
);
1225 strLine
<< m_strName
<< " = " << strVal
;
1227 if ( m_pLine
!= NULL
) {
1228 // entry was read from the local config file, just modify the line
1229 m_pLine
->SetText(strLine
);
1232 // add a new line to the file
1233 wxASSERT( m_nLine
== NOT_FOUND
); // consistency check
1235 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1236 Group()->GetLastEntryLine());
1237 Group()->SetLastEntry(this);
1244 void wxFileConfig::ConfigEntry::SetDirty()
1247 Group()->SetDirty();
1250 // ============================================================================
1252 // ============================================================================
1254 // ----------------------------------------------------------------------------
1255 // compare functions for array sorting
1256 // ----------------------------------------------------------------------------
1258 int CompareEntries(wxFileConfig::ConfigEntry
*p1
,
1259 wxFileConfig::ConfigEntry
*p2
)
1261 #if wxCONFIG_CASE_SENSITIVE
1262 return strcmp(p1
->Name(), p2
->Name());
1264 return Stricmp(p1
->Name(), p2
->Name());
1268 int CompareGroups(wxFileConfig::ConfigGroup
*p1
,
1269 wxFileConfig::ConfigGroup
*p2
)
1271 #if wxCONFIG_CASE_SENSITIVE
1272 return strcmp(p1
->Name(), p2
->Name());
1274 return Stricmp(p1
->Name(), p2
->Name());
1278 // ----------------------------------------------------------------------------
1280 // ----------------------------------------------------------------------------
1283 wxString
FilterIn(const wxString
& str
)
1286 strResult
.Alloc(str
.Len());
1288 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1290 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1291 if ( str
[n
] == '\\' ) {
1292 switch ( str
[++n
] ) {
1315 if ( str
[n
] != '"' || !bQuoted
)
1316 strResult
+= str
[n
];
1317 else if ( n
!= str
.Len() - 1 ) {
1318 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1321 //else: it's the last quote of a quoted string, ok
1328 // quote the string before writing it to file
1329 wxString
FilterOut(const wxString
& str
)
1335 strResult
.Alloc(str
.Len());
1337 // quoting is necessary to preserve spaces in the beginning of the string
1338 bool bQuote
= isspace(str
[0]) || str
[0] == '"';
1344 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
1367 //else: fall through
1370 strResult
+= str
[n
];
1371 continue; // nothing special to do
1374 // we get here only for special characters
1375 strResult
<< '\\' << c
;