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 // ----------------------------------------------------------------------------
25 #include "wx/wxprec.h"
34 #include "wx/string.h"
39 #include "wx/dynarray.h"
42 #include "wx/textfile.h"
43 #include "wx/config.h"
44 #include "wx/fileconf.h"
46 #include "wx/utils.h" // for wxGetHomeDir
48 // _WINDOWS_ is defined when windows.h is included,
49 // __WXMSW__ is defined for MS Windows compilation
50 #if defined(__WXMSW__) && !defined(_WINDOWS_)
57 // ----------------------------------------------------------------------------
59 // ----------------------------------------------------------------------------
60 #define CONST_CAST ((wxFileConfig *)this)->
62 // ----------------------------------------------------------------------------
64 // ----------------------------------------------------------------------------
69 // ----------------------------------------------------------------------------
70 // global functions declarations
71 // ----------------------------------------------------------------------------
73 // compare functions for sorting the arrays
74 static int CompareEntries(ConfigEntry
*p1
, ConfigEntry
*p2
);
75 static int CompareGroups(ConfigGroup
*p1
, ConfigGroup
*p2
);
78 static wxString
FilterInValue(const wxString
& str
);
79 static wxString
FilterOutValue(const wxString
& str
);
81 static wxString
FilterInEntryName(const wxString
& str
);
82 static wxString
FilterOutEntryName(const wxString
& str
);
84 // get the name to use in wxFileConfig ctor
85 static wxString
GetAppName(const wxString
& appname
);
87 // ============================================================================
89 // ============================================================================
91 // ----------------------------------------------------------------------------
93 // ----------------------------------------------------------------------------
94 wxString
wxFileConfig::GetGlobalDir()
100 #elif defined(__WXSTUBS__)
101 wxASSERT_MSG( FALSE
, _T("TODO") ) ;
102 #elif defined(__WXMAC__)
103 wxASSERT_MSG( FALSE
, _T("TODO") ) ;
105 wxChar szWinDir
[MAX_PATH
];
106 ::GetWindowsDirectory(szWinDir
, MAX_PATH
);
110 #endif // Unix/Windows
115 wxString
wxFileConfig::GetLocalDir()
119 wxGetHomeDir(&strDir
);
122 if (strDir
.Last() != _T('/')) strDir
<< _T('/');
124 if (strDir
.Last() != _T('\\')) strDir
<< _T('\\');
130 wxString
wxFileConfig::GetGlobalFileName(const wxChar
*szFile
)
132 wxString str
= GetGlobalDir();
135 if ( wxStrchr(szFile
, _T('.')) == NULL
)
145 wxString
wxFileConfig::GetLocalFileName(const wxChar
*szFile
)
147 wxString str
= GetLocalDir();
156 if ( wxStrchr(szFile
, _T('.')) == NULL
)
163 // ----------------------------------------------------------------------------
165 // ----------------------------------------------------------------------------
167 void wxFileConfig::Init()
170 m_pRootGroup
= new ConfigGroup(NULL
, "", this);
175 // it's not an error if (one of the) file(s) doesn't exist
177 // parse the global file
178 if ( !m_strGlobalFile
.IsEmpty() && wxFile::Exists(m_strGlobalFile
) ) {
179 wxTextFile
fileGlobal(m_strGlobalFile
);
181 if ( fileGlobal
.Open() ) {
182 Parse(fileGlobal
, FALSE
/* global */);
186 wxLogWarning(_("can't open global configuration file '%s'."),
187 m_strGlobalFile
.c_str());
190 // parse the local file
191 if ( !m_strLocalFile
.IsEmpty() && wxFile::Exists(m_strLocalFile
) ) {
192 wxTextFile
fileLocal(m_strLocalFile
);
193 if ( fileLocal
.Open() ) {
194 Parse(fileLocal
, TRUE
/* local */);
198 wxLogWarning(_("can't open user configuration file '%s'."),
199 m_strLocalFile
.c_str());
203 // constructor supports creation of wxFileConfig objects of any type
204 wxFileConfig::wxFileConfig(const wxString
& appName
, const wxString
& vendorName
,
205 const wxString
& strLocal
, const wxString
& strGlobal
,
207 : wxConfigBase(::GetAppName(appName
), vendorName
,
210 m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
212 // Make up names for files if empty
213 if ( m_strLocalFile
.IsEmpty() && (style
& wxCONFIG_USE_LOCAL_FILE
) )
215 m_strLocalFile
= GetLocalFileName(GetAppName());
218 if ( m_strGlobalFile
.IsEmpty() && (style
& wxCONFIG_USE_GLOBAL_FILE
) )
220 m_strGlobalFile
= GetGlobalFileName(GetAppName());
223 // Check if styles are not supplied, but filenames are, in which case
224 // add the correct styles.
225 if ( !m_strLocalFile
.IsEmpty() )
226 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE
);
228 if ( !m_strGlobalFile
.IsEmpty() )
229 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE
);
231 // if the path is not absolute, prepend the standard directory to it
232 // UNLESS wxCONFIG_USE_RELATIVE_PATH style is set
233 if ( !(style
& wxCONFIG_USE_RELATIVE_PATH
) ){
234 if ( !m_strLocalFile
.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile
) )
236 wxString strLocal
= m_strLocalFile
;
237 m_strLocalFile
= GetLocalDir();
238 m_strLocalFile
<< strLocal
;
241 if ( !m_strGlobalFile
.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile
) )
243 wxString strGlobal
= m_strGlobalFile
;
244 m_strGlobalFile
= GetGlobalDir();
245 m_strGlobalFile
<< strGlobal
;
252 void wxFileConfig::CleanUp()
256 LineList
*pCur
= m_linesHead
;
257 while ( pCur
!= NULL
) {
258 LineList
*pNext
= pCur
->Next();
264 wxFileConfig::~wxFileConfig()
271 // ----------------------------------------------------------------------------
272 // parse a config file
273 // ----------------------------------------------------------------------------
275 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
277 const wxChar
*pStart
;
281 size_t nLineCount
= file
.GetLineCount();
282 for ( size_t n
= 0; n
< nLineCount
; n
++ ) {
285 // add the line to linked list
287 LineListAppend(strLine
);
289 // skip leading spaces
290 for ( pStart
= strLine
; wxIsspace(*pStart
); pStart
++ )
293 // skip blank/comment lines
294 if ( *pStart
== _T('\0')|| *pStart
== _T(';') || *pStart
== _T('#') )
297 if ( *pStart
== _T('[') ) { // a new group
300 while ( *++pEnd
!= _T(']') ) {
301 if ( *pEnd
== _T('\n') || *pEnd
== _T('\0') )
305 if ( *pEnd
!= _T(']') ) {
306 wxLogError(_("file '%s': unexpected character %c at line %d."),
307 file
.GetName(), *pEnd
, n
+ 1);
308 continue; // skip this line
311 // group name here is always considered as abs path
314 strGroup
<< wxCONFIG_PATH_SEPARATOR
315 << FilterInEntryName(wxString(pStart
, pEnd
- pStart
));
317 // will create it if doesn't yet exist
321 m_pCurrentGroup
->SetLine(m_linesTail
);
323 // check that there is nothing except comments left on this line
325 while ( *++pEnd
!= _T('\0') && bCont
) {
334 // ignore whitespace ('\n' impossible here)
338 wxLogWarning(_("file '%s', line %d: '%s' "
339 "ignored after group header."),
340 file
.GetName(), n
+ 1, pEnd
);
346 const wxChar
*pEnd
= pStart
;
347 while ( *pEnd
!= _T('=') && !wxIsspace(*pEnd
) ) {
348 if ( *pEnd
== _T('\\') ) {
349 // next character may be space or not - still take it because it's
357 wxString
strKey(FilterInEntryName(wxString(pStart
, pEnd
)));
360 while ( isspace(*pEnd
) )
363 if ( *pEnd
++ != _T('=') ) {
364 wxLogError(_("file '%s', line %d: '=' expected."),
365 file
.GetName(), n
+ 1);
368 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
370 if ( pEntry
== NULL
) {
372 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
375 pEntry
->SetLine(m_linesTail
);
378 if ( bLocal
&& pEntry
->IsImmutable() ) {
379 // immutable keys can't be changed by user
380 wxLogWarning(_("file '%s', line %d: value for "
381 "immutable key '%s' ignored."),
382 file
.GetName(), n
+ 1, strKey
.c_str());
385 // the condition below catches the cases (a) and (b) but not (c):
386 // (a) global key found second time in global file
387 // (b) key found second (or more) time in local file
388 // (c) key from global file now found in local one
389 // which is exactly what we want.
390 else if ( !bLocal
|| pEntry
->IsLocal() ) {
391 wxLogWarning(_("file '%s', line %d: key '%s' was first "
392 "found at line %d."),
393 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
396 pEntry
->SetLine(m_linesTail
);
401 while ( wxIsspace(*pEnd
) )
404 pEntry
->SetValue(FilterInValue(pEnd
), FALSE
/* read from file */);
410 // ----------------------------------------------------------------------------
412 // ----------------------------------------------------------------------------
414 void wxFileConfig::SetRootPath()
417 m_pCurrentGroup
= m_pRootGroup
;
420 void wxFileConfig::SetPath(const wxString
& strPath
)
422 wxArrayString aParts
;
424 if ( strPath
.IsEmpty() ) {
429 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
431 wxSplitPath(aParts
, strPath
);
434 // relative path, combine with current one
435 wxString strFullPath
= m_strPath
;
436 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
437 wxSplitPath(aParts
, strFullPath
);
440 // change current group
442 m_pCurrentGroup
= m_pRootGroup
;
443 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
444 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
445 if ( pNextGroup
== NULL
)
446 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
447 m_pCurrentGroup
= pNextGroup
;
450 // recombine path parts in one variable
452 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
453 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
457 // ----------------------------------------------------------------------------
459 // ----------------------------------------------------------------------------
461 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
464 return GetNextGroup(str
, lIndex
);
467 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
469 if ( size_t(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
470 str
= m_pCurrentGroup
->Groups()[lIndex
++]->Name();
477 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
480 return GetNextEntry(str
, lIndex
);
483 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
485 if ( size_t(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
486 str
= m_pCurrentGroup
->Entries()[lIndex
++]->Name();
493 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
495 size_t n
= m_pCurrentGroup
->Entries().Count();
497 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
498 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
499 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
500 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
501 n
+= GetNumberOfEntries(TRUE
);
502 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
509 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
511 size_t n
= m_pCurrentGroup
->Groups().Count();
513 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
514 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
515 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
516 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
517 n
+= GetNumberOfGroups(TRUE
);
518 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
525 // ----------------------------------------------------------------------------
526 // tests for existence
527 // ----------------------------------------------------------------------------
529 bool wxFileConfig::HasGroup(const wxString
& strName
) const
531 wxConfigPathChanger
path(this, strName
);
533 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
534 return pGroup
!= NULL
;
537 bool wxFileConfig::HasEntry(const wxString
& strName
) const
539 wxConfigPathChanger
path(this, strName
);
541 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
542 return pEntry
!= NULL
;
545 // ----------------------------------------------------------------------------
547 // ----------------------------------------------------------------------------
549 bool wxFileConfig::Read(const wxString
& key
,
550 wxString
* pStr
) const
552 wxConfigPathChanger
path(this, key
);
554 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
555 if (pEntry
== NULL
) {
559 *pStr
= ExpandEnvVars(pEntry
->Value());
564 bool wxFileConfig::Read(const wxString
& key
,
565 wxString
* pStr
, const wxString
& defVal
) const
567 wxConfigPathChanger
path(this, key
);
569 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
570 if (pEntry
== NULL
) {
571 if( IsRecordingDefaults() )
572 ((wxFileConfig
*)this)->Write(key
,defVal
);
573 *pStr
= ExpandEnvVars(defVal
);
577 *pStr
= ExpandEnvVars(pEntry
->Value());
582 bool wxFileConfig::Read(const wxString
& key
, long *pl
) const
585 if ( Read(key
, & str
) ) {
594 bool wxFileConfig::Write(const wxString
& key
, const wxString
& szValue
)
596 wxConfigPathChanger
path(this, key
);
598 wxString strName
= path
.Name();
599 if ( strName
.IsEmpty() ) {
600 // setting the value of a group is an error
601 wxASSERT_MSG( wxIsEmpty(szValue
), _T("can't set value of a group!") );
603 // ... except if it's empty in which case it's a way to force it's creation
604 m_pCurrentGroup
->SetDirty();
606 // this will add a line for this group if it didn't have it before
607 (void)m_pCurrentGroup
->GetGroupLine();
612 // check that the name is reasonable
613 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
614 wxLogError(_("Config entry name cannot start with '%c'."),
615 wxCONFIG_IMMUTABLE_PREFIX
);
619 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
620 if ( pEntry
== NULL
)
621 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
623 pEntry
->SetValue(szValue
);
629 bool wxFileConfig::Write(const wxString
& key
, long lValue
)
631 // ltoa() is not ANSI :-(
633 buf
.Printf(_T("%ld"), lValue
);
634 return Write(key
, buf
);
637 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
639 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() )
642 wxTempFile
file(m_strLocalFile
);
644 if ( !file
.IsOpened() ) {
645 wxLogError(_("can't open user configuration file."));
649 // write all strings to file
650 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
651 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
652 wxLogError(_("can't write user configuration file."));
657 return file
.Commit();
660 // ----------------------------------------------------------------------------
661 // renaming groups/entries
662 // ----------------------------------------------------------------------------
664 bool wxFileConfig::RenameEntry(const wxString
& oldName
,
665 const wxString
& newName
)
667 // check that the entry exists
668 ConfigEntry
*oldEntry
= m_pCurrentGroup
->FindEntry(oldName
);
672 // check that the new entry doesn't already exist
673 if ( m_pCurrentGroup
->FindEntry(newName
) )
676 // delete the old entry, create the new one
677 wxString value
= oldEntry
->Value();
678 if ( !m_pCurrentGroup
->DeleteEntry(oldName
) )
681 ConfigEntry
*newEntry
= m_pCurrentGroup
->AddEntry(newName
);
682 newEntry
->SetValue(value
);
687 bool wxFileConfig::RenameGroup(const wxString
& oldName
,
688 const wxString
& newName
)
690 // check that the group exists
691 ConfigGroup
*group
= m_pCurrentGroup
->FindSubgroup(oldName
);
695 // check that the new group doesn't already exist
696 if ( m_pCurrentGroup
->FindSubgroup(newName
) )
699 group
->Rename(newName
);
704 // ----------------------------------------------------------------------------
705 // delete groups/entries
706 // ----------------------------------------------------------------------------
708 bool wxFileConfig::DeleteEntry(const wxString
& key
, bool bGroupIfEmptyAlso
)
710 wxConfigPathChanger
path(this, key
);
712 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
715 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
716 if ( m_pCurrentGroup
!= m_pRootGroup
) {
717 ConfigGroup
*pGroup
= m_pCurrentGroup
;
718 SetPath(_T("..")); // changes m_pCurrentGroup!
719 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
721 //else: never delete the root group
727 bool wxFileConfig::DeleteGroup(const wxString
& key
)
729 wxConfigPathChanger
path(this, key
);
731 return m_pCurrentGroup
->DeleteSubgroupByName(path
.Name());
734 bool wxFileConfig::DeleteAll()
738 if ( remove(m_strLocalFile
.fn_str()) == -1 )
739 wxLogSysError(_("can't delete user configuration file '%s'"), m_strLocalFile
.c_str());
741 m_strLocalFile
= m_strGlobalFile
= _T("");
747 // ----------------------------------------------------------------------------
748 // linked list functions
749 // ----------------------------------------------------------------------------
751 // append a new line to the end of the list
752 LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
754 LineList
*pLine
= new LineList(str
);
756 if ( m_linesTail
== NULL
) {
762 m_linesTail
->SetNext(pLine
);
763 pLine
->SetPrev(m_linesTail
);
770 // insert a new line after the given one or in the very beginning if !pLine
771 LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
774 if ( pLine
== m_linesTail
)
775 return LineListAppend(str
);
777 LineList
*pNewLine
= new LineList(str
);
778 if ( pLine
== NULL
) {
779 // prepend to the list
780 pNewLine
->SetNext(m_linesHead
);
781 m_linesHead
->SetPrev(pNewLine
);
782 m_linesHead
= pNewLine
;
785 // insert before pLine
786 LineList
*pNext
= pLine
->Next();
787 pNewLine
->SetNext(pNext
);
788 pNewLine
->SetPrev(pLine
);
789 pNext
->SetPrev(pNewLine
);
790 pLine
->SetNext(pNewLine
);
796 void wxFileConfig::LineListRemove(LineList
*pLine
)
798 LineList
*pPrev
= pLine
->Prev(),
799 *pNext
= pLine
->Next();
805 pPrev
->SetNext(pNext
);
811 pNext
->SetPrev(pPrev
);
816 bool wxFileConfig::LineListIsEmpty()
818 return m_linesHead
== NULL
;
821 // ============================================================================
822 // wxFileConfig::ConfigGroup
823 // ============================================================================
825 // ----------------------------------------------------------------------------
827 // ----------------------------------------------------------------------------
830 ConfigGroup::ConfigGroup(ConfigGroup
*pParent
,
831 const wxString
& strName
,
832 wxFileConfig
*pConfig
)
833 : m_aEntries(CompareEntries
),
834 m_aSubgroups(CompareGroups
),
846 // dtor deletes all children
847 ConfigGroup::~ConfigGroup()
850 size_t n
, nCount
= m_aEntries
.Count();
851 for ( n
= 0; n
< nCount
; n
++ )
852 delete m_aEntries
[n
];
855 nCount
= m_aSubgroups
.Count();
856 for ( n
= 0; n
< nCount
; n
++ )
857 delete m_aSubgroups
[n
];
860 // ----------------------------------------------------------------------------
862 // ----------------------------------------------------------------------------
864 void ConfigGroup::SetLine(LineList
*pLine
)
866 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
872 This is a bit complicated, so let me explain it in details. All lines that
873 were read from the local file (the only one we will ever modify) are stored
874 in a (doubly) linked list. Our problem is to know at which position in this
875 list should we insert the new entries/subgroups. To solve it we keep three
876 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
878 m_pLine points to the line containing "[group_name]"
879 m_pLastEntry points to the last entry of this group in the local file.
880 m_pLastGroup subgroup
882 Initially, they're NULL all three. When the group (an entry/subgroup) is read
883 from the local file, the corresponding variable is set. However, if the group
884 was read from the global file and then modified or created by the application
885 these variables are still NULL and we need to create the corresponding lines.
886 See the following functions (and comments preceding them) for the details of
889 Also, when our last entry/group are deleted we need to find the new last
890 element - the code in DeleteEntry/Subgroup does this by backtracking the list
891 of lines until it either founds an entry/subgroup (and this is the new last
892 element) or the m_pLine of the group, in which case there are no more entries
893 (or subgroups) left and m_pLast<element> becomes NULL.
895 NB: This last problem could be avoided for entries if we added new entries
896 immediately after m_pLine, but in this case the entries would appear
897 backwards in the config file (OTOH, it's not that important) and as we
898 would still need to do it for the subgroups the code wouldn't have been
899 significantly less complicated.
902 // Return the line which contains "[our name]". If we're still not in the list,
903 // add our line to it immediately after the last line of our parent group if we
904 // have it or in the very beginning if we're the root group.
905 LineList
*ConfigGroup::GetGroupLine()
907 if ( m_pLine
== NULL
) {
908 ConfigGroup
*pParent
= Parent();
910 // this group wasn't present in local config file, add it now
911 if ( pParent
!= NULL
) {
912 wxString strFullName
;
913 strFullName
<< _T("[")
915 << FilterOutEntryName(GetFullName().c_str() + 1)
917 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
918 pParent
->GetLastGroupLine());
919 pParent
->SetLastGroup(this); // we're surely after all the others
922 // we return NULL, so that LineListInsert() will insert us in the
930 // Return the last line belonging to the subgroups of this group (after which
931 // we can add a new subgroup), if we don't have any subgroups or entries our
932 // last line is the group line (m_pLine) itself.
933 LineList
*ConfigGroup::GetLastGroupLine()
935 // if we have any subgroups, our last line is the last line of the last
937 if ( m_pLastGroup
!= NULL
) {
938 LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
940 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
944 // no subgroups, so the last line is the line of thelast entry (if any)
945 return GetLastEntryLine();
948 // return the last line belonging to the entries of this group (after which
949 // we can add a new entry), if we don't have any entries we will add the new
950 // one immediately after the group line itself.
951 LineList
*ConfigGroup::GetLastEntryLine()
953 if ( m_pLastEntry
!= NULL
) {
954 LineList
*pLine
= m_pLastEntry
->GetLine();
956 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
960 // no entries: insert after the group header
961 return GetGroupLine();
964 // ----------------------------------------------------------------------------
966 // ----------------------------------------------------------------------------
968 void ConfigGroup::Rename(const wxString
& newName
)
972 LineList
*line
= GetGroupLine();
973 wxString strFullName
;
974 strFullName
<< _T("[") << (GetFullName().c_str() + 1) << _T("]"); // +1: no '/'
975 line
->SetText(strFullName
);
980 wxString
ConfigGroup::GetFullName() const
983 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
988 // ----------------------------------------------------------------------------
990 // ----------------------------------------------------------------------------
992 // use binary search because the array is sorted
994 ConfigGroup::FindEntry(const wxChar
*szName
) const
998 hi
= m_aEntries
.Count();
1000 ConfigEntry
*pEntry
;
1004 pEntry
= m_aEntries
[i
];
1006 #if wxCONFIG_CASE_SENSITIVE
1007 res
= wxStrcmp(pEntry
->Name(), szName
);
1009 res
= wxStricmp(pEntry
->Name(), szName
);
1024 ConfigGroup::FindSubgroup(const wxChar
*szName
) const
1028 hi
= m_aSubgroups
.Count();
1030 ConfigGroup
*pGroup
;
1034 pGroup
= m_aSubgroups
[i
];
1036 #if wxCONFIG_CASE_SENSITIVE
1037 res
= wxStrcmp(pGroup
->Name(), szName
);
1039 res
= wxStricmp(pGroup
->Name(), szName
);
1053 // ----------------------------------------------------------------------------
1054 // create a new item
1055 // ----------------------------------------------------------------------------
1057 // create a new entry and add it to the current group
1059 ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
1061 wxASSERT( FindEntry(strName
) == NULL
);
1063 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
1064 m_aEntries
.Add(pEntry
);
1069 // create a new group and add it to the current group
1071 ConfigGroup::AddSubgroup(const wxString
& strName
)
1073 wxASSERT( FindSubgroup(strName
) == NULL
);
1075 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
1076 m_aSubgroups
.Add(pGroup
);
1081 // ----------------------------------------------------------------------------
1083 // ----------------------------------------------------------------------------
1086 The delete operations are _very_ slow if we delete the last item of this
1087 group (see comments before GetXXXLineXXX functions for more details),
1088 so it's much better to start with the first entry/group if we want to
1089 delete several of them.
1092 bool ConfigGroup::DeleteSubgroupByName(const wxChar
*szName
)
1094 return DeleteSubgroup(FindSubgroup(szName
));
1097 // doesn't delete the subgroup itself, but does remove references to it from
1098 // all other data structures (and normally the returned pointer should be
1099 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1100 bool ConfigGroup::DeleteSubgroup(ConfigGroup
*pGroup
)
1102 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1104 // delete all entries
1105 size_t nCount
= pGroup
->m_aEntries
.Count();
1106 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1107 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1108 if ( pLine
!= NULL
)
1109 m_pConfig
->LineListRemove(pLine
);
1112 // and subgroups of this sungroup
1113 nCount
= pGroup
->m_aSubgroups
.Count();
1114 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1115 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[0]);
1118 LineList
*pLine
= pGroup
->m_pLine
;
1119 if ( pLine
!= NULL
) {
1120 // notice that we may do this test inside the previous "if" because the
1121 // last entry's line is surely !NULL
1122 if ( pGroup
== m_pLastGroup
) {
1123 // our last entry is being deleted - find the last one which stays
1124 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1126 // go back until we find a subgroup or reach the group's line
1127 ConfigGroup
*pNewLast
= NULL
;
1128 size_t n
, nSubgroups
= m_aSubgroups
.Count();
1130 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1131 // is it our subgroup?
1132 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1133 // do _not_ call GetGroupLine! we don't want to add it to the local
1134 // file if it's not already there
1135 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1136 pNewLast
= m_aSubgroups
[n
];
1139 if ( pNewLast
!= NULL
) // found?
1143 if ( pl
== m_pLine
) {
1144 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1146 // we've reached the group line without finding any subgroups
1147 m_pLastGroup
= NULL
;
1150 m_pLastGroup
= pNewLast
;
1153 m_pConfig
->LineListRemove(pLine
);
1158 m_aSubgroups
.Remove(pGroup
);
1164 bool ConfigGroup::DeleteEntry(const wxChar
*szName
)
1166 ConfigEntry
*pEntry
= FindEntry(szName
);
1167 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1169 LineList
*pLine
= pEntry
->GetLine();
1170 if ( pLine
!= NULL
) {
1171 // notice that we may do this test inside the previous "if" because the
1172 // last entry's line is surely !NULL
1173 if ( pEntry
== m_pLastEntry
) {
1174 // our last entry is being deleted - find the last one which stays
1175 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1177 // go back until we find another entry or reach the group's line
1178 ConfigEntry
*pNewLast
= NULL
;
1179 size_t n
, nEntries
= m_aEntries
.Count();
1181 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1182 // is it our subgroup?
1183 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1184 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1185 pNewLast
= m_aEntries
[n
];
1188 if ( pNewLast
!= NULL
) // found?
1192 if ( pl
== m_pLine
) {
1193 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1195 // we've reached the group line without finding any subgroups
1196 m_pLastEntry
= NULL
;
1199 m_pLastEntry
= pNewLast
;
1202 m_pConfig
->LineListRemove(pLine
);
1205 // we must be written back for the changes to be saved
1208 m_aEntries
.Remove(pEntry
);
1214 // ----------------------------------------------------------------------------
1216 // ----------------------------------------------------------------------------
1217 void ConfigGroup::SetDirty()
1220 if ( Parent() != NULL
) // propagate upwards
1221 Parent()->SetDirty();
1224 // ============================================================================
1225 // wxFileConfig::ConfigEntry
1226 // ============================================================================
1228 // ----------------------------------------------------------------------------
1230 // ----------------------------------------------------------------------------
1231 ConfigEntry::ConfigEntry(ConfigGroup
*pParent
,
1232 const wxString
& strName
,
1234 : m_strName(strName
)
1236 wxASSERT( !strName
.IsEmpty() );
1238 m_pParent
= pParent
;
1244 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1246 m_strName
.erase(0, 1); // remove first character
1249 // ----------------------------------------------------------------------------
1251 // ----------------------------------------------------------------------------
1253 void ConfigEntry::SetLine(LineList
*pLine
)
1255 if ( m_pLine
!= NULL
) {
1256 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1257 Name().c_str(), m_pParent
->GetFullName().c_str());
1261 Group()->SetLastEntry(this);
1264 // second parameter is FALSE if we read the value from file and prevents the
1265 // entry from being marked as 'dirty'
1266 void ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1268 if ( bUser
&& IsImmutable() ) {
1269 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1274 // do nothing if it's the same value
1275 if ( strValue
== m_strValue
)
1278 m_strValue
= strValue
;
1281 wxString strVal
= FilterOutValue(strValue
);
1283 strLine
<< FilterOutEntryName(m_strName
) << _T('=') << strVal
;
1285 if ( m_pLine
!= NULL
) {
1286 // entry was read from the local config file, just modify the line
1287 m_pLine
->SetText(strLine
);
1290 // add a new line to the file
1291 wxASSERT( m_nLine
== wxNOT_FOUND
); // consistency check
1293 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1294 Group()->GetLastEntryLine());
1295 Group()->SetLastEntry(this);
1302 void ConfigEntry::SetDirty()
1305 Group()->SetDirty();
1308 // ============================================================================
1310 // ============================================================================
1312 // ----------------------------------------------------------------------------
1313 // compare functions for array sorting
1314 // ----------------------------------------------------------------------------
1316 int CompareEntries(ConfigEntry
*p1
,
1319 #if wxCONFIG_CASE_SENSITIVE
1320 return wxStrcmp(p1
->Name(), p2
->Name());
1322 return wxStricmp(p1
->Name(), p2
->Name());
1326 int CompareGroups(ConfigGroup
*p1
,
1329 #if wxCONFIG_CASE_SENSITIVE
1330 return wxStrcmp(p1
->Name(), p2
->Name());
1332 return wxStricmp(p1
->Name(), p2
->Name());
1336 // ----------------------------------------------------------------------------
1338 // ----------------------------------------------------------------------------
1340 // undo FilterOutValue
1341 static wxString
FilterInValue(const wxString
& str
)
1344 strResult
.Alloc(str
.Len());
1346 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1348 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1349 if ( str
[n
] == _T('\\') ) {
1350 switch ( str
[++n
] ) {
1352 strResult
+= _T('\n');
1356 strResult
+= _T('\r');
1360 strResult
+= _T('\t');
1364 strResult
+= _T('\\');
1368 strResult
+= _T('"');
1373 if ( str
[n
] != _T('"') || !bQuoted
)
1374 strResult
+= str
[n
];
1375 else if ( n
!= str
.Len() - 1 ) {
1376 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1379 //else: it's the last quote of a quoted string, ok
1386 // quote the string before writing it to file
1387 static wxString
FilterOutValue(const wxString
& str
)
1393 strResult
.Alloc(str
.Len());
1395 // quoting is necessary to preserve spaces in the beginning of the string
1396 bool bQuote
= wxIsspace(str
[0]) || str
[0] == _T('"');
1399 strResult
+= _T('"');
1402 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
1425 //else: fall through
1428 strResult
+= str
[n
];
1429 continue; // nothing special to do
1432 // we get here only for special characters
1433 strResult
<< _T('\\') << c
;
1437 strResult
+= _T('"');
1442 // undo FilterOutEntryName
1443 static wxString
FilterInEntryName(const wxString
& str
)
1446 strResult
.Alloc(str
.Len());
1448 for ( const wxChar
*pc
= str
.c_str(); *pc
!= '\0'; pc
++ ) {
1449 if ( *pc
== _T('\\') )
1458 // sanitize entry or group name: insert '\\' before any special characters
1459 static wxString
FilterOutEntryName(const wxString
& str
)
1462 strResult
.Alloc(str
.Len());
1464 for ( const wxChar
*pc
= str
.c_str(); *pc
!= _T('\0'); pc
++ ) {
1467 // we explicitly allow some of "safe" chars and 8bit ASCII characters
1468 // which will probably never have special meaning
1469 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
1470 // should *not* be quoted
1471 if ( !wxIsalnum(c
) && !wxStrchr(_T("@_/-!.*%"), c
) && ((c
& 0x80) == 0) )
1472 strResult
+= _T('\\');
1480 // we can't put ?: in the ctor initializer list because it confuses some
1481 // broken compilers (Borland C++)
1482 static wxString
GetAppName(const wxString
& appName
)
1484 if ( !appName
&& wxTheApp
)
1485 return wxTheApp
->GetAppName();
1490 #endif // wxUSE_CONFIG