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>
35 #include <wx/dynarray.h>
38 #include <wx/textfile.h>
39 #include <wx/config.h>
40 #include <wx/fileconf.h>
42 // _WINDOWS_ is defined when windows.h is included,
43 // __WXMSW__ is defined for MS Windows compilation
44 #if defined(__WXMSW__) && !defined(_WINDOWS_)
51 // ----------------------------------------------------------------------------
53 // ----------------------------------------------------------------------------
54 #define CONST_CAST ((wxFileConfig *)this)->
56 // ----------------------------------------------------------------------------
57 // global functions declarations
58 // ----------------------------------------------------------------------------
60 // is 'c' a valid character in group name?
61 // NB: wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR must be valid chars,
62 // but _not_ ']' (group name delimiter)
63 inline bool IsValid(char c
) { return isalnum(c
) || strchr("@_/-!.*%", c
); }
65 // compare functions for sorting the arrays
66 static int CompareEntries(wxFileConfig::ConfigEntry
*p1
,
67 wxFileConfig::ConfigEntry
*p2
);
68 static int CompareGroups(wxFileConfig::ConfigGroup
*p1
,
69 wxFileConfig::ConfigGroup
*p2
);
72 static wxString
FilterIn(const wxString
& str
);
73 static wxString
FilterOut(const wxString
& str
);
75 // ============================================================================
77 // ============================================================================
79 // ----------------------------------------------------------------------------
81 // ----------------------------------------------------------------------------
82 wxString
wxFileConfig::GetGlobalDir()
88 #elif defined(__WXSTUBS__)
96 char szWinDir
[_MAX_PATH
];
97 ::GetWindowsDirectory(szWinDir
, _MAX_PATH
);
101 #endif // Unix/Windows
106 wxString
wxFileConfig::GetLocalDir()
111 const char *szHome
= getenv("HOME");
112 if ( szHome
== NULL
) {
114 wxLogWarning(_("can't find user's HOME, using current directory."));
119 strDir
<< '/'; // a double slash is no problem, a missin one yes
122 const char *szHome
= getenv("HOMEDRIVE");
123 if ( szHome
!= NULL
)
125 szHome
= getenv("HOMEPATH");
126 if ( szHome
!= NULL
)
129 // Win16 has no idea about home, so use the current directory instead
137 wxString
wxFileConfig::GetGlobalFileName(const char *szFile
)
139 wxString str
= GetGlobalDir();
142 if ( strchr(szFile
, '.') == NULL
)
152 wxString
wxFileConfig::GetLocalFileName(const char *szFile
)
154 wxString str
= GetLocalDir();
163 if ( strchr(szFile
, '.') == NULL
)
170 // ----------------------------------------------------------------------------
172 // ----------------------------------------------------------------------------
174 void wxFileConfig::Init()
177 m_pRootGroup
= new ConfigGroup(NULL
, "", this);
182 // it's not an error if (one of the) file(s) doesn't exist
184 // parse the global file
185 if ( !m_strGlobalFile
.IsEmpty() && wxFile::Exists(m_strGlobalFile
) ) {
186 wxTextFile
fileGlobal(m_strGlobalFile
);
188 if ( fileGlobal
.Open() ) {
189 Parse(fileGlobal
, FALSE
/* global */);
193 wxLogWarning(_("can't open global configuration file '%s'."),
194 m_strGlobalFile
.c_str());
197 // parse the local file
198 if ( !m_strLocalFile
.IsEmpty() && wxFile::Exists(m_strLocalFile
) ) {
199 wxTextFile
fileLocal(m_strLocalFile
);
200 if ( fileLocal
.Open() ) {
201 Parse(fileLocal
, TRUE
/* local */);
205 wxLogWarning(_("can't open user configuration file '%s'."),
206 m_strLocalFile
.c_str());
210 wxFileConfig::wxFileConfig(const char *szAppName
, bool bLocalOnly
)
212 wxASSERT( !IsEmpty(szAppName
) ); // invent a name for your application!
214 m_strLocalFile
= GetLocalFileName(szAppName
);
216 m_strGlobalFile
= GetGlobalFileName(szAppName
);
217 //else: it's going to be empty and we won't use the global file
222 wxFileConfig::wxFileConfig(const wxString
& strLocal
, const wxString
& strGlobal
)
223 : m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
225 // if the path is not absolute, prepend the standard directory to it
226 if ( !strLocal
.IsEmpty() && !wxIsAbsolutePath(strLocal
) )
228 m_strLocalFile
= GetLocalDir();
229 m_strLocalFile
<< strLocal
;
232 if ( !strGlobal
.IsEmpty() && !wxIsAbsolutePath(strGlobal
) )
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 PathChanger
path(this, strName
);
514 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
515 return pGroup
!= NULL
;
518 bool wxFileConfig::HasEntry(const wxString
& strName
) const
520 PathChanger
path(this, strName
);
522 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
523 return pEntry
!= NULL
;
526 // ----------------------------------------------------------------------------
528 // ----------------------------------------------------------------------------
530 bool wxFileConfig::Read(wxString
*pstr
,
532 const char *szDefault
) const
534 PathChanger
path(this, szKey
);
536 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
537 if (pEntry
== NULL
) {
538 if( IsRecordingDefaults() )
539 ((wxFileConfig
*)this)->Write(szKey
,szDefault
);
540 *pstr
= ExpandEnvVars(szDefault
);
544 *pstr
= ExpandEnvVars(pEntry
->Value());
549 const char *wxFileConfig::Read(const char *szKey
,
550 const char *szDefault
) const
552 static wxString s_str
;
553 Read(&s_str
, szKey
, szDefault
);
555 return s_str
.c_str();
558 bool wxFileConfig::Read(long *pl
, const char *szKey
, long lDefault
) const
561 if ( Read(&str
, szKey
) ) {
571 bool wxFileConfig::Write(const char *szKey
, const char *szValue
)
573 PathChanger
path(this, szKey
);
575 wxString strName
= path
.Name();
576 if ( strName
.IsEmpty() ) {
577 // setting the value of a group is an error
578 wxASSERT_MSG( IsEmpty(szValue
), _("can't set value of a group!") );
580 // ... except if it's empty in which case it's a way to force it's creation
581 m_pCurrentGroup
->SetDirty();
583 // this will add a line for this group if it didn't have it before
584 (void)m_pCurrentGroup
->GetGroupLine();
589 // check that the name is reasonable
590 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
591 wxLogError(_("Entry name can't start with '%c'."),
592 wxCONFIG_IMMUTABLE_PREFIX
);
596 for ( const char *pc
= strName
; *pc
!= '\0'; pc
++ ) {
597 if ( !IsValid(*pc
) ) {
598 wxLogError(_("Character '%c' is invalid in a config entry name."),
604 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
605 if ( pEntry
== NULL
)
606 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
608 pEntry
->SetValue(szValue
);
614 bool wxFileConfig::Write(const char *szKey
, long lValue
)
616 // ltoa() is not ANSI :-(
617 char szBuf
[40]; // should be good for sizeof(long) <= 16 (128 bits)
618 sprintf(szBuf
, "%ld", lValue
);
619 return Write(szKey
, szBuf
);
622 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
624 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() )
627 wxTempFile
file(m_strLocalFile
);
629 if ( !file
.IsOpened() ) {
630 wxLogError(_("can't open user configuration file."));
634 // write all strings to file
635 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
636 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
637 wxLogError(_("can't write user configuration file."));
642 return file
.Commit();
645 // ----------------------------------------------------------------------------
646 // delete groups/entries
647 // ----------------------------------------------------------------------------
649 bool wxFileConfig::DeleteEntry(const char *szKey
, bool bGroupIfEmptyAlso
)
651 PathChanger
path(this, szKey
);
653 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
656 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
657 if ( m_pCurrentGroup
!= m_pRootGroup
) {
658 ConfigGroup
*pGroup
= m_pCurrentGroup
;
659 SetPath(".."); // changes m_pCurrentGroup!
660 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
662 //else: never delete the root group
668 bool wxFileConfig::DeleteGroup(const char *szKey
)
670 PathChanger
path(this, szKey
);
672 return m_pCurrentGroup
->DeleteSubgroupByName(path
.Name());
675 bool wxFileConfig::DeleteAll()
679 const char *szFile
= m_strLocalFile
;
681 if ( remove(szFile
) == -1 )
682 wxLogSysError(_("can't delete user configuration file '%s'"), szFile
);
684 m_strLocalFile
= m_strGlobalFile
= "";
690 // ----------------------------------------------------------------------------
691 // linked list functions
692 // ----------------------------------------------------------------------------
694 // append a new line to the end of the list
695 wxFileConfig::LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
697 LineList
*pLine
= new LineList(str
);
699 if ( m_linesTail
== NULL
) {
705 m_linesTail
->SetNext(pLine
);
706 pLine
->SetPrev(m_linesTail
);
713 // insert a new line after the given one or in the very beginning if !pLine
714 wxFileConfig::LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
717 if ( pLine
== m_linesTail
)
718 return LineListAppend(str
);
720 LineList
*pNewLine
= new LineList(str
);
721 if ( pLine
== NULL
) {
722 // prepend to the list
723 pNewLine
->SetNext(m_linesHead
);
724 m_linesHead
->SetPrev(pNewLine
);
725 m_linesHead
= pNewLine
;
728 // insert before pLine
729 LineList
*pNext
= pLine
->Next();
730 pNewLine
->SetNext(pNext
);
731 pNewLine
->SetPrev(pLine
);
732 pNext
->SetPrev(pNewLine
);
733 pLine
->SetNext(pNewLine
);
739 void wxFileConfig::LineListRemove(LineList
*pLine
)
741 LineList
*pPrev
= pLine
->Prev(),
742 *pNext
= pLine
->Next();
748 pPrev
->SetNext(pNext
);
754 pNext
->SetPrev(pPrev
);
759 bool wxFileConfig::LineListIsEmpty()
761 return m_linesHead
== NULL
;
764 // ============================================================================
765 // wxFileConfig::ConfigGroup
766 // ============================================================================
768 // ----------------------------------------------------------------------------
770 // ----------------------------------------------------------------------------
773 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup
*pParent
,
774 const wxString
& strName
,
775 wxFileConfig
*pConfig
)
776 : m_aEntries(CompareEntries
),
777 m_aSubgroups(CompareGroups
),
789 // dtor deletes all children
790 wxFileConfig::ConfigGroup::~ConfigGroup()
793 size_t n
, nCount
= m_aEntries
.Count();
794 for ( n
= 0; n
< nCount
; n
++ )
795 delete m_aEntries
[n
];
798 nCount
= m_aSubgroups
.Count();
799 for ( n
= 0; n
< nCount
; n
++ )
800 delete m_aSubgroups
[n
];
803 // ----------------------------------------------------------------------------
805 // ----------------------------------------------------------------------------
807 void wxFileConfig::ConfigGroup::SetLine(LineList
*pLine
)
809 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
815 This is a bit complicated, so let me explain it in details. All lines that
816 were read from the local file (the only one we will ever modify) are stored
817 in a (doubly) linked list. Our problem is to know at which position in this
818 list should we insert the new entries/subgroups. To solve it we keep three
819 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
821 m_pLine points to the line containing "[group_name]"
822 m_pLastEntry points to the last entry of this group in the local file.
823 m_pLastGroup subgroup
825 Initially, they're NULL all three. When the group (an entry/subgroup) is read
826 from the local file, the corresponding variable is set. However, if the group
827 was read from the global file and then modified or created by the application
828 these variables are still NULL and we need to create the corresponding lines.
829 See the following functions (and comments preceding them) for the details of
832 Also, when our last entry/group are deleted we need to find the new last
833 element - the code in DeleteEntry/Subgroup does this by backtracking the list
834 of lines until it either founds an entry/subgroup (and this is the new last
835 element) or the m_pLine of the group, in which case there are no more entries
836 (or subgroups) left and m_pLast<element> becomes NULL.
838 NB: This last problem could be avoided for entries if we added new entries
839 immediately after m_pLine, but in this case the entries would appear
840 backwards in the config file (OTOH, it's not that important) and as we
841 would still need to do it for the subgroups the code wouldn't have been
842 significantly less complicated.
845 // Return the line which contains "[our name]". If we're still not in the list,
846 // add our line to it immediately after the last line of our parent group if we
847 // have it or in the very beginning if we're the root group.
848 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetGroupLine()
850 if ( m_pLine
== NULL
) {
851 ConfigGroup
*pParent
= Parent();
853 // this group wasn't present in local config file, add it now
854 if ( pParent
!= NULL
) {
855 wxString strFullName
;
856 strFullName
<< "[" << GetFullName().c_str() + 1 << "]"; // +1: no '/'
857 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
858 pParent
->GetLastGroupLine());
859 pParent
->SetLastGroup(this); // we're surely after all the others
862 // we return NULL, so that LineListInsert() will insert us in the
870 // Return the last line belonging to the subgroups of this group (after which
871 // we can add a new subgroup), if we don't have any subgroups or entries our
872 // last line is the group line (m_pLine) itself.
873 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastGroupLine()
875 // if we have any subgroups, our last line is the last line of the last
877 if ( m_pLastGroup
!= NULL
) {
878 wxFileConfig::LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
880 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
884 // no subgroups, so the last line is the line of thelast entry (if any)
885 return GetLastEntryLine();
888 // return the last line belonging to the entries of this group (after which
889 // we can add a new entry), if we don't have any entries we will add the new
890 // one immediately after the group line itself.
891 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastEntryLine()
893 if ( m_pLastEntry
!= NULL
) {
894 wxFileConfig::LineList
*pLine
= m_pLastEntry
->GetLine();
896 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
900 // no entries: insert after the group header
901 return GetGroupLine();
904 // ----------------------------------------------------------------------------
906 // ----------------------------------------------------------------------------
908 wxString
wxFileConfig::ConfigGroup::GetFullName() const
911 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
916 // ----------------------------------------------------------------------------
918 // ----------------------------------------------------------------------------
920 // use binary search because the array is sorted
921 wxFileConfig::ConfigEntry
*
922 wxFileConfig::ConfigGroup::FindEntry(const char *szName
) const
926 hi
= m_aEntries
.Count();
928 wxFileConfig::ConfigEntry
*pEntry
;
932 pEntry
= m_aEntries
[i
];
934 #if wxCONFIG_CASE_SENSITIVE
935 res
= strcmp(pEntry
->Name(), szName
);
937 res
= Stricmp(pEntry
->Name(), szName
);
951 wxFileConfig::ConfigGroup
*
952 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName
) const
956 hi
= m_aSubgroups
.Count();
958 wxFileConfig::ConfigGroup
*pGroup
;
962 pGroup
= m_aSubgroups
[i
];
964 #if wxCONFIG_CASE_SENSITIVE
965 res
= strcmp(pGroup
->Name(), szName
);
967 res
= Stricmp(pGroup
->Name(), szName
);
981 // ----------------------------------------------------------------------------
983 // ----------------------------------------------------------------------------
985 // create a new entry and add it to the current group
986 wxFileConfig::ConfigEntry
*
987 wxFileConfig::ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
989 wxASSERT( FindEntry(strName
) == NULL
);
991 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
992 m_aEntries
.Add(pEntry
);
997 // create a new group and add it to the current group
998 wxFileConfig::ConfigGroup
*
999 wxFileConfig::ConfigGroup::AddSubgroup(const wxString
& strName
)
1001 wxASSERT( FindSubgroup(strName
) == NULL
);
1003 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
1004 m_aSubgroups
.Add(pGroup
);
1009 // ----------------------------------------------------------------------------
1011 // ----------------------------------------------------------------------------
1014 The delete operations are _very_ slow if we delete the last item of this
1015 group (see comments before GetXXXLineXXX functions for more details),
1016 so it's much better to start with the first entry/group if we want to
1017 delete several of them.
1020 bool wxFileConfig::ConfigGroup::DeleteSubgroupByName(const char *szName
)
1022 return DeleteSubgroup(FindSubgroup(szName
));
1025 // doesn't delete the subgroup itself, but does remove references to it from
1026 // all other data structures (and normally the returned pointer should be
1027 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1028 bool wxFileConfig::ConfigGroup::DeleteSubgroup(ConfigGroup
*pGroup
)
1030 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1032 // delete all entries
1033 size_t nCount
= pGroup
->m_aEntries
.Count();
1034 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1035 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1036 if ( pLine
!= NULL
)
1037 m_pConfig
->LineListRemove(pLine
);
1040 // and subgroups of this sungroup
1041 nCount
= pGroup
->m_aSubgroups
.Count();
1042 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1043 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[nGroup
]);
1046 LineList
*pLine
= pGroup
->m_pLine
;
1047 if ( pLine
!= NULL
) {
1048 // notice that we may do this test inside the previous "if" because the
1049 // last entry's line is surely !NULL
1050 if ( pGroup
== m_pLastGroup
) {
1051 // our last entry is being deleted - find the last one which stays
1052 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1054 // go back until we find a subgroup or reach the group's line
1055 ConfigGroup
*pNewLast
= NULL
;
1056 size_t n
, nSubgroups
= m_aSubgroups
.Count();
1058 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1059 // is it our subgroup?
1060 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1061 // do _not_ call GetGroupLine! we don't want to add it to the local
1062 // file if it's not already there
1063 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1064 pNewLast
= m_aSubgroups
[n
];
1067 if ( pNewLast
!= NULL
) // found?
1071 if ( pl
== m_pLine
) {
1072 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1074 // we've reached the group line without finding any subgroups
1075 m_pLastGroup
= NULL
;
1078 m_pLastGroup
= pNewLast
;
1081 m_pConfig
->LineListRemove(pLine
);
1086 m_aSubgroups
.Remove(pGroup
);
1092 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName
)
1094 ConfigEntry
*pEntry
= FindEntry(szName
);
1095 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1097 LineList
*pLine
= pEntry
->GetLine();
1098 if ( pLine
!= NULL
) {
1099 // notice that we may do this test inside the previous "if" because the
1100 // last entry's line is surely !NULL
1101 if ( pEntry
== m_pLastEntry
) {
1102 // our last entry is being deleted - find the last one which stays
1103 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1105 // go back until we find another entry or reach the group's line
1106 ConfigEntry
*pNewLast
= NULL
;
1107 size_t n
, nEntries
= m_aEntries
.Count();
1109 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1110 // is it our subgroup?
1111 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1112 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1113 pNewLast
= m_aEntries
[n
];
1116 if ( pNewLast
!= NULL
) // found?
1120 if ( pl
== m_pLine
) {
1121 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1123 // we've reached the group line without finding any subgroups
1124 m_pLastEntry
= NULL
;
1127 m_pLastEntry
= pNewLast
;
1130 m_pConfig
->LineListRemove(pLine
);
1133 // we must be written back for the changes to be saved
1136 m_aEntries
.Remove(pEntry
);
1142 // ----------------------------------------------------------------------------
1144 // ----------------------------------------------------------------------------
1145 void wxFileConfig::ConfigGroup::SetDirty()
1148 if ( Parent() != NULL
) // propagate upwards
1149 Parent()->SetDirty();
1152 // ============================================================================
1153 // wxFileConfig::ConfigEntry
1154 // ============================================================================
1156 // ----------------------------------------------------------------------------
1158 // ----------------------------------------------------------------------------
1159 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup
*pParent
,
1160 const wxString
& strName
,
1162 : m_strName(strName
)
1164 wxASSERT( !strName
.IsEmpty() );
1166 m_pParent
= pParent
;
1172 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1174 m_strName
.erase(0, 1); // remove first character
1177 // ----------------------------------------------------------------------------
1179 // ----------------------------------------------------------------------------
1181 void wxFileConfig::ConfigEntry::SetLine(LineList
*pLine
)
1183 if ( m_pLine
!= NULL
) {
1184 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1185 Name().c_str(), m_pParent
->GetFullName().c_str());
1189 Group()->SetLastEntry(this);
1192 // second parameter is FALSE if we read the value from file and prevents the
1193 // entry from being marked as 'dirty'
1194 void wxFileConfig::ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1196 if ( bUser
&& IsImmutable() ) {
1197 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1202 // do nothing if it's the same value
1203 if ( strValue
== m_strValue
)
1206 m_strValue
= strValue
;
1209 wxString strVal
= FilterOut(strValue
);
1211 strLine
<< m_strName
<< " = " << strVal
;
1213 if ( m_pLine
!= NULL
) {
1214 // entry was read from the local config file, just modify the line
1215 m_pLine
->SetText(strLine
);
1218 // add a new line to the file
1219 wxASSERT( m_nLine
== NOT_FOUND
); // consistency check
1221 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1222 Group()->GetLastEntryLine());
1223 Group()->SetLastEntry(this);
1230 void wxFileConfig::ConfigEntry::SetDirty()
1233 Group()->SetDirty();
1236 // ============================================================================
1238 // ============================================================================
1240 // ----------------------------------------------------------------------------
1241 // compare functions for array sorting
1242 // ----------------------------------------------------------------------------
1244 int CompareEntries(wxFileConfig::ConfigEntry
*p1
,
1245 wxFileConfig::ConfigEntry
*p2
)
1247 #if wxCONFIG_CASE_SENSITIVE
1248 return strcmp(p1
->Name(), p2
->Name());
1250 return Stricmp(p1
->Name(), p2
->Name());
1254 int CompareGroups(wxFileConfig::ConfigGroup
*p1
,
1255 wxFileConfig::ConfigGroup
*p2
)
1257 #if wxCONFIG_CASE_SENSITIVE
1258 return strcmp(p1
->Name(), p2
->Name());
1260 return Stricmp(p1
->Name(), p2
->Name());
1264 // ----------------------------------------------------------------------------
1266 // ----------------------------------------------------------------------------
1269 wxString
FilterIn(const wxString
& str
)
1272 strResult
.Alloc(str
.Len());
1274 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1276 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1277 if ( str
[n
] == '\\' ) {
1278 switch ( str
[++n
] ) {
1301 if ( str
[n
] != '"' || !bQuoted
)
1302 strResult
+= str
[n
];
1303 else if ( n
!= str
.Len() - 1 ) {
1304 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1307 //else: it's the last quote of a quoted string, ok
1314 // quote the string before writing it to file
1315 wxString
FilterOut(const wxString
& str
)
1321 strResult
.Alloc(str
.Len());
1323 // quoting is necessary to preserve spaces in the beginning of the string
1324 bool bQuote
= isspace(str
[0]) || str
[0] == '"';
1330 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
1353 //else: fall through
1356 strResult
+= str
[n
];
1357 continue; // nothing special to do
1360 // we get here only for special characters
1361 strResult
<< '\\' << c
;