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()
93 char szWinDir
[_MAX_PATH
];
94 ::GetWindowsDirectory(szWinDir
, _MAX_PATH
);
98 #endif // Unix/Windows
103 wxString
wxFileConfig::GetLocalDir()
108 const char *szHome
= getenv("HOME");
109 if ( szHome
== NULL
) {
111 wxLogWarning(_("can't find user's HOME, using current directory."));
116 strDir
<< '/'; // a double slash is no problem, a missin one yes
119 const char *szHome
= getenv("HOMEDRIVE");
120 if ( szHome
!= NULL
)
122 szHome
= getenv("HOMEPATH");
123 if ( szHome
!= NULL
)
126 // Win16 has no idea about home, so use the current directory instead
134 wxString
wxFileConfig::GetGlobalFileName(const char *szFile
)
136 wxString str
= GetLocalDir();
139 if ( strchr(szFile
, '.') == NULL
)
149 wxString
wxFileConfig::GetLocalFileName(const char *szFile
)
151 wxString str
= GetLocalDir();
160 if ( strchr(szFile
, '.') == NULL
)
167 // ----------------------------------------------------------------------------
169 // ----------------------------------------------------------------------------
171 void wxFileConfig::Init()
174 m_pRootGroup
= new ConfigGroup(NULL
, "", this);
179 // it's not an error if (one of the) file(s) doesn't exist
181 // parse the global file
182 if ( !m_strGlobalFile
.IsEmpty() && wxFile::Exists(m_strGlobalFile
) ) {
183 wxTextFile
fileGlobal(m_strGlobalFile
);
185 if ( fileGlobal
.Open() ) {
186 Parse(fileGlobal
, FALSE
/* global */);
190 wxLogWarning(_("can't open global configuration file '%s'."),
191 m_strGlobalFile
.c_str());
194 // parse the local file
195 if ( !m_strLocalFile
.IsEmpty() && wxFile::Exists(m_strLocalFile
) ) {
196 wxTextFile
fileLocal(m_strLocalFile
);
197 if ( fileLocal
.Open() ) {
198 Parse(fileLocal
, TRUE
/* local */);
202 wxLogWarning(_("can't open user configuration file '%s'."),
203 m_strLocalFile
.c_str());
207 wxFileConfig::wxFileConfig(const char *szAppName
, bool bLocalOnly
)
209 wxASSERT( !IsEmpty(szAppName
) ); // invent a name for your application!
211 m_strLocalFile
= GetLocalFileName(szAppName
);
213 m_strGlobalFile
= GetGlobalFileName(szAppName
);
214 //else: it's going to be empty and we won't use the global file
219 wxFileConfig::wxFileConfig(const wxString
& strLocal
, const wxString
& strGlobal
)
220 : m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
222 // if the path is not absolute, prepend the standard directory to it
224 if ( !strLocal
.IsEmpty() && !wxIsPathSeparator(strLocal
[0u]) )
225 m_strLocalFile
= GetLocalDir();
226 m_strLocalFile
<< strLocal
;
228 if ( !strGlobal
.IsEmpty() && !wxIsPathSeparator(strGlobal
[0u]) )
229 m_strGlobalFile
= GetGlobalDir();
230 m_strGlobalFile
<< strGlobal
;
235 void wxFileConfig::CleanUp()
239 LineList
*pCur
= m_linesHead
;
240 while ( pCur
!= NULL
) {
241 LineList
*pNext
= pCur
->Next();
247 wxFileConfig::~wxFileConfig()
254 // ----------------------------------------------------------------------------
255 // parse a config file
256 // ----------------------------------------------------------------------------
258 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
264 uint nLineCount
= file
.GetLineCount();
265 for ( uint n
= 0; n
< nLineCount
; n
++ ) {
268 // add the line to linked list
270 LineListAppend(strLine
);
272 // skip leading spaces
273 for ( pStart
= strLine
; isspace(*pStart
); pStart
++ )
276 // skip blank/comment lines
277 if ( *pStart
== '\0'|| *pStart
== ';' || *pStart
== '#' )
280 if ( *pStart
== '[' ) { // a new group
283 while ( *++pEnd
!= ']' ) {
284 if ( !IsValid(*pEnd
) && *pEnd
!= ' ' ) // allow spaces in group names
288 if ( *pEnd
!= ']' ) {
289 wxLogError(_("file '%s': unexpected character %c at line %d."),
290 file
.GetName(), *pEnd
, n
+ 1);
291 continue; // skip this line
294 // group name here is always considered as abs path
297 strGroup
<< wxCONFIG_PATH_SEPARATOR
<< wxString(pStart
, pEnd
- pStart
);
299 // will create it if doesn't yet exist
303 m_pCurrentGroup
->SetLine(m_linesTail
);
305 // check that there is nothing except comments left on this line
307 while ( *++pEnd
!= '\0' && bCont
) {
316 // ignore whitespace ('\n' impossible here)
320 wxLogWarning(_("file '%s', line %d: '%s' "
321 "ignored after group header."),
322 file
.GetName(), n
+ 1, pEnd
);
328 const char *pEnd
= pStart
;
329 while ( IsValid(*pEnd
) )
332 wxString
strKey(pStart
, pEnd
);
335 while ( isspace(*pEnd
) )
338 if ( *pEnd
++ != '=' ) {
339 wxLogError(_("file '%s', line %d: '=' expected."),
340 file
.GetName(), n
+ 1);
343 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
345 if ( pEntry
== NULL
) {
347 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
350 pEntry
->SetLine(m_linesTail
);
353 if ( bLocal
&& pEntry
->IsImmutable() ) {
354 // immutable keys can't be changed by user
355 wxLogWarning(_("file '%s', line %d: value for "
356 "immutable key '%s' ignored."),
357 file
.GetName(), n
+ 1, strKey
.c_str());
360 // the condition below catches the cases (a) and (b) but not (c):
361 // (a) global key found second time in global file
362 // (b) key found second (or more) time in local file
363 // (c) key from global file now found in local one
364 // which is exactly what we want.
365 else if ( !bLocal
|| pEntry
->IsLocal() ) {
366 wxLogWarning(_("file '%s', line %d: key '%s' was first "
367 "found at line %d."),
368 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
371 pEntry
->SetLine(m_linesTail
);
376 while ( isspace(*pEnd
) )
379 pEntry
->SetValue(FilterIn(pEnd
), FALSE
/* read from file */);
385 // ----------------------------------------------------------------------------
387 // ----------------------------------------------------------------------------
389 void wxFileConfig::SetRootPath()
392 m_pCurrentGroup
= m_pRootGroup
;
395 void wxFileConfig::SetPath(const wxString
& strPath
)
397 wxArrayString aParts
;
399 if ( strPath
.IsEmpty() ) {
404 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
406 wxSplitPath(aParts
, strPath
);
409 // relative path, combine with current one
410 wxString strFullPath
= m_strPath
;
411 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
412 wxSplitPath(aParts
, strFullPath
);
415 // change current group
417 m_pCurrentGroup
= m_pRootGroup
;
418 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
419 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
420 if ( pNextGroup
== NULL
)
421 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
422 m_pCurrentGroup
= pNextGroup
;
425 // recombine path parts in one variable
427 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
428 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
432 // ----------------------------------------------------------------------------
434 // ----------------------------------------------------------------------------
436 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
439 return GetNextGroup(str
, lIndex
);
442 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
444 if ( uint(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
445 str
= m_pCurrentGroup
->Groups()[lIndex
++]->Name();
452 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
455 return GetNextEntry(str
, lIndex
);
458 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
460 if ( uint(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
461 str
= m_pCurrentGroup
->Entries()[lIndex
++]->Name();
468 uint
wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
470 uint n
= m_pCurrentGroup
->Entries().Count();
472 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
473 uint nSubgroups
= m_pCurrentGroup
->Groups().Count();
474 for ( uint nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
475 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
476 n
+= GetNumberOfEntries(TRUE
);
477 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
484 uint
wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
486 uint n
= m_pCurrentGroup
->Groups().Count();
488 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
489 uint nSubgroups
= m_pCurrentGroup
->Groups().Count();
490 for ( uint nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
491 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
492 n
+= GetNumberOfGroups(TRUE
);
493 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
500 // ----------------------------------------------------------------------------
501 // tests for existence
502 // ----------------------------------------------------------------------------
504 bool wxFileConfig::HasGroup(const wxString
& strName
) const
506 PathChanger
path(this, strName
);
508 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
509 return pGroup
!= NULL
;
512 bool wxFileConfig::HasEntry(const wxString
& strName
) const
514 PathChanger
path(this, strName
);
516 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
517 return pEntry
!= NULL
;
520 // ----------------------------------------------------------------------------
522 // ----------------------------------------------------------------------------
524 bool wxFileConfig::Read(wxString
*pstr
,
526 const char *szDefault
) const
528 PathChanger
path(this, szKey
);
530 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
531 if (pEntry
== NULL
) {
532 *pstr
= ExpandEnvVars(szDefault
);
536 *pstr
= ExpandEnvVars(pEntry
->Value());
541 const char *wxFileConfig::Read(const char *szKey
,
542 const char *szDefault
) const
544 static wxString s_str
;
545 Read(&s_str
, szKey
, szDefault
);
547 return s_str
.c_str();
550 bool wxFileConfig::Read(long *pl
, const char *szKey
, long lDefault
) const
553 if ( Read(&str
, szKey
) ) {
563 bool wxFileConfig::Write(const char *szKey
, const char *szValue
)
565 PathChanger
path(this, szKey
);
567 wxString strName
= path
.Name();
568 if ( strName
.IsEmpty() ) {
569 // setting the value of a group is an error
570 wxASSERT_MSG( IsEmpty(szValue
), "can't set value of a group!" );
572 // ... except if it's empty in which case it's a way to force it's creation
573 m_pCurrentGroup
->SetDirty();
575 // this will add a line for this group if it didn't have it before
576 (void)m_pCurrentGroup
->GetGroupLine();
581 // check that the name is reasonable
582 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
583 wxLogError(_("Entry name can't start with '%c'."),
584 wxCONFIG_IMMUTABLE_PREFIX
);
588 for ( const char *pc
= strName
; *pc
!= '\0'; pc
++ ) {
589 if ( !IsValid(*pc
) ) {
590 wxLogError(_("Character '%c' is invalid in a config entry name."),
596 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
597 if ( pEntry
== NULL
)
598 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
600 pEntry
->SetValue(szValue
);
606 bool wxFileConfig::Write(const char *szKey
, long lValue
)
608 // ltoa() is not ANSI :-(
609 char szBuf
[40]; // should be good for sizeof(long) <= 16 (128 bits)
610 sprintf(szBuf
, "%ld", lValue
);
611 return Write(szKey
, szBuf
);
614 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
616 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() )
619 wxTempFile
file(m_strLocalFile
);
621 if ( !file
.IsOpened() ) {
622 wxLogError(_("can't open user configuration file."));
626 // write all strings to file
627 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
628 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
629 wxLogError(_("can't write user configuration file."));
634 return file
.Commit();
637 // ----------------------------------------------------------------------------
638 // delete groups/entries
639 // ----------------------------------------------------------------------------
641 bool wxFileConfig::DeleteEntry(const char *szKey
, bool bGroupIfEmptyAlso
)
643 PathChanger
path(this, szKey
);
645 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
648 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
649 if ( m_pCurrentGroup
!= m_pRootGroup
) {
650 ConfigGroup
*pGroup
= m_pCurrentGroup
;
651 SetPath(".."); // changes m_pCurrentGroup!
652 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
654 //else: never delete the root group
660 bool wxFileConfig::DeleteGroup(const char *szKey
)
662 PathChanger
path(this, szKey
);
664 return m_pCurrentGroup
->DeleteSubgroupByName(path
.Name());
667 bool wxFileConfig::DeleteAll()
671 m_strLocalFile
= m_strGlobalFile
= "";
674 const char *szFile
= m_strLocalFile
;
676 if ( remove(szFile
) == -1 )
677 wxLogSysError(_("can't delete user configuration file '%s'"), szFile
);
679 szFile
= m_strGlobalFile
;
680 if ( remove(szFile
) )
681 wxLogSysError(_("can't delete system configuration file '%s'"), szFile
);
686 // ----------------------------------------------------------------------------
687 // linked list functions
688 // ----------------------------------------------------------------------------
690 // append a new line to the end of the list
691 wxFileConfig::LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
693 LineList
*pLine
= new LineList(str
);
695 if ( m_linesTail
== NULL
) {
701 m_linesTail
->SetNext(pLine
);
702 pLine
->SetPrev(m_linesTail
);
709 // insert a new line after the given one or in the very beginning if !pLine
710 wxFileConfig::LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
713 if ( pLine
== m_linesTail
)
714 return LineListAppend(str
);
716 LineList
*pNewLine
= new LineList(str
);
717 if ( pLine
== NULL
) {
718 // prepend to the list
719 pNewLine
->SetNext(m_linesHead
);
720 m_linesHead
->SetPrev(pNewLine
);
721 m_linesHead
= pNewLine
;
724 // insert before pLine
725 LineList
*pNext
= pLine
->Next();
726 pNewLine
->SetNext(pNext
);
727 pNewLine
->SetPrev(pLine
);
728 pNext
->SetPrev(pNewLine
);
729 pLine
->SetNext(pNewLine
);
735 void wxFileConfig::LineListRemove(LineList
*pLine
)
737 LineList
*pPrev
= pLine
->Prev(),
738 *pNext
= pLine
->Next();
744 pPrev
->SetNext(pNext
);
750 pNext
->SetPrev(pPrev
);
755 bool wxFileConfig::LineListIsEmpty()
757 return m_linesHead
== NULL
;
760 // ============================================================================
761 // wxFileConfig::ConfigGroup
762 // ============================================================================
764 // ----------------------------------------------------------------------------
766 // ----------------------------------------------------------------------------
769 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup
*pParent
,
770 const wxString
& strName
,
771 wxFileConfig
*pConfig
)
772 : m_aEntries(CompareEntries
),
773 m_aSubgroups(CompareGroups
),
785 // dtor deletes all children
786 wxFileConfig::ConfigGroup::~ConfigGroup()
789 uint n
, nCount
= m_aEntries
.Count();
790 for ( n
= 0; n
< nCount
; n
++ )
791 delete m_aEntries
[n
];
794 nCount
= m_aSubgroups
.Count();
795 for ( n
= 0; n
< nCount
; n
++ )
796 delete m_aSubgroups
[n
];
799 // ----------------------------------------------------------------------------
801 // ----------------------------------------------------------------------------
803 void wxFileConfig::ConfigGroup::SetLine(LineList
*pLine
)
805 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
811 This is a bit complicated, so let me explain it in details. All lines that
812 were read from the local file (the only one we will ever modify) are stored
813 in a (doubly) linked list. Our problem is to know at which position in this
814 list should we insert the new entries/subgroups. To solve it we keep three
815 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
817 m_pLine points to the line containing "[group_name]"
818 m_pLastEntry points to the last entry of this group in the local file.
819 m_pLastGroup subgroup
821 Initially, they're NULL all three. When the group (an entry/subgroup) is read
822 from the local file, the corresponding variable is set. However, if the group
823 was read from the global file and then modified or created by the application
824 these variables are still NULL and we need to create the corresponding lines.
825 See the following functions (and comments preceding them) for the details of
828 Also, when our last entry/group are deleted we need to find the new last
829 element - the code in DeleteEntry/Subgroup does this by backtracking the list
830 of lines until it either founds an entry/subgroup (and this is the new last
831 element) or the m_pLine of the group, in which case there are no more entries
832 (or subgroups) left and m_pLast<element> becomes NULL.
834 NB: This last problem could be avoided for entries if we added new entries
835 immediately after m_pLine, but in this case the entries would appear
836 backwards in the config file (OTOH, it's not that important) and as we
837 would still need to do it for the subgroups the code wouldn't have been
838 significantly less complicated.
841 // Return the line which contains "[our name]". If we're still not in the list,
842 // add our line to it immediately after the last line of our parent group if we
843 // have it or in the very beginning if we're the root group.
844 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetGroupLine()
846 if ( m_pLine
== NULL
) {
847 ConfigGroup
*pParent
= Parent();
849 // this group wasn't present in local config file, add it now
850 if ( pParent
!= NULL
) {
851 wxString strFullName
;
852 strFullName
<< "[" << GetFullName().c_str() + 1 << "]"; // +1: no '/'
853 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
854 pParent
->GetLastGroupLine());
855 pParent
->SetLastGroup(this); // we're surely after all the others
858 // we return NULL, so that LineListInsert() will insert us in the
866 // Return the last line belonging to the subgroups of this group (after which
867 // we can add a new subgroup), if we don't have any subgroups or entries our
868 // last line is the group line (m_pLine) itself.
869 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastGroupLine()
871 // if we have any subgroups, our last line is the last line of the last
873 if ( m_pLastGroup
!= NULL
) {
874 wxFileConfig::LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
876 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
880 // no subgroups, so the last line is the line of thelast entry (if any)
881 return GetLastEntryLine();
884 // return the last line belonging to the entries of this group (after which
885 // we can add a new entry), if we don't have any entries we will add the new
886 // one immediately after the group line itself.
887 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastEntryLine()
889 if ( m_pLastEntry
!= NULL
) {
890 wxFileConfig::LineList
*pLine
= m_pLastEntry
->GetLine();
892 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
896 // no entries: insert after the group header
897 return GetGroupLine();
900 // ----------------------------------------------------------------------------
902 // ----------------------------------------------------------------------------
904 wxString
wxFileConfig::ConfigGroup::GetFullName() const
907 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
912 // ----------------------------------------------------------------------------
914 // ----------------------------------------------------------------------------
916 // use binary search because the array is sorted
917 wxFileConfig::ConfigEntry
*
918 wxFileConfig::ConfigGroup::FindEntry(const char *szName
) const
922 hi
= m_aEntries
.Count();
924 wxFileConfig::ConfigEntry
*pEntry
;
928 pEntry
= m_aEntries
[i
];
930 #if wxCONFIG_CASE_SENSITIVE
931 res
= strcmp(pEntry
->Name(), szName
);
933 res
= Stricmp(pEntry
->Name(), szName
);
947 wxFileConfig::ConfigGroup
*
948 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName
) const
952 hi
= m_aSubgroups
.Count();
954 wxFileConfig::ConfigGroup
*pGroup
;
958 pGroup
= m_aSubgroups
[i
];
960 #if wxCONFIG_CASE_SENSITIVE
961 res
= strcmp(pGroup
->Name(), szName
);
963 res
= Stricmp(pGroup
->Name(), szName
);
977 // ----------------------------------------------------------------------------
979 // ----------------------------------------------------------------------------
981 // create a new entry and add it to the current group
982 wxFileConfig::ConfigEntry
*
983 wxFileConfig::ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
985 wxASSERT( FindEntry(strName
) == NULL
);
987 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
988 m_aEntries
.Add(pEntry
);
993 // create a new group and add it to the current group
994 wxFileConfig::ConfigGroup
*
995 wxFileConfig::ConfigGroup::AddSubgroup(const wxString
& strName
)
997 wxASSERT( FindSubgroup(strName
) == NULL
);
999 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
1000 m_aSubgroups
.Add(pGroup
);
1005 // ----------------------------------------------------------------------------
1007 // ----------------------------------------------------------------------------
1010 The delete operations are _very_ slow if we delete the last item of this
1011 group (see comments before GetXXXLineXXX functions for more details),
1012 so it's much better to start with the first entry/group if we want to
1013 delete several of them.
1016 bool wxFileConfig::ConfigGroup::DeleteSubgroupByName(const char *szName
)
1018 return DeleteSubgroup(FindSubgroup(szName
));
1021 // doesn't delete the subgroup itself, but does remove references to it from
1022 // all other data structures (and normally the returned pointer should be
1023 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1024 bool wxFileConfig::ConfigGroup::DeleteSubgroup(ConfigGroup
*pGroup
)
1026 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1028 // delete all entries
1029 uint nCount
= pGroup
->m_aEntries
.Count();
1030 for ( uint nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1031 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1032 if ( pLine
!= NULL
)
1033 m_pConfig
->LineListRemove(pLine
);
1036 // and subgroups of this sungroup
1037 nCount
= pGroup
->m_aSubgroups
.Count();
1038 for ( uint nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1039 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[nGroup
]);
1042 LineList
*pLine
= pGroup
->m_pLine
;
1043 if ( pLine
!= NULL
) {
1044 // notice that we may do this test inside the previous "if" because the
1045 // last entry's line is surely !NULL
1046 if ( pGroup
== m_pLastGroup
) {
1047 // our last entry is being deleted - find the last one which stays
1048 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1050 // go back until we find a subgroup or reach the group's line
1051 ConfigGroup
*pNewLast
= NULL
;
1052 uint n
, nSubgroups
= m_aSubgroups
.Count();
1054 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1055 // is it our subgroup?
1056 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1057 // do _not_ call GetGroupLine! we don't want to add it to the local
1058 // file if it's not already there
1059 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1060 pNewLast
= m_aSubgroups
[n
];
1063 if ( pNewLast
!= NULL
) // found?
1067 if ( pl
== m_pLine
) {
1068 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1070 // we've reached the group line without finding any subgroups
1071 m_pLastGroup
= NULL
;
1074 m_pLastGroup
= pNewLast
;
1077 m_pConfig
->LineListRemove(pLine
);
1082 m_aSubgroups
.Remove(pGroup
);
1088 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName
)
1090 ConfigEntry
*pEntry
= FindEntry(szName
);
1091 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1093 LineList
*pLine
= pEntry
->GetLine();
1094 if ( pLine
!= NULL
) {
1095 // notice that we may do this test inside the previous "if" because the
1096 // last entry's line is surely !NULL
1097 if ( pEntry
== m_pLastEntry
) {
1098 // our last entry is being deleted - find the last one which stays
1099 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1101 // go back until we find another entry or reach the group's line
1102 ConfigEntry
*pNewLast
= NULL
;
1103 uint n
, nEntries
= m_aEntries
.Count();
1105 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1106 // is it our subgroup?
1107 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1108 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1109 pNewLast
= m_aEntries
[n
];
1112 if ( pNewLast
!= NULL
) // found?
1116 if ( pl
== m_pLine
) {
1117 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1119 // we've reached the group line without finding any subgroups
1120 m_pLastEntry
= NULL
;
1123 m_pLastEntry
= pNewLast
;
1126 m_pConfig
->LineListRemove(pLine
);
1129 // we must be written back for the changes to be saved
1132 m_aEntries
.Remove(pEntry
);
1138 // ----------------------------------------------------------------------------
1140 // ----------------------------------------------------------------------------
1141 void wxFileConfig::ConfigGroup::SetDirty()
1144 if ( Parent() != NULL
) // propagate upwards
1145 Parent()->SetDirty();
1148 // ============================================================================
1149 // wxFileConfig::ConfigEntry
1150 // ============================================================================
1152 // ----------------------------------------------------------------------------
1154 // ----------------------------------------------------------------------------
1155 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup
*pParent
,
1156 const wxString
& strName
,
1158 : m_strName(strName
)
1160 wxASSERT( !strName
.IsEmpty() );
1162 m_pParent
= pParent
;
1168 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1170 m_strName
.erase(0, 1); // remove first character
1173 // ----------------------------------------------------------------------------
1175 // ----------------------------------------------------------------------------
1177 void wxFileConfig::ConfigEntry::SetLine(LineList
*pLine
)
1179 if ( m_pLine
!= NULL
) {
1180 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1181 Name().c_str(), m_pParent
->GetFullName().c_str());
1185 Group()->SetLastEntry(this);
1188 // second parameter is FALSE if we read the value from file and prevents the
1189 // entry from being marked as 'dirty'
1190 void wxFileConfig::ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1192 if ( bUser
&& IsImmutable() ) {
1193 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1198 // do nothing if it's the same value
1199 if ( strValue
== m_strValue
)
1202 m_strValue
= strValue
;
1205 wxString strVal
= FilterOut(strValue
);
1207 strLine
<< m_strName
<< " = " << strVal
;
1209 if ( m_pLine
!= NULL
) {
1210 // entry was read from the local config file, just modify the line
1211 m_pLine
->SetText(strLine
);
1214 // add a new line to the file
1215 wxASSERT( m_nLine
== NOT_FOUND
); // consistency check
1217 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1218 Group()->GetLastEntryLine());
1219 Group()->SetLastEntry(this);
1226 void wxFileConfig::ConfigEntry::SetDirty()
1229 Group()->SetDirty();
1232 // ============================================================================
1234 // ============================================================================
1236 // ----------------------------------------------------------------------------
1237 // compare functions for array sorting
1238 // ----------------------------------------------------------------------------
1240 int CompareEntries(wxFileConfig::ConfigEntry
*p1
,
1241 wxFileConfig::ConfigEntry
*p2
)
1243 #if wxCONFIG_CASE_SENSITIVE
1244 return strcmp(p1
->Name(), p2
->Name());
1246 return Stricmp(p1
->Name(), p2
->Name());
1250 int CompareGroups(wxFileConfig::ConfigGroup
*p1
,
1251 wxFileConfig::ConfigGroup
*p2
)
1253 #if wxCONFIG_CASE_SENSITIVE
1254 return strcmp(p1
->Name(), p2
->Name());
1256 return Stricmp(p1
->Name(), p2
->Name());
1260 // ----------------------------------------------------------------------------
1262 // ----------------------------------------------------------------------------
1265 wxString
FilterIn(const wxString
& str
)
1268 strResult
.Alloc(str
.Len());
1270 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1272 for ( uint n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1273 if ( str
[n
] == '\\' ) {
1274 switch ( str
[++n
] ) {
1297 if ( str
[n
] != '"' || !bQuoted
)
1298 strResult
+= str
[n
];
1299 else if ( n
!= str
.Len() - 1 ) {
1300 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1303 //else: it's the last quote of a quoted string, ok
1310 // quote the string before writing it to file
1311 wxString
FilterOut(const wxString
& str
)
1314 strResult
.Alloc(str
.Len());
1316 // quoting is necessary to preserve spaces in the beginning of the string
1317 bool bQuote
= isspace(str
[0]) || str
[0] == '"';
1323 for ( uint n
= 0; n
< str
.Len(); n
++ ) {
1344 //else: fall through
1347 strResult
+= str
[n
];
1348 continue; // nothing special to do
1351 // we get here only for special characters
1352 strResult
<< '\\' << c
;