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: APPCONF_IMMUTABLE_PREFIX and APPCONF_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::GetGlobalFileName(const char *szFile
)
86 bool bNoExt
= strchr(szFile
, '.') == NULL
;
89 str
<< "/etc/" << szFile
;
97 char szWinDir
[_MAX_PATH
];
98 ::GetWindowsDirectory(szWinDir
, _MAX_PATH
);
99 str
<< szWinDir
<< "\\" << szFile
;
107 wxString
wxFileConfig::GetLocalFileName(const char *szFile
)
112 const char *szHome
= getenv("HOME");
113 if ( szHome
== NULL
) {
115 wxLogWarning(_("can't find user's HOME, using current directory."));
118 str
<< szHome
<< "/." << szFile
;
121 const char *szHome
= getenv("HOMEDRIVE");
122 if ( szHome
!= NULL
)
124 szHome
= getenv("HOMEPATH");
125 if ( szHome
!= NULL
)
128 if ( strchr(szFile
, '.') == NULL
)
131 // Win16 has no idea about home, so use the current directory instead
132 str
<< ".\\" << szFile
;
139 // ----------------------------------------------------------------------------
141 // ----------------------------------------------------------------------------
143 void wxFileConfig::Init()
146 m_pRootGroup
= new ConfigGroup(NULL
, "", this);
154 wxFileConfig::wxFileConfig(const wxString
& strLocal
, const wxString
& strGlobal
)
155 : m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
159 // it's not an error if (one of the) file(s) doesn't exist
161 // parse the global file
162 if ( !strGlobal
.IsEmpty() ) {
163 if ( wxFile::Exists(strGlobal
) ) {
164 wxTextFile
fileGlobal(strGlobal
);
166 if ( fileGlobal
.Open() ) {
167 Parse(fileGlobal
, FALSE
/* global */);
171 wxLogWarning(_("can't open global configuration file '%s'."),
176 // parse the local file
177 if ( wxFile::Exists(strLocal
) ) {
178 wxTextFile
fileLocal(strLocal
);
179 if ( fileLocal
.Open() ) {
180 Parse(fileLocal
, TRUE
/* local */);
184 wxLogWarning(_("can't open user configuration file '%s'."),
189 wxFileConfig::~wxFileConfig()
194 LineList
*pCur
= m_linesHead
;
195 while ( pCur
!= NULL
) {
196 LineList
*pNext
= pCur
->Next();
202 // ----------------------------------------------------------------------------
203 // parse a config file
204 // ----------------------------------------------------------------------------
206 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
212 uint nLineCount
= file
.GetLineCount();
213 for ( uint n
= 0; n
< nLineCount
; n
++ ) {
216 // add the line to linked list
218 LineListAppend(strLine
);
220 // skip leading spaces
221 for ( pStart
= strLine
; isspace(*pStart
); pStart
++ )
224 // skip blank/comment lines
225 if ( *pStart
== '\0'|| *pStart
== ';' || *pStart
== '#' )
228 if ( *pStart
== '[' ) { // a new group
231 while ( *++pEnd
!= ']' ) {
232 if ( !IsValid(*pEnd
) && *pEnd
!= ' ' ) // allow spaces in group names
236 if ( *pEnd
!= ']' ) {
237 wxLogError(_("file '%s': unexpected character %c at line %d."),
238 file
.GetName(), *pEnd
, n
+ 1);
239 continue; // skip this line
242 // group name here is always considered as abs path
245 strGroup
<< APPCONF_PATH_SEPARATOR
<< wxString(pStart
, pEnd
- pStart
);
247 // will create it if doesn't yet exist
251 m_pCurrentGroup
->SetLine(m_linesTail
);
253 // check that there is nothing except comments left on this line
255 while ( *++pEnd
!= '\0' && bCont
) {
264 // ignore whitespace ('\n' impossible here)
268 wxLogWarning(_("file '%s', line %d: '%s' "
269 "ignored after group header."),
270 file
.GetName(), n
+ 1, pEnd
);
276 const char *pEnd
= pStart
;
277 while ( IsValid(*pEnd
) )
280 wxString
strKey(pStart
, pEnd
);
283 while ( isspace(*pEnd
) )
286 if ( *pEnd
++ != '=' ) {
287 wxLogError(_("file '%s', line %d: '=' expected."),
288 file
.GetName(), n
+ 1);
291 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
293 if ( pEntry
== NULL
) {
295 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
298 pEntry
->SetLine(m_linesTail
);
301 if ( bLocal
&& pEntry
->IsImmutable() ) {
302 // immutable keys can't be changed by user
303 wxLogWarning(_("file '%s', line %d: value for "
304 "immutable key '%s' ignored."),
305 file
.GetName(), n
+ 1, strKey
.c_str());
308 // the condition below catches the cases (a) and (b) but not (c):
309 // (a) global key found second time in global file
310 // (b) key found second (or more) time in local file
311 // (c) key from global file now found in local one
312 // which is exactly what we want.
313 else if ( !bLocal
|| pEntry
->IsLocal() ) {
314 wxLogWarning(_("file '%s', line %d: key '%s' was first "
315 "found at line %d."),
316 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
319 pEntry
->SetLine(m_linesTail
);
324 while ( isspace(*pEnd
) )
327 pEntry
->SetValue(FilterIn(pEnd
), FALSE
/* read from file */);
333 // ----------------------------------------------------------------------------
335 // ----------------------------------------------------------------------------
337 void wxFileConfig::SetRootPath()
340 m_pCurrentGroup
= m_pRootGroup
;
343 void wxFileConfig::SetPath(const wxString
& strPath
)
345 wxArrayString aParts
;
347 if ( strPath
.IsEmpty() ) {
352 if ( strPath
[0] == APPCONF_PATH_SEPARATOR
) {
354 wxSplitPath(aParts
, strPath
);
357 // relative path, combine with current one
358 wxString strFullPath
= m_strPath
;
359 strFullPath
<< APPCONF_PATH_SEPARATOR
<< strPath
;
360 wxSplitPath(aParts
, strFullPath
);
363 // change current group
365 m_pCurrentGroup
= m_pRootGroup
;
366 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
367 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
368 if ( pNextGroup
== NULL
)
369 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
370 m_pCurrentGroup
= pNextGroup
;
373 // recombine path parts in one variable
375 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
376 m_strPath
<< APPCONF_PATH_SEPARATOR
<< aParts
[n
];
380 // ----------------------------------------------------------------------------
382 // ----------------------------------------------------------------------------
384 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
)
387 return GetNextGroup(str
, lIndex
);
390 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
)
392 if ( uint(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
393 str
= m_pCurrentGroup
->Groups()[lIndex
++]->Name();
400 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
)
403 return GetNextEntry(str
, lIndex
);
406 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
)
408 if ( uint(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
409 str
= m_pCurrentGroup
->Entries()[lIndex
++]->Name();
416 uint
wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
418 uint n
= m_pCurrentGroup
->Entries().Count();
420 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
421 uint nSubgroups
= m_pCurrentGroup
->Groups().Count();
422 for ( uint nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
423 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
424 n
+= GetNumberOfEntries(TRUE
);
425 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
432 uint
wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
434 uint n
= m_pCurrentGroup
->Groups().Count();
436 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
437 uint nSubgroups
= m_pCurrentGroup
->Groups().Count();
438 for ( uint nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
439 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
440 n
+= GetNumberOfGroups(TRUE
);
441 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
448 // ----------------------------------------------------------------------------
449 // tests for existence
450 // ----------------------------------------------------------------------------
452 bool wxFileConfig::HasGroup(const wxString
& strName
) const
454 PathChanger
path(this, strName
);
456 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
457 return pGroup
!= NULL
;
460 bool wxFileConfig::HasEntry(const wxString
& strName
) const
462 PathChanger
path(this, strName
);
464 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
465 return pEntry
!= NULL
;
468 // ----------------------------------------------------------------------------
470 // ----------------------------------------------------------------------------
472 bool wxFileConfig::Read(wxString
*pstr
,
474 const char *szDefault
) const
476 PathChanger
path(this, szKey
);
478 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
479 if (pEntry
== NULL
) {
480 *pstr
= ExpandEnvVars(szDefault
);
484 *pstr
= ExpandEnvVars(pEntry
->Value());
489 const char *wxFileConfig::Read(const char *szKey
,
490 const char *szDefault
) const
492 static wxString s_str
;
493 Read(&s_str
, szKey
, szDefault
);
495 return s_str
.c_str();
498 bool wxFileConfig::Read(long *pl
, const char *szKey
, long lDefault
) const
501 if ( Read(&str
, szKey
) ) {
511 bool wxFileConfig::Write(const char *szKey
, const char *szValue
)
513 PathChanger
path(this, szKey
);
515 wxString strName
= path
.Name();
516 if ( strName
.IsEmpty() ) {
517 // setting the value of a group is an error
518 wxASSERT_MSG( IsEmpty(szValue
), "can't set value of a group!" );
520 // ... except if it's empty in which case it's a way to force it's creation
521 m_pCurrentGroup
->SetDirty();
523 // this will add a line for this group if it didn't have it before
524 (void)m_pCurrentGroup
->GetGroupLine();
529 // check that the name is reasonable
530 if ( strName
[0u] == APPCONF_IMMUTABLE_PREFIX
) {
531 wxLogError(_("Entry name can't start with '%c'."),
532 APPCONF_IMMUTABLE_PREFIX
);
536 for ( const char *pc
= strName
; *pc
!= '\0'; pc
++ ) {
537 if ( !IsValid(*pc
) ) {
538 wxLogError(_("Character '%c' is invalid in a config entry name."),
544 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
545 if ( pEntry
== NULL
)
546 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
548 pEntry
->SetValue(szValue
);
554 bool wxFileConfig::Write(const char *szKey
, long lValue
)
556 // ltoa() is not ANSI :-(
557 char szBuf
[40]; // should be good for sizeof(long) <= 16 (128 bits)
558 sprintf(szBuf
, "%ld", lValue
);
559 return Write(szKey
, szBuf
);
562 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
564 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() )
567 wxTempFile
file(m_strLocalFile
);
569 if ( !file
.IsOpened() ) {
570 wxLogError(_("can't open user configuration file."));
574 // write all strings to file
575 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
576 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
577 wxLogError(_("can't write user configuration file."));
582 return file
.Commit();
585 // ----------------------------------------------------------------------------
586 // delete groups/entries
587 // ----------------------------------------------------------------------------
589 bool wxFileConfig::DeleteEntry(const char *szKey
, bool bGroupIfEmptyAlso
)
591 PathChanger
path(this, szKey
);
593 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
596 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
597 if ( m_pCurrentGroup
!= m_pRootGroup
) {
598 ConfigGroup
*pGroup
= m_pCurrentGroup
;
599 SetPath(".."); // changes m_pCurrentGroup!
600 m_pCurrentGroup
->DeleteSubgroup(pGroup
->Name());
602 //else: never delete the root group
608 bool wxFileConfig::DeleteGroup(const char *szKey
)
610 PathChanger
path(this, szKey
);
612 return m_pCurrentGroup
->DeleteSubgroup(path
.Name());
615 bool wxFileConfig::DeleteAll()
617 const char *szFile
= m_strLocalFile
;
621 if ( remove(szFile
) == -1 )
622 wxLogSysError(_("can't delete user configuration file '%s'"), szFile
);
624 szFile
= m_strGlobalFile
;
625 if ( remove(szFile
) )
626 wxLogSysError(_("can't delete system configuration file '%s'"), szFile
);
631 // ----------------------------------------------------------------------------
632 // linked list functions
633 // ----------------------------------------------------------------------------
635 // append a new line to the end of the list
636 wxFileConfig::LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
638 LineList
*pLine
= new LineList(str
);
640 if ( m_linesTail
== NULL
) {
646 m_linesTail
->SetNext(pLine
);
647 pLine
->SetPrev(m_linesTail
);
654 // insert a new line after the given one or in the very beginning if !pLine
655 wxFileConfig::LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
658 if ( pLine
== m_linesTail
)
659 return LineListAppend(str
);
661 LineList
*pNewLine
= new LineList(str
);
662 if ( pLine
== NULL
) {
663 // prepend to the list
664 pNewLine
->SetNext(m_linesHead
);
665 m_linesHead
->SetPrev(pNewLine
);
666 m_linesHead
= pNewLine
;
669 // insert before pLine
670 LineList
*pNext
= pLine
->Next();
671 pNewLine
->SetNext(pNext
);
672 pNewLine
->SetPrev(pLine
);
673 pNext
->SetPrev(pNewLine
);
674 pLine
->SetNext(pNewLine
);
680 void wxFileConfig::LineListRemove(LineList
*pLine
)
682 LineList
*pPrev
= pLine
->Prev(),
683 *pNext
= pLine
->Next();
689 pPrev
->SetNext(pNext
);
695 pNext
->SetPrev(pPrev
);
700 bool wxFileConfig::LineListIsEmpty()
702 return m_linesHead
== NULL
;
705 // ============================================================================
706 // wxFileConfig::ConfigGroup
707 // ============================================================================
709 // ----------------------------------------------------------------------------
711 // ----------------------------------------------------------------------------
714 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup
*pParent
,
715 const wxString
& strName
,
716 wxFileConfig
*pConfig
)
717 : m_aEntries(CompareEntries
),
718 m_aSubgroups(CompareGroups
),
730 // dtor deletes all children
731 wxFileConfig::ConfigGroup::~ConfigGroup()
734 uint n
, nCount
= m_aEntries
.Count();
735 for ( n
= 0; n
< nCount
; n
++ )
736 delete m_aEntries
[n
];
739 nCount
= m_aSubgroups
.Count();
740 for ( n
= 0; n
< nCount
; n
++ )
741 delete m_aSubgroups
[n
];
744 // ----------------------------------------------------------------------------
746 // ----------------------------------------------------------------------------
748 void wxFileConfig::ConfigGroup::SetLine(LineList
*pLine
)
750 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
756 This is a bit complicated, so let me explain it in details. All lines that
757 were read from the local file (the only one we will ever modify) are stored
758 in a (doubly) linked list. Our problem is to know at which position in this
759 list should we insert the new entries/subgroups. To solve it we keep three
760 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
762 m_pLine points to the line containing "[group_name]"
763 m_pLastEntry points to the last entry of this group in the local file.
764 m_pLastGroup subgroup
766 Initially, they're NULL all three. When the group (an entry/subgroup) is read
767 from the local file, the corresponding variable is set. However, if the group
768 was read from the global file and then modified or created by the application
769 these variables are still NULL and we need to create the corresponding lines.
770 See the following functions (and comments preceding them) for the details of
773 Also, when our last entry/group are deleted we need to find the new last
774 element - the code in DeleteEntry/Subgroup does this by backtracking the list
775 of lines until it either founds an entry/subgroup (and this is the new last
776 element) or the m_pLine of the group, in which case there are no more entries
777 (or subgroups) left and m_pLast<element> becomes NULL.
779 NB: This last problem could be avoided for entries if we added new entries
780 immediately after m_pLine, but in this case the entries would appear
781 backwards in the config file (OTOH, it's not that important) and as we
782 would still need to do it for the subgroups the code wouldn't have been
783 significantly less complicated.
786 // Return the line which contains "[our name]". If we're still not in the list,
787 // add our line to it immediately after the last line of our parent group if we
788 // have it or in the very beginning if we're the root group.
789 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetGroupLine()
791 if ( m_pLine
== NULL
) {
792 ConfigGroup
*pParent
= Parent();
794 // this group wasn't present in local config file, add it now
795 if ( pParent
!= NULL
) {
796 wxString strFullName
;
797 strFullName
<< "[" << GetFullName().c_str() + 1 << "]"; // +1: no '/'
798 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
799 pParent
->GetLastGroupLine());
800 pParent
->SetLastGroup(this); // we're surely after all the others
803 // we return NULL, so that LineListInsert() will insert us in the
811 // Return the last line belonging to the subgroups of this group (after which
812 // we can add a new subgroup), if we don't have any subgroups or entries our
813 // last line is the group line (m_pLine) itself.
814 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastGroupLine()
816 // if we have any subgroups, our last line is the last line of the last
818 if ( m_pLastGroup
!= NULL
) {
819 wxFileConfig::LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
821 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
825 // no subgroups, so the last line is the line of thelast entry (if any)
826 return GetLastEntryLine();
829 // return the last line belonging to the entries of this group (after which
830 // we can add a new entry), if we don't have any entries we will add the new
831 // one immediately after the group line itself.
832 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastEntryLine()
834 if ( m_pLastEntry
!= NULL
) {
835 wxFileConfig::LineList
*pLine
= m_pLastEntry
->GetLine();
837 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
841 // no entries: insert after the group header
842 return GetGroupLine();
845 // ----------------------------------------------------------------------------
847 // ----------------------------------------------------------------------------
849 wxString
wxFileConfig::ConfigGroup::GetFullName() const
852 return Parent()->GetFullName() + APPCONF_PATH_SEPARATOR
+ Name();
857 // ----------------------------------------------------------------------------
859 // ----------------------------------------------------------------------------
861 // use binary search because the array is sorted
862 wxFileConfig::ConfigEntry
*
863 wxFileConfig::ConfigGroup::FindEntry(const char *szName
) const
867 hi
= m_aEntries
.Count();
869 wxFileConfig::ConfigEntry
*pEntry
;
873 pEntry
= m_aEntries
[i
];
875 #if APPCONF_CASE_SENSITIVE
876 res
= strcmp(pEntry
->Name(), szName
);
878 res
= Stricmp(pEntry
->Name(), szName
);
892 wxFileConfig::ConfigGroup
*
893 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName
) const
897 hi
= m_aSubgroups
.Count();
899 wxFileConfig::ConfigGroup
*pGroup
;
903 pGroup
= m_aSubgroups
[i
];
905 #if APPCONF_CASE_SENSITIVE
906 res
= strcmp(pGroup
->Name(), szName
);
908 res
= Stricmp(pGroup
->Name(), szName
);
922 // ----------------------------------------------------------------------------
924 // ----------------------------------------------------------------------------
926 // create a new entry and add it to the current group
927 wxFileConfig::ConfigEntry
*
928 wxFileConfig::ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
930 wxASSERT( FindEntry(strName
) == NULL
);
932 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
933 m_aEntries
.Add(pEntry
);
938 // create a new group and add it to the current group
939 wxFileConfig::ConfigGroup
*
940 wxFileConfig::ConfigGroup::AddSubgroup(const wxString
& strName
)
942 wxASSERT( FindSubgroup(strName
) == NULL
);
944 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
945 m_aSubgroups
.Add(pGroup
);
950 // ----------------------------------------------------------------------------
952 // ----------------------------------------------------------------------------
955 The delete operations are _very_ slow if we delete the last item of this
956 group (see comments before GetXXXLineXXX functions for more details),
957 so it's much better to start with the first entry/group if we want to
958 delete several of them.
961 bool wxFileConfig::ConfigGroup::DeleteSubgroup(const char *szName
)
963 ConfigGroup
*pGroup
= FindSubgroup(szName
);
964 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
966 // delete all entries
967 uint nCount
= pGroup
->m_aEntries
.Count();
968 for ( uint nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
969 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
971 m_pConfig
->LineListRemove(pLine
);
974 LineList
*pLine
= pGroup
->m_pLine
;
975 if ( pLine
!= NULL
) {
976 // notice that we may do this test inside the previous "if" because the
977 // last entry's line is surely !NULL
978 if ( pGroup
== m_pLastGroup
) {
979 // our last entry is being deleted - find the last one which stays
980 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
982 // go back until we find a subgroup or reach the group's line
983 ConfigGroup
*pNewLast
= NULL
;
984 uint n
, nSubgroups
= m_aSubgroups
.Count();
986 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
987 // is it our subgroup?
988 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
989 // do _not_ call GetGroupLine! we don't want to add it to the local
990 // file if it's not already there
991 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
992 pNewLast
= m_aSubgroups
[n
];
995 if ( pNewLast
!= NULL
) // found?
999 if ( pl
== m_pLine
) {
1000 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1002 // we've reached the group line without finding any subgroups
1003 m_pLastGroup
= NULL
;
1006 m_pLastGroup
= pNewLast
;
1009 m_pConfig
->LineListRemove(pLine
);
1014 m_aSubgroups
.Remove(pGroup
);
1020 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName
)
1022 ConfigEntry
*pEntry
= FindEntry(szName
);
1023 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1025 LineList
*pLine
= pEntry
->GetLine();
1026 if ( pLine
!= NULL
) {
1027 // notice that we may do this test inside the previous "if" because the
1028 // last entry's line is surely !NULL
1029 if ( pEntry
== m_pLastEntry
) {
1030 // our last entry is being deleted - find the last one which stays
1031 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1033 // go back until we find another entry or reach the group's line
1034 ConfigEntry
*pNewLast
= NULL
;
1035 uint n
, nEntries
= m_aEntries
.Count();
1037 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1038 // is it our subgroup?
1039 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1040 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1041 pNewLast
= m_aEntries
[n
];
1044 if ( pNewLast
!= NULL
) // found?
1048 if ( pl
== m_pLine
) {
1049 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1051 // we've reached the group line without finding any subgroups
1052 m_pLastEntry
= NULL
;
1055 m_pLastEntry
= pNewLast
;
1058 m_pConfig
->LineListRemove(pLine
);
1061 // we must be written back for the changes to be saved
1064 m_aEntries
.Remove(pEntry
);
1070 // ----------------------------------------------------------------------------
1072 // ----------------------------------------------------------------------------
1073 void wxFileConfig::ConfigGroup::SetDirty()
1076 if ( Parent() != NULL
) // propagate upwards
1077 Parent()->SetDirty();
1080 // ============================================================================
1081 // wxFileConfig::ConfigEntry
1082 // ============================================================================
1084 // ----------------------------------------------------------------------------
1086 // ----------------------------------------------------------------------------
1087 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup
*pParent
,
1088 const wxString
& strName
,
1090 : m_strName(strName
)
1092 wxASSERT( !strName
.IsEmpty() );
1094 m_pParent
= pParent
;
1100 m_bImmutable
= strName
[0] == APPCONF_IMMUTABLE_PREFIX
;
1102 m_strName
.erase(0, 1); // remove first character
1105 // ----------------------------------------------------------------------------
1107 // ----------------------------------------------------------------------------
1109 void wxFileConfig::ConfigEntry::SetLine(LineList
*pLine
)
1111 if ( m_pLine
!= NULL
) {
1112 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1113 Name().c_str(), m_pParent
->GetFullName().c_str());
1117 Group()->SetLastEntry(this);
1120 // second parameter is FALSE if we read the value from file and prevents the
1121 // entry from being marked as 'dirty'
1122 void wxFileConfig::ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1124 if ( bUser
&& IsImmutable() ) {
1125 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1130 // do nothing if it's the same value
1131 if ( strValue
== m_strValue
)
1134 m_strValue
= strValue
;
1137 wxString strVal
= FilterOut(strValue
);
1139 strLine
<< m_strName
<< " = " << strVal
;
1141 if ( m_pLine
!= NULL
) {
1142 // entry was read from the local config file, just modify the line
1143 m_pLine
->SetText(strLine
);
1146 // add a new line to the file
1147 wxASSERT( m_nLine
== NOT_FOUND
); // consistency check
1149 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1150 Group()->GetLastEntryLine());
1151 Group()->SetLastEntry(this);
1158 void wxFileConfig::ConfigEntry::SetDirty()
1161 Group()->SetDirty();
1164 // ============================================================================
1166 // ============================================================================
1168 // ----------------------------------------------------------------------------
1169 // compare functions for array sorting
1170 // ----------------------------------------------------------------------------
1172 int CompareEntries(wxFileConfig::ConfigEntry
*p1
,
1173 wxFileConfig::ConfigEntry
*p2
)
1175 #if APPCONF_CASE_SENSITIVE
1176 return strcmp(p1
->Name(), p2
->Name());
1178 return Stricmp(p1
->Name(), p2
->Name());
1182 int CompareGroups(wxFileConfig::ConfigGroup
*p1
,
1183 wxFileConfig::ConfigGroup
*p2
)
1185 #if APPCONF_CASE_SENSITIVE
1186 return strcmp(p1
->Name(), p2
->Name());
1188 return Stricmp(p1
->Name(), p2
->Name());
1192 // ----------------------------------------------------------------------------
1194 // ----------------------------------------------------------------------------
1197 wxString
FilterIn(const wxString
& str
)
1200 strResult
.Alloc(str
.Len());
1202 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1204 for ( uint n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1205 if ( str
[n
] == '\\' ) {
1206 switch ( str
[++n
] ) {
1229 if ( str
[n
] != '"' || !bQuoted
)
1230 strResult
+= str
[n
];
1231 else if ( n
!= str
.Len() - 1 ) {
1232 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1235 //else: it's the last quote of a quoted string, ok
1242 // quote the string before writing it to file
1243 wxString
FilterOut(const wxString
& str
)
1246 strResult
.Alloc(str
.Len());
1248 // quoting is necessary to preserve spaces in the beginning of the string
1249 bool bQuote
= isspace(str
[0]) || str
[0] == '"';
1255 for ( uint n
= 0; n
< str
.Len(); n
++ ) {
1276 //else: fall through
1279 strResult
+= str
[n
];
1280 continue; // nothing special to do
1283 // we get here only for special characters
1284 strResult
<< '\\' << c
;