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
= GetGlobalDir();
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 const char *szFile
= m_strLocalFile
;
673 if ( remove(szFile
) == -1 )
674 wxLogSysError(_("can't delete user configuration file '%s'"), szFile
);
676 m_strLocalFile
= m_strGlobalFile
= "";
682 // ----------------------------------------------------------------------------
683 // linked list functions
684 // ----------------------------------------------------------------------------
686 // append a new line to the end of the list
687 wxFileConfig::LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
689 LineList
*pLine
= new LineList(str
);
691 if ( m_linesTail
== NULL
) {
697 m_linesTail
->SetNext(pLine
);
698 pLine
->SetPrev(m_linesTail
);
705 // insert a new line after the given one or in the very beginning if !pLine
706 wxFileConfig::LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
709 if ( pLine
== m_linesTail
)
710 return LineListAppend(str
);
712 LineList
*pNewLine
= new LineList(str
);
713 if ( pLine
== NULL
) {
714 // prepend to the list
715 pNewLine
->SetNext(m_linesHead
);
716 m_linesHead
->SetPrev(pNewLine
);
717 m_linesHead
= pNewLine
;
720 // insert before pLine
721 LineList
*pNext
= pLine
->Next();
722 pNewLine
->SetNext(pNext
);
723 pNewLine
->SetPrev(pLine
);
724 pNext
->SetPrev(pNewLine
);
725 pLine
->SetNext(pNewLine
);
731 void wxFileConfig::LineListRemove(LineList
*pLine
)
733 LineList
*pPrev
= pLine
->Prev(),
734 *pNext
= pLine
->Next();
740 pPrev
->SetNext(pNext
);
746 pNext
->SetPrev(pPrev
);
751 bool wxFileConfig::LineListIsEmpty()
753 return m_linesHead
== NULL
;
756 // ============================================================================
757 // wxFileConfig::ConfigGroup
758 // ============================================================================
760 // ----------------------------------------------------------------------------
762 // ----------------------------------------------------------------------------
765 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup
*pParent
,
766 const wxString
& strName
,
767 wxFileConfig
*pConfig
)
768 : m_aEntries(CompareEntries
),
769 m_aSubgroups(CompareGroups
),
781 // dtor deletes all children
782 wxFileConfig::ConfigGroup::~ConfigGroup()
785 uint n
, nCount
= m_aEntries
.Count();
786 for ( n
= 0; n
< nCount
; n
++ )
787 delete m_aEntries
[n
];
790 nCount
= m_aSubgroups
.Count();
791 for ( n
= 0; n
< nCount
; n
++ )
792 delete m_aSubgroups
[n
];
795 // ----------------------------------------------------------------------------
797 // ----------------------------------------------------------------------------
799 void wxFileConfig::ConfigGroup::SetLine(LineList
*pLine
)
801 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
807 This is a bit complicated, so let me explain it in details. All lines that
808 were read from the local file (the only one we will ever modify) are stored
809 in a (doubly) linked list. Our problem is to know at which position in this
810 list should we insert the new entries/subgroups. To solve it we keep three
811 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
813 m_pLine points to the line containing "[group_name]"
814 m_pLastEntry points to the last entry of this group in the local file.
815 m_pLastGroup subgroup
817 Initially, they're NULL all three. When the group (an entry/subgroup) is read
818 from the local file, the corresponding variable is set. However, if the group
819 was read from the global file and then modified or created by the application
820 these variables are still NULL and we need to create the corresponding lines.
821 See the following functions (and comments preceding them) for the details of
824 Also, when our last entry/group are deleted we need to find the new last
825 element - the code in DeleteEntry/Subgroup does this by backtracking the list
826 of lines until it either founds an entry/subgroup (and this is the new last
827 element) or the m_pLine of the group, in which case there are no more entries
828 (or subgroups) left and m_pLast<element> becomes NULL.
830 NB: This last problem could be avoided for entries if we added new entries
831 immediately after m_pLine, but in this case the entries would appear
832 backwards in the config file (OTOH, it's not that important) and as we
833 would still need to do it for the subgroups the code wouldn't have been
834 significantly less complicated.
837 // Return the line which contains "[our name]". If we're still not in the list,
838 // add our line to it immediately after the last line of our parent group if we
839 // have it or in the very beginning if we're the root group.
840 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetGroupLine()
842 if ( m_pLine
== NULL
) {
843 ConfigGroup
*pParent
= Parent();
845 // this group wasn't present in local config file, add it now
846 if ( pParent
!= NULL
) {
847 wxString strFullName
;
848 strFullName
<< "[" << GetFullName().c_str() + 1 << "]"; // +1: no '/'
849 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
850 pParent
->GetLastGroupLine());
851 pParent
->SetLastGroup(this); // we're surely after all the others
854 // we return NULL, so that LineListInsert() will insert us in the
862 // Return the last line belonging to the subgroups of this group (after which
863 // we can add a new subgroup), if we don't have any subgroups or entries our
864 // last line is the group line (m_pLine) itself.
865 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastGroupLine()
867 // if we have any subgroups, our last line is the last line of the last
869 if ( m_pLastGroup
!= NULL
) {
870 wxFileConfig::LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
872 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
876 // no subgroups, so the last line is the line of thelast entry (if any)
877 return GetLastEntryLine();
880 // return the last line belonging to the entries of this group (after which
881 // we can add a new entry), if we don't have any entries we will add the new
882 // one immediately after the group line itself.
883 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastEntryLine()
885 if ( m_pLastEntry
!= NULL
) {
886 wxFileConfig::LineList
*pLine
= m_pLastEntry
->GetLine();
888 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
892 // no entries: insert after the group header
893 return GetGroupLine();
896 // ----------------------------------------------------------------------------
898 // ----------------------------------------------------------------------------
900 wxString
wxFileConfig::ConfigGroup::GetFullName() const
903 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
908 // ----------------------------------------------------------------------------
910 // ----------------------------------------------------------------------------
912 // use binary search because the array is sorted
913 wxFileConfig::ConfigEntry
*
914 wxFileConfig::ConfigGroup::FindEntry(const char *szName
) const
918 hi
= m_aEntries
.Count();
920 wxFileConfig::ConfigEntry
*pEntry
;
924 pEntry
= m_aEntries
[i
];
926 #if wxCONFIG_CASE_SENSITIVE
927 res
= strcmp(pEntry
->Name(), szName
);
929 res
= Stricmp(pEntry
->Name(), szName
);
943 wxFileConfig::ConfigGroup
*
944 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName
) const
948 hi
= m_aSubgroups
.Count();
950 wxFileConfig::ConfigGroup
*pGroup
;
954 pGroup
= m_aSubgroups
[i
];
956 #if wxCONFIG_CASE_SENSITIVE
957 res
= strcmp(pGroup
->Name(), szName
);
959 res
= Stricmp(pGroup
->Name(), szName
);
973 // ----------------------------------------------------------------------------
975 // ----------------------------------------------------------------------------
977 // create a new entry and add it to the current group
978 wxFileConfig::ConfigEntry
*
979 wxFileConfig::ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
981 wxASSERT( FindEntry(strName
) == NULL
);
983 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
984 m_aEntries
.Add(pEntry
);
989 // create a new group and add it to the current group
990 wxFileConfig::ConfigGroup
*
991 wxFileConfig::ConfigGroup::AddSubgroup(const wxString
& strName
)
993 wxASSERT( FindSubgroup(strName
) == NULL
);
995 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
996 m_aSubgroups
.Add(pGroup
);
1001 // ----------------------------------------------------------------------------
1003 // ----------------------------------------------------------------------------
1006 The delete operations are _very_ slow if we delete the last item of this
1007 group (see comments before GetXXXLineXXX functions for more details),
1008 so it's much better to start with the first entry/group if we want to
1009 delete several of them.
1012 bool wxFileConfig::ConfigGroup::DeleteSubgroupByName(const char *szName
)
1014 return DeleteSubgroup(FindSubgroup(szName
));
1017 // doesn't delete the subgroup itself, but does remove references to it from
1018 // all other data structures (and normally the returned pointer should be
1019 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1020 bool wxFileConfig::ConfigGroup::DeleteSubgroup(ConfigGroup
*pGroup
)
1022 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1024 // delete all entries
1025 uint nCount
= pGroup
->m_aEntries
.Count();
1026 for ( uint nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1027 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1028 if ( pLine
!= NULL
)
1029 m_pConfig
->LineListRemove(pLine
);
1032 // and subgroups of this sungroup
1033 nCount
= pGroup
->m_aSubgroups
.Count();
1034 for ( uint nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1035 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[nGroup
]);
1038 LineList
*pLine
= pGroup
->m_pLine
;
1039 if ( pLine
!= NULL
) {
1040 // notice that we may do this test inside the previous "if" because the
1041 // last entry's line is surely !NULL
1042 if ( pGroup
== m_pLastGroup
) {
1043 // our last entry is being deleted - find the last one which stays
1044 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1046 // go back until we find a subgroup or reach the group's line
1047 ConfigGroup
*pNewLast
= NULL
;
1048 uint n
, nSubgroups
= m_aSubgroups
.Count();
1050 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1051 // is it our subgroup?
1052 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1053 // do _not_ call GetGroupLine! we don't want to add it to the local
1054 // file if it's not already there
1055 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1056 pNewLast
= m_aSubgroups
[n
];
1059 if ( pNewLast
!= NULL
) // found?
1063 if ( pl
== m_pLine
) {
1064 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1066 // we've reached the group line without finding any subgroups
1067 m_pLastGroup
= NULL
;
1070 m_pLastGroup
= pNewLast
;
1073 m_pConfig
->LineListRemove(pLine
);
1078 m_aSubgroups
.Remove(pGroup
);
1084 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName
)
1086 ConfigEntry
*pEntry
= FindEntry(szName
);
1087 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1089 LineList
*pLine
= pEntry
->GetLine();
1090 if ( pLine
!= NULL
) {
1091 // notice that we may do this test inside the previous "if" because the
1092 // last entry's line is surely !NULL
1093 if ( pEntry
== m_pLastEntry
) {
1094 // our last entry is being deleted - find the last one which stays
1095 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1097 // go back until we find another entry or reach the group's line
1098 ConfigEntry
*pNewLast
= NULL
;
1099 uint n
, nEntries
= m_aEntries
.Count();
1101 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1102 // is it our subgroup?
1103 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1104 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1105 pNewLast
= m_aEntries
[n
];
1108 if ( pNewLast
!= NULL
) // found?
1112 if ( pl
== m_pLine
) {
1113 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1115 // we've reached the group line without finding any subgroups
1116 m_pLastEntry
= NULL
;
1119 m_pLastEntry
= pNewLast
;
1122 m_pConfig
->LineListRemove(pLine
);
1125 // we must be written back for the changes to be saved
1128 m_aEntries
.Remove(pEntry
);
1134 // ----------------------------------------------------------------------------
1136 // ----------------------------------------------------------------------------
1137 void wxFileConfig::ConfigGroup::SetDirty()
1140 if ( Parent() != NULL
) // propagate upwards
1141 Parent()->SetDirty();
1144 // ============================================================================
1145 // wxFileConfig::ConfigEntry
1146 // ============================================================================
1148 // ----------------------------------------------------------------------------
1150 // ----------------------------------------------------------------------------
1151 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup
*pParent
,
1152 const wxString
& strName
,
1154 : m_strName(strName
)
1156 wxASSERT( !strName
.IsEmpty() );
1158 m_pParent
= pParent
;
1164 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1166 m_strName
.erase(0, 1); // remove first character
1169 // ----------------------------------------------------------------------------
1171 // ----------------------------------------------------------------------------
1173 void wxFileConfig::ConfigEntry::SetLine(LineList
*pLine
)
1175 if ( m_pLine
!= NULL
) {
1176 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1177 Name().c_str(), m_pParent
->GetFullName().c_str());
1181 Group()->SetLastEntry(this);
1184 // second parameter is FALSE if we read the value from file and prevents the
1185 // entry from being marked as 'dirty'
1186 void wxFileConfig::ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1188 if ( bUser
&& IsImmutable() ) {
1189 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1194 // do nothing if it's the same value
1195 if ( strValue
== m_strValue
)
1198 m_strValue
= strValue
;
1201 wxString strVal
= FilterOut(strValue
);
1203 strLine
<< m_strName
<< " = " << strVal
;
1205 if ( m_pLine
!= NULL
) {
1206 // entry was read from the local config file, just modify the line
1207 m_pLine
->SetText(strLine
);
1210 // add a new line to the file
1211 wxASSERT( m_nLine
== NOT_FOUND
); // consistency check
1213 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1214 Group()->GetLastEntryLine());
1215 Group()->SetLastEntry(this);
1222 void wxFileConfig::ConfigEntry::SetDirty()
1225 Group()->SetDirty();
1228 // ============================================================================
1230 // ============================================================================
1232 // ----------------------------------------------------------------------------
1233 // compare functions for array sorting
1234 // ----------------------------------------------------------------------------
1236 int CompareEntries(wxFileConfig::ConfigEntry
*p1
,
1237 wxFileConfig::ConfigEntry
*p2
)
1239 #if wxCONFIG_CASE_SENSITIVE
1240 return strcmp(p1
->Name(), p2
->Name());
1242 return Stricmp(p1
->Name(), p2
->Name());
1246 int CompareGroups(wxFileConfig::ConfigGroup
*p1
,
1247 wxFileConfig::ConfigGroup
*p2
)
1249 #if wxCONFIG_CASE_SENSITIVE
1250 return strcmp(p1
->Name(), p2
->Name());
1252 return Stricmp(p1
->Name(), p2
->Name());
1256 // ----------------------------------------------------------------------------
1258 // ----------------------------------------------------------------------------
1261 wxString
FilterIn(const wxString
& str
)
1264 strResult
.Alloc(str
.Len());
1266 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1268 for ( uint n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1269 if ( str
[n
] == '\\' ) {
1270 switch ( str
[++n
] ) {
1293 if ( str
[n
] != '"' || !bQuoted
)
1294 strResult
+= str
[n
];
1295 else if ( n
!= str
.Len() - 1 ) {
1296 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1299 //else: it's the last quote of a quoted string, ok
1306 // quote the string before writing it to file
1307 wxString
FilterOut(const wxString
& str
)
1310 strResult
.Alloc(str
.Len());
1312 // quoting is necessary to preserve spaces in the beginning of the string
1313 bool bQuote
= isspace(str
[0]) || str
[0] == '"';
1319 for ( uint n
= 0; n
< str
.Len(); n
++ ) {
1342 //else: fall through
1345 strResult
+= str
[n
];
1346 continue; // nothing special to do
1349 // we get here only for special characters
1350 strResult
<< '\\' << c
;