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>
35 #include <wx/dynarray.h>
38 #include <wx/textfile.h>
39 #include <wx/config.h>
40 #include <wx/fileconf.h>
42 // _WINDOWS_ is defined when windows.h is included,
43 // __WXMSW__ is defined for MS Windows compilation
44 #if defined(__WXMSW__) && !defined(_WINDOWS_)
51 // ----------------------------------------------------------------------------
53 // ----------------------------------------------------------------------------
54 #define CONST_CAST ((wxFileConfig *)this)->
56 // ----------------------------------------------------------------------------
57 // global functions declarations
58 // ----------------------------------------------------------------------------
60 // is 'c' a valid character in group name?
61 // NB: wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR must be valid chars,
62 // but _not_ ']' (group name delimiter)
63 inline bool IsValid(char c
) { return isalnum(c
) || strchr("@_/-!.*%", c
); }
65 // compare functions for sorting the arrays
66 static int CompareEntries(wxFileConfig::ConfigEntry
*p1
,
67 wxFileConfig::ConfigEntry
*p2
);
68 static int CompareGroups(wxFileConfig::ConfigGroup
*p1
,
69 wxFileConfig::ConfigGroup
*p2
);
72 static wxString
FilterIn(const wxString
& str
);
73 static wxString
FilterOut(const wxString
& str
);
75 // ============================================================================
77 // ============================================================================
79 // ----------------------------------------------------------------------------
81 // ----------------------------------------------------------------------------
82 wxString
wxFileConfig::GetGlobalDir()
93 char szWinDir
[_MAX_PATH
];
94 ::GetWindowsDirectory(szWinDir
, _MAX_PATH
);
98 #endif // Unix/Windows
103 wxString
wxFileConfig::GetLocalDir()
108 const char *szHome
= getenv("HOME");
109 if ( szHome
== NULL
) {
111 wxLogWarning(_("can't find user's HOME, using current directory."));
116 strDir
<< '/'; // a double slash is no problem, a missin one yes
119 const char *szHome
= getenv("HOMEDRIVE");
120 if ( szHome
!= NULL
)
122 szHome
= getenv("HOMEPATH");
123 if ( szHome
!= NULL
)
126 // Win16 has no idea about home, so use the current directory instead
134 wxString
wxFileConfig::GetGlobalFileName(const char *szFile
)
136 wxString str
= GetLocalDir();
139 if ( strchr(szFile
, '.') == NULL
)
149 wxString
wxFileConfig::GetLocalFileName(const char *szFile
)
151 wxString str
= GetLocalDir();
166 // ----------------------------------------------------------------------------
168 // ----------------------------------------------------------------------------
170 void wxFileConfig::Init()
173 m_pRootGroup
= new ConfigGroup(NULL
, "", this);
178 // it's not an error if (one of the) file(s) doesn't exist
180 // parse the global file
181 if ( !m_strGlobalFile
.IsEmpty() && wxFile::Exists(m_strGlobalFile
) ) {
182 wxTextFile
fileGlobal(m_strGlobalFile
);
184 if ( fileGlobal
.Open() ) {
185 Parse(fileGlobal
, FALSE
/* global */);
189 wxLogWarning(_("can't open global configuration file '%s'."),
190 m_strGlobalFile
.c_str());
193 // parse the local file
194 if ( !m_strLocalFile
.IsEmpty() && wxFile::Exists(m_strLocalFile
) ) {
195 wxTextFile
fileLocal(m_strLocalFile
);
196 if ( fileLocal
.Open() ) {
197 Parse(fileLocal
, TRUE
/* local */);
201 wxLogWarning(_("can't open user configuration file '%s'."),
202 m_strLocalFile
.c_str());
206 wxFileConfig::wxFileConfig(const char *szAppName
, bool bLocalOnly
)
208 wxASSERT( !IsEmpty(szAppName
) ); // invent a name for your application!
210 m_strLocalFile
= GetLocalFileName(szAppName
);
212 m_strGlobalFile
= GetGlobalFileName(szAppName
);
213 //else: it's going to be empty and we won't use the global file
218 wxFileConfig::wxFileConfig(const wxString
& strLocal
, const wxString
& strGlobal
)
219 : m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
221 // if the path is not absolute, prepend the standard directory to it
223 if ( !strLocal
.IsEmpty() && !wxIsPathSeparator(strLocal
[0u]) )
224 m_strLocalFile
= GetLocalDir();
225 m_strLocalFile
<< strLocal
;
227 if ( !strGlobal
.IsEmpty() && !wxIsPathSeparator(strGlobal
[0u]) )
228 m_strGlobalFile
= GetGlobalDir();
229 m_strGlobalFile
<< strGlobal
;
234 void wxFileConfig::CleanUp()
238 LineList
*pCur
= m_linesHead
;
239 while ( pCur
!= NULL
) {
240 LineList
*pNext
= pCur
->Next();
246 wxFileConfig::~wxFileConfig()
253 // ----------------------------------------------------------------------------
254 // parse a config file
255 // ----------------------------------------------------------------------------
257 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
263 uint nLineCount
= file
.GetLineCount();
264 for ( uint n
= 0; n
< nLineCount
; n
++ ) {
267 // add the line to linked list
269 LineListAppend(strLine
);
271 // skip leading spaces
272 for ( pStart
= strLine
; isspace(*pStart
); pStart
++ )
275 // skip blank/comment lines
276 if ( *pStart
== '\0'|| *pStart
== ';' || *pStart
== '#' )
279 if ( *pStart
== '[' ) { // a new group
282 while ( *++pEnd
!= ']' ) {
283 if ( !IsValid(*pEnd
) && *pEnd
!= ' ' ) // allow spaces in group names
287 if ( *pEnd
!= ']' ) {
288 wxLogError(_("file '%s': unexpected character %c at line %d."),
289 file
.GetName(), *pEnd
, n
+ 1);
290 continue; // skip this line
293 // group name here is always considered as abs path
296 strGroup
<< wxCONFIG_PATH_SEPARATOR
<< wxString(pStart
, pEnd
- pStart
);
298 // will create it if doesn't yet exist
302 m_pCurrentGroup
->SetLine(m_linesTail
);
304 // check that there is nothing except comments left on this line
306 while ( *++pEnd
!= '\0' && bCont
) {
315 // ignore whitespace ('\n' impossible here)
319 wxLogWarning(_("file '%s', line %d: '%s' "
320 "ignored after group header."),
321 file
.GetName(), n
+ 1, pEnd
);
327 const char *pEnd
= pStart
;
328 while ( IsValid(*pEnd
) )
331 wxString
strKey(pStart
, pEnd
);
334 while ( isspace(*pEnd
) )
337 if ( *pEnd
++ != '=' ) {
338 wxLogError(_("file '%s', line %d: '=' expected."),
339 file
.GetName(), n
+ 1);
342 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
344 if ( pEntry
== NULL
) {
346 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
349 pEntry
->SetLine(m_linesTail
);
352 if ( bLocal
&& pEntry
->IsImmutable() ) {
353 // immutable keys can't be changed by user
354 wxLogWarning(_("file '%s', line %d: value for "
355 "immutable key '%s' ignored."),
356 file
.GetName(), n
+ 1, strKey
.c_str());
359 // the condition below catches the cases (a) and (b) but not (c):
360 // (a) global key found second time in global file
361 // (b) key found second (or more) time in local file
362 // (c) key from global file now found in local one
363 // which is exactly what we want.
364 else if ( !bLocal
|| pEntry
->IsLocal() ) {
365 wxLogWarning(_("file '%s', line %d: key '%s' was first "
366 "found at line %d."),
367 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
370 pEntry
->SetLine(m_linesTail
);
375 while ( isspace(*pEnd
) )
378 pEntry
->SetValue(FilterIn(pEnd
), FALSE
/* read from file */);
384 // ----------------------------------------------------------------------------
386 // ----------------------------------------------------------------------------
388 void wxFileConfig::SetRootPath()
391 m_pCurrentGroup
= m_pRootGroup
;
394 void wxFileConfig::SetPath(const wxString
& strPath
)
396 wxArrayString aParts
;
398 if ( strPath
.IsEmpty() ) {
403 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
405 wxSplitPath(aParts
, strPath
);
408 // relative path, combine with current one
409 wxString strFullPath
= m_strPath
;
410 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
411 wxSplitPath(aParts
, strFullPath
);
414 // change current group
416 m_pCurrentGroup
= m_pRootGroup
;
417 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
418 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
419 if ( pNextGroup
== NULL
)
420 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
421 m_pCurrentGroup
= pNextGroup
;
424 // recombine path parts in one variable
426 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
427 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
431 // ----------------------------------------------------------------------------
433 // ----------------------------------------------------------------------------
435 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
)
438 return GetNextGroup(str
, lIndex
);
441 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
)
443 if ( uint(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
444 str
= m_pCurrentGroup
->Groups()[lIndex
++]->Name();
451 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
)
454 return GetNextEntry(str
, lIndex
);
457 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
)
459 if ( uint(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
460 str
= m_pCurrentGroup
->Entries()[lIndex
++]->Name();
467 uint
wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
469 uint n
= m_pCurrentGroup
->Entries().Count();
471 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
472 uint nSubgroups
= m_pCurrentGroup
->Groups().Count();
473 for ( uint nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
474 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
475 n
+= GetNumberOfEntries(TRUE
);
476 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
483 uint
wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
485 uint n
= m_pCurrentGroup
->Groups().Count();
487 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
488 uint nSubgroups
= m_pCurrentGroup
->Groups().Count();
489 for ( uint nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
490 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
491 n
+= GetNumberOfGroups(TRUE
);
492 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
499 // ----------------------------------------------------------------------------
500 // tests for existence
501 // ----------------------------------------------------------------------------
503 bool wxFileConfig::HasGroup(const wxString
& strName
) const
505 PathChanger
path(this, strName
);
507 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
508 return pGroup
!= NULL
;
511 bool wxFileConfig::HasEntry(const wxString
& strName
) const
513 PathChanger
path(this, strName
);
515 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
516 return pEntry
!= NULL
;
519 // ----------------------------------------------------------------------------
521 // ----------------------------------------------------------------------------
523 bool wxFileConfig::Read(wxString
*pstr
,
525 const char *szDefault
) const
527 PathChanger
path(this, szKey
);
529 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
530 if (pEntry
== NULL
) {
531 *pstr
= ExpandEnvVars(szDefault
);
535 *pstr
= ExpandEnvVars(pEntry
->Value());
540 const char *wxFileConfig::Read(const char *szKey
,
541 const char *szDefault
) const
543 static wxString s_str
;
544 Read(&s_str
, szKey
, szDefault
);
546 return s_str
.c_str();
549 bool wxFileConfig::Read(long *pl
, const char *szKey
, long lDefault
) const
552 if ( Read(&str
, szKey
) ) {
562 bool wxFileConfig::Write(const char *szKey
, const char *szValue
)
564 PathChanger
path(this, szKey
);
566 wxString strName
= path
.Name();
567 if ( strName
.IsEmpty() ) {
568 // setting the value of a group is an error
569 wxASSERT_MSG( IsEmpty(szValue
), "can't set value of a group!" );
571 // ... except if it's empty in which case it's a way to force it's creation
572 m_pCurrentGroup
->SetDirty();
574 // this will add a line for this group if it didn't have it before
575 (void)m_pCurrentGroup
->GetGroupLine();
580 // check that the name is reasonable
581 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
582 wxLogError(_("Entry name can't start with '%c'."),
583 wxCONFIG_IMMUTABLE_PREFIX
);
587 for ( const char *pc
= strName
; *pc
!= '\0'; pc
++ ) {
588 if ( !IsValid(*pc
) ) {
589 wxLogError(_("Character '%c' is invalid in a config entry name."),
595 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
596 if ( pEntry
== NULL
)
597 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
599 pEntry
->SetValue(szValue
);
605 bool wxFileConfig::Write(const char *szKey
, long lValue
)
607 // ltoa() is not ANSI :-(
608 char szBuf
[40]; // should be good for sizeof(long) <= 16 (128 bits)
609 sprintf(szBuf
, "%ld", lValue
);
610 return Write(szKey
, szBuf
);
613 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
615 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() )
618 wxTempFile
file(m_strLocalFile
);
620 if ( !file
.IsOpened() ) {
621 wxLogError(_("can't open user configuration file."));
625 // write all strings to file
626 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
627 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
628 wxLogError(_("can't write user configuration file."));
633 return file
.Commit();
636 // ----------------------------------------------------------------------------
637 // delete groups/entries
638 // ----------------------------------------------------------------------------
640 bool wxFileConfig::DeleteEntry(const char *szKey
, bool bGroupIfEmptyAlso
)
642 PathChanger
path(this, szKey
);
644 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
647 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
648 if ( m_pCurrentGroup
!= m_pRootGroup
) {
649 ConfigGroup
*pGroup
= m_pCurrentGroup
;
650 SetPath(".."); // changes m_pCurrentGroup!
651 m_pCurrentGroup
->DeleteSubgroup(pGroup
->Name());
653 //else: never delete the root group
659 bool wxFileConfig::DeleteGroup(const char *szKey
)
661 PathChanger
path(this, szKey
);
663 return m_pCurrentGroup
->DeleteSubgroup(path
.Name());
666 bool wxFileConfig::DeleteAll()
670 m_strLocalFile
= m_strGlobalFile
= "";
673 const char *szFile
= m_strLocalFile
;
675 if ( remove(szFile
) == -1 )
676 wxLogSysError(_("can't delete user configuration file '%s'"), szFile
);
678 szFile
= m_strGlobalFile
;
679 if ( remove(szFile
) )
680 wxLogSysError(_("can't delete system configuration file '%s'"), szFile
);
685 // ----------------------------------------------------------------------------
686 // linked list functions
687 // ----------------------------------------------------------------------------
689 // append a new line to the end of the list
690 wxFileConfig::LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
692 LineList
*pLine
= new LineList(str
);
694 if ( m_linesTail
== NULL
) {
700 m_linesTail
->SetNext(pLine
);
701 pLine
->SetPrev(m_linesTail
);
708 // insert a new line after the given one or in the very beginning if !pLine
709 wxFileConfig::LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
712 if ( pLine
== m_linesTail
)
713 return LineListAppend(str
);
715 LineList
*pNewLine
= new LineList(str
);
716 if ( pLine
== NULL
) {
717 // prepend to the list
718 pNewLine
->SetNext(m_linesHead
);
719 m_linesHead
->SetPrev(pNewLine
);
720 m_linesHead
= pNewLine
;
723 // insert before pLine
724 LineList
*pNext
= pLine
->Next();
725 pNewLine
->SetNext(pNext
);
726 pNewLine
->SetPrev(pLine
);
727 pNext
->SetPrev(pNewLine
);
728 pLine
->SetNext(pNewLine
);
734 void wxFileConfig::LineListRemove(LineList
*pLine
)
736 LineList
*pPrev
= pLine
->Prev(),
737 *pNext
= pLine
->Next();
743 pPrev
->SetNext(pNext
);
749 pNext
->SetPrev(pPrev
);
754 bool wxFileConfig::LineListIsEmpty()
756 return m_linesHead
== NULL
;
759 // ============================================================================
760 // wxFileConfig::ConfigGroup
761 // ============================================================================
763 // ----------------------------------------------------------------------------
765 // ----------------------------------------------------------------------------
768 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup
*pParent
,
769 const wxString
& strName
,
770 wxFileConfig
*pConfig
)
771 : m_aEntries(CompareEntries
),
772 m_aSubgroups(CompareGroups
),
784 // dtor deletes all children
785 wxFileConfig::ConfigGroup::~ConfigGroup()
788 uint n
, nCount
= m_aEntries
.Count();
789 for ( n
= 0; n
< nCount
; n
++ )
790 delete m_aEntries
[n
];
793 nCount
= m_aSubgroups
.Count();
794 for ( n
= 0; n
< nCount
; n
++ )
795 delete m_aSubgroups
[n
];
798 // ----------------------------------------------------------------------------
800 // ----------------------------------------------------------------------------
802 void wxFileConfig::ConfigGroup::SetLine(LineList
*pLine
)
804 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
810 This is a bit complicated, so let me explain it in details. All lines that
811 were read from the local file (the only one we will ever modify) are stored
812 in a (doubly) linked list. Our problem is to know at which position in this
813 list should we insert the new entries/subgroups. To solve it we keep three
814 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
816 m_pLine points to the line containing "[group_name]"
817 m_pLastEntry points to the last entry of this group in the local file.
818 m_pLastGroup subgroup
820 Initially, they're NULL all three. When the group (an entry/subgroup) is read
821 from the local file, the corresponding variable is set. However, if the group
822 was read from the global file and then modified or created by the application
823 these variables are still NULL and we need to create the corresponding lines.
824 See the following functions (and comments preceding them) for the details of
827 Also, when our last entry/group are deleted we need to find the new last
828 element - the code in DeleteEntry/Subgroup does this by backtracking the list
829 of lines until it either founds an entry/subgroup (and this is the new last
830 element) or the m_pLine of the group, in which case there are no more entries
831 (or subgroups) left and m_pLast<element> becomes NULL.
833 NB: This last problem could be avoided for entries if we added new entries
834 immediately after m_pLine, but in this case the entries would appear
835 backwards in the config file (OTOH, it's not that important) and as we
836 would still need to do it for the subgroups the code wouldn't have been
837 significantly less complicated.
840 // Return the line which contains "[our name]". If we're still not in the list,
841 // add our line to it immediately after the last line of our parent group if we
842 // have it or in the very beginning if we're the root group.
843 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetGroupLine()
845 if ( m_pLine
== NULL
) {
846 ConfigGroup
*pParent
= Parent();
848 // this group wasn't present in local config file, add it now
849 if ( pParent
!= NULL
) {
850 wxString strFullName
;
851 strFullName
<< "[" << GetFullName().c_str() + 1 << "]"; // +1: no '/'
852 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
853 pParent
->GetLastGroupLine());
854 pParent
->SetLastGroup(this); // we're surely after all the others
857 // we return NULL, so that LineListInsert() will insert us in the
865 // Return the last line belonging to the subgroups of this group (after which
866 // we can add a new subgroup), if we don't have any subgroups or entries our
867 // last line is the group line (m_pLine) itself.
868 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastGroupLine()
870 // if we have any subgroups, our last line is the last line of the last
872 if ( m_pLastGroup
!= NULL
) {
873 wxFileConfig::LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
875 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
879 // no subgroups, so the last line is the line of thelast entry (if any)
880 return GetLastEntryLine();
883 // return the last line belonging to the entries of this group (after which
884 // we can add a new entry), if we don't have any entries we will add the new
885 // one immediately after the group line itself.
886 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastEntryLine()
888 if ( m_pLastEntry
!= NULL
) {
889 wxFileConfig::LineList
*pLine
= m_pLastEntry
->GetLine();
891 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
895 // no entries: insert after the group header
896 return GetGroupLine();
899 // ----------------------------------------------------------------------------
901 // ----------------------------------------------------------------------------
903 wxString
wxFileConfig::ConfigGroup::GetFullName() const
906 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
911 // ----------------------------------------------------------------------------
913 // ----------------------------------------------------------------------------
915 // use binary search because the array is sorted
916 wxFileConfig::ConfigEntry
*
917 wxFileConfig::ConfigGroup::FindEntry(const char *szName
) const
921 hi
= m_aEntries
.Count();
923 wxFileConfig::ConfigEntry
*pEntry
;
927 pEntry
= m_aEntries
[i
];
929 #if wxCONFIG_CASE_SENSITIVE
930 res
= strcmp(pEntry
->Name(), szName
);
932 res
= Stricmp(pEntry
->Name(), szName
);
946 wxFileConfig::ConfigGroup
*
947 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName
) const
951 hi
= m_aSubgroups
.Count();
953 wxFileConfig::ConfigGroup
*pGroup
;
957 pGroup
= m_aSubgroups
[i
];
959 #if wxCONFIG_CASE_SENSITIVE
960 res
= strcmp(pGroup
->Name(), szName
);
962 res
= Stricmp(pGroup
->Name(), szName
);
976 // ----------------------------------------------------------------------------
978 // ----------------------------------------------------------------------------
980 // create a new entry and add it to the current group
981 wxFileConfig::ConfigEntry
*
982 wxFileConfig::ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
984 wxASSERT( FindEntry(strName
) == NULL
);
986 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
987 m_aEntries
.Add(pEntry
);
992 // create a new group and add it to the current group
993 wxFileConfig::ConfigGroup
*
994 wxFileConfig::ConfigGroup::AddSubgroup(const wxString
& strName
)
996 wxASSERT( FindSubgroup(strName
) == NULL
);
998 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
999 m_aSubgroups
.Add(pGroup
);
1004 // ----------------------------------------------------------------------------
1006 // ----------------------------------------------------------------------------
1009 The delete operations are _very_ slow if we delete the last item of this
1010 group (see comments before GetXXXLineXXX functions for more details),
1011 so it's much better to start with the first entry/group if we want to
1012 delete several of them.
1015 bool wxFileConfig::ConfigGroup::DeleteSubgroup(const char *szName
)
1017 ConfigGroup
*pGroup
= FindSubgroup(szName
);
1018 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1020 // delete all entries
1021 uint nCount
= pGroup
->m_aEntries
.Count();
1022 for ( uint nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1023 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1024 if ( pLine
!= NULL
)
1025 m_pConfig
->LineListRemove(pLine
);
1028 LineList
*pLine
= pGroup
->m_pLine
;
1029 if ( pLine
!= NULL
) {
1030 // notice that we may do this test inside the previous "if" because the
1031 // last entry's line is surely !NULL
1032 if ( pGroup
== m_pLastGroup
) {
1033 // our last entry is being deleted - find the last one which stays
1034 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1036 // go back until we find a subgroup or reach the group's line
1037 ConfigGroup
*pNewLast
= NULL
;
1038 uint n
, nSubgroups
= m_aSubgroups
.Count();
1040 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1041 // is it our subgroup?
1042 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1043 // do _not_ call GetGroupLine! we don't want to add it to the local
1044 // file if it's not already there
1045 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1046 pNewLast
= m_aSubgroups
[n
];
1049 if ( pNewLast
!= NULL
) // found?
1053 if ( pl
== m_pLine
) {
1054 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1056 // we've reached the group line without finding any subgroups
1057 m_pLastGroup
= NULL
;
1060 m_pLastGroup
= pNewLast
;
1063 m_pConfig
->LineListRemove(pLine
);
1068 m_aSubgroups
.Remove(pGroup
);
1074 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName
)
1076 ConfigEntry
*pEntry
= FindEntry(szName
);
1077 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1079 LineList
*pLine
= pEntry
->GetLine();
1080 if ( pLine
!= NULL
) {
1081 // notice that we may do this test inside the previous "if" because the
1082 // last entry's line is surely !NULL
1083 if ( pEntry
== m_pLastEntry
) {
1084 // our last entry is being deleted - find the last one which stays
1085 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1087 // go back until we find another entry or reach the group's line
1088 ConfigEntry
*pNewLast
= NULL
;
1089 uint n
, nEntries
= m_aEntries
.Count();
1091 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1092 // is it our subgroup?
1093 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1094 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1095 pNewLast
= m_aEntries
[n
];
1098 if ( pNewLast
!= NULL
) // found?
1102 if ( pl
== m_pLine
) {
1103 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1105 // we've reached the group line without finding any subgroups
1106 m_pLastEntry
= NULL
;
1109 m_pLastEntry
= pNewLast
;
1112 m_pConfig
->LineListRemove(pLine
);
1115 // we must be written back for the changes to be saved
1118 m_aEntries
.Remove(pEntry
);
1124 // ----------------------------------------------------------------------------
1126 // ----------------------------------------------------------------------------
1127 void wxFileConfig::ConfigGroup::SetDirty()
1130 if ( Parent() != NULL
) // propagate upwards
1131 Parent()->SetDirty();
1134 // ============================================================================
1135 // wxFileConfig::ConfigEntry
1136 // ============================================================================
1138 // ----------------------------------------------------------------------------
1140 // ----------------------------------------------------------------------------
1141 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup
*pParent
,
1142 const wxString
& strName
,
1144 : m_strName(strName
)
1146 wxASSERT( !strName
.IsEmpty() );
1148 m_pParent
= pParent
;
1154 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1156 m_strName
.erase(0, 1); // remove first character
1159 // ----------------------------------------------------------------------------
1161 // ----------------------------------------------------------------------------
1163 void wxFileConfig::ConfigEntry::SetLine(LineList
*pLine
)
1165 if ( m_pLine
!= NULL
) {
1166 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1167 Name().c_str(), m_pParent
->GetFullName().c_str());
1171 Group()->SetLastEntry(this);
1174 // second parameter is FALSE if we read the value from file and prevents the
1175 // entry from being marked as 'dirty'
1176 void wxFileConfig::ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1178 if ( bUser
&& IsImmutable() ) {
1179 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1184 // do nothing if it's the same value
1185 if ( strValue
== m_strValue
)
1188 m_strValue
= strValue
;
1191 wxString strVal
= FilterOut(strValue
);
1193 strLine
<< m_strName
<< " = " << strVal
;
1195 if ( m_pLine
!= NULL
) {
1196 // entry was read from the local config file, just modify the line
1197 m_pLine
->SetText(strLine
);
1200 // add a new line to the file
1201 wxASSERT( m_nLine
== NOT_FOUND
); // consistency check
1203 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1204 Group()->GetLastEntryLine());
1205 Group()->SetLastEntry(this);
1212 void wxFileConfig::ConfigEntry::SetDirty()
1215 Group()->SetDirty();
1218 // ============================================================================
1220 // ============================================================================
1222 // ----------------------------------------------------------------------------
1223 // compare functions for array sorting
1224 // ----------------------------------------------------------------------------
1226 int CompareEntries(wxFileConfig::ConfigEntry
*p1
,
1227 wxFileConfig::ConfigEntry
*p2
)
1229 #if wxCONFIG_CASE_SENSITIVE
1230 return strcmp(p1
->Name(), p2
->Name());
1232 return Stricmp(p1
->Name(), p2
->Name());
1236 int CompareGroups(wxFileConfig::ConfigGroup
*p1
,
1237 wxFileConfig::ConfigGroup
*p2
)
1239 #if wxCONFIG_CASE_SENSITIVE
1240 return strcmp(p1
->Name(), p2
->Name());
1242 return Stricmp(p1
->Name(), p2
->Name());
1246 // ----------------------------------------------------------------------------
1248 // ----------------------------------------------------------------------------
1251 wxString
FilterIn(const wxString
& str
)
1254 strResult
.Alloc(str
.Len());
1256 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1258 for ( uint n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1259 if ( str
[n
] == '\\' ) {
1260 switch ( str
[++n
] ) {
1283 if ( str
[n
] != '"' || !bQuoted
)
1284 strResult
+= str
[n
];
1285 else if ( n
!= str
.Len() - 1 ) {
1286 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1289 //else: it's the last quote of a quoted string, ok
1296 // quote the string before writing it to file
1297 wxString
FilterOut(const wxString
& str
)
1300 strResult
.Alloc(str
.Len());
1302 // quoting is necessary to preserve spaces in the beginning of the string
1303 bool bQuote
= isspace(str
[0]) || str
[0] == '"';
1309 for ( uint n
= 0; n
< str
.Len(); n
++ ) {
1330 //else: fall through
1333 strResult
+= str
[n
];
1334 continue; // nothing special to do
1337 // we get here only for special characters
1338 strResult
<< '\\' << c
;