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" ) ;
101 char szWinDir
[MAX_PATH
];
102 ::GetWindowsDirectory(szWinDir
, MAX_PATH
);
106 #endif // Unix/Windows
111 wxString
wxFileConfig::GetLocalDir()
115 wxGetHomeDir(&strDir
);
117 if (strDir
.Last() != '/' && strDir
.Last() != '\\')
123 wxString
wxFileConfig::GetGlobalFileName(const char *szFile
)
125 wxString str
= GetGlobalDir();
128 if ( strchr(szFile
, '.') == NULL
)
138 wxString
wxFileConfig::GetLocalFileName(const char *szFile
)
140 wxString str
= GetLocalDir();
149 if ( strchr(szFile
, '.') == NULL
)
156 // ----------------------------------------------------------------------------
158 // ----------------------------------------------------------------------------
160 void wxFileConfig::Init()
163 m_pRootGroup
= new ConfigGroup(NULL
, "", this);
168 // it's not an error if (one of the) file(s) doesn't exist
170 // parse the global file
171 if ( !m_strGlobalFile
.IsEmpty() && wxFile::Exists(m_strGlobalFile
) ) {
172 wxTextFile
fileGlobal(m_strGlobalFile
);
174 if ( fileGlobal
.Open() ) {
175 Parse(fileGlobal
, FALSE
/* global */);
179 wxLogWarning(_("can't open global configuration file '%s'."),
180 m_strGlobalFile
.c_str());
183 // parse the local file
184 if ( !m_strLocalFile
.IsEmpty() && wxFile::Exists(m_strLocalFile
) ) {
185 wxTextFile
fileLocal(m_strLocalFile
);
186 if ( fileLocal
.Open() ) {
187 Parse(fileLocal
, TRUE
/* local */);
191 wxLogWarning(_("can't open user configuration file '%s'."),
192 m_strLocalFile
.c_str());
196 // constructor supports creation of wxFileConfig objects of any type
197 wxFileConfig::wxFileConfig(const wxString
& appName
, const wxString
& vendorName
,
198 const wxString
& strLocal
, const wxString
& strGlobal
,
200 : wxConfigBase(appName
, vendorName
, strLocal
, strGlobal
, style
),
201 m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
203 // Make up an application name if not supplied
204 if (appName
.IsEmpty() && wxTheApp
)
206 SetAppName(wxTheApp
->GetAppName());
209 // Make up names for files if empty
210 if ( m_strLocalFile
.IsEmpty() && (style
& wxCONFIG_USE_LOCAL_FILE
) )
212 m_strLocalFile
= GetLocalFileName(GetAppName());
215 if ( m_strGlobalFile
.IsEmpty() && (style
& wxCONFIG_USE_GLOBAL_FILE
) )
217 m_strGlobalFile
= GetGlobalFileName(GetAppName());
220 // Check if styles are not supplied, but filenames are, in which case
221 // add the correct styles.
222 if ( !m_strLocalFile
.IsEmpty() )
223 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE
);
225 if ( !m_strGlobalFile
.IsEmpty() )
226 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE
);
228 // if the path is not absolute, prepend the standard directory to it
229 if ( !m_strLocalFile
.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile
) )
231 wxString strLocal
= m_strLocalFile
;
232 m_strLocalFile
= GetLocalDir();
233 m_strLocalFile
<< strLocal
;
236 if ( !m_strGlobalFile
.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile
) )
238 wxString strGlobal
= m_strGlobalFile
;
239 m_strGlobalFile
= GetGlobalDir();
240 m_strGlobalFile
<< strGlobal
;
246 void wxFileConfig::CleanUp()
250 LineList
*pCur
= m_linesHead
;
251 while ( pCur
!= NULL
) {
252 LineList
*pNext
= pCur
->Next();
258 wxFileConfig::~wxFileConfig()
265 // ----------------------------------------------------------------------------
266 // parse a config file
267 // ----------------------------------------------------------------------------
269 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
275 size_t nLineCount
= file
.GetLineCount();
276 for ( size_t n
= 0; n
< nLineCount
; n
++ ) {
279 // add the line to linked list
281 LineListAppend(strLine
);
283 // skip leading spaces
284 for ( pStart
= strLine
; isspace(*pStart
); pStart
++ )
287 // skip blank/comment lines
288 if ( *pStart
== '\0'|| *pStart
== ';' || *pStart
== '#' )
291 if ( *pStart
== '[' ) { // a new group
294 while ( *++pEnd
!= ']' ) {
295 if ( !IsValid(*pEnd
) && *pEnd
!= ' ' ) // allow spaces in group names
299 if ( *pEnd
!= ']' ) {
300 wxLogError(_("file '%s': unexpected character %c at line %d."),
301 file
.GetName(), *pEnd
, n
+ 1);
302 continue; // skip this line
305 // group name here is always considered as abs path
308 strGroup
<< wxCONFIG_PATH_SEPARATOR
<< wxString(pStart
, pEnd
- pStart
);
310 // will create it if doesn't yet exist
314 m_pCurrentGroup
->SetLine(m_linesTail
);
316 // check that there is nothing except comments left on this line
318 while ( *++pEnd
!= '\0' && bCont
) {
327 // ignore whitespace ('\n' impossible here)
331 wxLogWarning(_("file '%s', line %d: '%s' "
332 "ignored after group header."),
333 file
.GetName(), n
+ 1, pEnd
);
339 const char *pEnd
= pStart
;
340 while ( IsValid(*pEnd
) )
343 wxString
strKey(pStart
, pEnd
);
346 while ( isspace(*pEnd
) )
349 if ( *pEnd
++ != '=' ) {
350 wxLogError(_("file '%s', line %d: '=' expected."),
351 file
.GetName(), n
+ 1);
354 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
356 if ( pEntry
== NULL
) {
358 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
361 pEntry
->SetLine(m_linesTail
);
364 if ( bLocal
&& pEntry
->IsImmutable() ) {
365 // immutable keys can't be changed by user
366 wxLogWarning(_("file '%s', line %d: value for "
367 "immutable key '%s' ignored."),
368 file
.GetName(), n
+ 1, strKey
.c_str());
371 // the condition below catches the cases (a) and (b) but not (c):
372 // (a) global key found second time in global file
373 // (b) key found second (or more) time in local file
374 // (c) key from global file now found in local one
375 // which is exactly what we want.
376 else if ( !bLocal
|| pEntry
->IsLocal() ) {
377 wxLogWarning(_("file '%s', line %d: key '%s' was first "
378 "found at line %d."),
379 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
382 pEntry
->SetLine(m_linesTail
);
387 while ( isspace(*pEnd
) )
390 pEntry
->SetValue(FilterIn(pEnd
), FALSE
/* read from file */);
396 // ----------------------------------------------------------------------------
398 // ----------------------------------------------------------------------------
400 void wxFileConfig::SetRootPath()
403 m_pCurrentGroup
= m_pRootGroup
;
406 void wxFileConfig::SetPath(const wxString
& strPath
)
408 wxArrayString aParts
;
410 if ( strPath
.IsEmpty() ) {
415 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
417 wxSplitPath(aParts
, strPath
);
420 // relative path, combine with current one
421 wxString strFullPath
= m_strPath
;
422 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
423 wxSplitPath(aParts
, strFullPath
);
426 // change current group
428 m_pCurrentGroup
= m_pRootGroup
;
429 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
430 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
431 if ( pNextGroup
== NULL
)
432 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
433 m_pCurrentGroup
= pNextGroup
;
436 // recombine path parts in one variable
438 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
439 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
443 // ----------------------------------------------------------------------------
445 // ----------------------------------------------------------------------------
447 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
450 return GetNextGroup(str
, lIndex
);
453 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
455 if ( size_t(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
456 str
= m_pCurrentGroup
->Groups()[lIndex
++]->Name();
463 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
466 return GetNextEntry(str
, lIndex
);
469 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
471 if ( size_t(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
472 str
= m_pCurrentGroup
->Entries()[lIndex
++]->Name();
479 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
481 size_t n
= m_pCurrentGroup
->Entries().Count();
483 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
484 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
485 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
486 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
487 n
+= GetNumberOfEntries(TRUE
);
488 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
495 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
497 size_t n
= m_pCurrentGroup
->Groups().Count();
499 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
500 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
501 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
502 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
503 n
+= GetNumberOfGroups(TRUE
);
504 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
511 // ----------------------------------------------------------------------------
512 // tests for existence
513 // ----------------------------------------------------------------------------
515 bool wxFileConfig::HasGroup(const wxString
& strName
) const
517 wxConfigPathChanger
path(this, strName
);
519 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
520 return pGroup
!= NULL
;
523 bool wxFileConfig::HasEntry(const wxString
& strName
) const
525 wxConfigPathChanger
path(this, strName
);
527 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
528 return pEntry
!= NULL
;
531 // ----------------------------------------------------------------------------
533 // ----------------------------------------------------------------------------
535 bool wxFileConfig::Read(const wxString
& key
,
536 wxString
* pStr
) const
538 wxConfigPathChanger
path(this, key
);
540 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
541 if (pEntry
== NULL
) {
545 *pStr
= ExpandEnvVars(pEntry
->Value());
550 bool wxFileConfig::Read(const wxString
& key
,
551 wxString
* pStr
, const wxString
& defVal
) const
553 wxConfigPathChanger
path(this, key
);
555 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
556 if (pEntry
== NULL
) {
557 if( IsRecordingDefaults() )
558 ((wxFileConfig
*)this)->Write(key
,defVal
);
559 *pStr
= ExpandEnvVars(defVal
);
563 *pStr
= ExpandEnvVars(pEntry
->Value());
568 bool wxFileConfig::Read(const wxString
& key
, long *pl
) const
571 if ( Read(key
, & str
) ) {
580 bool wxFileConfig::Write(const wxString
& key
, const wxString
& szValue
)
582 wxConfigPathChanger
path(this, key
);
584 wxString strName
= path
.Name();
585 if ( strName
.IsEmpty() ) {
586 // setting the value of a group is an error
587 wxASSERT_MSG( IsEmpty(szValue
), _("can't set value of a group!") );
589 // ... except if it's empty in which case it's a way to force it's creation
590 m_pCurrentGroup
->SetDirty();
592 // this will add a line for this group if it didn't have it before
593 (void)m_pCurrentGroup
->GetGroupLine();
598 // check that the name is reasonable
599 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
600 wxLogError(_("Entry name can't start with '%c'."),
601 wxCONFIG_IMMUTABLE_PREFIX
);
605 for ( const char *pc
= strName
; *pc
!= '\0'; pc
++ ) {
606 if ( !IsValid(*pc
) ) {
607 wxLogError(_("Character '%c' is invalid in a config entry name."),
613 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
614 if ( pEntry
== NULL
)
615 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
617 pEntry
->SetValue(szValue
);
623 bool wxFileConfig::Write(const wxString
& key
, long lValue
)
625 // ltoa() is not ANSI :-(
626 char szBuf
[40]; // should be good for sizeof(long) <= 16 (128 bits)
627 sprintf(szBuf
, "%ld", lValue
);
628 return Write(key
, szBuf
);
631 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
633 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() )
636 wxTempFile
file(m_strLocalFile
);
638 if ( !file
.IsOpened() ) {
639 wxLogError(_("can't open user configuration file."));
643 // write all strings to file
644 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
645 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
646 wxLogError(_("can't write user configuration file."));
651 return file
.Commit();
654 // ----------------------------------------------------------------------------
655 // delete groups/entries
656 // ----------------------------------------------------------------------------
658 bool wxFileConfig::DeleteEntry(const wxString
& key
, bool bGroupIfEmptyAlso
)
660 wxConfigPathChanger
path(this, key
);
662 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
665 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
666 if ( m_pCurrentGroup
!= m_pRootGroup
) {
667 ConfigGroup
*pGroup
= m_pCurrentGroup
;
668 SetPath(".."); // changes m_pCurrentGroup!
669 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
671 //else: never delete the root group
677 bool wxFileConfig::DeleteGroup(const wxString
& key
)
679 wxConfigPathChanger
path(this, key
);
681 return m_pCurrentGroup
->DeleteSubgroupByName(path
.Name());
684 bool wxFileConfig::DeleteAll()
688 const char *szFile
= m_strLocalFile
;
690 if ( remove(szFile
) == -1 )
691 wxLogSysError(_("can't delete user configuration file '%s'"), szFile
);
693 m_strLocalFile
= m_strGlobalFile
= "";
699 // ----------------------------------------------------------------------------
700 // linked list functions
701 // ----------------------------------------------------------------------------
703 // append a new line to the end of the list
704 wxFileConfig::LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
706 LineList
*pLine
= new LineList(str
);
708 if ( m_linesTail
== NULL
) {
714 m_linesTail
->SetNext(pLine
);
715 pLine
->SetPrev(m_linesTail
);
722 // insert a new line after the given one or in the very beginning if !pLine
723 wxFileConfig::LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
726 if ( pLine
== m_linesTail
)
727 return LineListAppend(str
);
729 LineList
*pNewLine
= new LineList(str
);
730 if ( pLine
== NULL
) {
731 // prepend to the list
732 pNewLine
->SetNext(m_linesHead
);
733 m_linesHead
->SetPrev(pNewLine
);
734 m_linesHead
= pNewLine
;
737 // insert before pLine
738 LineList
*pNext
= pLine
->Next();
739 pNewLine
->SetNext(pNext
);
740 pNewLine
->SetPrev(pLine
);
741 pNext
->SetPrev(pNewLine
);
742 pLine
->SetNext(pNewLine
);
748 void wxFileConfig::LineListRemove(LineList
*pLine
)
750 LineList
*pPrev
= pLine
->Prev(),
751 *pNext
= pLine
->Next();
757 pPrev
->SetNext(pNext
);
763 pNext
->SetPrev(pPrev
);
768 bool wxFileConfig::LineListIsEmpty()
770 return m_linesHead
== NULL
;
773 // ============================================================================
774 // wxFileConfig::ConfigGroup
775 // ============================================================================
777 // ----------------------------------------------------------------------------
779 // ----------------------------------------------------------------------------
782 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup
*pParent
,
783 const wxString
& strName
,
784 wxFileConfig
*pConfig
)
785 : m_aEntries(CompareEntries
),
786 m_aSubgroups(CompareGroups
),
798 // dtor deletes all children
799 wxFileConfig::ConfigGroup::~ConfigGroup()
802 size_t n
, nCount
= m_aEntries
.Count();
803 for ( n
= 0; n
< nCount
; n
++ )
804 delete m_aEntries
[n
];
807 nCount
= m_aSubgroups
.Count();
808 for ( n
= 0; n
< nCount
; n
++ )
809 delete m_aSubgroups
[n
];
812 // ----------------------------------------------------------------------------
814 // ----------------------------------------------------------------------------
816 void wxFileConfig::ConfigGroup::SetLine(LineList
*pLine
)
818 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
824 This is a bit complicated, so let me explain it in details. All lines that
825 were read from the local file (the only one we will ever modify) are stored
826 in a (doubly) linked list. Our problem is to know at which position in this
827 list should we insert the new entries/subgroups. To solve it we keep three
828 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
830 m_pLine points to the line containing "[group_name]"
831 m_pLastEntry points to the last entry of this group in the local file.
832 m_pLastGroup subgroup
834 Initially, they're NULL all three. When the group (an entry/subgroup) is read
835 from the local file, the corresponding variable is set. However, if the group
836 was read from the global file and then modified or created by the application
837 these variables are still NULL and we need to create the corresponding lines.
838 See the following functions (and comments preceding them) for the details of
841 Also, when our last entry/group are deleted we need to find the new last
842 element - the code in DeleteEntry/Subgroup does this by backtracking the list
843 of lines until it either founds an entry/subgroup (and this is the new last
844 element) or the m_pLine of the group, in which case there are no more entries
845 (or subgroups) left and m_pLast<element> becomes NULL.
847 NB: This last problem could be avoided for entries if we added new entries
848 immediately after m_pLine, but in this case the entries would appear
849 backwards in the config file (OTOH, it's not that important) and as we
850 would still need to do it for the subgroups the code wouldn't have been
851 significantly less complicated.
854 // Return the line which contains "[our name]". If we're still not in the list,
855 // add our line to it immediately after the last line of our parent group if we
856 // have it or in the very beginning if we're the root group.
857 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetGroupLine()
859 if ( m_pLine
== NULL
) {
860 ConfigGroup
*pParent
= Parent();
862 // this group wasn't present in local config file, add it now
863 if ( pParent
!= NULL
) {
864 wxString strFullName
;
865 strFullName
<< "[" << (GetFullName().c_str() + 1) << "]"; // +1: no '/'
866 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
867 pParent
->GetLastGroupLine());
868 pParent
->SetLastGroup(this); // we're surely after all the others
871 // we return NULL, so that LineListInsert() will insert us in the
879 // Return the last line belonging to the subgroups of this group (after which
880 // we can add a new subgroup), if we don't have any subgroups or entries our
881 // last line is the group line (m_pLine) itself.
882 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastGroupLine()
884 // if we have any subgroups, our last line is the last line of the last
886 if ( m_pLastGroup
!= NULL
) {
887 wxFileConfig::LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
889 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
893 // no subgroups, so the last line is the line of thelast entry (if any)
894 return GetLastEntryLine();
897 // return the last line belonging to the entries of this group (after which
898 // we can add a new entry), if we don't have any entries we will add the new
899 // one immediately after the group line itself.
900 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastEntryLine()
902 if ( m_pLastEntry
!= NULL
) {
903 wxFileConfig::LineList
*pLine
= m_pLastEntry
->GetLine();
905 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
909 // no entries: insert after the group header
910 return GetGroupLine();
913 // ----------------------------------------------------------------------------
915 // ----------------------------------------------------------------------------
917 wxString
wxFileConfig::ConfigGroup::GetFullName() const
920 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
925 // ----------------------------------------------------------------------------
927 // ----------------------------------------------------------------------------
929 // use binary search because the array is sorted
930 wxFileConfig::ConfigEntry
*
931 wxFileConfig::ConfigGroup::FindEntry(const char *szName
) const
935 hi
= m_aEntries
.Count();
937 wxFileConfig::ConfigEntry
*pEntry
;
941 pEntry
= m_aEntries
[i
];
943 #if wxCONFIG_CASE_SENSITIVE
944 res
= strcmp(pEntry
->Name(), szName
);
946 res
= Stricmp(pEntry
->Name(), szName
);
960 wxFileConfig::ConfigGroup
*
961 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName
) const
965 hi
= m_aSubgroups
.Count();
967 wxFileConfig::ConfigGroup
*pGroup
;
971 pGroup
= m_aSubgroups
[i
];
973 #if wxCONFIG_CASE_SENSITIVE
974 res
= strcmp(pGroup
->Name(), szName
);
976 res
= Stricmp(pGroup
->Name(), szName
);
990 // ----------------------------------------------------------------------------
992 // ----------------------------------------------------------------------------
994 // create a new entry and add it to the current group
995 wxFileConfig::ConfigEntry
*
996 wxFileConfig::ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
998 wxASSERT( FindEntry(strName
) == NULL
);
1000 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
1001 m_aEntries
.Add(pEntry
);
1006 // create a new group and add it to the current group
1007 wxFileConfig::ConfigGroup
*
1008 wxFileConfig::ConfigGroup::AddSubgroup(const wxString
& strName
)
1010 wxASSERT( FindSubgroup(strName
) == NULL
);
1012 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
1013 m_aSubgroups
.Add(pGroup
);
1018 // ----------------------------------------------------------------------------
1020 // ----------------------------------------------------------------------------
1023 The delete operations are _very_ slow if we delete the last item of this
1024 group (see comments before GetXXXLineXXX functions for more details),
1025 so it's much better to start with the first entry/group if we want to
1026 delete several of them.
1029 bool wxFileConfig::ConfigGroup::DeleteSubgroupByName(const char *szName
)
1031 return DeleteSubgroup(FindSubgroup(szName
));
1034 // doesn't delete the subgroup itself, but does remove references to it from
1035 // all other data structures (and normally the returned pointer should be
1036 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1037 bool wxFileConfig::ConfigGroup::DeleteSubgroup(ConfigGroup
*pGroup
)
1039 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1041 // delete all entries
1042 size_t nCount
= pGroup
->m_aEntries
.Count();
1043 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1044 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1045 if ( pLine
!= NULL
)
1046 m_pConfig
->LineListRemove(pLine
);
1049 // and subgroups of this sungroup
1050 nCount
= pGroup
->m_aSubgroups
.Count();
1051 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1052 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[nGroup
]);
1055 LineList
*pLine
= pGroup
->m_pLine
;
1056 if ( pLine
!= NULL
) {
1057 // notice that we may do this test inside the previous "if" because the
1058 // last entry's line is surely !NULL
1059 if ( pGroup
== m_pLastGroup
) {
1060 // our last entry is being deleted - find the last one which stays
1061 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1063 // go back until we find a subgroup or reach the group's line
1064 ConfigGroup
*pNewLast
= NULL
;
1065 size_t n
, nSubgroups
= m_aSubgroups
.Count();
1067 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1068 // is it our subgroup?
1069 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1070 // do _not_ call GetGroupLine! we don't want to add it to the local
1071 // file if it's not already there
1072 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1073 pNewLast
= m_aSubgroups
[n
];
1076 if ( pNewLast
!= NULL
) // found?
1080 if ( pl
== m_pLine
) {
1081 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1083 // we've reached the group line without finding any subgroups
1084 m_pLastGroup
= NULL
;
1087 m_pLastGroup
= pNewLast
;
1090 m_pConfig
->LineListRemove(pLine
);
1095 m_aSubgroups
.Remove(pGroup
);
1101 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName
)
1103 ConfigEntry
*pEntry
= FindEntry(szName
);
1104 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1106 LineList
*pLine
= pEntry
->GetLine();
1107 if ( pLine
!= NULL
) {
1108 // notice that we may do this test inside the previous "if" because the
1109 // last entry's line is surely !NULL
1110 if ( pEntry
== m_pLastEntry
) {
1111 // our last entry is being deleted - find the last one which stays
1112 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1114 // go back until we find another entry or reach the group's line
1115 ConfigEntry
*pNewLast
= NULL
;
1116 size_t n
, nEntries
= m_aEntries
.Count();
1118 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1119 // is it our subgroup?
1120 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1121 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1122 pNewLast
= m_aEntries
[n
];
1125 if ( pNewLast
!= NULL
) // found?
1129 if ( pl
== m_pLine
) {
1130 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1132 // we've reached the group line without finding any subgroups
1133 m_pLastEntry
= NULL
;
1136 m_pLastEntry
= pNewLast
;
1139 m_pConfig
->LineListRemove(pLine
);
1142 // we must be written back for the changes to be saved
1145 m_aEntries
.Remove(pEntry
);
1151 // ----------------------------------------------------------------------------
1153 // ----------------------------------------------------------------------------
1154 void wxFileConfig::ConfigGroup::SetDirty()
1157 if ( Parent() != NULL
) // propagate upwards
1158 Parent()->SetDirty();
1161 // ============================================================================
1162 // wxFileConfig::ConfigEntry
1163 // ============================================================================
1165 // ----------------------------------------------------------------------------
1167 // ----------------------------------------------------------------------------
1168 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup
*pParent
,
1169 const wxString
& strName
,
1171 : m_strName(strName
)
1173 wxASSERT( !strName
.IsEmpty() );
1175 m_pParent
= pParent
;
1181 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1183 m_strName
.erase(0, 1); // remove first character
1186 // ----------------------------------------------------------------------------
1188 // ----------------------------------------------------------------------------
1190 void wxFileConfig::ConfigEntry::SetLine(LineList
*pLine
)
1192 if ( m_pLine
!= NULL
) {
1193 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1194 Name().c_str(), m_pParent
->GetFullName().c_str());
1198 Group()->SetLastEntry(this);
1201 // second parameter is FALSE if we read the value from file and prevents the
1202 // entry from being marked as 'dirty'
1203 void wxFileConfig::ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1205 if ( bUser
&& IsImmutable() ) {
1206 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1211 // do nothing if it's the same value
1212 if ( strValue
== m_strValue
)
1215 m_strValue
= strValue
;
1218 wxString strVal
= FilterOut(strValue
);
1220 strLine
<< m_strName
<< " = " << strVal
;
1222 if ( m_pLine
!= NULL
) {
1223 // entry was read from the local config file, just modify the line
1224 m_pLine
->SetText(strLine
);
1227 // add a new line to the file
1228 wxASSERT( m_nLine
== NOT_FOUND
); // consistency check
1230 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1231 Group()->GetLastEntryLine());
1232 Group()->SetLastEntry(this);
1239 void wxFileConfig::ConfigEntry::SetDirty()
1242 Group()->SetDirty();
1245 // ============================================================================
1247 // ============================================================================
1249 // ----------------------------------------------------------------------------
1250 // compare functions for array sorting
1251 // ----------------------------------------------------------------------------
1253 int CompareEntries(wxFileConfig::ConfigEntry
*p1
,
1254 wxFileConfig::ConfigEntry
*p2
)
1256 #if wxCONFIG_CASE_SENSITIVE
1257 return strcmp(p1
->Name(), p2
->Name());
1259 return Stricmp(p1
->Name(), p2
->Name());
1263 int CompareGroups(wxFileConfig::ConfigGroup
*p1
,
1264 wxFileConfig::ConfigGroup
*p2
)
1266 #if wxCONFIG_CASE_SENSITIVE
1267 return strcmp(p1
->Name(), p2
->Name());
1269 return Stricmp(p1
->Name(), p2
->Name());
1273 // ----------------------------------------------------------------------------
1275 // ----------------------------------------------------------------------------
1278 wxString
FilterIn(const wxString
& str
)
1281 strResult
.Alloc(str
.Len());
1283 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1285 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1286 if ( str
[n
] == '\\' ) {
1287 switch ( str
[++n
] ) {
1310 if ( str
[n
] != '"' || !bQuoted
)
1311 strResult
+= str
[n
];
1312 else if ( n
!= str
.Len() - 1 ) {
1313 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1316 //else: it's the last quote of a quoted string, ok
1323 // quote the string before writing it to file
1324 wxString
FilterOut(const wxString
& str
)
1330 strResult
.Alloc(str
.Len());
1332 // quoting is necessary to preserve spaces in the beginning of the string
1333 bool bQuote
= isspace(str
[0]) || str
[0] == '"';
1339 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
1362 //else: fall through
1365 strResult
+= str
[n
];
1366 continue; // nothing special to do
1369 // we get here only for special characters
1370 strResult
<< '\\' << c
;