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]) )
226 m_strLocalFile
= GetLocalDir();
227 m_strLocalFile
<< strLocal
;
230 if ( !strGlobal
.IsEmpty() && !wxIsPathSeparator(strGlobal
[0u]) )
232 m_strGlobalFile
= GetGlobalDir();
233 m_strGlobalFile
<< strGlobal
;
238 void wxFileConfig::CleanUp()
242 LineList
*pCur
= m_linesHead
;
243 while ( pCur
!= NULL
) {
244 LineList
*pNext
= pCur
->Next();
250 wxFileConfig::~wxFileConfig()
257 // ----------------------------------------------------------------------------
258 // parse a config file
259 // ----------------------------------------------------------------------------
261 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
267 uint nLineCount
= file
.GetLineCount();
268 for ( uint n
= 0; n
< nLineCount
; n
++ ) {
271 // add the line to linked list
273 LineListAppend(strLine
);
275 // skip leading spaces
276 for ( pStart
= strLine
; isspace(*pStart
); pStart
++ )
279 // skip blank/comment lines
280 if ( *pStart
== '\0'|| *pStart
== ';' || *pStart
== '#' )
283 if ( *pStart
== '[' ) { // a new group
286 while ( *++pEnd
!= ']' ) {
287 if ( !IsValid(*pEnd
) && *pEnd
!= ' ' ) // allow spaces in group names
291 if ( *pEnd
!= ']' ) {
292 wxLogError(_("file '%s': unexpected character %c at line %d."),
293 file
.GetName(), *pEnd
, n
+ 1);
294 continue; // skip this line
297 // group name here is always considered as abs path
300 strGroup
<< wxCONFIG_PATH_SEPARATOR
<< wxString(pStart
, pEnd
- pStart
);
302 // will create it if doesn't yet exist
306 m_pCurrentGroup
->SetLine(m_linesTail
);
308 // check that there is nothing except comments left on this line
310 while ( *++pEnd
!= '\0' && bCont
) {
319 // ignore whitespace ('\n' impossible here)
323 wxLogWarning(_("file '%s', line %d: '%s' "
324 "ignored after group header."),
325 file
.GetName(), n
+ 1, pEnd
);
331 const char *pEnd
= pStart
;
332 while ( IsValid(*pEnd
) )
335 wxString
strKey(pStart
, pEnd
);
338 while ( isspace(*pEnd
) )
341 if ( *pEnd
++ != '=' ) {
342 wxLogError(_("file '%s', line %d: '=' expected."),
343 file
.GetName(), n
+ 1);
346 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
348 if ( pEntry
== NULL
) {
350 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
353 pEntry
->SetLine(m_linesTail
);
356 if ( bLocal
&& pEntry
->IsImmutable() ) {
357 // immutable keys can't be changed by user
358 wxLogWarning(_("file '%s', line %d: value for "
359 "immutable key '%s' ignored."),
360 file
.GetName(), n
+ 1, strKey
.c_str());
363 // the condition below catches the cases (a) and (b) but not (c):
364 // (a) global key found second time in global file
365 // (b) key found second (or more) time in local file
366 // (c) key from global file now found in local one
367 // which is exactly what we want.
368 else if ( !bLocal
|| pEntry
->IsLocal() ) {
369 wxLogWarning(_("file '%s', line %d: key '%s' was first "
370 "found at line %d."),
371 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
374 pEntry
->SetLine(m_linesTail
);
379 while ( isspace(*pEnd
) )
382 pEntry
->SetValue(FilterIn(pEnd
), FALSE
/* read from file */);
388 // ----------------------------------------------------------------------------
390 // ----------------------------------------------------------------------------
392 void wxFileConfig::SetRootPath()
395 m_pCurrentGroup
= m_pRootGroup
;
398 void wxFileConfig::SetPath(const wxString
& strPath
)
400 wxArrayString aParts
;
402 if ( strPath
.IsEmpty() ) {
407 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
409 wxSplitPath(aParts
, strPath
);
412 // relative path, combine with current one
413 wxString strFullPath
= m_strPath
;
414 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
415 wxSplitPath(aParts
, strFullPath
);
418 // change current group
420 m_pCurrentGroup
= m_pRootGroup
;
421 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
422 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
423 if ( pNextGroup
== NULL
)
424 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
425 m_pCurrentGroup
= pNextGroup
;
428 // recombine path parts in one variable
430 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
431 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
435 // ----------------------------------------------------------------------------
437 // ----------------------------------------------------------------------------
439 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
442 return GetNextGroup(str
, lIndex
);
445 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
447 if ( uint(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
448 str
= m_pCurrentGroup
->Groups()[lIndex
++]->Name();
455 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
458 return GetNextEntry(str
, lIndex
);
461 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
463 if ( uint(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
464 str
= m_pCurrentGroup
->Entries()[lIndex
++]->Name();
471 uint
wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
473 uint n
= m_pCurrentGroup
->Entries().Count();
475 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
476 uint nSubgroups
= m_pCurrentGroup
->Groups().Count();
477 for ( uint nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
478 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
479 n
+= GetNumberOfEntries(TRUE
);
480 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
487 uint
wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
489 uint n
= m_pCurrentGroup
->Groups().Count();
491 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
492 uint nSubgroups
= m_pCurrentGroup
->Groups().Count();
493 for ( uint nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
494 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
495 n
+= GetNumberOfGroups(TRUE
);
496 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
503 // ----------------------------------------------------------------------------
504 // tests for existence
505 // ----------------------------------------------------------------------------
507 bool wxFileConfig::HasGroup(const wxString
& strName
) const
509 PathChanger
path(this, strName
);
511 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
512 return pGroup
!= NULL
;
515 bool wxFileConfig::HasEntry(const wxString
& strName
) const
517 PathChanger
path(this, strName
);
519 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
520 return pEntry
!= NULL
;
523 // ----------------------------------------------------------------------------
525 // ----------------------------------------------------------------------------
527 bool wxFileConfig::Read(wxString
*pstr
,
529 const char *szDefault
) const
531 PathChanger
path(this, szKey
);
533 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
534 if (pEntry
== NULL
) {
535 *pstr
= ExpandEnvVars(szDefault
);
539 *pstr
= ExpandEnvVars(pEntry
->Value());
544 const char *wxFileConfig::Read(const char *szKey
,
545 const char *szDefault
) const
547 static wxString s_str
;
548 Read(&s_str
, szKey
, szDefault
);
550 return s_str
.c_str();
553 bool wxFileConfig::Read(long *pl
, const char *szKey
, long lDefault
) const
556 if ( Read(&str
, szKey
) ) {
566 bool wxFileConfig::Write(const char *szKey
, const char *szValue
)
568 PathChanger
path(this, szKey
);
570 wxString strName
= path
.Name();
571 if ( strName
.IsEmpty() ) {
572 // setting the value of a group is an error
573 wxASSERT_MSG( IsEmpty(szValue
), "can't set value of a group!" );
575 // ... except if it's empty in which case it's a way to force it's creation
576 m_pCurrentGroup
->SetDirty();
578 // this will add a line for this group if it didn't have it before
579 (void)m_pCurrentGroup
->GetGroupLine();
584 // check that the name is reasonable
585 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
586 wxLogError(_("Entry name can't start with '%c'."),
587 wxCONFIG_IMMUTABLE_PREFIX
);
591 for ( const char *pc
= strName
; *pc
!= '\0'; pc
++ ) {
592 if ( !IsValid(*pc
) ) {
593 wxLogError(_("Character '%c' is invalid in a config entry name."),
599 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
600 if ( pEntry
== NULL
)
601 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
603 pEntry
->SetValue(szValue
);
609 bool wxFileConfig::Write(const char *szKey
, long lValue
)
611 // ltoa() is not ANSI :-(
612 char szBuf
[40]; // should be good for sizeof(long) <= 16 (128 bits)
613 sprintf(szBuf
, "%ld", lValue
);
614 return Write(szKey
, szBuf
);
617 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
619 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() )
622 wxTempFile
file(m_strLocalFile
);
624 if ( !file
.IsOpened() ) {
625 wxLogError(_("can't open user configuration file."));
629 // write all strings to file
630 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
631 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
632 wxLogError(_("can't write user configuration file."));
637 return file
.Commit();
640 // ----------------------------------------------------------------------------
641 // delete groups/entries
642 // ----------------------------------------------------------------------------
644 bool wxFileConfig::DeleteEntry(const char *szKey
, bool bGroupIfEmptyAlso
)
646 PathChanger
path(this, szKey
);
648 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
651 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
652 if ( m_pCurrentGroup
!= m_pRootGroup
) {
653 ConfigGroup
*pGroup
= m_pCurrentGroup
;
654 SetPath(".."); // changes m_pCurrentGroup!
655 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
657 //else: never delete the root group
663 bool wxFileConfig::DeleteGroup(const char *szKey
)
665 PathChanger
path(this, szKey
);
667 return m_pCurrentGroup
->DeleteSubgroupByName(path
.Name());
670 bool wxFileConfig::DeleteAll()
674 m_strLocalFile
= m_strGlobalFile
= "";
677 const char *szFile
= m_strLocalFile
;
679 if ( remove(szFile
) == -1 )
680 wxLogSysError(_("can't delete user configuration file '%s'"), szFile
);
682 szFile
= m_strGlobalFile
;
683 if ( remove(szFile
) )
684 wxLogSysError(_("can't delete system configuration file '%s'"), szFile
);
689 // ----------------------------------------------------------------------------
690 // linked list functions
691 // ----------------------------------------------------------------------------
693 // append a new line to the end of the list
694 wxFileConfig::LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
696 LineList
*pLine
= new LineList(str
);
698 if ( m_linesTail
== NULL
) {
704 m_linesTail
->SetNext(pLine
);
705 pLine
->SetPrev(m_linesTail
);
712 // insert a new line after the given one or in the very beginning if !pLine
713 wxFileConfig::LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
716 if ( pLine
== m_linesTail
)
717 return LineListAppend(str
);
719 LineList
*pNewLine
= new LineList(str
);
720 if ( pLine
== NULL
) {
721 // prepend to the list
722 pNewLine
->SetNext(m_linesHead
);
723 m_linesHead
->SetPrev(pNewLine
);
724 m_linesHead
= pNewLine
;
727 // insert before pLine
728 LineList
*pNext
= pLine
->Next();
729 pNewLine
->SetNext(pNext
);
730 pNewLine
->SetPrev(pLine
);
731 pNext
->SetPrev(pNewLine
);
732 pLine
->SetNext(pNewLine
);
738 void wxFileConfig::LineListRemove(LineList
*pLine
)
740 LineList
*pPrev
= pLine
->Prev(),
741 *pNext
= pLine
->Next();
747 pPrev
->SetNext(pNext
);
753 pNext
->SetPrev(pPrev
);
758 bool wxFileConfig::LineListIsEmpty()
760 return m_linesHead
== NULL
;
763 // ============================================================================
764 // wxFileConfig::ConfigGroup
765 // ============================================================================
767 // ----------------------------------------------------------------------------
769 // ----------------------------------------------------------------------------
772 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup
*pParent
,
773 const wxString
& strName
,
774 wxFileConfig
*pConfig
)
775 : m_aEntries(CompareEntries
),
776 m_aSubgroups(CompareGroups
),
788 // dtor deletes all children
789 wxFileConfig::ConfigGroup::~ConfigGroup()
792 uint n
, nCount
= m_aEntries
.Count();
793 for ( n
= 0; n
< nCount
; n
++ )
794 delete m_aEntries
[n
];
797 nCount
= m_aSubgroups
.Count();
798 for ( n
= 0; n
< nCount
; n
++ )
799 delete m_aSubgroups
[n
];
802 // ----------------------------------------------------------------------------
804 // ----------------------------------------------------------------------------
806 void wxFileConfig::ConfigGroup::SetLine(LineList
*pLine
)
808 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
814 This is a bit complicated, so let me explain it in details. All lines that
815 were read from the local file (the only one we will ever modify) are stored
816 in a (doubly) linked list. Our problem is to know at which position in this
817 list should we insert the new entries/subgroups. To solve it we keep three
818 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
820 m_pLine points to the line containing "[group_name]"
821 m_pLastEntry points to the last entry of this group in the local file.
822 m_pLastGroup subgroup
824 Initially, they're NULL all three. When the group (an entry/subgroup) is read
825 from the local file, the corresponding variable is set. However, if the group
826 was read from the global file and then modified or created by the application
827 these variables are still NULL and we need to create the corresponding lines.
828 See the following functions (and comments preceding them) for the details of
831 Also, when our last entry/group are deleted we need to find the new last
832 element - the code in DeleteEntry/Subgroup does this by backtracking the list
833 of lines until it either founds an entry/subgroup (and this is the new last
834 element) or the m_pLine of the group, in which case there are no more entries
835 (or subgroups) left and m_pLast<element> becomes NULL.
837 NB: This last problem could be avoided for entries if we added new entries
838 immediately after m_pLine, but in this case the entries would appear
839 backwards in the config file (OTOH, it's not that important) and as we
840 would still need to do it for the subgroups the code wouldn't have been
841 significantly less complicated.
844 // Return the line which contains "[our name]". If we're still not in the list,
845 // add our line to it immediately after the last line of our parent group if we
846 // have it or in the very beginning if we're the root group.
847 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetGroupLine()
849 if ( m_pLine
== NULL
) {
850 ConfigGroup
*pParent
= Parent();
852 // this group wasn't present in local config file, add it now
853 if ( pParent
!= NULL
) {
854 wxString strFullName
;
855 strFullName
<< "[" << GetFullName().c_str() + 1 << "]"; // +1: no '/'
856 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
857 pParent
->GetLastGroupLine());
858 pParent
->SetLastGroup(this); // we're surely after all the others
861 // we return NULL, so that LineListInsert() will insert us in the
869 // Return the last line belonging to the subgroups of this group (after which
870 // we can add a new subgroup), if we don't have any subgroups or entries our
871 // last line is the group line (m_pLine) itself.
872 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastGroupLine()
874 // if we have any subgroups, our last line is the last line of the last
876 if ( m_pLastGroup
!= NULL
) {
877 wxFileConfig::LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
879 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
883 // no subgroups, so the last line is the line of thelast entry (if any)
884 return GetLastEntryLine();
887 // return the last line belonging to the entries of this group (after which
888 // we can add a new entry), if we don't have any entries we will add the new
889 // one immediately after the group line itself.
890 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastEntryLine()
892 if ( m_pLastEntry
!= NULL
) {
893 wxFileConfig::LineList
*pLine
= m_pLastEntry
->GetLine();
895 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
899 // no entries: insert after the group header
900 return GetGroupLine();
903 // ----------------------------------------------------------------------------
905 // ----------------------------------------------------------------------------
907 wxString
wxFileConfig::ConfigGroup::GetFullName() const
910 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
915 // ----------------------------------------------------------------------------
917 // ----------------------------------------------------------------------------
919 // use binary search because the array is sorted
920 wxFileConfig::ConfigEntry
*
921 wxFileConfig::ConfigGroup::FindEntry(const char *szName
) const
925 hi
= m_aEntries
.Count();
927 wxFileConfig::ConfigEntry
*pEntry
;
931 pEntry
= m_aEntries
[i
];
933 #if wxCONFIG_CASE_SENSITIVE
934 res
= strcmp(pEntry
->Name(), szName
);
936 res
= Stricmp(pEntry
->Name(), szName
);
950 wxFileConfig::ConfigGroup
*
951 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName
) const
955 hi
= m_aSubgroups
.Count();
957 wxFileConfig::ConfigGroup
*pGroup
;
961 pGroup
= m_aSubgroups
[i
];
963 #if wxCONFIG_CASE_SENSITIVE
964 res
= strcmp(pGroup
->Name(), szName
);
966 res
= Stricmp(pGroup
->Name(), szName
);
980 // ----------------------------------------------------------------------------
982 // ----------------------------------------------------------------------------
984 // create a new entry and add it to the current group
985 wxFileConfig::ConfigEntry
*
986 wxFileConfig::ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
988 wxASSERT( FindEntry(strName
) == NULL
);
990 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
991 m_aEntries
.Add(pEntry
);
996 // create a new group and add it to the current group
997 wxFileConfig::ConfigGroup
*
998 wxFileConfig::ConfigGroup::AddSubgroup(const wxString
& strName
)
1000 wxASSERT( FindSubgroup(strName
) == NULL
);
1002 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
1003 m_aSubgroups
.Add(pGroup
);
1008 // ----------------------------------------------------------------------------
1010 // ----------------------------------------------------------------------------
1013 The delete operations are _very_ slow if we delete the last item of this
1014 group (see comments before GetXXXLineXXX functions for more details),
1015 so it's much better to start with the first entry/group if we want to
1016 delete several of them.
1019 bool wxFileConfig::ConfigGroup::DeleteSubgroupByName(const char *szName
)
1021 return DeleteSubgroup(FindSubgroup(szName
));
1024 // doesn't delete the subgroup itself, but does remove references to it from
1025 // all other data structures (and normally the returned pointer should be
1026 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1027 bool wxFileConfig::ConfigGroup::DeleteSubgroup(ConfigGroup
*pGroup
)
1029 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1031 // delete all entries
1032 uint nCount
= pGroup
->m_aEntries
.Count();
1033 for ( uint nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1034 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1035 if ( pLine
!= NULL
)
1036 m_pConfig
->LineListRemove(pLine
);
1039 // and subgroups of this sungroup
1040 nCount
= pGroup
->m_aSubgroups
.Count();
1041 for ( uint nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1042 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[nGroup
]);
1045 LineList
*pLine
= pGroup
->m_pLine
;
1046 if ( pLine
!= NULL
) {
1047 // notice that we may do this test inside the previous "if" because the
1048 // last entry's line is surely !NULL
1049 if ( pGroup
== m_pLastGroup
) {
1050 // our last entry is being deleted - find the last one which stays
1051 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1053 // go back until we find a subgroup or reach the group's line
1054 ConfigGroup
*pNewLast
= NULL
;
1055 uint n
, nSubgroups
= m_aSubgroups
.Count();
1057 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1058 // is it our subgroup?
1059 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1060 // do _not_ call GetGroupLine! we don't want to add it to the local
1061 // file if it's not already there
1062 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1063 pNewLast
= m_aSubgroups
[n
];
1066 if ( pNewLast
!= NULL
) // found?
1070 if ( pl
== m_pLine
) {
1071 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1073 // we've reached the group line without finding any subgroups
1074 m_pLastGroup
= NULL
;
1077 m_pLastGroup
= pNewLast
;
1080 m_pConfig
->LineListRemove(pLine
);
1085 m_aSubgroups
.Remove(pGroup
);
1091 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName
)
1093 ConfigEntry
*pEntry
= FindEntry(szName
);
1094 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1096 LineList
*pLine
= pEntry
->GetLine();
1097 if ( pLine
!= NULL
) {
1098 // notice that we may do this test inside the previous "if" because the
1099 // last entry's line is surely !NULL
1100 if ( pEntry
== m_pLastEntry
) {
1101 // our last entry is being deleted - find the last one which stays
1102 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1104 // go back until we find another entry or reach the group's line
1105 ConfigEntry
*pNewLast
= NULL
;
1106 uint n
, nEntries
= m_aEntries
.Count();
1108 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1109 // is it our subgroup?
1110 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1111 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1112 pNewLast
= m_aEntries
[n
];
1115 if ( pNewLast
!= NULL
) // found?
1119 if ( pl
== m_pLine
) {
1120 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1122 // we've reached the group line without finding any subgroups
1123 m_pLastEntry
= NULL
;
1126 m_pLastEntry
= pNewLast
;
1129 m_pConfig
->LineListRemove(pLine
);
1132 // we must be written back for the changes to be saved
1135 m_aEntries
.Remove(pEntry
);
1141 // ----------------------------------------------------------------------------
1143 // ----------------------------------------------------------------------------
1144 void wxFileConfig::ConfigGroup::SetDirty()
1147 if ( Parent() != NULL
) // propagate upwards
1148 Parent()->SetDirty();
1151 // ============================================================================
1152 // wxFileConfig::ConfigEntry
1153 // ============================================================================
1155 // ----------------------------------------------------------------------------
1157 // ----------------------------------------------------------------------------
1158 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup
*pParent
,
1159 const wxString
& strName
,
1161 : m_strName(strName
)
1163 wxASSERT( !strName
.IsEmpty() );
1165 m_pParent
= pParent
;
1171 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1173 m_strName
.erase(0, 1); // remove first character
1176 // ----------------------------------------------------------------------------
1178 // ----------------------------------------------------------------------------
1180 void wxFileConfig::ConfigEntry::SetLine(LineList
*pLine
)
1182 if ( m_pLine
!= NULL
) {
1183 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1184 Name().c_str(), m_pParent
->GetFullName().c_str());
1188 Group()->SetLastEntry(this);
1191 // second parameter is FALSE if we read the value from file and prevents the
1192 // entry from being marked as 'dirty'
1193 void wxFileConfig::ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1195 if ( bUser
&& IsImmutable() ) {
1196 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1201 // do nothing if it's the same value
1202 if ( strValue
== m_strValue
)
1205 m_strValue
= strValue
;
1208 wxString strVal
= FilterOut(strValue
);
1210 strLine
<< m_strName
<< " = " << strVal
;
1212 if ( m_pLine
!= NULL
) {
1213 // entry was read from the local config file, just modify the line
1214 m_pLine
->SetText(strLine
);
1217 // add a new line to the file
1218 wxASSERT( m_nLine
== NOT_FOUND
); // consistency check
1220 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1221 Group()->GetLastEntryLine());
1222 Group()->SetLastEntry(this);
1229 void wxFileConfig::ConfigEntry::SetDirty()
1232 Group()->SetDirty();
1235 // ============================================================================
1237 // ============================================================================
1239 // ----------------------------------------------------------------------------
1240 // compare functions for array sorting
1241 // ----------------------------------------------------------------------------
1243 int CompareEntries(wxFileConfig::ConfigEntry
*p1
,
1244 wxFileConfig::ConfigEntry
*p2
)
1246 #if wxCONFIG_CASE_SENSITIVE
1247 return strcmp(p1
->Name(), p2
->Name());
1249 return Stricmp(p1
->Name(), p2
->Name());
1253 int CompareGroups(wxFileConfig::ConfigGroup
*p1
,
1254 wxFileConfig::ConfigGroup
*p2
)
1256 #if wxCONFIG_CASE_SENSITIVE
1257 return strcmp(p1
->Name(), p2
->Name());
1259 return Stricmp(p1
->Name(), p2
->Name());
1263 // ----------------------------------------------------------------------------
1265 // ----------------------------------------------------------------------------
1268 wxString
FilterIn(const wxString
& str
)
1271 strResult
.Alloc(str
.Len());
1273 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1275 for ( uint n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1276 if ( str
[n
] == '\\' ) {
1277 switch ( str
[++n
] ) {
1300 if ( str
[n
] != '"' || !bQuoted
)
1301 strResult
+= str
[n
];
1302 else if ( n
!= str
.Len() - 1 ) {
1303 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1306 //else: it's the last quote of a quoted string, ok
1313 // quote the string before writing it to file
1314 wxString
FilterOut(const wxString
& str
)
1317 strResult
.Alloc(str
.Len());
1319 // quoting is necessary to preserve spaces in the beginning of the string
1320 bool bQuote
= isspace(str
[0]) || str
[0] == '"';
1326 for ( uint n
= 0; n
< str
.Len(); n
++ ) {
1347 //else: fall through
1350 strResult
+= str
[n
];
1351 continue; // nothing special to do
1354 // we get here only for special characters
1355 strResult
<< '\\' << c
;