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 // compare functions for sorting the arrays
71 static int CompareEntries(ConfigEntry
*p1
, ConfigEntry
*p2
);
72 static int CompareGroups(ConfigGroup
*p1
, ConfigGroup
*p2
);
75 static wxString
FilterInValue(const wxString
& str
);
76 static wxString
FilterOutValue(const wxString
& str
);
78 static wxString
FilterInEntryName(const wxString
& str
);
79 static wxString
FilterOutEntryName(const wxString
& str
);
81 // ============================================================================
83 // ============================================================================
85 // ----------------------------------------------------------------------------
87 // ----------------------------------------------------------------------------
88 wxString
wxFileConfig::GetGlobalDir()
94 #elif defined(__WXSTUBS__)
95 wxASSERT_MSG( FALSE
, _T("TODO") ) ;
96 #elif defined(__WXMAC__)
97 wxASSERT_MSG( FALSE
, _T("TODO") ) ;
99 wxChar szWinDir
[MAX_PATH
];
100 ::GetWindowsDirectory(szWinDir
, MAX_PATH
);
104 #endif // Unix/Windows
109 wxString
wxFileConfig::GetLocalDir()
113 wxGetHomeDir(&strDir
);
116 if (strDir
.Last() != _T('/')) strDir
<< _T('/');
118 if (strDir
.Last() != _T('\\')) strDir
<< _T('\\');
124 wxString
wxFileConfig::GetGlobalFileName(const wxChar
*szFile
)
126 wxString str
= GetGlobalDir();
129 if ( wxStrchr(szFile
, _T('.')) == NULL
)
139 wxString
wxFileConfig::GetLocalFileName(const wxChar
*szFile
)
141 wxString str
= GetLocalDir();
150 if ( wxStrchr(szFile
, _T('.')) == NULL
)
157 // ----------------------------------------------------------------------------
159 // ----------------------------------------------------------------------------
161 void wxFileConfig::Init()
164 m_pRootGroup
= new ConfigGroup(NULL
, "", this);
169 // it's not an error if (one of the) file(s) doesn't exist
171 // parse the global file
172 if ( !m_strGlobalFile
.IsEmpty() && wxFile::Exists(m_strGlobalFile
) ) {
173 wxTextFile
fileGlobal(m_strGlobalFile
);
175 if ( fileGlobal
.Open() ) {
176 Parse(fileGlobal
, FALSE
/* global */);
180 wxLogWarning(_("can't open global configuration file '%s'."),
181 m_strGlobalFile
.c_str());
184 // parse the local file
185 if ( !m_strLocalFile
.IsEmpty() && wxFile::Exists(m_strLocalFile
) ) {
186 wxTextFile
fileLocal(m_strLocalFile
);
187 if ( fileLocal
.Open() ) {
188 Parse(fileLocal
, TRUE
/* local */);
192 wxLogWarning(_("can't open user configuration file '%s'."),
193 m_strLocalFile
.c_str());
197 // constructor supports creation of wxFileConfig objects of any type
198 wxFileConfig::wxFileConfig(const wxString
& appName
, const wxString
& vendorName
,
199 const wxString
& strLocal
, const wxString
& strGlobal
,
201 : wxConfigBase(!appName
&& wxTheApp
? wxTheApp
->GetAppName()
203 vendorName
, strLocal
, strGlobal
, style
),
204 m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
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
)
268 const wxChar
*pStart
;
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
; wxIsspace(*pStart
); pStart
++ )
284 // skip blank/comment lines
285 if ( *pStart
== _T('\0')|| *pStart
== _T(';') || *pStart
== _T('#') )
288 if ( *pStart
== _T('[') ) { // a new group
291 while ( *++pEnd
!= _T(']') ) {
292 if ( *pEnd
== _T('\n') || *pEnd
== _T('\0') )
296 if ( *pEnd
!= _T(']') ) {
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
306 << FilterInEntryName(wxString(pStart
, pEnd
- pStart
));
308 // will create it if doesn't yet exist
312 m_pCurrentGroup
->SetLine(m_linesTail
);
314 // check that there is nothing except comments left on this line
316 while ( *++pEnd
!= _T('\0') && bCont
) {
325 // ignore whitespace ('\n' impossible here)
329 wxLogWarning(_("file '%s', line %d: '%s' "
330 "ignored after group header."),
331 file
.GetName(), n
+ 1, pEnd
);
337 const wxChar
*pEnd
= pStart
;
338 while ( !wxIsspace(*pEnd
) ) {
339 if ( *pEnd
== _T('\\') ) {
340 // next character may be space or not - still take it because it's
348 wxString
strKey(FilterInEntryName(wxString(pStart
, pEnd
)));
351 while ( isspace(*pEnd
) )
354 if ( *pEnd
++ != _T('=') ) {
355 wxLogError(_("file '%s', line %d: '=' expected."),
356 file
.GetName(), n
+ 1);
359 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
361 if ( pEntry
== NULL
) {
363 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
366 pEntry
->SetLine(m_linesTail
);
369 if ( bLocal
&& pEntry
->IsImmutable() ) {
370 // immutable keys can't be changed by user
371 wxLogWarning(_("file '%s', line %d: value for "
372 "immutable key '%s' ignored."),
373 file
.GetName(), n
+ 1, strKey
.c_str());
376 // the condition below catches the cases (a) and (b) but not (c):
377 // (a) global key found second time in global file
378 // (b) key found second (or more) time in local file
379 // (c) key from global file now found in local one
380 // which is exactly what we want.
381 else if ( !bLocal
|| pEntry
->IsLocal() ) {
382 wxLogWarning(_("file '%s', line %d: key '%s' was first "
383 "found at line %d."),
384 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
387 pEntry
->SetLine(m_linesTail
);
392 while ( wxIsspace(*pEnd
) )
395 pEntry
->SetValue(FilterInValue(pEnd
), FALSE
/* read from file */);
401 // ----------------------------------------------------------------------------
403 // ----------------------------------------------------------------------------
405 void wxFileConfig::SetRootPath()
408 m_pCurrentGroup
= m_pRootGroup
;
411 void wxFileConfig::SetPath(const wxString
& strPath
)
413 wxArrayString aParts
;
415 if ( strPath
.IsEmpty() ) {
420 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
422 wxSplitPath(aParts
, strPath
);
425 // relative path, combine with current one
426 wxString strFullPath
= m_strPath
;
427 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
428 wxSplitPath(aParts
, strFullPath
);
431 // change current group
433 m_pCurrentGroup
= m_pRootGroup
;
434 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
435 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
436 if ( pNextGroup
== NULL
)
437 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
438 m_pCurrentGroup
= pNextGroup
;
441 // recombine path parts in one variable
443 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
444 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
448 // ----------------------------------------------------------------------------
450 // ----------------------------------------------------------------------------
452 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
455 return GetNextGroup(str
, lIndex
);
458 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
460 if ( size_t(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
461 str
= m_pCurrentGroup
->Groups()[lIndex
++]->Name();
468 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
471 return GetNextEntry(str
, lIndex
);
474 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
476 if ( size_t(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
477 str
= m_pCurrentGroup
->Entries()[lIndex
++]->Name();
484 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
486 size_t n
= m_pCurrentGroup
->Entries().Count();
488 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
489 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
490 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
491 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
492 n
+= GetNumberOfEntries(TRUE
);
493 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
500 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
502 size_t n
= m_pCurrentGroup
->Groups().Count();
504 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
505 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
506 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
507 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
508 n
+= GetNumberOfGroups(TRUE
);
509 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
516 // ----------------------------------------------------------------------------
517 // tests for existence
518 // ----------------------------------------------------------------------------
520 bool wxFileConfig::HasGroup(const wxString
& strName
) const
522 wxConfigPathChanger
path(this, strName
);
524 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
525 return pGroup
!= NULL
;
528 bool wxFileConfig::HasEntry(const wxString
& strName
) const
530 wxConfigPathChanger
path(this, strName
);
532 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
533 return pEntry
!= NULL
;
536 // ----------------------------------------------------------------------------
538 // ----------------------------------------------------------------------------
540 bool wxFileConfig::Read(const wxString
& key
,
541 wxString
* pStr
) const
543 wxConfigPathChanger
path(this, key
);
545 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
546 if (pEntry
== NULL
) {
550 *pStr
= ExpandEnvVars(pEntry
->Value());
555 bool wxFileConfig::Read(const wxString
& key
,
556 wxString
* pStr
, const wxString
& defVal
) const
558 wxConfigPathChanger
path(this, key
);
560 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
561 if (pEntry
== NULL
) {
562 if( IsRecordingDefaults() )
563 ((wxFileConfig
*)this)->Write(key
,defVal
);
564 *pStr
= ExpandEnvVars(defVal
);
568 *pStr
= ExpandEnvVars(pEntry
->Value());
573 bool wxFileConfig::Read(const wxString
& key
, long *pl
) const
576 if ( Read(key
, & str
) ) {
585 bool wxFileConfig::Write(const wxString
& key
, const wxString
& szValue
)
587 wxConfigPathChanger
path(this, key
);
589 wxString strName
= path
.Name();
590 if ( strName
.IsEmpty() ) {
591 // setting the value of a group is an error
592 wxASSERT_MSG( wxIsEmpty(szValue
), _T("can't set value of a group!") );
594 // ... except if it's empty in which case it's a way to force it's creation
595 m_pCurrentGroup
->SetDirty();
597 // this will add a line for this group if it didn't have it before
598 (void)m_pCurrentGroup
->GetGroupLine();
603 // check that the name is reasonable
604 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
605 wxLogError(_("Config entry name cannot start with '%c'."),
606 wxCONFIG_IMMUTABLE_PREFIX
);
610 strName
= FilterOutEntryName(strName
);
612 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
613 if ( pEntry
== NULL
)
614 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
616 pEntry
->SetValue(szValue
);
622 bool wxFileConfig::Write(const wxString
& key
, long lValue
)
624 // ltoa() is not ANSI :-(
626 buf
.Printf(_T("%ld"), lValue
);
627 return Write(key
, buf
);
630 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
632 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() )
635 wxTempFile
file(m_strLocalFile
);
637 if ( !file
.IsOpened() ) {
638 wxLogError(_("can't open user configuration file."));
642 // write all strings to file
643 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
644 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
645 wxLogError(_("can't write user configuration file."));
650 return file
.Commit();
653 // ----------------------------------------------------------------------------
654 // renaming groups/entries
655 // ----------------------------------------------------------------------------
657 bool wxFileConfig::RenameEntry(const wxString
& oldName
,
658 const wxString
& newName
)
660 // check that the entry exists
661 ConfigEntry
*oldEntry
= m_pCurrentGroup
->FindEntry(oldName
);
665 // check that the new entry doesn't already exist
666 if ( m_pCurrentGroup
->FindEntry(newName
) )
669 // delete the old entry, create the new one
670 wxString value
= oldEntry
->Value();
671 if ( !m_pCurrentGroup
->DeleteEntry(oldName
) )
674 ConfigEntry
*newEntry
= m_pCurrentGroup
->AddEntry(newName
);
675 newEntry
->SetValue(value
);
680 bool wxFileConfig::RenameGroup(const wxString
& oldName
,
681 const wxString
& newName
)
683 // check that the group exists
684 ConfigGroup
*group
= m_pCurrentGroup
->FindSubgroup(oldName
);
688 // check that the new group doesn't already exist
689 if ( m_pCurrentGroup
->FindSubgroup(newName
) )
692 group
->Rename(newName
);
697 // ----------------------------------------------------------------------------
698 // delete groups/entries
699 // ----------------------------------------------------------------------------
701 bool wxFileConfig::DeleteEntry(const wxString
& key
, bool bGroupIfEmptyAlso
)
703 wxConfigPathChanger
path(this, key
);
705 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
708 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
709 if ( m_pCurrentGroup
!= m_pRootGroup
) {
710 ConfigGroup
*pGroup
= m_pCurrentGroup
;
711 SetPath(_T("..")); // changes m_pCurrentGroup!
712 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
714 //else: never delete the root group
720 bool wxFileConfig::DeleteGroup(const wxString
& key
)
722 wxConfigPathChanger
path(this, key
);
724 return m_pCurrentGroup
->DeleteSubgroupByName(path
.Name());
727 bool wxFileConfig::DeleteAll()
731 if ( remove(m_strLocalFile
.fn_str()) == -1 )
732 wxLogSysError(_("can't delete user configuration file '%s'"), m_strLocalFile
.c_str());
734 m_strLocalFile
= m_strGlobalFile
= _T("");
740 // ----------------------------------------------------------------------------
741 // linked list functions
742 // ----------------------------------------------------------------------------
744 // append a new line to the end of the list
745 LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
747 LineList
*pLine
= new LineList(str
);
749 if ( m_linesTail
== NULL
) {
755 m_linesTail
->SetNext(pLine
);
756 pLine
->SetPrev(m_linesTail
);
763 // insert a new line after the given one or in the very beginning if !pLine
764 LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
767 if ( pLine
== m_linesTail
)
768 return LineListAppend(str
);
770 LineList
*pNewLine
= new LineList(str
);
771 if ( pLine
== NULL
) {
772 // prepend to the list
773 pNewLine
->SetNext(m_linesHead
);
774 m_linesHead
->SetPrev(pNewLine
);
775 m_linesHead
= pNewLine
;
778 // insert before pLine
779 LineList
*pNext
= pLine
->Next();
780 pNewLine
->SetNext(pNext
);
781 pNewLine
->SetPrev(pLine
);
782 pNext
->SetPrev(pNewLine
);
783 pLine
->SetNext(pNewLine
);
789 void wxFileConfig::LineListRemove(LineList
*pLine
)
791 LineList
*pPrev
= pLine
->Prev(),
792 *pNext
= pLine
->Next();
798 pPrev
->SetNext(pNext
);
804 pNext
->SetPrev(pPrev
);
809 bool wxFileConfig::LineListIsEmpty()
811 return m_linesHead
== NULL
;
814 // ============================================================================
815 // wxFileConfig::ConfigGroup
816 // ============================================================================
818 // ----------------------------------------------------------------------------
820 // ----------------------------------------------------------------------------
823 ConfigGroup::ConfigGroup(ConfigGroup
*pParent
,
824 const wxString
& strName
,
825 wxFileConfig
*pConfig
)
826 : m_aEntries(CompareEntries
),
827 m_aSubgroups(CompareGroups
),
839 // dtor deletes all children
840 ConfigGroup::~ConfigGroup()
843 size_t n
, nCount
= m_aEntries
.Count();
844 for ( n
= 0; n
< nCount
; n
++ )
845 delete m_aEntries
[n
];
848 nCount
= m_aSubgroups
.Count();
849 for ( n
= 0; n
< nCount
; n
++ )
850 delete m_aSubgroups
[n
];
853 // ----------------------------------------------------------------------------
855 // ----------------------------------------------------------------------------
857 void ConfigGroup::SetLine(LineList
*pLine
)
859 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
865 This is a bit complicated, so let me explain it in details. All lines that
866 were read from the local file (the only one we will ever modify) are stored
867 in a (doubly) linked list. Our problem is to know at which position in this
868 list should we insert the new entries/subgroups. To solve it we keep three
869 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
871 m_pLine points to the line containing "[group_name]"
872 m_pLastEntry points to the last entry of this group in the local file.
873 m_pLastGroup subgroup
875 Initially, they're NULL all three. When the group (an entry/subgroup) is read
876 from the local file, the corresponding variable is set. However, if the group
877 was read from the global file and then modified or created by the application
878 these variables are still NULL and we need to create the corresponding lines.
879 See the following functions (and comments preceding them) for the details of
882 Also, when our last entry/group are deleted we need to find the new last
883 element - the code in DeleteEntry/Subgroup does this by backtracking the list
884 of lines until it either founds an entry/subgroup (and this is the new last
885 element) or the m_pLine of the group, in which case there are no more entries
886 (or subgroups) left and m_pLast<element> becomes NULL.
888 NB: This last problem could be avoided for entries if we added new entries
889 immediately after m_pLine, but in this case the entries would appear
890 backwards in the config file (OTOH, it's not that important) and as we
891 would still need to do it for the subgroups the code wouldn't have been
892 significantly less complicated.
895 // Return the line which contains "[our name]". If we're still not in the list,
896 // add our line to it immediately after the last line of our parent group if we
897 // have it or in the very beginning if we're the root group.
898 LineList
*ConfigGroup::GetGroupLine()
900 if ( m_pLine
== NULL
) {
901 ConfigGroup
*pParent
= Parent();
903 // this group wasn't present in local config file, add it now
904 if ( pParent
!= NULL
) {
905 wxString strFullName
;
906 strFullName
<< _T("[") << (GetFullName().c_str() + 1) << _T("]"); // +1: no '/'
907 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
908 pParent
->GetLastGroupLine());
909 pParent
->SetLastGroup(this); // we're surely after all the others
912 // we return NULL, so that LineListInsert() will insert us in the
920 // Return the last line belonging to the subgroups of this group (after which
921 // we can add a new subgroup), if we don't have any subgroups or entries our
922 // last line is the group line (m_pLine) itself.
923 LineList
*ConfigGroup::GetLastGroupLine()
925 // if we have any subgroups, our last line is the last line of the last
927 if ( m_pLastGroup
!= NULL
) {
928 LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
930 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
934 // no subgroups, so the last line is the line of thelast entry (if any)
935 return GetLastEntryLine();
938 // return the last line belonging to the entries of this group (after which
939 // we can add a new entry), if we don't have any entries we will add the new
940 // one immediately after the group line itself.
941 LineList
*ConfigGroup::GetLastEntryLine()
943 if ( m_pLastEntry
!= NULL
) {
944 LineList
*pLine
= m_pLastEntry
->GetLine();
946 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
950 // no entries: insert after the group header
951 return GetGroupLine();
954 // ----------------------------------------------------------------------------
956 // ----------------------------------------------------------------------------
958 void ConfigGroup::Rename(const wxString
& newName
)
962 LineList
*line
= GetGroupLine();
963 wxString strFullName
;
964 strFullName
<< _T("[") << (GetFullName().c_str() + 1) << _T("]"); // +1: no '/'
965 line
->SetText(strFullName
);
970 wxString
ConfigGroup::GetFullName() const
973 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
978 // ----------------------------------------------------------------------------
980 // ----------------------------------------------------------------------------
982 // use binary search because the array is sorted
984 ConfigGroup::FindEntry(const wxChar
*szName
) const
988 hi
= m_aEntries
.Count();
994 pEntry
= m_aEntries
[i
];
996 #if wxCONFIG_CASE_SENSITIVE
997 res
= wxStrcmp(pEntry
->Name(), szName
);
999 res
= wxStricmp(pEntry
->Name(), szName
);
1014 ConfigGroup::FindSubgroup(const wxChar
*szName
) const
1018 hi
= m_aSubgroups
.Count();
1020 ConfigGroup
*pGroup
;
1024 pGroup
= m_aSubgroups
[i
];
1026 #if wxCONFIG_CASE_SENSITIVE
1027 res
= wxStrcmp(pGroup
->Name(), szName
);
1029 res
= wxStricmp(pGroup
->Name(), szName
);
1043 // ----------------------------------------------------------------------------
1044 // create a new item
1045 // ----------------------------------------------------------------------------
1047 // create a new entry and add it to the current group
1049 ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
1051 wxASSERT( FindEntry(strName
) == NULL
);
1053 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
1054 m_aEntries
.Add(pEntry
);
1059 // create a new group and add it to the current group
1061 ConfigGroup::AddSubgroup(const wxString
& strName
)
1063 wxASSERT( FindSubgroup(strName
) == NULL
);
1065 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
1066 m_aSubgroups
.Add(pGroup
);
1071 // ----------------------------------------------------------------------------
1073 // ----------------------------------------------------------------------------
1076 The delete operations are _very_ slow if we delete the last item of this
1077 group (see comments before GetXXXLineXXX functions for more details),
1078 so it's much better to start with the first entry/group if we want to
1079 delete several of them.
1082 bool ConfigGroup::DeleteSubgroupByName(const wxChar
*szName
)
1084 return DeleteSubgroup(FindSubgroup(szName
));
1087 // doesn't delete the subgroup itself, but does remove references to it from
1088 // all other data structures (and normally the returned pointer should be
1089 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1090 bool ConfigGroup::DeleteSubgroup(ConfigGroup
*pGroup
)
1092 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1094 // delete all entries
1095 size_t nCount
= pGroup
->m_aEntries
.Count();
1096 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1097 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1098 if ( pLine
!= NULL
)
1099 m_pConfig
->LineListRemove(pLine
);
1102 // and subgroups of this sungroup
1103 nCount
= pGroup
->m_aSubgroups
.Count();
1104 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1105 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[0]);
1108 LineList
*pLine
= pGroup
->m_pLine
;
1109 if ( pLine
!= NULL
) {
1110 // notice that we may do this test inside the previous "if" because the
1111 // last entry's line is surely !NULL
1112 if ( pGroup
== m_pLastGroup
) {
1113 // our last entry is being deleted - find the last one which stays
1114 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1116 // go back until we find a subgroup or reach the group's line
1117 ConfigGroup
*pNewLast
= NULL
;
1118 size_t n
, nSubgroups
= m_aSubgroups
.Count();
1120 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1121 // is it our subgroup?
1122 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1123 // do _not_ call GetGroupLine! we don't want to add it to the local
1124 // file if it's not already there
1125 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1126 pNewLast
= m_aSubgroups
[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_pLastGroup
= NULL
;
1140 m_pLastGroup
= pNewLast
;
1143 m_pConfig
->LineListRemove(pLine
);
1148 m_aSubgroups
.Remove(pGroup
);
1154 bool ConfigGroup::DeleteEntry(const wxChar
*szName
)
1156 ConfigEntry
*pEntry
= FindEntry(szName
);
1157 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1159 LineList
*pLine
= pEntry
->GetLine();
1160 if ( pLine
!= NULL
) {
1161 // notice that we may do this test inside the previous "if" because the
1162 // last entry's line is surely !NULL
1163 if ( pEntry
== m_pLastEntry
) {
1164 // our last entry is being deleted - find the last one which stays
1165 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1167 // go back until we find another entry or reach the group's line
1168 ConfigEntry
*pNewLast
= NULL
;
1169 size_t n
, nEntries
= m_aEntries
.Count();
1171 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1172 // is it our subgroup?
1173 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1174 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1175 pNewLast
= m_aEntries
[n
];
1178 if ( pNewLast
!= NULL
) // found?
1182 if ( pl
== m_pLine
) {
1183 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1185 // we've reached the group line without finding any subgroups
1186 m_pLastEntry
= NULL
;
1189 m_pLastEntry
= pNewLast
;
1192 m_pConfig
->LineListRemove(pLine
);
1195 // we must be written back for the changes to be saved
1198 m_aEntries
.Remove(pEntry
);
1204 // ----------------------------------------------------------------------------
1206 // ----------------------------------------------------------------------------
1207 void ConfigGroup::SetDirty()
1210 if ( Parent() != NULL
) // propagate upwards
1211 Parent()->SetDirty();
1214 // ============================================================================
1215 // wxFileConfig::ConfigEntry
1216 // ============================================================================
1218 // ----------------------------------------------------------------------------
1220 // ----------------------------------------------------------------------------
1221 ConfigEntry::ConfigEntry(ConfigGroup
*pParent
,
1222 const wxString
& strName
,
1224 : m_strName(strName
)
1226 wxASSERT( !strName
.IsEmpty() );
1228 m_pParent
= pParent
;
1234 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1236 m_strName
.erase(0, 1); // remove first character
1239 // ----------------------------------------------------------------------------
1241 // ----------------------------------------------------------------------------
1243 void ConfigEntry::SetLine(LineList
*pLine
)
1245 if ( m_pLine
!= NULL
) {
1246 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1247 Name().c_str(), m_pParent
->GetFullName().c_str());
1251 Group()->SetLastEntry(this);
1254 // second parameter is FALSE if we read the value from file and prevents the
1255 // entry from being marked as 'dirty'
1256 void ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1258 if ( bUser
&& IsImmutable() ) {
1259 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1264 // do nothing if it's the same value
1265 if ( strValue
== m_strValue
)
1268 m_strValue
= strValue
;
1271 wxString strVal
= FilterOutValue(strValue
);
1273 strLine
<< m_strName
<< _T(" = ") << strVal
;
1275 if ( m_pLine
!= NULL
) {
1276 // entry was read from the local config file, just modify the line
1277 m_pLine
->SetText(strLine
);
1280 // add a new line to the file
1281 wxASSERT( m_nLine
== wxNOT_FOUND
); // consistency check
1283 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1284 Group()->GetLastEntryLine());
1285 Group()->SetLastEntry(this);
1292 void ConfigEntry::SetDirty()
1295 Group()->SetDirty();
1298 // ============================================================================
1300 // ============================================================================
1302 // ----------------------------------------------------------------------------
1303 // compare functions for array sorting
1304 // ----------------------------------------------------------------------------
1306 int CompareEntries(ConfigEntry
*p1
,
1309 #if wxCONFIG_CASE_SENSITIVE
1310 return wxStrcmp(p1
->Name(), p2
->Name());
1312 return wxStricmp(p1
->Name(), p2
->Name());
1316 int CompareGroups(ConfigGroup
*p1
,
1319 #if wxCONFIG_CASE_SENSITIVE
1320 return wxStrcmp(p1
->Name(), p2
->Name());
1322 return wxStricmp(p1
->Name(), p2
->Name());
1326 // ----------------------------------------------------------------------------
1328 // ----------------------------------------------------------------------------
1330 // undo FilterOutValue
1331 static wxString
FilterInValue(const wxString
& str
)
1334 strResult
.Alloc(str
.Len());
1336 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1338 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1339 if ( str
[n
] == _T('\\') ) {
1340 switch ( str
[++n
] ) {
1342 strResult
+= _T('\n');
1346 strResult
+= _T('\r');
1350 strResult
+= _T('\t');
1354 strResult
+= _T('\\');
1358 strResult
+= _T('"');
1363 if ( str
[n
] != _T('"') || !bQuoted
)
1364 strResult
+= str
[n
];
1365 else if ( n
!= str
.Len() - 1 ) {
1366 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1369 //else: it's the last quote of a quoted string, ok
1376 // quote the string before writing it to file
1377 static wxString
FilterOutValue(const wxString
& str
)
1383 strResult
.Alloc(str
.Len());
1385 // quoting is necessary to preserve spaces in the beginning of the string
1386 bool bQuote
= wxIsspace(str
[0]) || str
[0] == _T('"');
1389 strResult
+= _T('"');
1392 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
1415 //else: fall through
1418 strResult
+= str
[n
];
1419 continue; // nothing special to do
1422 // we get here only for special characters
1423 strResult
<< _T('\\') << c
;
1427 strResult
+= _T('"');
1432 // undo FilterOutEntryName
1433 static wxString
FilterInEntryName(const wxString
& str
)
1436 strResult
.Alloc(str
.Len());
1438 for ( const wxChar
*pc
= str
.c_str(); *pc
!= '\0'; pc
++ ) {
1439 if ( *pc
== _T('\\') )
1448 // sanitize entry or group name: insert '\\' before any special characters
1449 static wxString
FilterOutEntryName(const wxString
& str
)
1452 strResult
.Alloc(str
.Len());
1454 for ( const wxChar
*pc
= str
.c_str(); *pc
!= _T('\0'); pc
++ ) {
1457 // we explicitly allow some of "safe" chars and 8bit ASCII characters
1458 // which will probably never have special meaning
1459 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
1460 // should *not* be quoted
1461 if ( !wxIsalnum(c
) && !wxStrchr(_T("@_/-!.*%"), c
) && ((c
& 0x80) == 0) )
1462 strResult
+= _T('\\');