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 // get the name to use in wxFileConfig ctor
82 static wxString
GetAppName(const wxString
& appname
);
84 // ============================================================================
86 // ============================================================================
88 // ----------------------------------------------------------------------------
90 // ----------------------------------------------------------------------------
91 wxString
wxFileConfig::GetGlobalDir()
97 #elif defined(__WXSTUBS__)
98 wxASSERT_MSG( FALSE
, _T("TODO") ) ;
99 #elif defined(__WXMAC__)
100 wxASSERT_MSG( FALSE
, _T("TODO") ) ;
102 wxChar szWinDir
[MAX_PATH
];
103 ::GetWindowsDirectory(szWinDir
, MAX_PATH
);
107 #endif // Unix/Windows
112 wxString
wxFileConfig::GetLocalDir()
116 wxGetHomeDir(&strDir
);
119 if (strDir
.Last() != _T('/')) strDir
<< _T('/');
121 if (strDir
.Last() != _T('\\')) strDir
<< _T('\\');
127 wxString
wxFileConfig::GetGlobalFileName(const wxChar
*szFile
)
129 wxString str
= GetGlobalDir();
132 if ( wxStrchr(szFile
, _T('.')) == NULL
)
142 wxString
wxFileConfig::GetLocalFileName(const wxChar
*szFile
)
144 wxString str
= GetLocalDir();
153 if ( wxStrchr(szFile
, _T('.')) == 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(::GetAppName(appName
), vendorName
,
207 m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
209 // Make up names for files if empty
210 if ( m_strLocalFile
.IsEmpty() && (style
& wxCONFIG_USE_LOCAL_FILE
) )
212 m_strLocalFile
= GetLocalFileName(GetAppName());
215 if ( m_strGlobalFile
.IsEmpty() && (style
& wxCONFIG_USE_GLOBAL_FILE
) )
217 m_strGlobalFile
= GetGlobalFileName(GetAppName());
220 // Check if styles are not supplied, but filenames are, in which case
221 // add the correct styles.
222 if ( !m_strLocalFile
.IsEmpty() )
223 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE
);
225 if ( !m_strGlobalFile
.IsEmpty() )
226 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE
);
228 // if the path is not absolute, prepend the standard directory to it
229 if ( !m_strLocalFile
.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile
) )
231 wxString strLocal
= m_strLocalFile
;
232 m_strLocalFile
= GetLocalDir();
233 m_strLocalFile
<< strLocal
;
236 if ( !m_strGlobalFile
.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile
) )
238 wxString strGlobal
= m_strGlobalFile
;
239 m_strGlobalFile
= GetGlobalDir();
240 m_strGlobalFile
<< strGlobal
;
246 void wxFileConfig::CleanUp()
250 LineList
*pCur
= m_linesHead
;
251 while ( pCur
!= NULL
) {
252 LineList
*pNext
= pCur
->Next();
258 wxFileConfig::~wxFileConfig()
265 // ----------------------------------------------------------------------------
266 // parse a config file
267 // ----------------------------------------------------------------------------
269 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
271 const wxChar
*pStart
;
275 size_t nLineCount
= file
.GetLineCount();
276 for ( size_t n
= 0; n
< nLineCount
; n
++ ) {
279 // add the line to linked list
281 LineListAppend(strLine
);
283 // skip leading spaces
284 for ( pStart
= strLine
; wxIsspace(*pStart
); pStart
++ )
287 // skip blank/comment lines
288 if ( *pStart
== _T('\0')|| *pStart
== _T(';') || *pStart
== _T('#') )
291 if ( *pStart
== _T('[') ) { // a new group
294 while ( *++pEnd
!= _T(']') ) {
295 if ( *pEnd
== _T('\n') || *pEnd
== _T('\0') )
299 if ( *pEnd
!= _T(']') ) {
300 wxLogError(_("file '%s': unexpected character %c at line %d."),
301 file
.GetName(), *pEnd
, n
+ 1);
302 continue; // skip this line
305 // group name here is always considered as abs path
308 strGroup
<< wxCONFIG_PATH_SEPARATOR
309 << FilterInEntryName(wxString(pStart
, pEnd
- pStart
));
311 // will create it if doesn't yet exist
315 m_pCurrentGroup
->SetLine(m_linesTail
);
317 // check that there is nothing except comments left on this line
319 while ( *++pEnd
!= _T('\0') && bCont
) {
328 // ignore whitespace ('\n' impossible here)
332 wxLogWarning(_("file '%s', line %d: '%s' "
333 "ignored after group header."),
334 file
.GetName(), n
+ 1, pEnd
);
340 const wxChar
*pEnd
= pStart
;
341 while ( !wxIsspace(*pEnd
) ) {
342 if ( *pEnd
== _T('\\') ) {
343 // next character may be space or not - still take it because it's
351 wxString
strKey(FilterInEntryName(wxString(pStart
, pEnd
)));
354 while ( isspace(*pEnd
) )
357 if ( *pEnd
++ != _T('=') ) {
358 wxLogError(_("file '%s', line %d: '=' expected."),
359 file
.GetName(), n
+ 1);
362 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
364 if ( pEntry
== NULL
) {
366 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
369 pEntry
->SetLine(m_linesTail
);
372 if ( bLocal
&& pEntry
->IsImmutable() ) {
373 // immutable keys can't be changed by user
374 wxLogWarning(_("file '%s', line %d: value for "
375 "immutable key '%s' ignored."),
376 file
.GetName(), n
+ 1, strKey
.c_str());
379 // the condition below catches the cases (a) and (b) but not (c):
380 // (a) global key found second time in global file
381 // (b) key found second (or more) time in local file
382 // (c) key from global file now found in local one
383 // which is exactly what we want.
384 else if ( !bLocal
|| pEntry
->IsLocal() ) {
385 wxLogWarning(_("file '%s', line %d: key '%s' was first "
386 "found at line %d."),
387 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
390 pEntry
->SetLine(m_linesTail
);
395 while ( wxIsspace(*pEnd
) )
398 pEntry
->SetValue(FilterInValue(pEnd
), FALSE
/* read from file */);
404 // ----------------------------------------------------------------------------
406 // ----------------------------------------------------------------------------
408 void wxFileConfig::SetRootPath()
411 m_pCurrentGroup
= m_pRootGroup
;
414 void wxFileConfig::SetPath(const wxString
& strPath
)
416 wxArrayString aParts
;
418 if ( strPath
.IsEmpty() ) {
423 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
425 wxSplitPath(aParts
, strPath
);
428 // relative path, combine with current one
429 wxString strFullPath
= m_strPath
;
430 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
431 wxSplitPath(aParts
, strFullPath
);
434 // change current group
436 m_pCurrentGroup
= m_pRootGroup
;
437 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
438 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
439 if ( pNextGroup
== NULL
)
440 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
441 m_pCurrentGroup
= pNextGroup
;
444 // recombine path parts in one variable
446 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
447 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
451 // ----------------------------------------------------------------------------
453 // ----------------------------------------------------------------------------
455 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
458 return GetNextGroup(str
, lIndex
);
461 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
463 if ( size_t(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
464 str
= m_pCurrentGroup
->Groups()[lIndex
++]->Name();
471 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
474 return GetNextEntry(str
, lIndex
);
477 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
479 if ( size_t(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
480 str
= m_pCurrentGroup
->Entries()[lIndex
++]->Name();
487 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
489 size_t n
= m_pCurrentGroup
->Entries().Count();
491 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
492 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
493 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
494 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
495 n
+= GetNumberOfEntries(TRUE
);
496 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
503 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
505 size_t n
= m_pCurrentGroup
->Groups().Count();
507 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
508 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
509 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
510 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
511 n
+= GetNumberOfGroups(TRUE
);
512 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
519 // ----------------------------------------------------------------------------
520 // tests for existence
521 // ----------------------------------------------------------------------------
523 bool wxFileConfig::HasGroup(const wxString
& strName
) const
525 wxConfigPathChanger
path(this, strName
);
527 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
528 return pGroup
!= NULL
;
531 bool wxFileConfig::HasEntry(const wxString
& strName
) const
533 wxConfigPathChanger
path(this, strName
);
535 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
536 return pEntry
!= NULL
;
539 // ----------------------------------------------------------------------------
541 // ----------------------------------------------------------------------------
543 bool wxFileConfig::Read(const wxString
& key
,
544 wxString
* pStr
) const
546 wxConfigPathChanger
path(this, key
);
548 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
549 if (pEntry
== NULL
) {
553 *pStr
= ExpandEnvVars(pEntry
->Value());
558 bool wxFileConfig::Read(const wxString
& key
,
559 wxString
* pStr
, const wxString
& defVal
) const
561 wxConfigPathChanger
path(this, key
);
563 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
564 if (pEntry
== NULL
) {
565 if( IsRecordingDefaults() )
566 ((wxFileConfig
*)this)->Write(key
,defVal
);
567 *pStr
= ExpandEnvVars(defVal
);
571 *pStr
= ExpandEnvVars(pEntry
->Value());
576 bool wxFileConfig::Read(const wxString
& key
, long *pl
) const
579 if ( Read(key
, & str
) ) {
588 bool wxFileConfig::Write(const wxString
& key
, const wxString
& szValue
)
590 wxConfigPathChanger
path(this, key
);
592 wxString strName
= path
.Name();
593 if ( strName
.IsEmpty() ) {
594 // setting the value of a group is an error
595 wxASSERT_MSG( wxIsEmpty(szValue
), _T("can't set value of a group!") );
597 // ... except if it's empty in which case it's a way to force it's creation
598 m_pCurrentGroup
->SetDirty();
600 // this will add a line for this group if it didn't have it before
601 (void)m_pCurrentGroup
->GetGroupLine();
606 // check that the name is reasonable
607 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
608 wxLogError(_("Config entry name cannot start with '%c'."),
609 wxCONFIG_IMMUTABLE_PREFIX
);
613 strName
= FilterOutEntryName(strName
);
615 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
616 if ( pEntry
== NULL
)
617 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
619 pEntry
->SetValue(szValue
);
625 bool wxFileConfig::Write(const wxString
& key
, long lValue
)
627 // ltoa() is not ANSI :-(
629 buf
.Printf(_T("%ld"), lValue
);
630 return Write(key
, buf
);
633 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
635 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() )
638 wxTempFile
file(m_strLocalFile
);
640 if ( !file
.IsOpened() ) {
641 wxLogError(_("can't open user configuration file."));
645 // write all strings to file
646 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
647 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
648 wxLogError(_("can't write user configuration file."));
653 return file
.Commit();
656 // ----------------------------------------------------------------------------
657 // renaming groups/entries
658 // ----------------------------------------------------------------------------
660 bool wxFileConfig::RenameEntry(const wxString
& oldName
,
661 const wxString
& newName
)
663 // check that the entry exists
664 ConfigEntry
*oldEntry
= m_pCurrentGroup
->FindEntry(oldName
);
668 // check that the new entry doesn't already exist
669 if ( m_pCurrentGroup
->FindEntry(newName
) )
672 // delete the old entry, create the new one
673 wxString value
= oldEntry
->Value();
674 if ( !m_pCurrentGroup
->DeleteEntry(oldName
) )
677 ConfigEntry
*newEntry
= m_pCurrentGroup
->AddEntry(newName
);
678 newEntry
->SetValue(value
);
683 bool wxFileConfig::RenameGroup(const wxString
& oldName
,
684 const wxString
& newName
)
686 // check that the group exists
687 ConfigGroup
*group
= m_pCurrentGroup
->FindSubgroup(oldName
);
691 // check that the new group doesn't already exist
692 if ( m_pCurrentGroup
->FindSubgroup(newName
) )
695 group
->Rename(newName
);
700 // ----------------------------------------------------------------------------
701 // delete groups/entries
702 // ----------------------------------------------------------------------------
704 bool wxFileConfig::DeleteEntry(const wxString
& key
, bool bGroupIfEmptyAlso
)
706 wxConfigPathChanger
path(this, key
);
708 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
711 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
712 if ( m_pCurrentGroup
!= m_pRootGroup
) {
713 ConfigGroup
*pGroup
= m_pCurrentGroup
;
714 SetPath(_T("..")); // changes m_pCurrentGroup!
715 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
717 //else: never delete the root group
723 bool wxFileConfig::DeleteGroup(const wxString
& key
)
725 wxConfigPathChanger
path(this, key
);
727 return m_pCurrentGroup
->DeleteSubgroupByName(path
.Name());
730 bool wxFileConfig::DeleteAll()
734 if ( remove(m_strLocalFile
.fn_str()) == -1 )
735 wxLogSysError(_("can't delete user configuration file '%s'"), m_strLocalFile
.c_str());
737 m_strLocalFile
= m_strGlobalFile
= _T("");
743 // ----------------------------------------------------------------------------
744 // linked list functions
745 // ----------------------------------------------------------------------------
747 // append a new line to the end of the list
748 LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
750 LineList
*pLine
= new LineList(str
);
752 if ( m_linesTail
== NULL
) {
758 m_linesTail
->SetNext(pLine
);
759 pLine
->SetPrev(m_linesTail
);
766 // insert a new line after the given one or in the very beginning if !pLine
767 LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
770 if ( pLine
== m_linesTail
)
771 return LineListAppend(str
);
773 LineList
*pNewLine
= new LineList(str
);
774 if ( pLine
== NULL
) {
775 // prepend to the list
776 pNewLine
->SetNext(m_linesHead
);
777 m_linesHead
->SetPrev(pNewLine
);
778 m_linesHead
= pNewLine
;
781 // insert before pLine
782 LineList
*pNext
= pLine
->Next();
783 pNewLine
->SetNext(pNext
);
784 pNewLine
->SetPrev(pLine
);
785 pNext
->SetPrev(pNewLine
);
786 pLine
->SetNext(pNewLine
);
792 void wxFileConfig::LineListRemove(LineList
*pLine
)
794 LineList
*pPrev
= pLine
->Prev(),
795 *pNext
= pLine
->Next();
801 pPrev
->SetNext(pNext
);
807 pNext
->SetPrev(pPrev
);
812 bool wxFileConfig::LineListIsEmpty()
814 return m_linesHead
== NULL
;
817 // ============================================================================
818 // wxFileConfig::ConfigGroup
819 // ============================================================================
821 // ----------------------------------------------------------------------------
823 // ----------------------------------------------------------------------------
826 ConfigGroup::ConfigGroup(ConfigGroup
*pParent
,
827 const wxString
& strName
,
828 wxFileConfig
*pConfig
)
829 : m_aEntries(CompareEntries
),
830 m_aSubgroups(CompareGroups
),
842 // dtor deletes all children
843 ConfigGroup::~ConfigGroup()
846 size_t n
, nCount
= m_aEntries
.Count();
847 for ( n
= 0; n
< nCount
; n
++ )
848 delete m_aEntries
[n
];
851 nCount
= m_aSubgroups
.Count();
852 for ( n
= 0; n
< nCount
; n
++ )
853 delete m_aSubgroups
[n
];
856 // ----------------------------------------------------------------------------
858 // ----------------------------------------------------------------------------
860 void ConfigGroup::SetLine(LineList
*pLine
)
862 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
868 This is a bit complicated, so let me explain it in details. All lines that
869 were read from the local file (the only one we will ever modify) are stored
870 in a (doubly) linked list. Our problem is to know at which position in this
871 list should we insert the new entries/subgroups. To solve it we keep three
872 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
874 m_pLine points to the line containing "[group_name]"
875 m_pLastEntry points to the last entry of this group in the local file.
876 m_pLastGroup subgroup
878 Initially, they're NULL all three. When the group (an entry/subgroup) is read
879 from the local file, the corresponding variable is set. However, if the group
880 was read from the global file and then modified or created by the application
881 these variables are still NULL and we need to create the corresponding lines.
882 See the following functions (and comments preceding them) for the details of
885 Also, when our last entry/group are deleted we need to find the new last
886 element - the code in DeleteEntry/Subgroup does this by backtracking the list
887 of lines until it either founds an entry/subgroup (and this is the new last
888 element) or the m_pLine of the group, in which case there are no more entries
889 (or subgroups) left and m_pLast<element> becomes NULL.
891 NB: This last problem could be avoided for entries if we added new entries
892 immediately after m_pLine, but in this case the entries would appear
893 backwards in the config file (OTOH, it's not that important) and as we
894 would still need to do it for the subgroups the code wouldn't have been
895 significantly less complicated.
898 // Return the line which contains "[our name]". If we're still not in the list,
899 // add our line to it immediately after the last line of our parent group if we
900 // have it or in the very beginning if we're the root group.
901 LineList
*ConfigGroup::GetGroupLine()
903 if ( m_pLine
== NULL
) {
904 ConfigGroup
*pParent
= Parent();
906 // this group wasn't present in local config file, add it now
907 if ( pParent
!= NULL
) {
908 wxString strFullName
;
909 strFullName
<< _T("[") << (GetFullName().c_str() + 1) << _T("]"); // +1: no '/'
910 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
911 pParent
->GetLastGroupLine());
912 pParent
->SetLastGroup(this); // we're surely after all the others
915 // we return NULL, so that LineListInsert() will insert us in the
923 // Return the last line belonging to the subgroups of this group (after which
924 // we can add a new subgroup), if we don't have any subgroups or entries our
925 // last line is the group line (m_pLine) itself.
926 LineList
*ConfigGroup::GetLastGroupLine()
928 // if we have any subgroups, our last line is the last line of the last
930 if ( m_pLastGroup
!= NULL
) {
931 LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
933 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
937 // no subgroups, so the last line is the line of thelast entry (if any)
938 return GetLastEntryLine();
941 // return the last line belonging to the entries of this group (after which
942 // we can add a new entry), if we don't have any entries we will add the new
943 // one immediately after the group line itself.
944 LineList
*ConfigGroup::GetLastEntryLine()
946 if ( m_pLastEntry
!= NULL
) {
947 LineList
*pLine
= m_pLastEntry
->GetLine();
949 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
953 // no entries: insert after the group header
954 return GetGroupLine();
957 // ----------------------------------------------------------------------------
959 // ----------------------------------------------------------------------------
961 void ConfigGroup::Rename(const wxString
& newName
)
965 LineList
*line
= GetGroupLine();
966 wxString strFullName
;
967 strFullName
<< _T("[") << (GetFullName().c_str() + 1) << _T("]"); // +1: no '/'
968 line
->SetText(strFullName
);
973 wxString
ConfigGroup::GetFullName() const
976 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
981 // ----------------------------------------------------------------------------
983 // ----------------------------------------------------------------------------
985 // use binary search because the array is sorted
987 ConfigGroup::FindEntry(const wxChar
*szName
) const
991 hi
= m_aEntries
.Count();
997 pEntry
= m_aEntries
[i
];
999 #if wxCONFIG_CASE_SENSITIVE
1000 res
= wxStrcmp(pEntry
->Name(), szName
);
1002 res
= wxStricmp(pEntry
->Name(), szName
);
1017 ConfigGroup::FindSubgroup(const wxChar
*szName
) const
1021 hi
= m_aSubgroups
.Count();
1023 ConfigGroup
*pGroup
;
1027 pGroup
= m_aSubgroups
[i
];
1029 #if wxCONFIG_CASE_SENSITIVE
1030 res
= wxStrcmp(pGroup
->Name(), szName
);
1032 res
= wxStricmp(pGroup
->Name(), szName
);
1046 // ----------------------------------------------------------------------------
1047 // create a new item
1048 // ----------------------------------------------------------------------------
1050 // create a new entry and add it to the current group
1052 ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
1054 wxASSERT( FindEntry(strName
) == NULL
);
1056 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
1057 m_aEntries
.Add(pEntry
);
1062 // create a new group and add it to the current group
1064 ConfigGroup::AddSubgroup(const wxString
& strName
)
1066 wxASSERT( FindSubgroup(strName
) == NULL
);
1068 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
1069 m_aSubgroups
.Add(pGroup
);
1074 // ----------------------------------------------------------------------------
1076 // ----------------------------------------------------------------------------
1079 The delete operations are _very_ slow if we delete the last item of this
1080 group (see comments before GetXXXLineXXX functions for more details),
1081 so it's much better to start with the first entry/group if we want to
1082 delete several of them.
1085 bool ConfigGroup::DeleteSubgroupByName(const wxChar
*szName
)
1087 return DeleteSubgroup(FindSubgroup(szName
));
1090 // doesn't delete the subgroup itself, but does remove references to it from
1091 // all other data structures (and normally the returned pointer should be
1092 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1093 bool ConfigGroup::DeleteSubgroup(ConfigGroup
*pGroup
)
1095 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1097 // delete all entries
1098 size_t nCount
= pGroup
->m_aEntries
.Count();
1099 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1100 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1101 if ( pLine
!= NULL
)
1102 m_pConfig
->LineListRemove(pLine
);
1105 // and subgroups of this sungroup
1106 nCount
= pGroup
->m_aSubgroups
.Count();
1107 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1108 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[0]);
1111 LineList
*pLine
= pGroup
->m_pLine
;
1112 if ( pLine
!= NULL
) {
1113 // notice that we may do this test inside the previous "if" because the
1114 // last entry's line is surely !NULL
1115 if ( pGroup
== m_pLastGroup
) {
1116 // our last entry is being deleted - find the last one which stays
1117 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1119 // go back until we find a subgroup or reach the group's line
1120 ConfigGroup
*pNewLast
= NULL
;
1121 size_t n
, nSubgroups
= m_aSubgroups
.Count();
1123 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1124 // is it our subgroup?
1125 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1126 // do _not_ call GetGroupLine! we don't want to add it to the local
1127 // file if it's not already there
1128 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1129 pNewLast
= m_aSubgroups
[n
];
1132 if ( pNewLast
!= NULL
) // found?
1136 if ( pl
== m_pLine
) {
1137 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1139 // we've reached the group line without finding any subgroups
1140 m_pLastGroup
= NULL
;
1143 m_pLastGroup
= pNewLast
;
1146 m_pConfig
->LineListRemove(pLine
);
1151 m_aSubgroups
.Remove(pGroup
);
1157 bool ConfigGroup::DeleteEntry(const wxChar
*szName
)
1159 ConfigEntry
*pEntry
= FindEntry(szName
);
1160 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1162 LineList
*pLine
= pEntry
->GetLine();
1163 if ( pLine
!= NULL
) {
1164 // notice that we may do this test inside the previous "if" because the
1165 // last entry's line is surely !NULL
1166 if ( pEntry
== m_pLastEntry
) {
1167 // our last entry is being deleted - find the last one which stays
1168 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1170 // go back until we find another entry or reach the group's line
1171 ConfigEntry
*pNewLast
= NULL
;
1172 size_t n
, nEntries
= m_aEntries
.Count();
1174 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1175 // is it our subgroup?
1176 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1177 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1178 pNewLast
= m_aEntries
[n
];
1181 if ( pNewLast
!= NULL
) // found?
1185 if ( pl
== m_pLine
) {
1186 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1188 // we've reached the group line without finding any subgroups
1189 m_pLastEntry
= NULL
;
1192 m_pLastEntry
= pNewLast
;
1195 m_pConfig
->LineListRemove(pLine
);
1198 // we must be written back for the changes to be saved
1201 m_aEntries
.Remove(pEntry
);
1207 // ----------------------------------------------------------------------------
1209 // ----------------------------------------------------------------------------
1210 void ConfigGroup::SetDirty()
1213 if ( Parent() != NULL
) // propagate upwards
1214 Parent()->SetDirty();
1217 // ============================================================================
1218 // wxFileConfig::ConfigEntry
1219 // ============================================================================
1221 // ----------------------------------------------------------------------------
1223 // ----------------------------------------------------------------------------
1224 ConfigEntry::ConfigEntry(ConfigGroup
*pParent
,
1225 const wxString
& strName
,
1227 : m_strName(strName
)
1229 wxASSERT( !strName
.IsEmpty() );
1231 m_pParent
= pParent
;
1237 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1239 m_strName
.erase(0, 1); // remove first character
1242 // ----------------------------------------------------------------------------
1244 // ----------------------------------------------------------------------------
1246 void ConfigEntry::SetLine(LineList
*pLine
)
1248 if ( m_pLine
!= NULL
) {
1249 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1250 Name().c_str(), m_pParent
->GetFullName().c_str());
1254 Group()->SetLastEntry(this);
1257 // second parameter is FALSE if we read the value from file and prevents the
1258 // entry from being marked as 'dirty'
1259 void ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1261 if ( bUser
&& IsImmutable() ) {
1262 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1267 // do nothing if it's the same value
1268 if ( strValue
== m_strValue
)
1271 m_strValue
= strValue
;
1274 wxString strVal
= FilterOutValue(strValue
);
1276 strLine
<< m_strName
<< _T(" = ") << strVal
;
1278 if ( m_pLine
!= NULL
) {
1279 // entry was read from the local config file, just modify the line
1280 m_pLine
->SetText(strLine
);
1283 // add a new line to the file
1284 wxASSERT( m_nLine
== wxNOT_FOUND
); // consistency check
1286 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1287 Group()->GetLastEntryLine());
1288 Group()->SetLastEntry(this);
1295 void ConfigEntry::SetDirty()
1298 Group()->SetDirty();
1301 // ============================================================================
1303 // ============================================================================
1305 // ----------------------------------------------------------------------------
1306 // compare functions for array sorting
1307 // ----------------------------------------------------------------------------
1309 int CompareEntries(ConfigEntry
*p1
,
1312 #if wxCONFIG_CASE_SENSITIVE
1313 return wxStrcmp(p1
->Name(), p2
->Name());
1315 return wxStricmp(p1
->Name(), p2
->Name());
1319 int CompareGroups(ConfigGroup
*p1
,
1322 #if wxCONFIG_CASE_SENSITIVE
1323 return wxStrcmp(p1
->Name(), p2
->Name());
1325 return wxStricmp(p1
->Name(), p2
->Name());
1329 // ----------------------------------------------------------------------------
1331 // ----------------------------------------------------------------------------
1333 // undo FilterOutValue
1334 static wxString
FilterInValue(const wxString
& str
)
1337 strResult
.Alloc(str
.Len());
1339 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1341 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1342 if ( str
[n
] == _T('\\') ) {
1343 switch ( str
[++n
] ) {
1345 strResult
+= _T('\n');
1349 strResult
+= _T('\r');
1353 strResult
+= _T('\t');
1357 strResult
+= _T('\\');
1361 strResult
+= _T('"');
1366 if ( str
[n
] != _T('"') || !bQuoted
)
1367 strResult
+= str
[n
];
1368 else if ( n
!= str
.Len() - 1 ) {
1369 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1372 //else: it's the last quote of a quoted string, ok
1379 // quote the string before writing it to file
1380 static wxString
FilterOutValue(const wxString
& str
)
1386 strResult
.Alloc(str
.Len());
1388 // quoting is necessary to preserve spaces in the beginning of the string
1389 bool bQuote
= wxIsspace(str
[0]) || str
[0] == _T('"');
1392 strResult
+= _T('"');
1395 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
1418 //else: fall through
1421 strResult
+= str
[n
];
1422 continue; // nothing special to do
1425 // we get here only for special characters
1426 strResult
<< _T('\\') << c
;
1430 strResult
+= _T('"');
1435 // undo FilterOutEntryName
1436 static wxString
FilterInEntryName(const wxString
& str
)
1439 strResult
.Alloc(str
.Len());
1441 for ( const wxChar
*pc
= str
.c_str(); *pc
!= '\0'; pc
++ ) {
1442 if ( *pc
== _T('\\') )
1451 // sanitize entry or group name: insert '\\' before any special characters
1452 static wxString
FilterOutEntryName(const wxString
& str
)
1455 strResult
.Alloc(str
.Len());
1457 for ( const wxChar
*pc
= str
.c_str(); *pc
!= _T('\0'); pc
++ ) {
1460 // we explicitly allow some of "safe" chars and 8bit ASCII characters
1461 // which will probably never have special meaning
1462 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
1463 // should *not* be quoted
1464 if ( !wxIsalnum(c
) && !wxStrchr(_T("@_/-!.*%"), c
) && ((c
& 0x80) == 0) )
1465 strResult
+= _T('\\');
1473 // we can't put ?: in the ctor initializer list because it confuses some
1474 // broken compilers (Borland C++)
1475 static wxString
GetAppName(const wxString
& appName
)
1477 if ( !appName
&& wxTheApp
)
1478 return wxTheApp
->GetAppName();