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."));
118 const char *szHome
= getenv("HOMEDRIVE");
119 if ( szHome
!= NULL
)
121 szHome
= getenv("HOMEPATH");
122 if ( szHome
!= NULL
)
125 // Win16 has no idea about home, so use the current directory instead
133 wxString
wxFileConfig::GetGlobalFileName(const char *szFile
)
135 wxString str
= GetLocalDir();
138 if ( strchr(szFile
, '.') == NULL
)
148 wxString
wxFileConfig::GetLocalFileName(const char *szFile
)
150 wxString str
= GetLocalDir();
165 // ----------------------------------------------------------------------------
167 // ----------------------------------------------------------------------------
169 void wxFileConfig::Init()
172 m_pRootGroup
= new ConfigGroup(NULL
, "", this);
177 // it's not an error if (one of the) file(s) doesn't exist
179 // parse the global file
180 if ( !m_strGlobalFile
.IsEmpty() && wxFile::Exists(m_strGlobalFile
) ) {
181 wxTextFile
fileGlobal(m_strGlobalFile
);
183 if ( fileGlobal
.Open() ) {
184 Parse(fileGlobal
, FALSE
/* global */);
188 wxLogWarning(_("can't open global configuration file '%s'."),
189 m_strGlobalFile
.c_str());
192 // parse the local file
193 if ( !m_strLocalFile
.IsEmpty() && wxFile::Exists(m_strLocalFile
) ) {
194 wxTextFile
fileLocal(m_strLocalFile
);
195 if ( fileLocal
.Open() ) {
196 Parse(fileLocal
, TRUE
/* local */);
200 wxLogWarning(_("can't open user configuration file '%s'."),
201 m_strLocalFile
.c_str());
205 wxFileConfig::wxFileConfig(const char *szAppName
, bool bLocalOnly
)
207 wxASSERT( !IsEmpty(szAppName
) ); // invent a name for your application!
209 m_strLocalFile
= GetLocalFileName(szAppName
);
211 m_strGlobalFile
= GetGlobalFileName(szAppName
);
212 //else: it's going to be empty and we won't use the global file
217 wxFileConfig::wxFileConfig(const wxString
& strLocal
, const wxString
& strGlobal
)
218 : m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
220 // if the path is not absolute, prepend the standard directory to it
222 if ( !strLocal
.IsEmpty() && !wxIsPathSeparator(strLocal
[0u]) )
223 m_strLocalFile
= GetLocalDir();
224 m_strLocalFile
<< strLocal
;
226 if ( !strGlobal
.IsEmpty() && !wxIsPathSeparator(strGlobal
[0u]) )
227 m_strGlobalFile
= GetGlobalDir();
228 m_strGlobalFile
<< strGlobal
;
233 void wxFileConfig::CleanUp()
237 LineList
*pCur
= m_linesHead
;
238 while ( pCur
!= NULL
) {
239 LineList
*pNext
= pCur
->Next();
245 wxFileConfig::~wxFileConfig()
252 // ----------------------------------------------------------------------------
253 // parse a config file
254 // ----------------------------------------------------------------------------
256 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
262 uint nLineCount
= file
.GetLineCount();
263 for ( uint n
= 0; n
< nLineCount
; n
++ ) {
266 // add the line to linked list
268 LineListAppend(strLine
);
270 // skip leading spaces
271 for ( pStart
= strLine
; isspace(*pStart
); pStart
++ )
274 // skip blank/comment lines
275 if ( *pStart
== '\0'|| *pStart
== ';' || *pStart
== '#' )
278 if ( *pStart
== '[' ) { // a new group
281 while ( *++pEnd
!= ']' ) {
282 if ( !IsValid(*pEnd
) && *pEnd
!= ' ' ) // allow spaces in group names
286 if ( *pEnd
!= ']' ) {
287 wxLogError(_("file '%s': unexpected character %c at line %d."),
288 file
.GetName(), *pEnd
, n
+ 1);
289 continue; // skip this line
292 // group name here is always considered as abs path
295 strGroup
<< wxCONFIG_PATH_SEPARATOR
<< wxString(pStart
, pEnd
- pStart
);
297 // will create it if doesn't yet exist
301 m_pCurrentGroup
->SetLine(m_linesTail
);
303 // check that there is nothing except comments left on this line
305 while ( *++pEnd
!= '\0' && bCont
) {
314 // ignore whitespace ('\n' impossible here)
318 wxLogWarning(_("file '%s', line %d: '%s' "
319 "ignored after group header."),
320 file
.GetName(), n
+ 1, pEnd
);
326 const char *pEnd
= pStart
;
327 while ( IsValid(*pEnd
) )
330 wxString
strKey(pStart
, pEnd
);
333 while ( isspace(*pEnd
) )
336 if ( *pEnd
++ != '=' ) {
337 wxLogError(_("file '%s', line %d: '=' expected."),
338 file
.GetName(), n
+ 1);
341 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
343 if ( pEntry
== NULL
) {
345 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
348 pEntry
->SetLine(m_linesTail
);
351 if ( bLocal
&& pEntry
->IsImmutable() ) {
352 // immutable keys can't be changed by user
353 wxLogWarning(_("file '%s', line %d: value for "
354 "immutable key '%s' ignored."),
355 file
.GetName(), n
+ 1, strKey
.c_str());
358 // the condition below catches the cases (a) and (b) but not (c):
359 // (a) global key found second time in global file
360 // (b) key found second (or more) time in local file
361 // (c) key from global file now found in local one
362 // which is exactly what we want.
363 else if ( !bLocal
|| pEntry
->IsLocal() ) {
364 wxLogWarning(_("file '%s', line %d: key '%s' was first "
365 "found at line %d."),
366 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
369 pEntry
->SetLine(m_linesTail
);
374 while ( isspace(*pEnd
) )
377 pEntry
->SetValue(FilterIn(pEnd
), FALSE
/* read from file */);
383 // ----------------------------------------------------------------------------
385 // ----------------------------------------------------------------------------
387 void wxFileConfig::SetRootPath()
390 m_pCurrentGroup
= m_pRootGroup
;
393 void wxFileConfig::SetPath(const wxString
& strPath
)
395 wxArrayString aParts
;
397 if ( strPath
.IsEmpty() ) {
402 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
404 wxSplitPath(aParts
, strPath
);
407 // relative path, combine with current one
408 wxString strFullPath
= m_strPath
;
409 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
410 wxSplitPath(aParts
, strFullPath
);
413 // change current group
415 m_pCurrentGroup
= m_pRootGroup
;
416 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
417 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
418 if ( pNextGroup
== NULL
)
419 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
420 m_pCurrentGroup
= pNextGroup
;
423 // recombine path parts in one variable
425 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
426 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
430 // ----------------------------------------------------------------------------
432 // ----------------------------------------------------------------------------
434 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
)
437 return GetNextGroup(str
, lIndex
);
440 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
)
442 if ( uint(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
443 str
= m_pCurrentGroup
->Groups()[lIndex
++]->Name();
450 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
)
453 return GetNextEntry(str
, lIndex
);
456 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
)
458 if ( uint(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
459 str
= m_pCurrentGroup
->Entries()[lIndex
++]->Name();
466 uint
wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
468 uint n
= m_pCurrentGroup
->Entries().Count();
470 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
471 uint nSubgroups
= m_pCurrentGroup
->Groups().Count();
472 for ( uint nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
473 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
474 n
+= GetNumberOfEntries(TRUE
);
475 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
482 uint
wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
484 uint n
= m_pCurrentGroup
->Groups().Count();
486 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
487 uint nSubgroups
= m_pCurrentGroup
->Groups().Count();
488 for ( uint nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
489 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
490 n
+= GetNumberOfGroups(TRUE
);
491 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
498 // ----------------------------------------------------------------------------
499 // tests for existence
500 // ----------------------------------------------------------------------------
502 bool wxFileConfig::HasGroup(const wxString
& strName
) const
504 PathChanger
path(this, strName
);
506 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
507 return pGroup
!= NULL
;
510 bool wxFileConfig::HasEntry(const wxString
& strName
) const
512 PathChanger
path(this, strName
);
514 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
515 return pEntry
!= NULL
;
518 // ----------------------------------------------------------------------------
520 // ----------------------------------------------------------------------------
522 bool wxFileConfig::Read(wxString
*pstr
,
524 const char *szDefault
) const
526 PathChanger
path(this, szKey
);
528 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
529 if (pEntry
== NULL
) {
530 *pstr
= ExpandEnvVars(szDefault
);
534 *pstr
= ExpandEnvVars(pEntry
->Value());
539 const char *wxFileConfig::Read(const char *szKey
,
540 const char *szDefault
) const
542 static wxString s_str
;
543 Read(&s_str
, szKey
, szDefault
);
545 return s_str
.c_str();
548 bool wxFileConfig::Read(long *pl
, const char *szKey
, long lDefault
) const
551 if ( Read(&str
, szKey
) ) {
561 bool wxFileConfig::Write(const char *szKey
, const char *szValue
)
563 PathChanger
path(this, szKey
);
565 wxString strName
= path
.Name();
566 if ( strName
.IsEmpty() ) {
567 // setting the value of a group is an error
568 wxASSERT_MSG( IsEmpty(szValue
), "can't set value of a group!" );
570 // ... except if it's empty in which case it's a way to force it's creation
571 m_pCurrentGroup
->SetDirty();
573 // this will add a line for this group if it didn't have it before
574 (void)m_pCurrentGroup
->GetGroupLine();
579 // check that the name is reasonable
580 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
581 wxLogError(_("Entry name can't start with '%c'."),
582 wxCONFIG_IMMUTABLE_PREFIX
);
586 for ( const char *pc
= strName
; *pc
!= '\0'; pc
++ ) {
587 if ( !IsValid(*pc
) ) {
588 wxLogError(_("Character '%c' is invalid in a config entry name."),
594 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
595 if ( pEntry
== NULL
)
596 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
598 pEntry
->SetValue(szValue
);
604 bool wxFileConfig::Write(const char *szKey
, long lValue
)
606 // ltoa() is not ANSI :-(
607 char szBuf
[40]; // should be good for sizeof(long) <= 16 (128 bits)
608 sprintf(szBuf
, "%ld", lValue
);
609 return Write(szKey
, szBuf
);
612 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
614 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() )
617 wxTempFile
file(m_strLocalFile
);
619 if ( !file
.IsOpened() ) {
620 wxLogError(_("can't open user configuration file."));
624 // write all strings to file
625 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
626 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
627 wxLogError(_("can't write user configuration file."));
632 return file
.Commit();
635 // ----------------------------------------------------------------------------
636 // delete groups/entries
637 // ----------------------------------------------------------------------------
639 bool wxFileConfig::DeleteEntry(const char *szKey
, bool bGroupIfEmptyAlso
)
641 PathChanger
path(this, szKey
);
643 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
646 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
647 if ( m_pCurrentGroup
!= m_pRootGroup
) {
648 ConfigGroup
*pGroup
= m_pCurrentGroup
;
649 SetPath(".."); // changes m_pCurrentGroup!
650 m_pCurrentGroup
->DeleteSubgroup(pGroup
->Name());
652 //else: never delete the root group
658 bool wxFileConfig::DeleteGroup(const char *szKey
)
660 PathChanger
path(this, szKey
);
662 return m_pCurrentGroup
->DeleteSubgroup(path
.Name());
665 bool wxFileConfig::DeleteAll()
669 m_strLocalFile
= m_strGlobalFile
= "";
672 const char *szFile
= m_strLocalFile
;
674 if ( remove(szFile
) == -1 )
675 wxLogSysError(_("can't delete user configuration file '%s'"), szFile
);
677 szFile
= m_strGlobalFile
;
678 if ( remove(szFile
) )
679 wxLogSysError(_("can't delete system configuration file '%s'"), szFile
);
684 // ----------------------------------------------------------------------------
685 // linked list functions
686 // ----------------------------------------------------------------------------
688 // append a new line to the end of the list
689 wxFileConfig::LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
691 LineList
*pLine
= new LineList(str
);
693 if ( m_linesTail
== NULL
) {
699 m_linesTail
->SetNext(pLine
);
700 pLine
->SetPrev(m_linesTail
);
707 // insert a new line after the given one or in the very beginning if !pLine
708 wxFileConfig::LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
711 if ( pLine
== m_linesTail
)
712 return LineListAppend(str
);
714 LineList
*pNewLine
= new LineList(str
);
715 if ( pLine
== NULL
) {
716 // prepend to the list
717 pNewLine
->SetNext(m_linesHead
);
718 m_linesHead
->SetPrev(pNewLine
);
719 m_linesHead
= pNewLine
;
722 // insert before pLine
723 LineList
*pNext
= pLine
->Next();
724 pNewLine
->SetNext(pNext
);
725 pNewLine
->SetPrev(pLine
);
726 pNext
->SetPrev(pNewLine
);
727 pLine
->SetNext(pNewLine
);
733 void wxFileConfig::LineListRemove(LineList
*pLine
)
735 LineList
*pPrev
= pLine
->Prev(),
736 *pNext
= pLine
->Next();
742 pPrev
->SetNext(pNext
);
748 pNext
->SetPrev(pPrev
);
753 bool wxFileConfig::LineListIsEmpty()
755 return m_linesHead
== NULL
;
758 // ============================================================================
759 // wxFileConfig::ConfigGroup
760 // ============================================================================
762 // ----------------------------------------------------------------------------
764 // ----------------------------------------------------------------------------
767 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup
*pParent
,
768 const wxString
& strName
,
769 wxFileConfig
*pConfig
)
770 : m_aEntries(CompareEntries
),
771 m_aSubgroups(CompareGroups
),
783 // dtor deletes all children
784 wxFileConfig::ConfigGroup::~ConfigGroup()
787 uint n
, nCount
= m_aEntries
.Count();
788 for ( n
= 0; n
< nCount
; n
++ )
789 delete m_aEntries
[n
];
792 nCount
= m_aSubgroups
.Count();
793 for ( n
= 0; n
< nCount
; n
++ )
794 delete m_aSubgroups
[n
];
797 // ----------------------------------------------------------------------------
799 // ----------------------------------------------------------------------------
801 void wxFileConfig::ConfigGroup::SetLine(LineList
*pLine
)
803 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
809 This is a bit complicated, so let me explain it in details. All lines that
810 were read from the local file (the only one we will ever modify) are stored
811 in a (doubly) linked list. Our problem is to know at which position in this
812 list should we insert the new entries/subgroups. To solve it we keep three
813 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
815 m_pLine points to the line containing "[group_name]"
816 m_pLastEntry points to the last entry of this group in the local file.
817 m_pLastGroup subgroup
819 Initially, they're NULL all three. When the group (an entry/subgroup) is read
820 from the local file, the corresponding variable is set. However, if the group
821 was read from the global file and then modified or created by the application
822 these variables are still NULL and we need to create the corresponding lines.
823 See the following functions (and comments preceding them) for the details of
826 Also, when our last entry/group are deleted we need to find the new last
827 element - the code in DeleteEntry/Subgroup does this by backtracking the list
828 of lines until it either founds an entry/subgroup (and this is the new last
829 element) or the m_pLine of the group, in which case there are no more entries
830 (or subgroups) left and m_pLast<element> becomes NULL.
832 NB: This last problem could be avoided for entries if we added new entries
833 immediately after m_pLine, but in this case the entries would appear
834 backwards in the config file (OTOH, it's not that important) and as we
835 would still need to do it for the subgroups the code wouldn't have been
836 significantly less complicated.
839 // Return the line which contains "[our name]". If we're still not in the list,
840 // add our line to it immediately after the last line of our parent group if we
841 // have it or in the very beginning if we're the root group.
842 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetGroupLine()
844 if ( m_pLine
== NULL
) {
845 ConfigGroup
*pParent
= Parent();
847 // this group wasn't present in local config file, add it now
848 if ( pParent
!= NULL
) {
849 wxString strFullName
;
850 strFullName
<< "[" << GetFullName().c_str() + 1 << "]"; // +1: no '/'
851 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
852 pParent
->GetLastGroupLine());
853 pParent
->SetLastGroup(this); // we're surely after all the others
856 // we return NULL, so that LineListInsert() will insert us in the
864 // Return the last line belonging to the subgroups of this group (after which
865 // we can add a new subgroup), if we don't have any subgroups or entries our
866 // last line is the group line (m_pLine) itself.
867 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastGroupLine()
869 // if we have any subgroups, our last line is the last line of the last
871 if ( m_pLastGroup
!= NULL
) {
872 wxFileConfig::LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
874 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
878 // no subgroups, so the last line is the line of thelast entry (if any)
879 return GetLastEntryLine();
882 // return the last line belonging to the entries of this group (after which
883 // we can add a new entry), if we don't have any entries we will add the new
884 // one immediately after the group line itself.
885 wxFileConfig::LineList
*wxFileConfig::ConfigGroup::GetLastEntryLine()
887 if ( m_pLastEntry
!= NULL
) {
888 wxFileConfig::LineList
*pLine
= m_pLastEntry
->GetLine();
890 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
894 // no entries: insert after the group header
895 return GetGroupLine();
898 // ----------------------------------------------------------------------------
900 // ----------------------------------------------------------------------------
902 wxString
wxFileConfig::ConfigGroup::GetFullName() const
905 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
910 // ----------------------------------------------------------------------------
912 // ----------------------------------------------------------------------------
914 // use binary search because the array is sorted
915 wxFileConfig::ConfigEntry
*
916 wxFileConfig::ConfigGroup::FindEntry(const char *szName
) const
920 hi
= m_aEntries
.Count();
922 wxFileConfig::ConfigEntry
*pEntry
;
926 pEntry
= m_aEntries
[i
];
928 #if wxCONFIG_CASE_SENSITIVE
929 res
= strcmp(pEntry
->Name(), szName
);
931 res
= Stricmp(pEntry
->Name(), szName
);
945 wxFileConfig::ConfigGroup
*
946 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName
) const
950 hi
= m_aSubgroups
.Count();
952 wxFileConfig::ConfigGroup
*pGroup
;
956 pGroup
= m_aSubgroups
[i
];
958 #if wxCONFIG_CASE_SENSITIVE
959 res
= strcmp(pGroup
->Name(), szName
);
961 res
= Stricmp(pGroup
->Name(), szName
);
975 // ----------------------------------------------------------------------------
977 // ----------------------------------------------------------------------------
979 // create a new entry and add it to the current group
980 wxFileConfig::ConfigEntry
*
981 wxFileConfig::ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
983 wxASSERT( FindEntry(strName
) == NULL
);
985 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
986 m_aEntries
.Add(pEntry
);
991 // create a new group and add it to the current group
992 wxFileConfig::ConfigGroup
*
993 wxFileConfig::ConfigGroup::AddSubgroup(const wxString
& strName
)
995 wxASSERT( FindSubgroup(strName
) == NULL
);
997 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
998 m_aSubgroups
.Add(pGroup
);
1003 // ----------------------------------------------------------------------------
1005 // ----------------------------------------------------------------------------
1008 The delete operations are _very_ slow if we delete the last item of this
1009 group (see comments before GetXXXLineXXX functions for more details),
1010 so it's much better to start with the first entry/group if we want to
1011 delete several of them.
1014 bool wxFileConfig::ConfigGroup::DeleteSubgroup(const char *szName
)
1016 ConfigGroup
*pGroup
= FindSubgroup(szName
);
1017 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1019 // delete all entries
1020 uint nCount
= pGroup
->m_aEntries
.Count();
1021 for ( uint nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1022 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1023 if ( pLine
!= NULL
)
1024 m_pConfig
->LineListRemove(pLine
);
1027 LineList
*pLine
= pGroup
->m_pLine
;
1028 if ( pLine
!= NULL
) {
1029 // notice that we may do this test inside the previous "if" because the
1030 // last entry's line is surely !NULL
1031 if ( pGroup
== m_pLastGroup
) {
1032 // our last entry is being deleted - find the last one which stays
1033 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1035 // go back until we find a subgroup or reach the group's line
1036 ConfigGroup
*pNewLast
= NULL
;
1037 uint n
, nSubgroups
= m_aSubgroups
.Count();
1039 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1040 // is it our subgroup?
1041 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1042 // do _not_ call GetGroupLine! we don't want to add it to the local
1043 // file if it's not already there
1044 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1045 pNewLast
= m_aSubgroups
[n
];
1048 if ( pNewLast
!= NULL
) // found?
1052 if ( pl
== m_pLine
) {
1053 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1055 // we've reached the group line without finding any subgroups
1056 m_pLastGroup
= NULL
;
1059 m_pLastGroup
= pNewLast
;
1062 m_pConfig
->LineListRemove(pLine
);
1067 m_aSubgroups
.Remove(pGroup
);
1073 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName
)
1075 ConfigEntry
*pEntry
= FindEntry(szName
);
1076 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1078 LineList
*pLine
= pEntry
->GetLine();
1079 if ( pLine
!= NULL
) {
1080 // notice that we may do this test inside the previous "if" because the
1081 // last entry's line is surely !NULL
1082 if ( pEntry
== m_pLastEntry
) {
1083 // our last entry is being deleted - find the last one which stays
1084 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1086 // go back until we find another entry or reach the group's line
1087 ConfigEntry
*pNewLast
= NULL
;
1088 uint n
, nEntries
= m_aEntries
.Count();
1090 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1091 // is it our subgroup?
1092 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1093 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1094 pNewLast
= m_aEntries
[n
];
1097 if ( pNewLast
!= NULL
) // found?
1101 if ( pl
== m_pLine
) {
1102 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1104 // we've reached the group line without finding any subgroups
1105 m_pLastEntry
= NULL
;
1108 m_pLastEntry
= pNewLast
;
1111 m_pConfig
->LineListRemove(pLine
);
1114 // we must be written back for the changes to be saved
1117 m_aEntries
.Remove(pEntry
);
1123 // ----------------------------------------------------------------------------
1125 // ----------------------------------------------------------------------------
1126 void wxFileConfig::ConfigGroup::SetDirty()
1129 if ( Parent() != NULL
) // propagate upwards
1130 Parent()->SetDirty();
1133 // ============================================================================
1134 // wxFileConfig::ConfigEntry
1135 // ============================================================================
1137 // ----------------------------------------------------------------------------
1139 // ----------------------------------------------------------------------------
1140 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup
*pParent
,
1141 const wxString
& strName
,
1143 : m_strName(strName
)
1145 wxASSERT( !strName
.IsEmpty() );
1147 m_pParent
= pParent
;
1153 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1155 m_strName
.erase(0, 1); // remove first character
1158 // ----------------------------------------------------------------------------
1160 // ----------------------------------------------------------------------------
1162 void wxFileConfig::ConfigEntry::SetLine(LineList
*pLine
)
1164 if ( m_pLine
!= NULL
) {
1165 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1166 Name().c_str(), m_pParent
->GetFullName().c_str());
1170 Group()->SetLastEntry(this);
1173 // second parameter is FALSE if we read the value from file and prevents the
1174 // entry from being marked as 'dirty'
1175 void wxFileConfig::ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1177 if ( bUser
&& IsImmutable() ) {
1178 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1183 // do nothing if it's the same value
1184 if ( strValue
== m_strValue
)
1187 m_strValue
= strValue
;
1190 wxString strVal
= FilterOut(strValue
);
1192 strLine
<< m_strName
<< " = " << strVal
;
1194 if ( m_pLine
!= NULL
) {
1195 // entry was read from the local config file, just modify the line
1196 m_pLine
->SetText(strLine
);
1199 // add a new line to the file
1200 wxASSERT( m_nLine
== NOT_FOUND
); // consistency check
1202 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1203 Group()->GetLastEntryLine());
1204 Group()->SetLastEntry(this);
1211 void wxFileConfig::ConfigEntry::SetDirty()
1214 Group()->SetDirty();
1217 // ============================================================================
1219 // ============================================================================
1221 // ----------------------------------------------------------------------------
1222 // compare functions for array sorting
1223 // ----------------------------------------------------------------------------
1225 int CompareEntries(wxFileConfig::ConfigEntry
*p1
,
1226 wxFileConfig::ConfigEntry
*p2
)
1228 #if wxCONFIG_CASE_SENSITIVE
1229 return strcmp(p1
->Name(), p2
->Name());
1231 return Stricmp(p1
->Name(), p2
->Name());
1235 int CompareGroups(wxFileConfig::ConfigGroup
*p1
,
1236 wxFileConfig::ConfigGroup
*p2
)
1238 #if wxCONFIG_CASE_SENSITIVE
1239 return strcmp(p1
->Name(), p2
->Name());
1241 return Stricmp(p1
->Name(), p2
->Name());
1245 // ----------------------------------------------------------------------------
1247 // ----------------------------------------------------------------------------
1250 wxString
FilterIn(const wxString
& str
)
1253 strResult
.Alloc(str
.Len());
1255 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1257 for ( uint n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1258 if ( str
[n
] == '\\' ) {
1259 switch ( str
[++n
] ) {
1282 if ( str
[n
] != '"' || !bQuoted
)
1283 strResult
+= str
[n
];
1284 else if ( n
!= str
.Len() - 1 ) {
1285 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1288 //else: it's the last quote of a quoted string, ok
1295 // quote the string before writing it to file
1296 wxString
FilterOut(const wxString
& str
)
1299 strResult
.Alloc(str
.Len());
1301 // quoting is necessary to preserve spaces in the beginning of the string
1302 bool bQuote
= isspace(str
[0]) || str
[0] == '"';
1308 for ( uint n
= 0; n
< str
.Len(); n
++ ) {
1329 //else: fall through
1332 strResult
+= str
[n
];
1333 continue; // nothing special to do
1336 // we get here only for special characters
1337 strResult
<< '\\' << c
;