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 // ----------------------------------------------------------------------------
20 #include "wx/wxprec.h"
22 #include "wx/fileconf.h"
31 #include "wx/string.h"
36 #include "wx/dynarray.h"
39 #include "wx/textfile.h"
40 #include "wx/config.h"
42 #include "wx/utils.h" // for wxGetHomeDir
44 // _WINDOWS_ is defined when windows.h is included,
45 // __WXMSW__ is defined for MS Windows compilation
46 #if defined(__WXMSW__) && !defined(_WINDOWS_)
53 // ----------------------------------------------------------------------------
55 // ----------------------------------------------------------------------------
56 #define CONST_CAST ((wxFileConfig *)this)->
58 // ----------------------------------------------------------------------------
60 // ----------------------------------------------------------------------------
65 // ----------------------------------------------------------------------------
66 // global functions declarations
67 // ----------------------------------------------------------------------------
69 // is 'c' a valid character in group name?
70 // NB: wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR must be valid chars,
71 // but _not_ ']' (group name delimiter)
72 inline bool IsValid(char c
) { return isalnum(c
) || strchr("@_/-!.*%", c
); }
74 // compare functions for sorting the arrays
75 static int CompareEntries(wxFileConfig::ConfigEntry
*p1
,
76 wxFileConfig::ConfigEntry
*p2
);
77 static int CompareGroups(wxFileConfig::ConfigGroup
*p1
,
78 wxFileConfig::ConfigGroup
*p2
);
81 static wxString
FilterIn(const wxString
& str
);
82 static wxString
FilterOut(const wxString
& str
);
84 // ============================================================================
86 // ============================================================================
88 // ----------------------------------------------------------------------------
90 // ----------------------------------------------------------------------------
91 wxString
wxFileConfig::GetGlobalDir()
97 #elif defined(__WXSTUBS__)
98 wxASSERT_MSG( FALSE
, "TODO" ) ;
99 #elif defined(__WXMAC__)
100 wxASSERT_MSG( FALSE
, "TODO" ) ;
102 char szWinDir
[MAX_PATH
];
103 ::GetWindowsDirectory(szWinDir
, MAX_PATH
);
107 #endif // Unix/Windows
112 wxString
wxFileConfig::GetLocalDir()
116 wxGetHomeDir(&strDir
);
119 if (strDir
.Last() != '/') strDir
<< '/';
121 if (strDir
.Last() != '\\') strDir
<< '\\';
127 wxString
wxFileConfig::GetGlobalFileName(const char *szFile
)
129 wxString str
= GetGlobalDir();
132 if ( strchr(szFile
, '.') == NULL
)
142 wxString
wxFileConfig::GetLocalFileName(const char *szFile
)
144 wxString str
= GetLocalDir();
153 if ( strchr(szFile
, '.') == NULL
)
160 // ----------------------------------------------------------------------------
162 // ----------------------------------------------------------------------------
164 void wxFileConfig::Init()
167 m_pRootGroup
= new ConfigGroup(NULL
, "", this);
172 // it's not an error if (one of the) file(s) doesn't exist
174 // parse the global file
175 if ( !m_strGlobalFile
.IsEmpty() && wxFile::Exists(m_strGlobalFile
) ) {
176 wxTextFile
fileGlobal(m_strGlobalFile
);
178 if ( fileGlobal
.Open() ) {
179 Parse(fileGlobal
, FALSE
/* global */);
183 wxLogWarning(_("can't open global configuration file '%s'."),
184 m_strGlobalFile
.c_str());
187 // parse the local file
188 if ( !m_strLocalFile
.IsEmpty() && wxFile::Exists(m_strLocalFile
) ) {
189 wxTextFile
fileLocal(m_strLocalFile
);
190 if ( fileLocal
.Open() ) {
191 Parse(fileLocal
, TRUE
/* local */);
195 wxLogWarning(_("can't open user configuration file '%s'."),
196 m_strLocalFile
.c_str());
200 // constructor supports creation of wxFileConfig objects of any type
201 wxFileConfig::wxFileConfig(const wxString
& appName
, const wxString
& vendorName
,
202 const wxString
& strLocal
, const wxString
& strGlobal
,
204 : wxConfigBase(appName
, vendorName
, strLocal
, strGlobal
, style
),
205 m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
207 // Make up an application name if not supplied
208 if (appName
.IsEmpty() && wxTheApp
)
210 SetAppName(wxTheApp
->GetAppName());
213 // Make up names for files if empty
214 if ( m_strLocalFile
.IsEmpty() && (style
& wxCONFIG_USE_LOCAL_FILE
) )
216 m_strLocalFile
= GetLocalFileName(GetAppName());
219 if ( m_strGlobalFile
.IsEmpty() && (style
& wxCONFIG_USE_GLOBAL_FILE
) )
221 m_strGlobalFile
= GetGlobalFileName(GetAppName());
224 // Check if styles are not supplied, but filenames are, in which case
225 // add the correct styles.
226 if ( !m_strLocalFile
.IsEmpty() )
227 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE
);
229 if ( !m_strGlobalFile
.IsEmpty() )
230 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE
);
232 // if the path is not absolute, prepend the standard directory to it
233 if ( !m_strLocalFile
.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile
) )
235 wxString strLocal
= m_strLocalFile
;
236 m_strLocalFile
= GetLocalDir();
237 m_strLocalFile
<< strLocal
;
240 if ( !m_strGlobalFile
.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile
) )
242 wxString strGlobal
= m_strGlobalFile
;
243 m_strGlobalFile
= GetGlobalDir();
244 m_strGlobalFile
<< strGlobal
;
250 void wxFileConfig::CleanUp()
254 LineList
*pCur
= m_linesHead
;
255 while ( pCur
!= NULL
) {
256 LineList
*pNext
= pCur
->Next();
262 wxFileConfig::~wxFileConfig()
269 // ----------------------------------------------------------------------------
270 // parse a config file
271 // ----------------------------------------------------------------------------
273 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
279 size_t nLineCount
= file
.GetLineCount();
280 for ( size_t n
= 0; n
< nLineCount
; n
++ ) {
283 // add the line to linked list
285 LineListAppend(strLine
);
287 // skip leading spaces
288 for ( pStart
= strLine
; isspace(*pStart
); pStart
++ )
291 // skip blank/comment lines
292 if ( *pStart
== '\0'|| *pStart
== ';' || *pStart
== '#' )
295 if ( *pStart
== '[' ) { // a new group
298 while ( *++pEnd
!= ']' ) {
299 if ( !IsValid(*pEnd
) && *pEnd
!= ' ' ) // allow spaces in group names
303 if ( *pEnd
!= ']' ) {
304 wxLogError(_("file '%s': unexpected character %c at line %d."),
305 file
.GetName(), *pEnd
, n
+ 1);
306 continue; // skip this line
309 // group name here is always considered as abs path
312 strGroup
<< wxCONFIG_PATH_SEPARATOR
<< wxString(pStart
, pEnd
- pStart
);
314 // will create it if doesn't yet exist
318 m_pCurrentGroup
->SetLine(m_linesTail
);
320 // check that there is nothing except comments left on this line
322 while ( *++pEnd
!= '\0' && bCont
) {
331 // ignore whitespace ('\n' impossible here)
335 wxLogWarning(_("file '%s', line %d: '%s' "
336 "ignored after group header."),
337 file
.GetName(), n
+ 1, pEnd
);
343 const char *pEnd
= pStart
;
344 while ( IsValid(*pEnd
) )
347 wxString
strKey(pStart
, pEnd
);
350 while ( isspace(*pEnd
) )
353 if ( *pEnd
++ != '=' ) {
354 wxLogError(_("file '%s', line %d: '=' expected."),
355 file
.GetName(), n
+ 1);
358 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
360 if ( pEntry
== NULL
) {
362 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
365 pEntry
->SetLine(m_linesTail
);
368 if ( bLocal
&& pEntry
->IsImmutable() ) {
369 // immutable keys can't be changed by user
370 wxLogWarning(_("file '%s', line %d: value for "
371 "immutable key '%s' ignored."),
372 file
.GetName(), n
+ 1, strKey
.c_str());
375 // the condition below catches the cases (a) and (b) but not (c):
376 // (a) global key found second time in global file
377 // (b) key found second (or more) time in local file
378 // (c) key from global file now found in local one
379 // which is exactly what we want.
380 else if ( !bLocal
|| pEntry
->IsLocal() ) {
381 wxLogWarning(_("file '%s', line %d: key '%s' was first "
382 "found at line %d."),
383 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
386 pEntry
->SetLine(m_linesTail
);
391 while ( isspace(*pEnd
) )
394 pEntry
->SetValue(FilterIn(pEnd
), FALSE
/* read from file */);
400 // ----------------------------------------------------------------------------
402 // ----------------------------------------------------------------------------
404 void wxFileConfig::SetRootPath()
407 m_pCurrentGroup
= m_pRootGroup
;
410 void wxFileConfig::SetPath(const wxString
& strPath
)
412 wxArrayString aParts
;
414 if ( strPath
.IsEmpty() ) {
419 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
421 wxSplitPath(aParts
, strPath
);
424 // relative path, combine with current one
425 wxString strFullPath
= m_strPath
;
426 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
427 wxSplitPath(aParts
, strFullPath
);
430 // change current group
432 m_pCurrentGroup
= m_pRootGroup
;
433 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
434 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
435 if ( pNextGroup
== NULL
)
436 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
437 m_pCurrentGroup
= pNextGroup
;
440 // recombine path parts in one variable
442 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
443 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
447 // ----------------------------------------------------------------------------
449 // ----------------------------------------------------------------------------
451 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
454 return GetNextGroup(str
, lIndex
);
457 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
459 if ( size_t(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
460 str
= m_pCurrentGroup
->Groups()[lIndex
++]->Name();
467 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
470 return GetNextEntry(str
, lIndex
);
473 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
475 if ( size_t(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
476 str
= m_pCurrentGroup
->Entries()[lIndex
++]->Name();
483 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
485 size_t n
= m_pCurrentGroup
->Entries().Count();
487 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
488 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
489 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
490 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
491 n
+= GetNumberOfEntries(TRUE
);
492 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
499 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
501 size_t n
= m_pCurrentGroup
->Groups().Count();
503 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
504 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
505 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
506 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
507 n
+= GetNumberOfGroups(TRUE
);
508 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
515 // ----------------------------------------------------------------------------
516 // tests for existence
517 // ----------------------------------------------------------------------------
519 bool wxFileConfig::HasGroup(const wxString
& strName
) const
521 wxConfigPathChanger
path(this, strName
);
523 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
524 return pGroup
!= NULL
;
527 bool wxFileConfig::HasEntry(const wxString
& strName
) const
529 wxConfigPathChanger
path(this, strName
);
531 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
532 return pEntry
!= NULL
;
535 // ----------------------------------------------------------------------------
537 // ----------------------------------------------------------------------------
539 bool wxFileConfig::Read(const wxString
& key
,
540 wxString
* pStr
) const
542 wxConfigPathChanger
path(this, key
);
544 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
545 if (pEntry
== NULL
) {
549 *pStr
= ExpandEnvVars(pEntry
->Value());
554 bool wxFileConfig::Read(const wxString
& key
,
555 wxString
* pStr
, const wxString
& defVal
) const
557 wxConfigPathChanger
path(this, key
);
559 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
560 if (pEntry
== NULL
) {
561 if( IsRecordingDefaults() )
562 ((wxFileConfig
*)this)->Write(key
,defVal
);
563 *pStr
= ExpandEnvVars(defVal
);
567 *pStr
= ExpandEnvVars(pEntry
->Value());
572 bool wxFileConfig::Read(const wxString
& key
, long *pl
) const
575 if ( Read(key
, & str
) ) {
584 bool wxFileConfig::Write(const wxString
& key
, const wxString
& szValue
)
586 wxConfigPathChanger
path(this, key
);
588 wxString strName
= path
.Name();
589 if ( strName
.IsEmpty() ) {
590 // setting the value of a group is an error
591 wxASSERT_MSG( IsEmpty(szValue
), _("can't set value of a group!") );
593 // ... except if it's empty in which case it's a way to force it's creation
594 m_pCurrentGroup
->SetDirty();
596 // this will add a line for this group if it didn't have it before
597 (void)m_pCurrentGroup
->GetGroupLine();
602 // check that the name is reasonable
603 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
604 wxLogError(_("Entry name can't start with '%c'."),
605 wxCONFIG_IMMUTABLE_PREFIX
);
609 for ( const char *pc
= strName
; *pc
!= '\0'; pc
++ ) {
610 if ( !IsValid(*pc
) ) {
611 wxLogError(_("Character '%c' is invalid in a config entry name."),
617 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
618 if ( pEntry
== NULL
)
619 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
621 pEntry
->SetValue(szValue
);
627 bool wxFileConfig::Write(const wxString
& key
, long lValue
)
629 // ltoa() is not ANSI :-(
631 buf
.Printf("%ld", lValue
);
632 return Write(key
, buf
);
635 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
637 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() )
640 wxTempFile
file(m_strLocalFile
);
642 if ( !file
.IsOpened() ) {
643 wxLogError(_("can't open user configuration file."));
647 // write all strings to file
648 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
649 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
650 wxLogError(_("can't write user configuration file."));
655 return file
.Commit();
658 // ----------------------------------------------------------------------------
659 // delete groups/entries
660 // ----------------------------------------------------------------------------
662 bool wxFileConfig::DeleteEntry(const wxString
& key
, bool bGroupIfEmptyAlso
)
664 wxConfigPathChanger
path(this, key
);
666 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
669 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
670 if ( m_pCurrentGroup
!= m_pRootGroup
) {
671 ConfigGroup
*pGroup
= m_pCurrentGroup
;
672 SetPath(".."); // changes m_pCurrentGroup!
673 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
675 //else: never delete the root group
681 bool wxFileConfig::DeleteGroup(const wxString
& key
)
683 wxConfigPathChanger
path(this, key
);
685 return m_pCurrentGroup
->DeleteSubgroupByName(path
.Name());
688 bool wxFileConfig::DeleteAll()
692 const char *szFile
= m_strLocalFile
;
694 if ( remove(szFile
) == -1 )
695 wxLogSysError(_("can't delete user configuration file '%s'"), szFile
);
697 m_strLocalFile
= m_strGlobalFile
= "";
703 // ----------------------------------------------------------------------------
704 // linked list functions
705 // ----------------------------------------------------------------------------
707 // append a new line to the end of the list
708 wxFileConfig::LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
710 LineList
*pLine
= new LineList(str
);
712 if ( m_linesTail
== NULL
) {
718 m_linesTail
->SetNext(pLine
);
719 pLine
->SetPrev(m_linesTail
);
726 // insert a new line after the given one or in the very beginning if !pLine
727 wxFileConfig::LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
730 if ( pLine
== m_linesTail
)
731 return LineListAppend(str
);
733 LineList
*pNewLine
= new LineList(str
);
734 if ( pLine
== NULL
) {
735 // prepend to the list
736 pNewLine
->SetNext(m_linesHead
);
737 m_linesHead
->SetPrev(pNewLine
);
738 m_linesHead
= pNewLine
;
741 // insert before pLine
742 LineList
*pNext
= pLine
->Next();
743 pNewLine
->SetNext(pNext
);
744 pNewLine
->SetPrev(pLine
);
745 pNext
->SetPrev(pNewLine
);
746 pLine
->SetNext(pNewLine
);
752 void wxFileConfig::LineListRemove(LineList
*pLine
)
754 LineList
*pPrev
= pLine
->Prev(),
755 *pNext
= pLine
->Next();
761 pPrev
->SetNext(pNext
);
767 pNext
->SetPrev(pPrev
);
772 bool wxFileConfig::LineListIsEmpty()
774 return m_linesHead
== NULL
;
777 // ============================================================================
778 // wxFileConfig::ConfigGroup
779 // ============================================================================
781 // ----------------------------------------------------------------------------
783 // ----------------------------------------------------------------------------
786 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup
*pParent
,
787 const wxString
& strName
,
788 wxFileConfig
*pConfig
)
789 : m_aEntries(CompareEntries
),
790 m_aSubgroups(CompareGroups
),
802 // dtor deletes all children
803 wxFileConfig::ConfigGroup::~ConfigGroup()
806 size_t n
, nCount
= m_aEntries
.Count();
807 for ( n
= 0; n
< nCount
; n
++ )
808 delete m_aEntries
[n
];
811 nCount
= m_aSubgroups
.Count();
812 for ( n
= 0; n
< nCount
; n
++ )
813 delete m_aSubgroups
[n
];
816 // ----------------------------------------------------------------------------
818 // ----------------------------------------------------------------------------
820 void wxFileConfig::ConfigGroup::SetLine(LineList
*pLine
)
822 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
828 This is a bit complicated, so let me explain it in details. All lines that
829 were read from the local file (the only one we will ever modify) are stored
830 in a (doubly) linked list. Our problem is to know at which position in this
831 list should we insert the new entries/subgroups. To solve it we keep three
832 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
834 m_pLine points to the line containing "[group_name]"
835 m_pLastEntry points to the last entry of this group in the local file.
836 m_pLastGroup subgroup
838 Initially, they're NULL all three. When the group (an entry/subgroup) is read
839 from the local file, the corresponding variable is set. However, if the group
840 was read from the global file and then modified or created by the application
841 these variables are still NULL and we need to create the corresponding lines.
842 See the following functions (and comments preceding them) for the details of
845 Also, when our last entry/group are deleted we need to find the new last
846 element - the code in DeleteEntry/Subgroup does this by backtracking the list
847 of lines until it either founds an entry/subgroup (and this is the new last
848 element) or the m_pLine of the group, in which case there are no more entries
849 (or subgroups) left and m_pLast<element> becomes NULL.
851 NB: This last problem could be avoided for entries if we added new entries
852 immediately after m_pLine, but in this case the entries would appear
853 backwards in the config file (OTOH, it's not that important) and as we
854 would still need to do it for the subgroups the code wouldn't have been
855 significantly less complicated.
858 // Return the line which contains "[our name]". If we're still not in the list,
859 // add our line to it immediately after the last line of our parent group if we
860 // have it or in the very beginning if we're the root group.
861 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetGroupLine()
863 if ( m_pLine
== NULL
) {
864 ConfigGroup
*pParent
= Parent();
866 // this group wasn't present in local config file, add it now
867 if ( pParent
!= NULL
) {
868 wxString strFullName
;
869 strFullName
<< "[" << (GetFullName().c_str() + 1) << "]"; // +1: no '/'
870 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
871 pParent
->GetLastGroupLine());
872 pParent
->SetLastGroup(this); // we're surely after all the others
875 // we return NULL, so that LineListInsert() will insert us in the
883 // Return the last line belonging to the subgroups of this group (after which
884 // we can add a new subgroup), if we don't have any subgroups or entries our
885 // last line is the group line (m_pLine) itself.
886 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastGroupLine()
888 // if we have any subgroups, our last line is the last line of the last
890 if ( m_pLastGroup
!= NULL
) {
891 wxFileConfig::LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
893 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
897 // no subgroups, so the last line is the line of thelast entry (if any)
898 return GetLastEntryLine();
901 // return the last line belonging to the entries of this group (after which
902 // we can add a new entry), if we don't have any entries we will add the new
903 // one immediately after the group line itself.
904 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastEntryLine()
906 if ( m_pLastEntry
!= NULL
) {
907 wxFileConfig::LineList
*pLine
= m_pLastEntry
->GetLine();
909 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
913 // no entries: insert after the group header
914 return GetGroupLine();
917 // ----------------------------------------------------------------------------
919 // ----------------------------------------------------------------------------
921 wxString
wxFileConfig::ConfigGroup::GetFullName() const
924 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
929 // ----------------------------------------------------------------------------
931 // ----------------------------------------------------------------------------
933 // use binary search because the array is sorted
934 wxFileConfig::ConfigEntry
*
935 wxFileConfig::ConfigGroup::FindEntry(const char *szName
) const
939 hi
= m_aEntries
.Count();
941 wxFileConfig::ConfigEntry
*pEntry
;
945 pEntry
= m_aEntries
[i
];
947 #if wxCONFIG_CASE_SENSITIVE
948 res
= strcmp(pEntry
->Name(), szName
);
950 res
= Stricmp(pEntry
->Name(), szName
);
964 wxFileConfig::ConfigGroup
*
965 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName
) const
969 hi
= m_aSubgroups
.Count();
971 wxFileConfig::ConfigGroup
*pGroup
;
975 pGroup
= m_aSubgroups
[i
];
977 #if wxCONFIG_CASE_SENSITIVE
978 res
= strcmp(pGroup
->Name(), szName
);
980 res
= Stricmp(pGroup
->Name(), szName
);
994 // ----------------------------------------------------------------------------
996 // ----------------------------------------------------------------------------
998 // create a new entry and add it to the current group
999 wxFileConfig::ConfigEntry
*
1000 wxFileConfig::ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
1002 wxASSERT( FindEntry(strName
) == NULL
);
1004 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
1005 m_aEntries
.Add(pEntry
);
1010 // create a new group and add it to the current group
1011 wxFileConfig::ConfigGroup
*
1012 wxFileConfig::ConfigGroup::AddSubgroup(const wxString
& strName
)
1014 wxASSERT( FindSubgroup(strName
) == NULL
);
1016 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
1017 m_aSubgroups
.Add(pGroup
);
1022 // ----------------------------------------------------------------------------
1024 // ----------------------------------------------------------------------------
1027 The delete operations are _very_ slow if we delete the last item of this
1028 group (see comments before GetXXXLineXXX functions for more details),
1029 so it's much better to start with the first entry/group if we want to
1030 delete several of them.
1033 bool wxFileConfig::ConfigGroup::DeleteSubgroupByName(const char *szName
)
1035 return DeleteSubgroup(FindSubgroup(szName
));
1038 // doesn't delete the subgroup itself, but does remove references to it from
1039 // all other data structures (and normally the returned pointer should be
1040 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1041 bool wxFileConfig::ConfigGroup::DeleteSubgroup(ConfigGroup
*pGroup
)
1043 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1045 // delete all entries
1046 size_t nCount
= pGroup
->m_aEntries
.Count();
1047 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1048 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1049 if ( pLine
!= NULL
)
1050 m_pConfig
->LineListRemove(pLine
);
1053 // and subgroups of this sungroup
1054 nCount
= pGroup
->m_aSubgroups
.Count();
1055 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1056 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[nGroup
]);
1059 LineList
*pLine
= pGroup
->m_pLine
;
1060 if ( pLine
!= NULL
) {
1061 // notice that we may do this test inside the previous "if" because the
1062 // last entry's line is surely !NULL
1063 if ( pGroup
== m_pLastGroup
) {
1064 // our last entry is being deleted - find the last one which stays
1065 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1067 // go back until we find a subgroup or reach the group's line
1068 ConfigGroup
*pNewLast
= NULL
;
1069 size_t n
, nSubgroups
= m_aSubgroups
.Count();
1071 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1072 // is it our subgroup?
1073 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1074 // do _not_ call GetGroupLine! we don't want to add it to the local
1075 // file if it's not already there
1076 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1077 pNewLast
= m_aSubgroups
[n
];
1080 if ( pNewLast
!= NULL
) // found?
1084 if ( pl
== m_pLine
) {
1085 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1087 // we've reached the group line without finding any subgroups
1088 m_pLastGroup
= NULL
;
1091 m_pLastGroup
= pNewLast
;
1094 m_pConfig
->LineListRemove(pLine
);
1099 m_aSubgroups
.Remove(pGroup
);
1105 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName
)
1107 ConfigEntry
*pEntry
= FindEntry(szName
);
1108 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1110 LineList
*pLine
= pEntry
->GetLine();
1111 if ( pLine
!= NULL
) {
1112 // notice that we may do this test inside the previous "if" because the
1113 // last entry's line is surely !NULL
1114 if ( pEntry
== m_pLastEntry
) {
1115 // our last entry is being deleted - find the last one which stays
1116 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1118 // go back until we find another entry or reach the group's line
1119 ConfigEntry
*pNewLast
= NULL
;
1120 size_t n
, nEntries
= m_aEntries
.Count();
1122 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1123 // is it our subgroup?
1124 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1125 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1126 pNewLast
= m_aEntries
[n
];
1129 if ( pNewLast
!= NULL
) // found?
1133 if ( pl
== m_pLine
) {
1134 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1136 // we've reached the group line without finding any subgroups
1137 m_pLastEntry
= NULL
;
1140 m_pLastEntry
= pNewLast
;
1143 m_pConfig
->LineListRemove(pLine
);
1146 // we must be written back for the changes to be saved
1149 m_aEntries
.Remove(pEntry
);
1155 // ----------------------------------------------------------------------------
1157 // ----------------------------------------------------------------------------
1158 void wxFileConfig::ConfigGroup::SetDirty()
1161 if ( Parent() != NULL
) // propagate upwards
1162 Parent()->SetDirty();
1165 // ============================================================================
1166 // wxFileConfig::ConfigEntry
1167 // ============================================================================
1169 // ----------------------------------------------------------------------------
1171 // ----------------------------------------------------------------------------
1172 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup
*pParent
,
1173 const wxString
& strName
,
1175 : m_strName(strName
)
1177 wxASSERT( !strName
.IsEmpty() );
1179 m_pParent
= pParent
;
1185 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1187 m_strName
.erase(0, 1); // remove first character
1190 // ----------------------------------------------------------------------------
1192 // ----------------------------------------------------------------------------
1194 void wxFileConfig::ConfigEntry::SetLine(LineList
*pLine
)
1196 if ( m_pLine
!= NULL
) {
1197 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1198 Name().c_str(), m_pParent
->GetFullName().c_str());
1202 Group()->SetLastEntry(this);
1205 // second parameter is FALSE if we read the value from file and prevents the
1206 // entry from being marked as 'dirty'
1207 void wxFileConfig::ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1209 if ( bUser
&& IsImmutable() ) {
1210 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1215 // do nothing if it's the same value
1216 if ( strValue
== m_strValue
)
1219 m_strValue
= strValue
;
1222 wxString strVal
= FilterOut(strValue
);
1224 strLine
<< m_strName
<< " = " << strVal
;
1226 if ( m_pLine
!= NULL
) {
1227 // entry was read from the local config file, just modify the line
1228 m_pLine
->SetText(strLine
);
1231 // add a new line to the file
1232 wxASSERT( m_nLine
== NOT_FOUND
); // consistency check
1234 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1235 Group()->GetLastEntryLine());
1236 Group()->SetLastEntry(this);
1243 void wxFileConfig::ConfigEntry::SetDirty()
1246 Group()->SetDirty();
1249 // ============================================================================
1251 // ============================================================================
1253 // ----------------------------------------------------------------------------
1254 // compare functions for array sorting
1255 // ----------------------------------------------------------------------------
1257 int CompareEntries(wxFileConfig::ConfigEntry
*p1
,
1258 wxFileConfig::ConfigEntry
*p2
)
1260 #if wxCONFIG_CASE_SENSITIVE
1261 return strcmp(p1
->Name(), p2
->Name());
1263 return Stricmp(p1
->Name(), p2
->Name());
1267 int CompareGroups(wxFileConfig::ConfigGroup
*p1
,
1268 wxFileConfig::ConfigGroup
*p2
)
1270 #if wxCONFIG_CASE_SENSITIVE
1271 return strcmp(p1
->Name(), p2
->Name());
1273 return Stricmp(p1
->Name(), p2
->Name());
1277 // ----------------------------------------------------------------------------
1279 // ----------------------------------------------------------------------------
1282 wxString
FilterIn(const wxString
& str
)
1285 strResult
.Alloc(str
.Len());
1287 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1289 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1290 if ( str
[n
] == '\\' ) {
1291 switch ( str
[++n
] ) {
1314 if ( str
[n
] != '"' || !bQuoted
)
1315 strResult
+= str
[n
];
1316 else if ( n
!= str
.Len() - 1 ) {
1317 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1320 //else: it's the last quote of a quoted string, ok
1327 // quote the string before writing it to file
1328 wxString
FilterOut(const wxString
& str
)
1334 strResult
.Alloc(str
.Len());
1336 // quoting is necessary to preserve spaces in the beginning of the string
1337 bool bQuote
= isspace(str
[0]) || str
[0] == '"';
1343 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
1366 //else: fall through
1369 strResult
+= str
[n
];
1370 continue; // nothing special to do
1373 // we get here only for special characters
1374 strResult
<< '\\' << c
;