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
, "TODO" ) ;
96 #elif defined(__WXMAC__)
97 wxASSERT_MSG( FALSE
, "TODO" ) ;
99 char szWinDir
[MAX_PATH
];
100 ::GetWindowsDirectory(szWinDir
, MAX_PATH
);
104 #endif // Unix/Windows
109 wxString
wxFileConfig::GetLocalDir()
113 wxGetHomeDir(&strDir
);
116 if (strDir
.Last() != '/') strDir
<< '/';
118 if (strDir
.Last() != '\\') strDir
<< '\\';
124 wxString
wxFileConfig::GetGlobalFileName(const char *szFile
)
126 wxString str
= GetGlobalDir();
129 if ( strchr(szFile
, '.') == NULL
)
139 wxString
wxFileConfig::GetLocalFileName(const char *szFile
)
141 wxString str
= GetLocalDir();
150 if ( strchr(szFile
, '.') == 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
)
272 size_t nLineCount
= file
.GetLineCount();
273 for ( size_t n
= 0; n
< nLineCount
; n
++ ) {
276 // add the line to linked list
278 LineListAppend(strLine
);
280 // skip leading spaces
281 for ( pStart
= strLine
; isspace(*pStart
); pStart
++ )
284 // skip blank/comment lines
285 if ( *pStart
== '\0'|| *pStart
== ';' || *pStart
== '#' )
288 if ( *pStart
== '[' ) { // a new group
291 while ( *++pEnd
!= ']' ) {
292 if ( *pEnd
== '\n' || *pEnd
== '\0' )
296 if ( *pEnd
!= ']' ) {
297 wxLogError(_("file '%s': unexpected character %c at line %d."),
298 file
.GetName(), *pEnd
, n
+ 1);
299 continue; // skip this line
302 // group name here is always considered as abs path
305 strGroup
<< wxCONFIG_PATH_SEPARATOR
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
!= '\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 char *pEnd
= pStart
;
338 while ( !isspace(*pEnd
) ) {
339 if ( *pEnd
== '\\' ) {
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
++ != '=' ) {
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 ( isspace(*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( IsEmpty(szValue
), "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("%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(".."); // 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 const char *szFile
= m_strLocalFile
;
733 if ( remove(szFile
) == -1 )
734 wxLogSysError(_("can't delete user configuration file '%s'"), szFile
);
736 m_strLocalFile
= m_strGlobalFile
= "";
742 // ----------------------------------------------------------------------------
743 // linked list functions
744 // ----------------------------------------------------------------------------
746 // append a new line to the end of the list
747 LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
749 LineList
*pLine
= new LineList(str
);
751 if ( m_linesTail
== NULL
) {
757 m_linesTail
->SetNext(pLine
);
758 pLine
->SetPrev(m_linesTail
);
765 // insert a new line after the given one or in the very beginning if !pLine
766 LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
769 if ( pLine
== m_linesTail
)
770 return LineListAppend(str
);
772 LineList
*pNewLine
= new LineList(str
);
773 if ( pLine
== NULL
) {
774 // prepend to the list
775 pNewLine
->SetNext(m_linesHead
);
776 m_linesHead
->SetPrev(pNewLine
);
777 m_linesHead
= pNewLine
;
780 // insert before pLine
781 LineList
*pNext
= pLine
->Next();
782 pNewLine
->SetNext(pNext
);
783 pNewLine
->SetPrev(pLine
);
784 pNext
->SetPrev(pNewLine
);
785 pLine
->SetNext(pNewLine
);
791 void wxFileConfig::LineListRemove(LineList
*pLine
)
793 LineList
*pPrev
= pLine
->Prev(),
794 *pNext
= pLine
->Next();
800 pPrev
->SetNext(pNext
);
806 pNext
->SetPrev(pPrev
);
811 bool wxFileConfig::LineListIsEmpty()
813 return m_linesHead
== NULL
;
816 // ============================================================================
817 // wxFileConfig::ConfigGroup
818 // ============================================================================
820 // ----------------------------------------------------------------------------
822 // ----------------------------------------------------------------------------
825 ConfigGroup::ConfigGroup(ConfigGroup
*pParent
,
826 const wxString
& strName
,
827 wxFileConfig
*pConfig
)
828 : m_aEntries(CompareEntries
),
829 m_aSubgroups(CompareGroups
),
841 // dtor deletes all children
842 ConfigGroup::~ConfigGroup()
845 size_t n
, nCount
= m_aEntries
.Count();
846 for ( n
= 0; n
< nCount
; n
++ )
847 delete m_aEntries
[n
];
850 nCount
= m_aSubgroups
.Count();
851 for ( n
= 0; n
< nCount
; n
++ )
852 delete m_aSubgroups
[n
];
855 // ----------------------------------------------------------------------------
857 // ----------------------------------------------------------------------------
859 void ConfigGroup::SetLine(LineList
*pLine
)
861 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
867 This is a bit complicated, so let me explain it in details. All lines that
868 were read from the local file (the only one we will ever modify) are stored
869 in a (doubly) linked list. Our problem is to know at which position in this
870 list should we insert the new entries/subgroups. To solve it we keep three
871 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
873 m_pLine points to the line containing "[group_name]"
874 m_pLastEntry points to the last entry of this group in the local file.
875 m_pLastGroup subgroup
877 Initially, they're NULL all three. When the group (an entry/subgroup) is read
878 from the local file, the corresponding variable is set. However, if the group
879 was read from the global file and then modified or created by the application
880 these variables are still NULL and we need to create the corresponding lines.
881 See the following functions (and comments preceding them) for the details of
884 Also, when our last entry/group are deleted we need to find the new last
885 element - the code in DeleteEntry/Subgroup does this by backtracking the list
886 of lines until it either founds an entry/subgroup (and this is the new last
887 element) or the m_pLine of the group, in which case there are no more entries
888 (or subgroups) left and m_pLast<element> becomes NULL.
890 NB: This last problem could be avoided for entries if we added new entries
891 immediately after m_pLine, but in this case the entries would appear
892 backwards in the config file (OTOH, it's not that important) and as we
893 would still need to do it for the subgroups the code wouldn't have been
894 significantly less complicated.
897 // Return the line which contains "[our name]". If we're still not in the list,
898 // add our line to it immediately after the last line of our parent group if we
899 // have it or in the very beginning if we're the root group.
900 LineList
*ConfigGroup::GetGroupLine()
902 if ( m_pLine
== NULL
) {
903 ConfigGroup
*pParent
= Parent();
905 // this group wasn't present in local config file, add it now
906 if ( pParent
!= NULL
) {
907 wxString strFullName
;
908 strFullName
<< "[" << (GetFullName().c_str() + 1) << "]"; // +1: no '/'
909 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
910 pParent
->GetLastGroupLine());
911 pParent
->SetLastGroup(this); // we're surely after all the others
914 // we return NULL, so that LineListInsert() will insert us in the
922 // Return the last line belonging to the subgroups of this group (after which
923 // we can add a new subgroup), if we don't have any subgroups or entries our
924 // last line is the group line (m_pLine) itself.
925 LineList
*ConfigGroup::GetLastGroupLine()
927 // if we have any subgroups, our last line is the last line of the last
929 if ( m_pLastGroup
!= NULL
) {
930 LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
932 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
936 // no subgroups, so the last line is the line of thelast entry (if any)
937 return GetLastEntryLine();
940 // return the last line belonging to the entries of this group (after which
941 // we can add a new entry), if we don't have any entries we will add the new
942 // one immediately after the group line itself.
943 LineList
*ConfigGroup::GetLastEntryLine()
945 if ( m_pLastEntry
!= NULL
) {
946 LineList
*pLine
= m_pLastEntry
->GetLine();
948 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
952 // no entries: insert after the group header
953 return GetGroupLine();
956 // ----------------------------------------------------------------------------
958 // ----------------------------------------------------------------------------
960 void ConfigGroup::Rename(const wxString
& newName
)
964 LineList
*line
= GetGroupLine();
965 wxString strFullName
;
966 strFullName
<< "[" << (GetFullName().c_str() + 1) << "]"; // +1: no '/'
967 line
->SetText(strFullName
);
972 wxString
ConfigGroup::GetFullName() const
975 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
980 // ----------------------------------------------------------------------------
982 // ----------------------------------------------------------------------------
984 // use binary search because the array is sorted
986 ConfigGroup::FindEntry(const char *szName
) const
990 hi
= m_aEntries
.Count();
996 pEntry
= m_aEntries
[i
];
998 #if wxCONFIG_CASE_SENSITIVE
999 res
= strcmp(pEntry
->Name(), szName
);
1001 res
= Stricmp(pEntry
->Name(), szName
);
1016 ConfigGroup::FindSubgroup(const char *szName
) const
1020 hi
= m_aSubgroups
.Count();
1022 ConfigGroup
*pGroup
;
1026 pGroup
= m_aSubgroups
[i
];
1028 #if wxCONFIG_CASE_SENSITIVE
1029 res
= strcmp(pGroup
->Name(), szName
);
1031 res
= Stricmp(pGroup
->Name(), szName
);
1045 // ----------------------------------------------------------------------------
1046 // create a new item
1047 // ----------------------------------------------------------------------------
1049 // create a new entry and add it to the current group
1051 ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
1053 wxASSERT( FindEntry(strName
) == NULL
);
1055 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
1056 m_aEntries
.Add(pEntry
);
1061 // create a new group and add it to the current group
1063 ConfigGroup::AddSubgroup(const wxString
& strName
)
1065 wxASSERT( FindSubgroup(strName
) == NULL
);
1067 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
1068 m_aSubgroups
.Add(pGroup
);
1073 // ----------------------------------------------------------------------------
1075 // ----------------------------------------------------------------------------
1078 The delete operations are _very_ slow if we delete the last item of this
1079 group (see comments before GetXXXLineXXX functions for more details),
1080 so it's much better to start with the first entry/group if we want to
1081 delete several of them.
1084 bool ConfigGroup::DeleteSubgroupByName(const char *szName
)
1086 return DeleteSubgroup(FindSubgroup(szName
));
1089 // doesn't delete the subgroup itself, but does remove references to it from
1090 // all other data structures (and normally the returned pointer should be
1091 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1092 bool ConfigGroup::DeleteSubgroup(ConfigGroup
*pGroup
)
1094 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1096 // delete all entries
1097 size_t nCount
= pGroup
->m_aEntries
.Count();
1098 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1099 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1100 if ( pLine
!= NULL
)
1101 m_pConfig
->LineListRemove(pLine
);
1104 // and subgroups of this sungroup
1105 nCount
= pGroup
->m_aSubgroups
.Count();
1106 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1107 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[0]);
1110 LineList
*pLine
= pGroup
->m_pLine
;
1111 if ( pLine
!= NULL
) {
1112 // notice that we may do this test inside the previous "if" because the
1113 // last entry's line is surely !NULL
1114 if ( pGroup
== m_pLastGroup
) {
1115 // our last entry is being deleted - find the last one which stays
1116 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1118 // go back until we find a subgroup or reach the group's line
1119 ConfigGroup
*pNewLast
= NULL
;
1120 size_t n
, nSubgroups
= m_aSubgroups
.Count();
1122 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1123 // is it our subgroup?
1124 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1125 // do _not_ call GetGroupLine! we don't want to add it to the local
1126 // file if it's not already there
1127 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1128 pNewLast
= m_aSubgroups
[n
];
1131 if ( pNewLast
!= NULL
) // found?
1135 if ( pl
== m_pLine
) {
1136 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1138 // we've reached the group line without finding any subgroups
1139 m_pLastGroup
= NULL
;
1142 m_pLastGroup
= pNewLast
;
1145 m_pConfig
->LineListRemove(pLine
);
1150 m_aSubgroups
.Remove(pGroup
);
1156 bool ConfigGroup::DeleteEntry(const char *szName
)
1158 ConfigEntry
*pEntry
= FindEntry(szName
);
1159 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1161 LineList
*pLine
= pEntry
->GetLine();
1162 if ( pLine
!= NULL
) {
1163 // notice that we may do this test inside the previous "if" because the
1164 // last entry's line is surely !NULL
1165 if ( pEntry
== m_pLastEntry
) {
1166 // our last entry is being deleted - find the last one which stays
1167 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1169 // go back until we find another entry or reach the group's line
1170 ConfigEntry
*pNewLast
= NULL
;
1171 size_t n
, nEntries
= m_aEntries
.Count();
1173 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1174 // is it our subgroup?
1175 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1176 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1177 pNewLast
= m_aEntries
[n
];
1180 if ( pNewLast
!= NULL
) // found?
1184 if ( pl
== m_pLine
) {
1185 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1187 // we've reached the group line without finding any subgroups
1188 m_pLastEntry
= NULL
;
1191 m_pLastEntry
= pNewLast
;
1194 m_pConfig
->LineListRemove(pLine
);
1197 // we must be written back for the changes to be saved
1200 m_aEntries
.Remove(pEntry
);
1206 // ----------------------------------------------------------------------------
1208 // ----------------------------------------------------------------------------
1209 void ConfigGroup::SetDirty()
1212 if ( Parent() != NULL
) // propagate upwards
1213 Parent()->SetDirty();
1216 // ============================================================================
1217 // wxFileConfig::ConfigEntry
1218 // ============================================================================
1220 // ----------------------------------------------------------------------------
1222 // ----------------------------------------------------------------------------
1223 ConfigEntry::ConfigEntry(ConfigGroup
*pParent
,
1224 const wxString
& strName
,
1226 : m_strName(strName
)
1228 wxASSERT( !strName
.IsEmpty() );
1230 m_pParent
= pParent
;
1236 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1238 m_strName
.erase(0, 1); // remove first character
1241 // ----------------------------------------------------------------------------
1243 // ----------------------------------------------------------------------------
1245 void ConfigEntry::SetLine(LineList
*pLine
)
1247 if ( m_pLine
!= NULL
) {
1248 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1249 Name().c_str(), m_pParent
->GetFullName().c_str());
1253 Group()->SetLastEntry(this);
1256 // second parameter is FALSE if we read the value from file and prevents the
1257 // entry from being marked as 'dirty'
1258 void ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1260 if ( bUser
&& IsImmutable() ) {
1261 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1266 // do nothing if it's the same value
1267 if ( strValue
== m_strValue
)
1270 m_strValue
= strValue
;
1273 wxString strVal
= FilterOutValue(strValue
);
1275 strLine
<< m_strName
<< " = " << strVal
;
1277 if ( m_pLine
!= NULL
) {
1278 // entry was read from the local config file, just modify the line
1279 m_pLine
->SetText(strLine
);
1282 // add a new line to the file
1283 wxASSERT( m_nLine
== wxNOT_FOUND
); // consistency check
1285 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1286 Group()->GetLastEntryLine());
1287 Group()->SetLastEntry(this);
1294 void ConfigEntry::SetDirty()
1297 Group()->SetDirty();
1300 // ============================================================================
1302 // ============================================================================
1304 // ----------------------------------------------------------------------------
1305 // compare functions for array sorting
1306 // ----------------------------------------------------------------------------
1308 int CompareEntries(ConfigEntry
*p1
,
1311 #if wxCONFIG_CASE_SENSITIVE
1312 return strcmp(p1
->Name(), p2
->Name());
1314 return Stricmp(p1
->Name(), p2
->Name());
1318 int CompareGroups(ConfigGroup
*p1
,
1321 #if wxCONFIG_CASE_SENSITIVE
1322 return strcmp(p1
->Name(), p2
->Name());
1324 return Stricmp(p1
->Name(), p2
->Name());
1328 // ----------------------------------------------------------------------------
1330 // ----------------------------------------------------------------------------
1332 // undo FilterOutValue
1333 static wxString
FilterInValue(const wxString
& str
)
1336 strResult
.Alloc(str
.Len());
1338 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1340 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1341 if ( str
[n
] == '\\' ) {
1342 switch ( str
[++n
] ) {
1365 if ( str
[n
] != '"' || !bQuoted
)
1366 strResult
+= str
[n
];
1367 else if ( n
!= str
.Len() - 1 ) {
1368 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1371 //else: it's the last quote of a quoted string, ok
1378 // quote the string before writing it to file
1379 static wxString
FilterOutValue(const wxString
& str
)
1385 strResult
.Alloc(str
.Len());
1387 // quoting is necessary to preserve spaces in the beginning of the string
1388 bool bQuote
= isspace(str
[0]) || str
[0] == '"';
1394 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
1417 //else: fall through
1420 strResult
+= str
[n
];
1421 continue; // nothing special to do
1424 // we get here only for special characters
1425 strResult
<< '\\' << c
;
1434 // undo FilterOutEntryName
1435 static wxString
FilterInEntryName(const wxString
& str
)
1438 strResult
.Alloc(str
.Len());
1440 for ( const char *pc
= str
.c_str(); *pc
!= '\0'; pc
++ ) {
1450 // sanitize entry or group name: insert '\\' before any special characters
1451 static wxString
FilterOutEntryName(const wxString
& str
)
1454 strResult
.Alloc(str
.Len());
1456 for ( const char *pc
= str
.c_str(); *pc
!= '\0'; pc
++ ) {
1459 // we explicitly allow some of "safe" chars and 8bit ASCII characters
1460 // which will probably never have special meaning
1461 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
1462 // should *not* be quoted
1463 if ( !isalnum(c
) && !strchr("@_/-!.*%", c
) && ((c
& 0x80) == 0) )