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>
36 #include <wx/dynarray.h>
39 #include <wx/textfile.h>
40 #include <wx/config.h>
41 #include <wx/fileconf.h>
43 // _WINDOWS_ is defined when windows.h is included,
44 // __WXMSW__ is defined for MS Windows compilation
45 #if defined(__WXMSW__) && !defined(_WINDOWS_)
52 // ----------------------------------------------------------------------------
54 // ----------------------------------------------------------------------------
55 #define CONST_CAST ((wxFileConfig *)this)->
57 // ----------------------------------------------------------------------------
58 // global functions declarations
59 // ----------------------------------------------------------------------------
61 // is 'c' a valid character in group name?
62 // NB: wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR must be valid chars,
63 // but _not_ ']' (group name delimiter)
64 inline bool IsValid(char c
) { return isalnum(c
) || strchr("@_/-!.*%", c
); }
66 // compare functions for sorting the arrays
67 static int CompareEntries(wxFileConfig::ConfigEntry
*p1
,
68 wxFileConfig::ConfigEntry
*p2
);
69 static int CompareGroups(wxFileConfig::ConfigGroup
*p1
,
70 wxFileConfig::ConfigGroup
*p2
);
73 static wxString
FilterIn(const wxString
& str
);
74 static wxString
FilterOut(const wxString
& str
);
76 // ============================================================================
78 // ============================================================================
80 // ----------------------------------------------------------------------------
82 // ----------------------------------------------------------------------------
83 wxString
wxFileConfig::GetGlobalDir()
89 #elif defined(__WXSTUBS__)
97 char szWinDir
[_MAX_PATH
];
98 ::GetWindowsDirectory(szWinDir
, _MAX_PATH
);
102 #endif // Unix/Windows
107 wxString
wxFileConfig::GetLocalDir()
112 const char *szHome
= getenv("HOME");
113 if ( szHome
== NULL
) {
115 wxLogWarning(_("can't find user's HOME, using current directory."));
120 strDir
<< '/'; // a double slash is no problem, a missin one yes
123 const char *szHome
= getenv("HOMEDRIVE");
124 if ( szHome
!= NULL
)
126 szHome
= getenv("HOMEPATH");
127 if ( szHome
!= NULL
)
130 // Win16 has no idea about home, so use the current directory instead
138 wxString
wxFileConfig::GetGlobalFileName(const char *szFile
)
140 wxString str
= GetGlobalDir();
143 if ( strchr(szFile
, '.') == NULL
)
153 wxString
wxFileConfig::GetLocalFileName(const char *szFile
)
155 wxString str
= GetLocalDir();
164 if ( strchr(szFile
, '.') == NULL
)
171 // ----------------------------------------------------------------------------
173 // ----------------------------------------------------------------------------
175 void wxFileConfig::Init()
178 m_pRootGroup
= new ConfigGroup(NULL
, "", this);
183 // it's not an error if (one of the) file(s) doesn't exist
185 // parse the global file
186 if ( !m_strGlobalFile
.IsEmpty() && wxFile::Exists(m_strGlobalFile
) ) {
187 wxTextFile
fileGlobal(m_strGlobalFile
);
189 if ( fileGlobal
.Open() ) {
190 Parse(fileGlobal
, FALSE
/* global */);
194 wxLogWarning(_("can't open global configuration file '%s'."),
195 m_strGlobalFile
.c_str());
198 // parse the local file
199 if ( !m_strLocalFile
.IsEmpty() && wxFile::Exists(m_strLocalFile
) ) {
200 wxTextFile
fileLocal(m_strLocalFile
);
201 if ( fileLocal
.Open() ) {
202 Parse(fileLocal
, TRUE
/* local */);
206 wxLogWarning(_("can't open user configuration file '%s'."),
207 m_strLocalFile
.c_str());
211 // constructor supports creation of wxFileConfig objects of any type
212 wxFileConfig::wxFileConfig(const wxString
& appName
, const wxString
& vendorName
,
213 const wxString
& strLocal
, const wxString
& strGlobal
,
215 : wxConfigBase(appName
, vendorName
, strLocal
, strGlobal
, style
),
216 m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
218 // Make up an application name if not supplied
219 if (appName
.IsEmpty() && wxTheApp
)
221 SetAppName(wxTheApp
->GetAppName());
224 // Make up names for files if empty
225 if ( m_strLocalFile
.IsEmpty() && (style
& wxCONFIG_USE_LOCAL_FILE
) )
227 m_strLocalFile
= GetLocalFileName(GetAppName());
230 if ( m_strGlobalFile
.IsEmpty() && (style
& wxCONFIG_USE_GLOBAL_FILE
) )
232 m_strGlobalFile
= GetGlobalFileName(GetAppName());
235 // Check if styles are not supplied, but filenames are, in which case
236 // add the correct styles.
237 if ( !m_strLocalFile
.IsEmpty() )
238 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE
);
240 if ( !m_strGlobalFile
.IsEmpty() )
241 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE
);
243 // if the path is not absolute, prepend the standard directory to it
244 if ( !m_strLocalFile
.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile
) )
246 wxString strLocal
= m_strLocalFile
;
247 m_strLocalFile
= GetLocalDir();
248 m_strLocalFile
<< strLocal
;
251 if ( !m_strGlobalFile
.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile
) )
253 wxString strGlobal
= m_strGlobalFile
;
254 m_strGlobalFile
= GetGlobalDir();
255 m_strGlobalFile
<< strGlobal
;
261 void wxFileConfig::CleanUp()
265 LineList
*pCur
= m_linesHead
;
266 while ( pCur
!= NULL
) {
267 LineList
*pNext
= pCur
->Next();
273 wxFileConfig::~wxFileConfig()
280 // ----------------------------------------------------------------------------
281 // parse a config file
282 // ----------------------------------------------------------------------------
284 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
290 size_t nLineCount
= file
.GetLineCount();
291 for ( size_t n
= 0; n
< nLineCount
; n
++ ) {
294 // add the line to linked list
296 LineListAppend(strLine
);
298 // skip leading spaces
299 for ( pStart
= strLine
; isspace(*pStart
); pStart
++ )
302 // skip blank/comment lines
303 if ( *pStart
== '\0'|| *pStart
== ';' || *pStart
== '#' )
306 if ( *pStart
== '[' ) { // a new group
309 while ( *++pEnd
!= ']' ) {
310 if ( !IsValid(*pEnd
) && *pEnd
!= ' ' ) // allow spaces in group names
314 if ( *pEnd
!= ']' ) {
315 wxLogError(_("file '%s': unexpected character %c at line %d."),
316 file
.GetName(), *pEnd
, n
+ 1);
317 continue; // skip this line
320 // group name here is always considered as abs path
323 strGroup
<< wxCONFIG_PATH_SEPARATOR
<< wxString(pStart
, pEnd
- pStart
);
325 // will create it if doesn't yet exist
329 m_pCurrentGroup
->SetLine(m_linesTail
);
331 // check that there is nothing except comments left on this line
333 while ( *++pEnd
!= '\0' && bCont
) {
342 // ignore whitespace ('\n' impossible here)
346 wxLogWarning(_("file '%s', line %d: '%s' "
347 "ignored after group header."),
348 file
.GetName(), n
+ 1, pEnd
);
354 const char *pEnd
= pStart
;
355 while ( IsValid(*pEnd
) )
358 wxString
strKey(pStart
, pEnd
);
361 while ( isspace(*pEnd
) )
364 if ( *pEnd
++ != '=' ) {
365 wxLogError(_("file '%s', line %d: '=' expected."),
366 file
.GetName(), n
+ 1);
369 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
371 if ( pEntry
== NULL
) {
373 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
376 pEntry
->SetLine(m_linesTail
);
379 if ( bLocal
&& pEntry
->IsImmutable() ) {
380 // immutable keys can't be changed by user
381 wxLogWarning(_("file '%s', line %d: value for "
382 "immutable key '%s' ignored."),
383 file
.GetName(), n
+ 1, strKey
.c_str());
386 // the condition below catches the cases (a) and (b) but not (c):
387 // (a) global key found second time in global file
388 // (b) key found second (or more) time in local file
389 // (c) key from global file now found in local one
390 // which is exactly what we want.
391 else if ( !bLocal
|| pEntry
->IsLocal() ) {
392 wxLogWarning(_("file '%s', line %d: key '%s' was first "
393 "found at line %d."),
394 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
397 pEntry
->SetLine(m_linesTail
);
402 while ( isspace(*pEnd
) )
405 pEntry
->SetValue(FilterIn(pEnd
), FALSE
/* read from file */);
411 // ----------------------------------------------------------------------------
413 // ----------------------------------------------------------------------------
415 void wxFileConfig::SetRootPath()
418 m_pCurrentGroup
= m_pRootGroup
;
421 void wxFileConfig::SetPath(const wxString
& strPath
)
423 wxArrayString aParts
;
425 if ( strPath
.IsEmpty() ) {
430 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
432 wxSplitPath(aParts
, strPath
);
435 // relative path, combine with current one
436 wxString strFullPath
= m_strPath
;
437 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
438 wxSplitPath(aParts
, strFullPath
);
441 // change current group
443 m_pCurrentGroup
= m_pRootGroup
;
444 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
445 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
446 if ( pNextGroup
== NULL
)
447 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
448 m_pCurrentGroup
= pNextGroup
;
451 // recombine path parts in one variable
453 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
454 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
458 // ----------------------------------------------------------------------------
460 // ----------------------------------------------------------------------------
462 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
465 return GetNextGroup(str
, lIndex
);
468 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
470 if ( size_t(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
471 str
= m_pCurrentGroup
->Groups()[lIndex
++]->Name();
478 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
481 return GetNextEntry(str
, lIndex
);
484 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
486 if ( size_t(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
487 str
= m_pCurrentGroup
->Entries()[lIndex
++]->Name();
494 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
496 size_t n
= m_pCurrentGroup
->Entries().Count();
498 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
499 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
500 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
501 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
502 n
+= GetNumberOfEntries(TRUE
);
503 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
510 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
512 size_t n
= m_pCurrentGroup
->Groups().Count();
514 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
515 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
516 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
517 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
518 n
+= GetNumberOfGroups(TRUE
);
519 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
526 // ----------------------------------------------------------------------------
527 // tests for existence
528 // ----------------------------------------------------------------------------
530 bool wxFileConfig::HasGroup(const wxString
& strName
) const
532 wxConfigPathChanger
path(this, strName
);
534 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
535 return pGroup
!= NULL
;
538 bool wxFileConfig::HasEntry(const wxString
& strName
) const
540 wxConfigPathChanger
path(this, strName
);
542 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
543 return pEntry
!= NULL
;
546 // ----------------------------------------------------------------------------
548 // ----------------------------------------------------------------------------
550 bool wxFileConfig::Read(const wxString
& key
,
551 wxString
* pStr
) const
553 wxConfigPathChanger
path(this, key
);
555 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
556 if (pEntry
== NULL
) {
560 *pStr
= ExpandEnvVars(pEntry
->Value());
565 bool wxFileConfig::Read(const wxString
& key
,
566 wxString
* pStr
, const wxString
& defVal
) const
568 wxConfigPathChanger
path(this, key
);
570 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
571 if (pEntry
== NULL
) {
572 if( IsRecordingDefaults() )
573 ((wxFileConfig
*)this)->Write(key
,defVal
);
574 *pStr
= ExpandEnvVars(defVal
);
578 *pStr
= ExpandEnvVars(pEntry
->Value());
583 bool wxFileConfig::Read(const wxString
& key
, long *pl
) const
586 if ( Read(key
, & str
) ) {
595 bool wxFileConfig::Write(const wxString
& key
, const wxString
& szValue
)
597 wxConfigPathChanger
path(this, key
);
599 wxString strName
= path
.Name();
600 if ( strName
.IsEmpty() ) {
601 // setting the value of a group is an error
602 wxASSERT_MSG( IsEmpty(szValue
), _("can't set value of a group!") );
604 // ... except if it's empty in which case it's a way to force it's creation
605 m_pCurrentGroup
->SetDirty();
607 // this will add a line for this group if it didn't have it before
608 (void)m_pCurrentGroup
->GetGroupLine();
613 // check that the name is reasonable
614 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
615 wxLogError(_("Entry name can't start with '%c'."),
616 wxCONFIG_IMMUTABLE_PREFIX
);
620 for ( const char *pc
= strName
; *pc
!= '\0'; pc
++ ) {
621 if ( !IsValid(*pc
) ) {
622 wxLogError(_("Character '%c' is invalid in a config entry name."),
628 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
629 if ( pEntry
== NULL
)
630 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
632 pEntry
->SetValue(szValue
);
638 bool wxFileConfig::Write(const wxString
& key
, long lValue
)
640 // ltoa() is not ANSI :-(
641 char szBuf
[40]; // should be good for sizeof(long) <= 16 (128 bits)
642 sprintf(szBuf
, "%ld", lValue
);
643 return Write(key
, szBuf
);
646 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
648 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() )
651 wxTempFile
file(m_strLocalFile
);
653 if ( !file
.IsOpened() ) {
654 wxLogError(_("can't open user configuration file."));
658 // write all strings to file
659 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
660 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
661 wxLogError(_("can't write user configuration file."));
666 return file
.Commit();
669 // ----------------------------------------------------------------------------
670 // delete groups/entries
671 // ----------------------------------------------------------------------------
673 bool wxFileConfig::DeleteEntry(const wxString
& key
, bool bGroupIfEmptyAlso
)
675 wxConfigPathChanger
path(this, key
);
677 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
680 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
681 if ( m_pCurrentGroup
!= m_pRootGroup
) {
682 ConfigGroup
*pGroup
= m_pCurrentGroup
;
683 SetPath(".."); // changes m_pCurrentGroup!
684 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
686 //else: never delete the root group
692 bool wxFileConfig::DeleteGroup(const wxString
& key
)
694 wxConfigPathChanger
path(this, key
);
696 return m_pCurrentGroup
->DeleteSubgroupByName(path
.Name());
699 bool wxFileConfig::DeleteAll()
703 const char *szFile
= m_strLocalFile
;
705 if ( remove(szFile
) == -1 )
706 wxLogSysError(_("can't delete user configuration file '%s'"), szFile
);
708 m_strLocalFile
= m_strGlobalFile
= "";
714 // ----------------------------------------------------------------------------
715 // linked list functions
716 // ----------------------------------------------------------------------------
718 // append a new line to the end of the list
719 wxFileConfig::LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
721 LineList
*pLine
= new LineList(str
);
723 if ( m_linesTail
== NULL
) {
729 m_linesTail
->SetNext(pLine
);
730 pLine
->SetPrev(m_linesTail
);
737 // insert a new line after the given one or in the very beginning if !pLine
738 wxFileConfig::LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
741 if ( pLine
== m_linesTail
)
742 return LineListAppend(str
);
744 LineList
*pNewLine
= new LineList(str
);
745 if ( pLine
== NULL
) {
746 // prepend to the list
747 pNewLine
->SetNext(m_linesHead
);
748 m_linesHead
->SetPrev(pNewLine
);
749 m_linesHead
= pNewLine
;
752 // insert before pLine
753 LineList
*pNext
= pLine
->Next();
754 pNewLine
->SetNext(pNext
);
755 pNewLine
->SetPrev(pLine
);
756 pNext
->SetPrev(pNewLine
);
757 pLine
->SetNext(pNewLine
);
763 void wxFileConfig::LineListRemove(LineList
*pLine
)
765 LineList
*pPrev
= pLine
->Prev(),
766 *pNext
= pLine
->Next();
772 pPrev
->SetNext(pNext
);
778 pNext
->SetPrev(pPrev
);
783 bool wxFileConfig::LineListIsEmpty()
785 return m_linesHead
== NULL
;
788 // ============================================================================
789 // wxFileConfig::ConfigGroup
790 // ============================================================================
792 // ----------------------------------------------------------------------------
794 // ----------------------------------------------------------------------------
797 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup
*pParent
,
798 const wxString
& strName
,
799 wxFileConfig
*pConfig
)
800 : m_aEntries(CompareEntries
),
801 m_aSubgroups(CompareGroups
),
813 // dtor deletes all children
814 wxFileConfig::ConfigGroup::~ConfigGroup()
817 size_t n
, nCount
= m_aEntries
.Count();
818 for ( n
= 0; n
< nCount
; n
++ )
819 delete m_aEntries
[n
];
822 nCount
= m_aSubgroups
.Count();
823 for ( n
= 0; n
< nCount
; n
++ )
824 delete m_aSubgroups
[n
];
827 // ----------------------------------------------------------------------------
829 // ----------------------------------------------------------------------------
831 void wxFileConfig::ConfigGroup::SetLine(LineList
*pLine
)
833 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
839 This is a bit complicated, so let me explain it in details. All lines that
840 were read from the local file (the only one we will ever modify) are stored
841 in a (doubly) linked list. Our problem is to know at which position in this
842 list should we insert the new entries/subgroups. To solve it we keep three
843 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
845 m_pLine points to the line containing "[group_name]"
846 m_pLastEntry points to the last entry of this group in the local file.
847 m_pLastGroup subgroup
849 Initially, they're NULL all three. When the group (an entry/subgroup) is read
850 from the local file, the corresponding variable is set. However, if the group
851 was read from the global file and then modified or created by the application
852 these variables are still NULL and we need to create the corresponding lines.
853 See the following functions (and comments preceding them) for the details of
856 Also, when our last entry/group are deleted we need to find the new last
857 element - the code in DeleteEntry/Subgroup does this by backtracking the list
858 of lines until it either founds an entry/subgroup (and this is the new last
859 element) or the m_pLine of the group, in which case there are no more entries
860 (or subgroups) left and m_pLast<element> becomes NULL.
862 NB: This last problem could be avoided for entries if we added new entries
863 immediately after m_pLine, but in this case the entries would appear
864 backwards in the config file (OTOH, it's not that important) and as we
865 would still need to do it for the subgroups the code wouldn't have been
866 significantly less complicated.
869 // Return the line which contains "[our name]". If we're still not in the list,
870 // add our line to it immediately after the last line of our parent group if we
871 // have it or in the very beginning if we're the root group.
872 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetGroupLine()
874 if ( m_pLine
== NULL
) {
875 ConfigGroup
*pParent
= Parent();
877 // this group wasn't present in local config file, add it now
878 if ( pParent
!= NULL
) {
879 wxString strFullName
;
880 strFullName
<< "[" << GetFullName().c_str() + 1 << "]"; // +1: no '/'
881 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
882 pParent
->GetLastGroupLine());
883 pParent
->SetLastGroup(this); // we're surely after all the others
886 // we return NULL, so that LineListInsert() will insert us in the
894 // Return the last line belonging to the subgroups of this group (after which
895 // we can add a new subgroup), if we don't have any subgroups or entries our
896 // last line is the group line (m_pLine) itself.
897 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastGroupLine()
899 // if we have any subgroups, our last line is the last line of the last
901 if ( m_pLastGroup
!= NULL
) {
902 wxFileConfig::LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
904 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
908 // no subgroups, so the last line is the line of thelast entry (if any)
909 return GetLastEntryLine();
912 // return the last line belonging to the entries of this group (after which
913 // we can add a new entry), if we don't have any entries we will add the new
914 // one immediately after the group line itself.
915 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastEntryLine()
917 if ( m_pLastEntry
!= NULL
) {
918 wxFileConfig::LineList
*pLine
= m_pLastEntry
->GetLine();
920 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
924 // no entries: insert after the group header
925 return GetGroupLine();
928 // ----------------------------------------------------------------------------
930 // ----------------------------------------------------------------------------
932 wxString
wxFileConfig::ConfigGroup::GetFullName() const
935 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
940 // ----------------------------------------------------------------------------
942 // ----------------------------------------------------------------------------
944 // use binary search because the array is sorted
945 wxFileConfig::ConfigEntry
*
946 wxFileConfig::ConfigGroup::FindEntry(const char *szName
) const
950 hi
= m_aEntries
.Count();
952 wxFileConfig::ConfigEntry
*pEntry
;
956 pEntry
= m_aEntries
[i
];
958 #if wxCONFIG_CASE_SENSITIVE
959 res
= strcmp(pEntry
->Name(), szName
);
961 res
= Stricmp(pEntry
->Name(), szName
);
975 wxFileConfig::ConfigGroup
*
976 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName
) const
980 hi
= m_aSubgroups
.Count();
982 wxFileConfig::ConfigGroup
*pGroup
;
986 pGroup
= m_aSubgroups
[i
];
988 #if wxCONFIG_CASE_SENSITIVE
989 res
= strcmp(pGroup
->Name(), szName
);
991 res
= Stricmp(pGroup
->Name(), szName
);
1005 // ----------------------------------------------------------------------------
1006 // create a new item
1007 // ----------------------------------------------------------------------------
1009 // create a new entry and add it to the current group
1010 wxFileConfig::ConfigEntry
*
1011 wxFileConfig::ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
1013 wxASSERT( FindEntry(strName
) == NULL
);
1015 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
1016 m_aEntries
.Add(pEntry
);
1021 // create a new group and add it to the current group
1022 wxFileConfig::ConfigGroup
*
1023 wxFileConfig::ConfigGroup::AddSubgroup(const wxString
& strName
)
1025 wxASSERT( FindSubgroup(strName
) == NULL
);
1027 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
1028 m_aSubgroups
.Add(pGroup
);
1033 // ----------------------------------------------------------------------------
1035 // ----------------------------------------------------------------------------
1038 The delete operations are _very_ slow if we delete the last item of this
1039 group (see comments before GetXXXLineXXX functions for more details),
1040 so it's much better to start with the first entry/group if we want to
1041 delete several of them.
1044 bool wxFileConfig::ConfigGroup::DeleteSubgroupByName(const char *szName
)
1046 return DeleteSubgroup(FindSubgroup(szName
));
1049 // doesn't delete the subgroup itself, but does remove references to it from
1050 // all other data structures (and normally the returned pointer should be
1051 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1052 bool wxFileConfig::ConfigGroup::DeleteSubgroup(ConfigGroup
*pGroup
)
1054 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1056 // delete all entries
1057 size_t nCount
= pGroup
->m_aEntries
.Count();
1058 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1059 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1060 if ( pLine
!= NULL
)
1061 m_pConfig
->LineListRemove(pLine
);
1064 // and subgroups of this sungroup
1065 nCount
= pGroup
->m_aSubgroups
.Count();
1066 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1067 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[nGroup
]);
1070 LineList
*pLine
= pGroup
->m_pLine
;
1071 if ( pLine
!= NULL
) {
1072 // notice that we may do this test inside the previous "if" because the
1073 // last entry's line is surely !NULL
1074 if ( pGroup
== m_pLastGroup
) {
1075 // our last entry is being deleted - find the last one which stays
1076 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1078 // go back until we find a subgroup or reach the group's line
1079 ConfigGroup
*pNewLast
= NULL
;
1080 size_t n
, nSubgroups
= m_aSubgroups
.Count();
1082 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1083 // is it our subgroup?
1084 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1085 // do _not_ call GetGroupLine! we don't want to add it to the local
1086 // file if it's not already there
1087 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1088 pNewLast
= m_aSubgroups
[n
];
1091 if ( pNewLast
!= NULL
) // found?
1095 if ( pl
== m_pLine
) {
1096 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1098 // we've reached the group line without finding any subgroups
1099 m_pLastGroup
= NULL
;
1102 m_pLastGroup
= pNewLast
;
1105 m_pConfig
->LineListRemove(pLine
);
1110 m_aSubgroups
.Remove(pGroup
);
1116 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName
)
1118 ConfigEntry
*pEntry
= FindEntry(szName
);
1119 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1121 LineList
*pLine
= pEntry
->GetLine();
1122 if ( pLine
!= NULL
) {
1123 // notice that we may do this test inside the previous "if" because the
1124 // last entry's line is surely !NULL
1125 if ( pEntry
== m_pLastEntry
) {
1126 // our last entry is being deleted - find the last one which stays
1127 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1129 // go back until we find another entry or reach the group's line
1130 ConfigEntry
*pNewLast
= NULL
;
1131 size_t n
, nEntries
= m_aEntries
.Count();
1133 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1134 // is it our subgroup?
1135 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1136 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1137 pNewLast
= m_aEntries
[n
];
1140 if ( pNewLast
!= NULL
) // found?
1144 if ( pl
== m_pLine
) {
1145 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1147 // we've reached the group line without finding any subgroups
1148 m_pLastEntry
= NULL
;
1151 m_pLastEntry
= pNewLast
;
1154 m_pConfig
->LineListRemove(pLine
);
1157 // we must be written back for the changes to be saved
1160 m_aEntries
.Remove(pEntry
);
1166 // ----------------------------------------------------------------------------
1168 // ----------------------------------------------------------------------------
1169 void wxFileConfig::ConfigGroup::SetDirty()
1172 if ( Parent() != NULL
) // propagate upwards
1173 Parent()->SetDirty();
1176 // ============================================================================
1177 // wxFileConfig::ConfigEntry
1178 // ============================================================================
1180 // ----------------------------------------------------------------------------
1182 // ----------------------------------------------------------------------------
1183 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup
*pParent
,
1184 const wxString
& strName
,
1186 : m_strName(strName
)
1188 wxASSERT( !strName
.IsEmpty() );
1190 m_pParent
= pParent
;
1196 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1198 m_strName
.erase(0, 1); // remove first character
1201 // ----------------------------------------------------------------------------
1203 // ----------------------------------------------------------------------------
1205 void wxFileConfig::ConfigEntry::SetLine(LineList
*pLine
)
1207 if ( m_pLine
!= NULL
) {
1208 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1209 Name().c_str(), m_pParent
->GetFullName().c_str());
1213 Group()->SetLastEntry(this);
1216 // second parameter is FALSE if we read the value from file and prevents the
1217 // entry from being marked as 'dirty'
1218 void wxFileConfig::ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1220 if ( bUser
&& IsImmutable() ) {
1221 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1226 // do nothing if it's the same value
1227 if ( strValue
== m_strValue
)
1230 m_strValue
= strValue
;
1233 wxString strVal
= FilterOut(strValue
);
1235 strLine
<< m_strName
<< " = " << strVal
;
1237 if ( m_pLine
!= NULL
) {
1238 // entry was read from the local config file, just modify the line
1239 m_pLine
->SetText(strLine
);
1242 // add a new line to the file
1243 wxASSERT( m_nLine
== NOT_FOUND
); // consistency check
1245 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1246 Group()->GetLastEntryLine());
1247 Group()->SetLastEntry(this);
1254 void wxFileConfig::ConfigEntry::SetDirty()
1257 Group()->SetDirty();
1260 // ============================================================================
1262 // ============================================================================
1264 // ----------------------------------------------------------------------------
1265 // compare functions for array sorting
1266 // ----------------------------------------------------------------------------
1268 int CompareEntries(wxFileConfig::ConfigEntry
*p1
,
1269 wxFileConfig::ConfigEntry
*p2
)
1271 #if wxCONFIG_CASE_SENSITIVE
1272 return strcmp(p1
->Name(), p2
->Name());
1274 return Stricmp(p1
->Name(), p2
->Name());
1278 int CompareGroups(wxFileConfig::ConfigGroup
*p1
,
1279 wxFileConfig::ConfigGroup
*p2
)
1281 #if wxCONFIG_CASE_SENSITIVE
1282 return strcmp(p1
->Name(), p2
->Name());
1284 return Stricmp(p1
->Name(), p2
->Name());
1288 // ----------------------------------------------------------------------------
1290 // ----------------------------------------------------------------------------
1293 wxString
FilterIn(const wxString
& str
)
1296 strResult
.Alloc(str
.Len());
1298 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1300 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1301 if ( str
[n
] == '\\' ) {
1302 switch ( str
[++n
] ) {
1325 if ( str
[n
] != '"' || !bQuoted
)
1326 strResult
+= str
[n
];
1327 else if ( n
!= str
.Len() - 1 ) {
1328 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1331 //else: it's the last quote of a quoted string, ok
1338 // quote the string before writing it to file
1339 wxString
FilterOut(const wxString
& str
)
1345 strResult
.Alloc(str
.Len());
1347 // quoting is necessary to preserve spaces in the beginning of the string
1348 bool bQuote
= isspace(str
[0]) || str
[0] == '"';
1354 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
1377 //else: fall through
1380 strResult
+= str
[n
];
1381 continue; // nothing special to do
1384 // we get here only for special characters
1385 strResult
<< '\\' << c
;