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 #include "wx/utils.h" // for wxGetHomeDir
45 // _WINDOWS_ is defined when windows.h is included,
46 // __WXMSW__ is defined for MS Windows compilation
47 #if defined(__WXMSW__) && !defined(_WINDOWS_)
54 // ----------------------------------------------------------------------------
56 // ----------------------------------------------------------------------------
57 #define CONST_CAST ((wxFileConfig *)this)->
59 // ----------------------------------------------------------------------------
61 // ----------------------------------------------------------------------------
66 // ----------------------------------------------------------------------------
67 // global functions declarations
68 // ----------------------------------------------------------------------------
70 // is 'c' a valid character in group name?
71 // NB: wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR must be valid chars,
72 // but _not_ ']' (group name delimiter)
73 inline bool IsValid(char c
) { return isalnum(c
) || strchr("@_/-!.*%", c
); }
75 // compare functions for sorting the arrays
76 static int CompareEntries(wxFileConfig::ConfigEntry
*p1
,
77 wxFileConfig::ConfigEntry
*p2
);
78 static int CompareGroups(wxFileConfig::ConfigGroup
*p1
,
79 wxFileConfig::ConfigGroup
*p2
);
82 static wxString
FilterIn(const wxString
& str
);
83 static wxString
FilterOut(const wxString
& str
);
85 // ============================================================================
87 // ============================================================================
89 // ----------------------------------------------------------------------------
91 // ----------------------------------------------------------------------------
92 wxString
wxFileConfig::GetGlobalDir()
98 #elif defined(__WXSTUBS__)
99 wxASSERT_MSG( FALSE
, "TODO" ) ;
101 char szWinDir
[MAX_PATH
];
102 ::GetWindowsDirectory(szWinDir
, MAX_PATH
);
106 #endif // Unix/Windows
111 wxString
wxFileConfig::GetLocalDir()
115 wxGetHomeDir(&strDir
);
120 wxString
wxFileConfig::GetGlobalFileName(const char *szFile
)
122 wxString str
= GetGlobalDir();
125 if ( strchr(szFile
, '.') == NULL
)
135 wxString
wxFileConfig::GetLocalFileName(const char *szFile
)
137 wxString str
= GetLocalDir();
146 if ( strchr(szFile
, '.') == NULL
)
153 // ----------------------------------------------------------------------------
155 // ----------------------------------------------------------------------------
157 void wxFileConfig::Init()
160 m_pRootGroup
= new ConfigGroup(NULL
, "", this);
165 // it's not an error if (one of the) file(s) doesn't exist
167 // parse the global file
168 if ( !m_strGlobalFile
.IsEmpty() && wxFile::Exists(m_strGlobalFile
) ) {
169 wxTextFile
fileGlobal(m_strGlobalFile
);
171 if ( fileGlobal
.Open() ) {
172 Parse(fileGlobal
, FALSE
/* global */);
176 wxLogWarning(_("can't open global configuration file '%s'."),
177 m_strGlobalFile
.c_str());
180 // parse the local file
181 if ( !m_strLocalFile
.IsEmpty() && wxFile::Exists(m_strLocalFile
) ) {
182 wxTextFile
fileLocal(m_strLocalFile
);
183 if ( fileLocal
.Open() ) {
184 Parse(fileLocal
, TRUE
/* local */);
188 wxLogWarning(_("can't open user configuration file '%s'."),
189 m_strLocalFile
.c_str());
193 // constructor supports creation of wxFileConfig objects of any type
194 wxFileConfig::wxFileConfig(const wxString
& appName
, const wxString
& vendorName
,
195 const wxString
& strLocal
, const wxString
& strGlobal
,
197 : wxConfigBase(appName
, vendorName
, strLocal
, strGlobal
, style
),
198 m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
200 // Make up an application name if not supplied
201 if (appName
.IsEmpty() && wxTheApp
)
203 SetAppName(wxTheApp
->GetAppName());
206 // Make up names for files if empty
207 if ( m_strLocalFile
.IsEmpty() && (style
& wxCONFIG_USE_LOCAL_FILE
) )
209 m_strLocalFile
= GetLocalFileName(GetAppName());
212 if ( m_strGlobalFile
.IsEmpty() && (style
& wxCONFIG_USE_GLOBAL_FILE
) )
214 m_strGlobalFile
= GetGlobalFileName(GetAppName());
217 // Check if styles are not supplied, but filenames are, in which case
218 // add the correct styles.
219 if ( !m_strLocalFile
.IsEmpty() )
220 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE
);
222 if ( !m_strGlobalFile
.IsEmpty() )
223 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE
);
225 // if the path is not absolute, prepend the standard directory to it
226 if ( !m_strLocalFile
.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile
) )
228 wxString strLocal
= m_strLocalFile
;
229 m_strLocalFile
= GetLocalDir();
230 m_strLocalFile
<< strLocal
;
233 if ( !m_strGlobalFile
.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile
) )
235 wxString strGlobal
= m_strGlobalFile
;
236 m_strGlobalFile
= GetGlobalDir();
237 m_strGlobalFile
<< strGlobal
;
243 void wxFileConfig::CleanUp()
247 LineList
*pCur
= m_linesHead
;
248 while ( pCur
!= NULL
) {
249 LineList
*pNext
= pCur
->Next();
255 wxFileConfig::~wxFileConfig()
262 // ----------------------------------------------------------------------------
263 // parse a config file
264 // ----------------------------------------------------------------------------
266 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
272 size_t nLineCount
= file
.GetLineCount();
273 for ( size_t n
= 0; n
< nLineCount
; n
++ ) {
276 // add the line to linked list
278 LineListAppend(strLine
);
280 // skip leading spaces
281 for ( pStart
= strLine
; isspace(*pStart
); pStart
++ )
284 // skip blank/comment lines
285 if ( *pStart
== '\0'|| *pStart
== ';' || *pStart
== '#' )
288 if ( *pStart
== '[' ) { // a new group
291 while ( *++pEnd
!= ']' ) {
292 if ( !IsValid(*pEnd
) && *pEnd
!= ' ' ) // allow spaces in group names
296 if ( *pEnd
!= ']' ) {
297 wxLogError(_("file '%s': unexpected character %c at line %d."),
298 file
.GetName(), *pEnd
, n
+ 1);
299 continue; // skip this line
302 // group name here is always considered as abs path
305 strGroup
<< wxCONFIG_PATH_SEPARATOR
<< wxString(pStart
, pEnd
- pStart
);
307 // will create it if doesn't yet exist
311 m_pCurrentGroup
->SetLine(m_linesTail
);
313 // check that there is nothing except comments left on this line
315 while ( *++pEnd
!= '\0' && bCont
) {
324 // ignore whitespace ('\n' impossible here)
328 wxLogWarning(_("file '%s', line %d: '%s' "
329 "ignored after group header."),
330 file
.GetName(), n
+ 1, pEnd
);
336 const char *pEnd
= pStart
;
337 while ( IsValid(*pEnd
) )
340 wxString
strKey(pStart
, pEnd
);
343 while ( isspace(*pEnd
) )
346 if ( *pEnd
++ != '=' ) {
347 wxLogError(_("file '%s', line %d: '=' expected."),
348 file
.GetName(), n
+ 1);
351 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
353 if ( pEntry
== NULL
) {
355 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
358 pEntry
->SetLine(m_linesTail
);
361 if ( bLocal
&& pEntry
->IsImmutable() ) {
362 // immutable keys can't be changed by user
363 wxLogWarning(_("file '%s', line %d: value for "
364 "immutable key '%s' ignored."),
365 file
.GetName(), n
+ 1, strKey
.c_str());
368 // the condition below catches the cases (a) and (b) but not (c):
369 // (a) global key found second time in global file
370 // (b) key found second (or more) time in local file
371 // (c) key from global file now found in local one
372 // which is exactly what we want.
373 else if ( !bLocal
|| pEntry
->IsLocal() ) {
374 wxLogWarning(_("file '%s', line %d: key '%s' was first "
375 "found at line %d."),
376 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
379 pEntry
->SetLine(m_linesTail
);
384 while ( isspace(*pEnd
) )
387 pEntry
->SetValue(FilterIn(pEnd
), FALSE
/* read from file */);
393 // ----------------------------------------------------------------------------
395 // ----------------------------------------------------------------------------
397 void wxFileConfig::SetRootPath()
400 m_pCurrentGroup
= m_pRootGroup
;
403 void wxFileConfig::SetPath(const wxString
& strPath
)
405 wxArrayString aParts
;
407 if ( strPath
.IsEmpty() ) {
412 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
414 wxSplitPath(aParts
, strPath
);
417 // relative path, combine with current one
418 wxString strFullPath
= m_strPath
;
419 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
420 wxSplitPath(aParts
, strFullPath
);
423 // change current group
425 m_pCurrentGroup
= m_pRootGroup
;
426 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
427 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
428 if ( pNextGroup
== NULL
)
429 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
430 m_pCurrentGroup
= pNextGroup
;
433 // recombine path parts in one variable
435 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
436 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
440 // ----------------------------------------------------------------------------
442 // ----------------------------------------------------------------------------
444 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
447 return GetNextGroup(str
, lIndex
);
450 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
452 if ( size_t(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
453 str
= m_pCurrentGroup
->Groups()[lIndex
++]->Name();
460 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
463 return GetNextEntry(str
, lIndex
);
466 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
468 if ( size_t(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
469 str
= m_pCurrentGroup
->Entries()[lIndex
++]->Name();
476 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
478 size_t n
= m_pCurrentGroup
->Entries().Count();
480 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
481 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
482 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
483 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
484 n
+= GetNumberOfEntries(TRUE
);
485 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
492 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
494 size_t n
= m_pCurrentGroup
->Groups().Count();
496 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
497 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
498 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
499 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
500 n
+= GetNumberOfGroups(TRUE
);
501 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
508 // ----------------------------------------------------------------------------
509 // tests for existence
510 // ----------------------------------------------------------------------------
512 bool wxFileConfig::HasGroup(const wxString
& strName
) const
514 wxConfigPathChanger
path(this, strName
);
516 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
517 return pGroup
!= NULL
;
520 bool wxFileConfig::HasEntry(const wxString
& strName
) const
522 wxConfigPathChanger
path(this, strName
);
524 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
525 return pEntry
!= NULL
;
528 // ----------------------------------------------------------------------------
530 // ----------------------------------------------------------------------------
532 bool wxFileConfig::Read(const wxString
& key
,
533 wxString
* pStr
) const
535 wxConfigPathChanger
path(this, key
);
537 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
538 if (pEntry
== NULL
) {
542 *pStr
= ExpandEnvVars(pEntry
->Value());
547 bool wxFileConfig::Read(const wxString
& key
,
548 wxString
* pStr
, const wxString
& defVal
) const
550 wxConfigPathChanger
path(this, key
);
552 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
553 if (pEntry
== NULL
) {
554 if( IsRecordingDefaults() )
555 ((wxFileConfig
*)this)->Write(key
,defVal
);
556 *pStr
= ExpandEnvVars(defVal
);
560 *pStr
= ExpandEnvVars(pEntry
->Value());
565 bool wxFileConfig::Read(const wxString
& key
, long *pl
) const
568 if ( Read(key
, & str
) ) {
577 bool wxFileConfig::Write(const wxString
& key
, const wxString
& szValue
)
579 wxConfigPathChanger
path(this, key
);
581 wxString strName
= path
.Name();
582 if ( strName
.IsEmpty() ) {
583 // setting the value of a group is an error
584 wxASSERT_MSG( IsEmpty(szValue
), _("can't set value of a group!") );
586 // ... except if it's empty in which case it's a way to force it's creation
587 m_pCurrentGroup
->SetDirty();
589 // this will add a line for this group if it didn't have it before
590 (void)m_pCurrentGroup
->GetGroupLine();
595 // check that the name is reasonable
596 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
597 wxLogError(_("Entry name can't start with '%c'."),
598 wxCONFIG_IMMUTABLE_PREFIX
);
602 for ( const char *pc
= strName
; *pc
!= '\0'; pc
++ ) {
603 if ( !IsValid(*pc
) ) {
604 wxLogError(_("Character '%c' is invalid in a config entry name."),
610 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
611 if ( pEntry
== NULL
)
612 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
614 pEntry
->SetValue(szValue
);
620 bool wxFileConfig::Write(const wxString
& key
, long lValue
)
622 // ltoa() is not ANSI :-(
623 char szBuf
[40]; // should be good for sizeof(long) <= 16 (128 bits)
624 sprintf(szBuf
, "%ld", lValue
);
625 return Write(key
, szBuf
);
628 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
630 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() )
633 wxTempFile
file(m_strLocalFile
);
635 if ( !file
.IsOpened() ) {
636 wxLogError(_("can't open user configuration file."));
640 // write all strings to file
641 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
642 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
643 wxLogError(_("can't write user configuration file."));
648 return file
.Commit();
651 // ----------------------------------------------------------------------------
652 // delete groups/entries
653 // ----------------------------------------------------------------------------
655 bool wxFileConfig::DeleteEntry(const wxString
& key
, bool bGroupIfEmptyAlso
)
657 wxConfigPathChanger
path(this, key
);
659 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
662 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
663 if ( m_pCurrentGroup
!= m_pRootGroup
) {
664 ConfigGroup
*pGroup
= m_pCurrentGroup
;
665 SetPath(".."); // changes m_pCurrentGroup!
666 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
668 //else: never delete the root group
674 bool wxFileConfig::DeleteGroup(const wxString
& key
)
676 wxConfigPathChanger
path(this, key
);
678 return m_pCurrentGroup
->DeleteSubgroupByName(path
.Name());
681 bool wxFileConfig::DeleteAll()
685 const char *szFile
= m_strLocalFile
;
687 if ( remove(szFile
) == -1 )
688 wxLogSysError(_("can't delete user configuration file '%s'"), szFile
);
690 m_strLocalFile
= m_strGlobalFile
= "";
696 // ----------------------------------------------------------------------------
697 // linked list functions
698 // ----------------------------------------------------------------------------
700 // append a new line to the end of the list
701 wxFileConfig::LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
703 LineList
*pLine
= new LineList(str
);
705 if ( m_linesTail
== NULL
) {
711 m_linesTail
->SetNext(pLine
);
712 pLine
->SetPrev(m_linesTail
);
719 // insert a new line after the given one or in the very beginning if !pLine
720 wxFileConfig::LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
723 if ( pLine
== m_linesTail
)
724 return LineListAppend(str
);
726 LineList
*pNewLine
= new LineList(str
);
727 if ( pLine
== NULL
) {
728 // prepend to the list
729 pNewLine
->SetNext(m_linesHead
);
730 m_linesHead
->SetPrev(pNewLine
);
731 m_linesHead
= pNewLine
;
734 // insert before pLine
735 LineList
*pNext
= pLine
->Next();
736 pNewLine
->SetNext(pNext
);
737 pNewLine
->SetPrev(pLine
);
738 pNext
->SetPrev(pNewLine
);
739 pLine
->SetNext(pNewLine
);
745 void wxFileConfig::LineListRemove(LineList
*pLine
)
747 LineList
*pPrev
= pLine
->Prev(),
748 *pNext
= pLine
->Next();
754 pPrev
->SetNext(pNext
);
760 pNext
->SetPrev(pPrev
);
765 bool wxFileConfig::LineListIsEmpty()
767 return m_linesHead
== NULL
;
770 // ============================================================================
771 // wxFileConfig::ConfigGroup
772 // ============================================================================
774 // ----------------------------------------------------------------------------
776 // ----------------------------------------------------------------------------
779 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup
*pParent
,
780 const wxString
& strName
,
781 wxFileConfig
*pConfig
)
782 : m_aEntries(CompareEntries
),
783 m_aSubgroups(CompareGroups
),
795 // dtor deletes all children
796 wxFileConfig::ConfigGroup::~ConfigGroup()
799 size_t n
, nCount
= m_aEntries
.Count();
800 for ( n
= 0; n
< nCount
; n
++ )
801 delete m_aEntries
[n
];
804 nCount
= m_aSubgroups
.Count();
805 for ( n
= 0; n
< nCount
; n
++ )
806 delete m_aSubgroups
[n
];
809 // ----------------------------------------------------------------------------
811 // ----------------------------------------------------------------------------
813 void wxFileConfig::ConfigGroup::SetLine(LineList
*pLine
)
815 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
821 This is a bit complicated, so let me explain it in details. All lines that
822 were read from the local file (the only one we will ever modify) are stored
823 in a (doubly) linked list. Our problem is to know at which position in this
824 list should we insert the new entries/subgroups. To solve it we keep three
825 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
827 m_pLine points to the line containing "[group_name]"
828 m_pLastEntry points to the last entry of this group in the local file.
829 m_pLastGroup subgroup
831 Initially, they're NULL all three. When the group (an entry/subgroup) is read
832 from the local file, the corresponding variable is set. However, if the group
833 was read from the global file and then modified or created by the application
834 these variables are still NULL and we need to create the corresponding lines.
835 See the following functions (and comments preceding them) for the details of
838 Also, when our last entry/group are deleted we need to find the new last
839 element - the code in DeleteEntry/Subgroup does this by backtracking the list
840 of lines until it either founds an entry/subgroup (and this is the new last
841 element) or the m_pLine of the group, in which case there are no more entries
842 (or subgroups) left and m_pLast<element> becomes NULL.
844 NB: This last problem could be avoided for entries if we added new entries
845 immediately after m_pLine, but in this case the entries would appear
846 backwards in the config file (OTOH, it's not that important) and as we
847 would still need to do it for the subgroups the code wouldn't have been
848 significantly less complicated.
851 // Return the line which contains "[our name]". If we're still not in the list,
852 // add our line to it immediately after the last line of our parent group if we
853 // have it or in the very beginning if we're the root group.
854 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetGroupLine()
856 if ( m_pLine
== NULL
) {
857 ConfigGroup
*pParent
= Parent();
859 // this group wasn't present in local config file, add it now
860 if ( pParent
!= NULL
) {
861 wxString strFullName
;
862 strFullName
<< "[" << (GetFullName().c_str() + 1) << "]"; // +1: no '/'
863 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
864 pParent
->GetLastGroupLine());
865 pParent
->SetLastGroup(this); // we're surely after all the others
868 // we return NULL, so that LineListInsert() will insert us in the
876 // Return the last line belonging to the subgroups of this group (after which
877 // we can add a new subgroup), if we don't have any subgroups or entries our
878 // last line is the group line (m_pLine) itself.
879 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastGroupLine()
881 // if we have any subgroups, our last line is the last line of the last
883 if ( m_pLastGroup
!= NULL
) {
884 wxFileConfig::LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
886 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
890 // no subgroups, so the last line is the line of thelast entry (if any)
891 return GetLastEntryLine();
894 // return the last line belonging to the entries of this group (after which
895 // we can add a new entry), if we don't have any entries we will add the new
896 // one immediately after the group line itself.
897 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastEntryLine()
899 if ( m_pLastEntry
!= NULL
) {
900 wxFileConfig::LineList
*pLine
= m_pLastEntry
->GetLine();
902 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
906 // no entries: insert after the group header
907 return GetGroupLine();
910 // ----------------------------------------------------------------------------
912 // ----------------------------------------------------------------------------
914 wxString
wxFileConfig::ConfigGroup::GetFullName() const
917 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
922 // ----------------------------------------------------------------------------
924 // ----------------------------------------------------------------------------
926 // use binary search because the array is sorted
927 wxFileConfig::ConfigEntry
*
928 wxFileConfig::ConfigGroup::FindEntry(const char *szName
) const
932 hi
= m_aEntries
.Count();
934 wxFileConfig::ConfigEntry
*pEntry
;
938 pEntry
= m_aEntries
[i
];
940 #if wxCONFIG_CASE_SENSITIVE
941 res
= strcmp(pEntry
->Name(), szName
);
943 res
= Stricmp(pEntry
->Name(), szName
);
957 wxFileConfig::ConfigGroup
*
958 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName
) const
962 hi
= m_aSubgroups
.Count();
964 wxFileConfig::ConfigGroup
*pGroup
;
968 pGroup
= m_aSubgroups
[i
];
970 #if wxCONFIG_CASE_SENSITIVE
971 res
= strcmp(pGroup
->Name(), szName
);
973 res
= Stricmp(pGroup
->Name(), szName
);
987 // ----------------------------------------------------------------------------
989 // ----------------------------------------------------------------------------
991 // create a new entry and add it to the current group
992 wxFileConfig::ConfigEntry
*
993 wxFileConfig::ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
995 wxASSERT( FindEntry(strName
) == NULL
);
997 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
998 m_aEntries
.Add(pEntry
);
1003 // create a new group and add it to the current group
1004 wxFileConfig::ConfigGroup
*
1005 wxFileConfig::ConfigGroup::AddSubgroup(const wxString
& strName
)
1007 wxASSERT( FindSubgroup(strName
) == NULL
);
1009 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
1010 m_aSubgroups
.Add(pGroup
);
1015 // ----------------------------------------------------------------------------
1017 // ----------------------------------------------------------------------------
1020 The delete operations are _very_ slow if we delete the last item of this
1021 group (see comments before GetXXXLineXXX functions for more details),
1022 so it's much better to start with the first entry/group if we want to
1023 delete several of them.
1026 bool wxFileConfig::ConfigGroup::DeleteSubgroupByName(const char *szName
)
1028 return DeleteSubgroup(FindSubgroup(szName
));
1031 // doesn't delete the subgroup itself, but does remove references to it from
1032 // all other data structures (and normally the returned pointer should be
1033 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1034 bool wxFileConfig::ConfigGroup::DeleteSubgroup(ConfigGroup
*pGroup
)
1036 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1038 // delete all entries
1039 size_t nCount
= pGroup
->m_aEntries
.Count();
1040 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1041 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1042 if ( pLine
!= NULL
)
1043 m_pConfig
->LineListRemove(pLine
);
1046 // and subgroups of this sungroup
1047 nCount
= pGroup
->m_aSubgroups
.Count();
1048 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1049 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[nGroup
]);
1052 LineList
*pLine
= pGroup
->m_pLine
;
1053 if ( pLine
!= NULL
) {
1054 // notice that we may do this test inside the previous "if" because the
1055 // last entry's line is surely !NULL
1056 if ( pGroup
== m_pLastGroup
) {
1057 // our last entry is being deleted - find the last one which stays
1058 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1060 // go back until we find a subgroup or reach the group's line
1061 ConfigGroup
*pNewLast
= NULL
;
1062 size_t n
, nSubgroups
= m_aSubgroups
.Count();
1064 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1065 // is it our subgroup?
1066 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1067 // do _not_ call GetGroupLine! we don't want to add it to the local
1068 // file if it's not already there
1069 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1070 pNewLast
= m_aSubgroups
[n
];
1073 if ( pNewLast
!= NULL
) // found?
1077 if ( pl
== m_pLine
) {
1078 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1080 // we've reached the group line without finding any subgroups
1081 m_pLastGroup
= NULL
;
1084 m_pLastGroup
= pNewLast
;
1087 m_pConfig
->LineListRemove(pLine
);
1092 m_aSubgroups
.Remove(pGroup
);
1098 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName
)
1100 ConfigEntry
*pEntry
= FindEntry(szName
);
1101 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1103 LineList
*pLine
= pEntry
->GetLine();
1104 if ( pLine
!= NULL
) {
1105 // notice that we may do this test inside the previous "if" because the
1106 // last entry's line is surely !NULL
1107 if ( pEntry
== m_pLastEntry
) {
1108 // our last entry is being deleted - find the last one which stays
1109 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1111 // go back until we find another entry or reach the group's line
1112 ConfigEntry
*pNewLast
= NULL
;
1113 size_t n
, nEntries
= m_aEntries
.Count();
1115 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1116 // is it our subgroup?
1117 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1118 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1119 pNewLast
= m_aEntries
[n
];
1122 if ( pNewLast
!= NULL
) // found?
1126 if ( pl
== m_pLine
) {
1127 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1129 // we've reached the group line without finding any subgroups
1130 m_pLastEntry
= NULL
;
1133 m_pLastEntry
= pNewLast
;
1136 m_pConfig
->LineListRemove(pLine
);
1139 // we must be written back for the changes to be saved
1142 m_aEntries
.Remove(pEntry
);
1148 // ----------------------------------------------------------------------------
1150 // ----------------------------------------------------------------------------
1151 void wxFileConfig::ConfigGroup::SetDirty()
1154 if ( Parent() != NULL
) // propagate upwards
1155 Parent()->SetDirty();
1158 // ============================================================================
1159 // wxFileConfig::ConfigEntry
1160 // ============================================================================
1162 // ----------------------------------------------------------------------------
1164 // ----------------------------------------------------------------------------
1165 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup
*pParent
,
1166 const wxString
& strName
,
1168 : m_strName(strName
)
1170 wxASSERT( !strName
.IsEmpty() );
1172 m_pParent
= pParent
;
1178 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1180 m_strName
.erase(0, 1); // remove first character
1183 // ----------------------------------------------------------------------------
1185 // ----------------------------------------------------------------------------
1187 void wxFileConfig::ConfigEntry::SetLine(LineList
*pLine
)
1189 if ( m_pLine
!= NULL
) {
1190 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1191 Name().c_str(), m_pParent
->GetFullName().c_str());
1195 Group()->SetLastEntry(this);
1198 // second parameter is FALSE if we read the value from file and prevents the
1199 // entry from being marked as 'dirty'
1200 void wxFileConfig::ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1202 if ( bUser
&& IsImmutable() ) {
1203 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1208 // do nothing if it's the same value
1209 if ( strValue
== m_strValue
)
1212 m_strValue
= strValue
;
1215 wxString strVal
= FilterOut(strValue
);
1217 strLine
<< m_strName
<< " = " << strVal
;
1219 if ( m_pLine
!= NULL
) {
1220 // entry was read from the local config file, just modify the line
1221 m_pLine
->SetText(strLine
);
1224 // add a new line to the file
1225 wxASSERT( m_nLine
== NOT_FOUND
); // consistency check
1227 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1228 Group()->GetLastEntryLine());
1229 Group()->SetLastEntry(this);
1236 void wxFileConfig::ConfigEntry::SetDirty()
1239 Group()->SetDirty();
1242 // ============================================================================
1244 // ============================================================================
1246 // ----------------------------------------------------------------------------
1247 // compare functions for array sorting
1248 // ----------------------------------------------------------------------------
1250 int CompareEntries(wxFileConfig::ConfigEntry
*p1
,
1251 wxFileConfig::ConfigEntry
*p2
)
1253 #if wxCONFIG_CASE_SENSITIVE
1254 return strcmp(p1
->Name(), p2
->Name());
1256 return Stricmp(p1
->Name(), p2
->Name());
1260 int CompareGroups(wxFileConfig::ConfigGroup
*p1
,
1261 wxFileConfig::ConfigGroup
*p2
)
1263 #if wxCONFIG_CASE_SENSITIVE
1264 return strcmp(p1
->Name(), p2
->Name());
1266 return Stricmp(p1
->Name(), p2
->Name());
1270 // ----------------------------------------------------------------------------
1272 // ----------------------------------------------------------------------------
1275 wxString
FilterIn(const wxString
& str
)
1278 strResult
.Alloc(str
.Len());
1280 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1282 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1283 if ( str
[n
] == '\\' ) {
1284 switch ( str
[++n
] ) {
1307 if ( str
[n
] != '"' || !bQuoted
)
1308 strResult
+= str
[n
];
1309 else if ( n
!= str
.Len() - 1 ) {
1310 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1313 //else: it's the last quote of a quoted string, ok
1320 // quote the string before writing it to file
1321 wxString
FilterOut(const wxString
& str
)
1327 strResult
.Alloc(str
.Len());
1329 // quoting is necessary to preserve spaces in the beginning of the string
1330 bool bQuote
= isspace(str
[0]) || str
[0] == '"';
1336 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
1359 //else: fall through
1362 strResult
+= str
[n
];
1363 continue; // nothing special to do
1366 // we get here only for special characters
1367 strResult
<< '\\' << c
;