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 // is 'c' a valid character in group name?
71 // NB: wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR must be valid chars,
72 // but _not_ ']' (group name delimiter)
73 // NB2: we explicitly allow symbols from the 2nd half of the ASCII table
74 inline bool IsValid(char c
)
76 return isalnum(c
) || strchr("@_/-!.*%", c
) || ((c
& 0x80) != 0);
79 // compare functions for sorting the arrays
80 static int CompareEntries(ConfigEntry
*p1
, ConfigEntry
*p2
);
81 static int CompareGroups(ConfigGroup
*p1
, ConfigGroup
*p2
);
84 static wxString
FilterIn(const wxString
& str
);
85 static wxString
FilterOut(const wxString
& str
);
87 // ============================================================================
89 // ============================================================================
91 // ----------------------------------------------------------------------------
93 // ----------------------------------------------------------------------------
94 wxString
wxFileConfig::GetGlobalDir()
100 #elif defined(__WXSTUBS__)
101 wxASSERT_MSG( FALSE
, "TODO" ) ;
102 #elif defined(__WXMAC__)
103 wxASSERT_MSG( FALSE
, "TODO" ) ;
105 char szWinDir
[MAX_PATH
];
106 ::GetWindowsDirectory(szWinDir
, MAX_PATH
);
110 #endif // Unix/Windows
115 wxString
wxFileConfig::GetLocalDir()
119 wxGetHomeDir(&strDir
);
122 if (strDir
.Last() != '/') strDir
<< '/';
124 if (strDir
.Last() != '\\') strDir
<< '\\';
130 wxString
wxFileConfig::GetGlobalFileName(const char *szFile
)
132 wxString str
= GetGlobalDir();
135 if ( strchr(szFile
, '.') == NULL
)
145 wxString
wxFileConfig::GetLocalFileName(const char *szFile
)
147 wxString str
= GetLocalDir();
156 if ( strchr(szFile
, '.') == NULL
)
163 // ----------------------------------------------------------------------------
165 // ----------------------------------------------------------------------------
167 void wxFileConfig::Init()
170 m_pRootGroup
= new ConfigGroup(NULL
, "", this);
175 // it's not an error if (one of the) file(s) doesn't exist
177 // parse the global file
178 if ( !m_strGlobalFile
.IsEmpty() && wxFile::Exists(m_strGlobalFile
) ) {
179 wxTextFile
fileGlobal(m_strGlobalFile
);
181 if ( fileGlobal
.Open() ) {
182 Parse(fileGlobal
, FALSE
/* global */);
186 wxLogWarning(_("can't open global configuration file '%s'."),
187 m_strGlobalFile
.c_str());
190 // parse the local file
191 if ( !m_strLocalFile
.IsEmpty() && wxFile::Exists(m_strLocalFile
) ) {
192 wxTextFile
fileLocal(m_strLocalFile
);
193 if ( fileLocal
.Open() ) {
194 Parse(fileLocal
, TRUE
/* local */);
198 wxLogWarning(_("can't open user configuration file '%s'."),
199 m_strLocalFile
.c_str());
203 // constructor supports creation of wxFileConfig objects of any type
204 wxFileConfig::wxFileConfig(const wxString
& appName
, const wxString
& vendorName
,
205 const wxString
& strLocal
, const wxString
& strGlobal
,
207 : wxConfigBase(appName
, vendorName
, strLocal
, strGlobal
, style
),
208 m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
210 // Make up an application name if not supplied
211 if (appName
.IsEmpty() && wxTheApp
)
213 SetAppName(wxTheApp
->GetAppName());
216 // Make up names for files if empty
217 if ( m_strLocalFile
.IsEmpty() && (style
& wxCONFIG_USE_LOCAL_FILE
) )
219 m_strLocalFile
= GetLocalFileName(GetAppName());
222 if ( m_strGlobalFile
.IsEmpty() && (style
& wxCONFIG_USE_GLOBAL_FILE
) )
224 m_strGlobalFile
= GetGlobalFileName(GetAppName());
227 // Check if styles are not supplied, but filenames are, in which case
228 // add the correct styles.
229 if ( !m_strLocalFile
.IsEmpty() )
230 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE
);
232 if ( !m_strGlobalFile
.IsEmpty() )
233 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE
);
235 // if the path is not absolute, prepend the standard directory to it
236 if ( !m_strLocalFile
.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile
) )
238 wxString strLocal
= m_strLocalFile
;
239 m_strLocalFile
= GetLocalDir();
240 m_strLocalFile
<< strLocal
;
243 if ( !m_strGlobalFile
.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile
) )
245 wxString strGlobal
= m_strGlobalFile
;
246 m_strGlobalFile
= GetGlobalDir();
247 m_strGlobalFile
<< strGlobal
;
253 void wxFileConfig::CleanUp()
257 LineList
*pCur
= m_linesHead
;
258 while ( pCur
!= NULL
) {
259 LineList
*pNext
= pCur
->Next();
265 wxFileConfig::~wxFileConfig()
272 // ----------------------------------------------------------------------------
273 // parse a config file
274 // ----------------------------------------------------------------------------
276 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
282 size_t nLineCount
= file
.GetLineCount();
283 for ( size_t n
= 0; n
< nLineCount
; n
++ ) {
286 // add the line to linked list
288 LineListAppend(strLine
);
290 // skip leading spaces
291 for ( pStart
= strLine
; isspace(*pStart
); pStart
++ )
294 // skip blank/comment lines
295 if ( *pStart
== '\0'|| *pStart
== ';' || *pStart
== '#' )
298 if ( *pStart
== '[' ) { // a new group
301 while ( *++pEnd
!= ']' ) {
302 if ( !IsValid(*pEnd
) && *pEnd
!= ' ' ) // allow spaces in group names
306 if ( *pEnd
!= ']' ) {
307 wxLogError(_("file '%s': unexpected character %c at line %d."),
308 file
.GetName(), *pEnd
, n
+ 1);
309 continue; // skip this line
312 // group name here is always considered as abs path
315 strGroup
<< wxCONFIG_PATH_SEPARATOR
<< wxString(pStart
, pEnd
- pStart
);
317 // will create it if doesn't yet exist
321 m_pCurrentGroup
->SetLine(m_linesTail
);
323 // check that there is nothing except comments left on this line
325 while ( *++pEnd
!= '\0' && bCont
) {
334 // ignore whitespace ('\n' impossible here)
338 wxLogWarning(_("file '%s', line %d: '%s' "
339 "ignored after group header."),
340 file
.GetName(), n
+ 1, pEnd
);
346 const char *pEnd
= pStart
;
347 while ( IsValid(*pEnd
) )
350 wxString
strKey(pStart
, pEnd
);
353 while ( isspace(*pEnd
) )
356 if ( *pEnd
++ != '=' ) {
357 wxLogError(_("file '%s', line %d: '=' expected."),
358 file
.GetName(), n
+ 1);
361 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
363 if ( pEntry
== NULL
) {
365 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
368 pEntry
->SetLine(m_linesTail
);
371 if ( bLocal
&& pEntry
->IsImmutable() ) {
372 // immutable keys can't be changed by user
373 wxLogWarning(_("file '%s', line %d: value for "
374 "immutable key '%s' ignored."),
375 file
.GetName(), n
+ 1, strKey
.c_str());
378 // the condition below catches the cases (a) and (b) but not (c):
379 // (a) global key found second time in global file
380 // (b) key found second (or more) time in local file
381 // (c) key from global file now found in local one
382 // which is exactly what we want.
383 else if ( !bLocal
|| pEntry
->IsLocal() ) {
384 wxLogWarning(_("file '%s', line %d: key '%s' was first "
385 "found at line %d."),
386 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
389 pEntry
->SetLine(m_linesTail
);
394 while ( isspace(*pEnd
) )
397 pEntry
->SetValue(FilterIn(pEnd
), FALSE
/* read from file */);
403 // ----------------------------------------------------------------------------
405 // ----------------------------------------------------------------------------
407 void wxFileConfig::SetRootPath()
410 m_pCurrentGroup
= m_pRootGroup
;
413 void wxFileConfig::SetPath(const wxString
& strPath
)
415 wxArrayString aParts
;
417 if ( strPath
.IsEmpty() ) {
422 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
424 wxSplitPath(aParts
, strPath
);
427 // relative path, combine with current one
428 wxString strFullPath
= m_strPath
;
429 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
430 wxSplitPath(aParts
, strFullPath
);
433 // change current group
435 m_pCurrentGroup
= m_pRootGroup
;
436 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
437 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
438 if ( pNextGroup
== NULL
)
439 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
440 m_pCurrentGroup
= pNextGroup
;
443 // recombine path parts in one variable
445 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
446 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
450 // ----------------------------------------------------------------------------
452 // ----------------------------------------------------------------------------
454 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
457 return GetNextGroup(str
, lIndex
);
460 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
462 if ( size_t(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
463 str
= m_pCurrentGroup
->Groups()[lIndex
++]->Name();
470 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
473 return GetNextEntry(str
, lIndex
);
476 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
478 if ( size_t(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
479 str
= m_pCurrentGroup
->Entries()[lIndex
++]->Name();
486 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
488 size_t n
= m_pCurrentGroup
->Entries().Count();
490 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
491 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
492 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
493 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
494 n
+= GetNumberOfEntries(TRUE
);
495 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
502 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
504 size_t n
= m_pCurrentGroup
->Groups().Count();
506 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
507 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
508 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
509 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
510 n
+= GetNumberOfGroups(TRUE
);
511 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
518 // ----------------------------------------------------------------------------
519 // tests for existence
520 // ----------------------------------------------------------------------------
522 bool wxFileConfig::HasGroup(const wxString
& strName
) const
524 wxConfigPathChanger
path(this, strName
);
526 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
527 return pGroup
!= NULL
;
530 bool wxFileConfig::HasEntry(const wxString
& strName
) const
532 wxConfigPathChanger
path(this, strName
);
534 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
535 return pEntry
!= NULL
;
538 // ----------------------------------------------------------------------------
540 // ----------------------------------------------------------------------------
542 bool wxFileConfig::Read(const wxString
& key
,
543 wxString
* pStr
) const
545 wxConfigPathChanger
path(this, key
);
547 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
548 if (pEntry
== NULL
) {
552 *pStr
= ExpandEnvVars(pEntry
->Value());
557 bool wxFileConfig::Read(const wxString
& key
,
558 wxString
* pStr
, const wxString
& defVal
) const
560 wxConfigPathChanger
path(this, key
);
562 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
563 if (pEntry
== NULL
) {
564 if( IsRecordingDefaults() )
565 ((wxFileConfig
*)this)->Write(key
,defVal
);
566 *pStr
= ExpandEnvVars(defVal
);
570 *pStr
= ExpandEnvVars(pEntry
->Value());
575 bool wxFileConfig::Read(const wxString
& key
, long *pl
) const
578 if ( Read(key
, & str
) ) {
587 bool wxFileConfig::Write(const wxString
& key
, const wxString
& szValue
)
589 wxConfigPathChanger
path(this, key
);
591 wxString strName
= path
.Name();
592 if ( strName
.IsEmpty() ) {
593 // setting the value of a group is an error
594 wxASSERT_MSG( IsEmpty(szValue
), _("can't set value of a group!") );
596 // ... except if it's empty in which case it's a way to force it's creation
597 m_pCurrentGroup
->SetDirty();
599 // this will add a line for this group if it didn't have it before
600 (void)m_pCurrentGroup
->GetGroupLine();
605 // check that the name is reasonable
606 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
607 wxLogError(_("Entry name can't start with '%c'."),
608 wxCONFIG_IMMUTABLE_PREFIX
);
612 for ( const char *pc
= strName
; *pc
!= '\0'; pc
++ ) {
613 if ( !IsValid(*pc
) ) {
614 wxLogError(_("Character '%c' is invalid in a config entry name."),
620 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
621 if ( pEntry
== NULL
)
622 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
624 pEntry
->SetValue(szValue
);
630 bool wxFileConfig::Write(const wxString
& key
, long lValue
)
632 // ltoa() is not ANSI :-(
634 buf
.Printf("%ld", lValue
);
635 return Write(key
, buf
);
638 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
640 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() )
643 wxTempFile
file(m_strLocalFile
);
645 if ( !file
.IsOpened() ) {
646 wxLogError(_("can't open user configuration file."));
650 // write all strings to file
651 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
652 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
653 wxLogError(_("can't write user configuration file."));
658 return file
.Commit();
661 // ----------------------------------------------------------------------------
662 // renaming groups/entries
663 // ----------------------------------------------------------------------------
665 bool wxFileConfig::RenameEntry(const wxString
& oldName
,
666 const wxString
& newName
)
668 // check that the entry exists
669 ConfigEntry
*oldEntry
= m_pCurrentGroup
->FindEntry(oldName
);
673 // check that the new entry doesn't already exist
674 if ( m_pCurrentGroup
->FindEntry(newName
) )
677 // delete the old entry, create the new one
678 wxString value
= oldEntry
->Value();
679 if ( !m_pCurrentGroup
->DeleteEntry(oldName
) )
682 ConfigEntry
*newEntry
= m_pCurrentGroup
->AddEntry(newName
);
683 newEntry
->SetValue(value
);
688 bool wxFileConfig::RenameGroup(const wxString
& oldName
,
689 const wxString
& newName
)
691 // check that the group exists
692 ConfigGroup
*group
= m_pCurrentGroup
->FindSubgroup(oldName
);
696 // check that the new group doesn't already exist
697 if ( m_pCurrentGroup
->FindSubgroup(newName
) )
700 group
->Rename(newName
);
705 // ----------------------------------------------------------------------------
706 // delete groups/entries
707 // ----------------------------------------------------------------------------
709 bool wxFileConfig::DeleteEntry(const wxString
& key
, bool bGroupIfEmptyAlso
)
711 wxConfigPathChanger
path(this, key
);
713 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
716 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
717 if ( m_pCurrentGroup
!= m_pRootGroup
) {
718 ConfigGroup
*pGroup
= m_pCurrentGroup
;
719 SetPath(".."); // changes m_pCurrentGroup!
720 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
722 //else: never delete the root group
728 bool wxFileConfig::DeleteGroup(const wxString
& key
)
730 wxConfigPathChanger
path(this, key
);
732 return m_pCurrentGroup
->DeleteSubgroupByName(path
.Name());
735 bool wxFileConfig::DeleteAll()
739 const char *szFile
= m_strLocalFile
;
741 if ( remove(szFile
) == -1 )
742 wxLogSysError(_("can't delete user configuration file '%s'"), szFile
);
744 m_strLocalFile
= m_strGlobalFile
= "";
750 // ----------------------------------------------------------------------------
751 // linked list functions
752 // ----------------------------------------------------------------------------
754 // append a new line to the end of the list
755 LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
757 LineList
*pLine
= new LineList(str
);
759 if ( m_linesTail
== NULL
) {
765 m_linesTail
->SetNext(pLine
);
766 pLine
->SetPrev(m_linesTail
);
773 // insert a new line after the given one or in the very beginning if !pLine
774 LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
777 if ( pLine
== m_linesTail
)
778 return LineListAppend(str
);
780 LineList
*pNewLine
= new LineList(str
);
781 if ( pLine
== NULL
) {
782 // prepend to the list
783 pNewLine
->SetNext(m_linesHead
);
784 m_linesHead
->SetPrev(pNewLine
);
785 m_linesHead
= pNewLine
;
788 // insert before pLine
789 LineList
*pNext
= pLine
->Next();
790 pNewLine
->SetNext(pNext
);
791 pNewLine
->SetPrev(pLine
);
792 pNext
->SetPrev(pNewLine
);
793 pLine
->SetNext(pNewLine
);
799 void wxFileConfig::LineListRemove(LineList
*pLine
)
801 LineList
*pPrev
= pLine
->Prev(),
802 *pNext
= pLine
->Next();
808 pPrev
->SetNext(pNext
);
814 pNext
->SetPrev(pPrev
);
819 bool wxFileConfig::LineListIsEmpty()
821 return m_linesHead
== NULL
;
824 // ============================================================================
825 // wxFileConfig::ConfigGroup
826 // ============================================================================
828 // ----------------------------------------------------------------------------
830 // ----------------------------------------------------------------------------
833 ConfigGroup::ConfigGroup(ConfigGroup
*pParent
,
834 const wxString
& strName
,
835 wxFileConfig
*pConfig
)
836 : m_aEntries(CompareEntries
),
837 m_aSubgroups(CompareGroups
),
849 // dtor deletes all children
850 ConfigGroup::~ConfigGroup()
853 size_t n
, nCount
= m_aEntries
.Count();
854 for ( n
= 0; n
< nCount
; n
++ )
855 delete m_aEntries
[n
];
858 nCount
= m_aSubgroups
.Count();
859 for ( n
= 0; n
< nCount
; n
++ )
860 delete m_aSubgroups
[n
];
863 // ----------------------------------------------------------------------------
865 // ----------------------------------------------------------------------------
867 void ConfigGroup::SetLine(LineList
*pLine
)
869 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
875 This is a bit complicated, so let me explain it in details. All lines that
876 were read from the local file (the only one we will ever modify) are stored
877 in a (doubly) linked list. Our problem is to know at which position in this
878 list should we insert the new entries/subgroups. To solve it we keep three
879 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
881 m_pLine points to the line containing "[group_name]"
882 m_pLastEntry points to the last entry of this group in the local file.
883 m_pLastGroup subgroup
885 Initially, they're NULL all three. When the group (an entry/subgroup) is read
886 from the local file, the corresponding variable is set. However, if the group
887 was read from the global file and then modified or created by the application
888 these variables are still NULL and we need to create the corresponding lines.
889 See the following functions (and comments preceding them) for the details of
892 Also, when our last entry/group are deleted we need to find the new last
893 element - the code in DeleteEntry/Subgroup does this by backtracking the list
894 of lines until it either founds an entry/subgroup (and this is the new last
895 element) or the m_pLine of the group, in which case there are no more entries
896 (or subgroups) left and m_pLast<element> becomes NULL.
898 NB: This last problem could be avoided for entries if we added new entries
899 immediately after m_pLine, but in this case the entries would appear
900 backwards in the config file (OTOH, it's not that important) and as we
901 would still need to do it for the subgroups the code wouldn't have been
902 significantly less complicated.
905 // Return the line which contains "[our name]". If we're still not in the list,
906 // add our line to it immediately after the last line of our parent group if we
907 // have it or in the very beginning if we're the root group.
908 LineList
*ConfigGroup::GetGroupLine()
910 if ( m_pLine
== NULL
) {
911 ConfigGroup
*pParent
= Parent();
913 // this group wasn't present in local config file, add it now
914 if ( pParent
!= NULL
) {
915 wxString strFullName
;
916 strFullName
<< "[" << (GetFullName().c_str() + 1) << "]"; // +1: no '/'
917 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
918 pParent
->GetLastGroupLine());
919 pParent
->SetLastGroup(this); // we're surely after all the others
922 // we return NULL, so that LineListInsert() will insert us in the
930 // Return the last line belonging to the subgroups of this group (after which
931 // we can add a new subgroup), if we don't have any subgroups or entries our
932 // last line is the group line (m_pLine) itself.
933 LineList
*ConfigGroup::GetLastGroupLine()
935 // if we have any subgroups, our last line is the last line of the last
937 if ( m_pLastGroup
!= NULL
) {
938 LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
940 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
944 // no subgroups, so the last line is the line of thelast entry (if any)
945 return GetLastEntryLine();
948 // return the last line belonging to the entries of this group (after which
949 // we can add a new entry), if we don't have any entries we will add the new
950 // one immediately after the group line itself.
951 LineList
*ConfigGroup::GetLastEntryLine()
953 if ( m_pLastEntry
!= NULL
) {
954 LineList
*pLine
= m_pLastEntry
->GetLine();
956 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
960 // no entries: insert after the group header
961 return GetGroupLine();
964 // ----------------------------------------------------------------------------
966 // ----------------------------------------------------------------------------
968 void ConfigGroup::Rename(const wxString
& newName
)
972 LineList
*line
= GetGroupLine();
973 wxString strFullName
;
974 strFullName
<< "[" << (GetFullName().c_str() + 1) << "]"; // +1: no '/'
975 line
->SetText(strFullName
);
980 wxString
ConfigGroup::GetFullName() const
983 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
988 // ----------------------------------------------------------------------------
990 // ----------------------------------------------------------------------------
992 // use binary search because the array is sorted
994 ConfigGroup::FindEntry(const char *szName
) const
998 hi
= m_aEntries
.Count();
1000 ConfigEntry
*pEntry
;
1004 pEntry
= m_aEntries
[i
];
1006 #if wxCONFIG_CASE_SENSITIVE
1007 res
= strcmp(pEntry
->Name(), szName
);
1009 res
= Stricmp(pEntry
->Name(), szName
);
1024 ConfigGroup::FindSubgroup(const char *szName
) const
1028 hi
= m_aSubgroups
.Count();
1030 ConfigGroup
*pGroup
;
1034 pGroup
= m_aSubgroups
[i
];
1036 #if wxCONFIG_CASE_SENSITIVE
1037 res
= strcmp(pGroup
->Name(), szName
);
1039 res
= Stricmp(pGroup
->Name(), szName
);
1053 // ----------------------------------------------------------------------------
1054 // create a new item
1055 // ----------------------------------------------------------------------------
1057 // create a new entry and add it to the current group
1059 ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
1061 wxASSERT( FindEntry(strName
) == NULL
);
1063 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
1064 m_aEntries
.Add(pEntry
);
1069 // create a new group and add it to the current group
1071 ConfigGroup::AddSubgroup(const wxString
& strName
)
1073 wxASSERT( FindSubgroup(strName
) == NULL
);
1075 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
1076 m_aSubgroups
.Add(pGroup
);
1081 // ----------------------------------------------------------------------------
1083 // ----------------------------------------------------------------------------
1086 The delete operations are _very_ slow if we delete the last item of this
1087 group (see comments before GetXXXLineXXX functions for more details),
1088 so it's much better to start with the first entry/group if we want to
1089 delete several of them.
1092 bool ConfigGroup::DeleteSubgroupByName(const char *szName
)
1094 return DeleteSubgroup(FindSubgroup(szName
));
1097 // doesn't delete the subgroup itself, but does remove references to it from
1098 // all other data structures (and normally the returned pointer should be
1099 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1100 bool ConfigGroup::DeleteSubgroup(ConfigGroup
*pGroup
)
1102 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1104 // delete all entries
1105 size_t nCount
= pGroup
->m_aEntries
.Count();
1106 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1107 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1108 if ( pLine
!= NULL
)
1109 m_pConfig
->LineListRemove(pLine
);
1112 // and subgroups of this sungroup
1113 nCount
= pGroup
->m_aSubgroups
.Count();
1114 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1115 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[nGroup
]);
1118 LineList
*pLine
= pGroup
->m_pLine
;
1119 if ( pLine
!= NULL
) {
1120 // notice that we may do this test inside the previous "if" because the
1121 // last entry's line is surely !NULL
1122 if ( pGroup
== m_pLastGroup
) {
1123 // our last entry is being deleted - find the last one which stays
1124 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1126 // go back until we find a subgroup or reach the group's line
1127 ConfigGroup
*pNewLast
= NULL
;
1128 size_t n
, nSubgroups
= m_aSubgroups
.Count();
1130 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1131 // is it our subgroup?
1132 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1133 // do _not_ call GetGroupLine! we don't want to add it to the local
1134 // file if it's not already there
1135 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1136 pNewLast
= m_aSubgroups
[n
];
1139 if ( pNewLast
!= NULL
) // found?
1143 if ( pl
== m_pLine
) {
1144 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1146 // we've reached the group line without finding any subgroups
1147 m_pLastGroup
= NULL
;
1150 m_pLastGroup
= pNewLast
;
1153 m_pConfig
->LineListRemove(pLine
);
1158 m_aSubgroups
.Remove(pGroup
);
1164 bool ConfigGroup::DeleteEntry(const char *szName
)
1166 ConfigEntry
*pEntry
= FindEntry(szName
);
1167 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1169 LineList
*pLine
= pEntry
->GetLine();
1170 if ( pLine
!= NULL
) {
1171 // notice that we may do this test inside the previous "if" because the
1172 // last entry's line is surely !NULL
1173 if ( pEntry
== m_pLastEntry
) {
1174 // our last entry is being deleted - find the last one which stays
1175 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1177 // go back until we find another entry or reach the group's line
1178 ConfigEntry
*pNewLast
= NULL
;
1179 size_t n
, nEntries
= m_aEntries
.Count();
1181 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1182 // is it our subgroup?
1183 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1184 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1185 pNewLast
= m_aEntries
[n
];
1188 if ( pNewLast
!= NULL
) // found?
1192 if ( pl
== m_pLine
) {
1193 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1195 // we've reached the group line without finding any subgroups
1196 m_pLastEntry
= NULL
;
1199 m_pLastEntry
= pNewLast
;
1202 m_pConfig
->LineListRemove(pLine
);
1205 // we must be written back for the changes to be saved
1208 m_aEntries
.Remove(pEntry
);
1214 // ----------------------------------------------------------------------------
1216 // ----------------------------------------------------------------------------
1217 void ConfigGroup::SetDirty()
1220 if ( Parent() != NULL
) // propagate upwards
1221 Parent()->SetDirty();
1224 // ============================================================================
1225 // wxFileConfig::ConfigEntry
1226 // ============================================================================
1228 // ----------------------------------------------------------------------------
1230 // ----------------------------------------------------------------------------
1231 ConfigEntry::ConfigEntry(ConfigGroup
*pParent
,
1232 const wxString
& strName
,
1234 : m_strName(strName
)
1236 wxASSERT( !strName
.IsEmpty() );
1238 m_pParent
= pParent
;
1244 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1246 m_strName
.erase(0, 1); // remove first character
1249 // ----------------------------------------------------------------------------
1251 // ----------------------------------------------------------------------------
1253 void ConfigEntry::SetLine(LineList
*pLine
)
1255 if ( m_pLine
!= NULL
) {
1256 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1257 Name().c_str(), m_pParent
->GetFullName().c_str());
1261 Group()->SetLastEntry(this);
1264 // second parameter is FALSE if we read the value from file and prevents the
1265 // entry from being marked as 'dirty'
1266 void ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1268 if ( bUser
&& IsImmutable() ) {
1269 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1274 // do nothing if it's the same value
1275 if ( strValue
== m_strValue
)
1278 m_strValue
= strValue
;
1281 wxString strVal
= FilterOut(strValue
);
1283 strLine
<< m_strName
<< " = " << strVal
;
1285 if ( m_pLine
!= NULL
) {
1286 // entry was read from the local config file, just modify the line
1287 m_pLine
->SetText(strLine
);
1290 // add a new line to the file
1291 wxASSERT( m_nLine
== wxNOT_FOUND
); // consistency check
1293 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1294 Group()->GetLastEntryLine());
1295 Group()->SetLastEntry(this);
1302 void ConfigEntry::SetDirty()
1305 Group()->SetDirty();
1308 // ============================================================================
1310 // ============================================================================
1312 // ----------------------------------------------------------------------------
1313 // compare functions for array sorting
1314 // ----------------------------------------------------------------------------
1316 int CompareEntries(ConfigEntry
*p1
,
1319 #if wxCONFIG_CASE_SENSITIVE
1320 return strcmp(p1
->Name(), p2
->Name());
1322 return Stricmp(p1
->Name(), p2
->Name());
1326 int CompareGroups(ConfigGroup
*p1
,
1329 #if wxCONFIG_CASE_SENSITIVE
1330 return strcmp(p1
->Name(), p2
->Name());
1332 return Stricmp(p1
->Name(), p2
->Name());
1336 // ----------------------------------------------------------------------------
1338 // ----------------------------------------------------------------------------
1341 wxString
FilterIn(const wxString
& str
)
1344 strResult
.Alloc(str
.Len());
1346 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1348 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1349 if ( str
[n
] == '\\' ) {
1350 switch ( str
[++n
] ) {
1373 if ( str
[n
] != '"' || !bQuoted
)
1374 strResult
+= str
[n
];
1375 else if ( n
!= str
.Len() - 1 ) {
1376 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1379 //else: it's the last quote of a quoted string, ok
1386 // quote the string before writing it to file
1387 wxString
FilterOut(const wxString
& str
)
1393 strResult
.Alloc(str
.Len());
1395 // quoting is necessary to preserve spaces in the beginning of the string
1396 bool bQuote
= isspace(str
[0]) || str
[0] == '"';
1402 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
1425 //else: fall through
1428 strResult
+= str
[n
];
1429 continue; // nothing special to do
1432 // we get here only for special characters
1433 strResult
<< '\\' << c
;