1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/common/fileconf.cpp
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 licence
11 ///////////////////////////////////////////////////////////////////////////////
13 // ----------------------------------------------------------------------------
15 // ----------------------------------------------------------------------------
17 // For compilers that support precompilation, includes "wx.h".
18 #include "wx/wxprec.h"
24 #if wxUSE_CONFIG && wxUSE_FILECONFIG
27 #include "wx/dynarray.h"
28 #include "wx/string.h"
32 #include "wx/utils.h" // for wxGetHomeDir
34 #include "wx/stream.h"
35 #endif // wxUSE_STREAMS
39 #include "wx/textfile.h"
40 #include "wx/memtext.h"
41 #include "wx/config.h"
42 #include "wx/fileconf.h"
43 #include "wx/filefn.h"
45 #include "wx/base64.h"
47 #include "wx/stdpaths.h"
49 #if defined(__WXMAC__)
50 #include "wx/mac/private.h" // includes mac headers
53 #if defined(__WXMSW__)
54 #include "wx/msw/private.h"
64 // ----------------------------------------------------------------------------
66 // ----------------------------------------------------------------------------
67 #define CONST_CAST ((wxFileConfig *)this)->
69 // ----------------------------------------------------------------------------
71 // ----------------------------------------------------------------------------
77 #define FILECONF_TRACE_MASK _T("fileconf")
79 // ----------------------------------------------------------------------------
80 // global functions declarations
81 // ----------------------------------------------------------------------------
83 // compare functions for sorting the arrays
84 static int LINKAGEMODE
CompareEntries(wxFileConfigEntry
*p1
, wxFileConfigEntry
*p2
);
85 static int LINKAGEMODE
CompareGroups(wxFileConfigGroup
*p1
, wxFileConfigGroup
*p2
);
88 static wxString
FilterInValue(const wxString
& str
);
89 static wxString
FilterOutValue(const wxString
& str
);
91 static wxString
FilterInEntryName(const wxString
& str
);
92 static wxString
FilterOutEntryName(const wxString
& str
);
94 // get the name to use in wxFileConfig ctor
95 static wxString
GetAppName(const wxString
& appname
);
97 // ============================================================================
99 // ============================================================================
101 // ----------------------------------------------------------------------------
102 // "template" array types
103 // ----------------------------------------------------------------------------
105 #ifdef WXMAKINGDLL_BASE
106 WX_DEFINE_SORTED_USER_EXPORTED_ARRAY(wxFileConfigEntry
*, ArrayEntries
,
108 WX_DEFINE_SORTED_USER_EXPORTED_ARRAY(wxFileConfigGroup
*, ArrayGroups
,
111 WX_DEFINE_SORTED_ARRAY(wxFileConfigEntry
*, ArrayEntries
);
112 WX_DEFINE_SORTED_ARRAY(wxFileConfigGroup
*, ArrayGroups
);
115 // ----------------------------------------------------------------------------
116 // wxFileConfigLineList
117 // ----------------------------------------------------------------------------
119 // we store all lines of the local config file as a linked list in memory
120 class wxFileConfigLineList
123 void SetNext(wxFileConfigLineList
*pNext
) { m_pNext
= pNext
; }
124 void SetPrev(wxFileConfigLineList
*pPrev
) { m_pPrev
= pPrev
; }
127 wxFileConfigLineList(const wxString
& str
,
128 wxFileConfigLineList
*pNext
= NULL
) : m_strLine(str
)
129 { SetNext(pNext
); SetPrev(NULL
); }
131 // next/prev nodes in the linked list
132 wxFileConfigLineList
*Next() const { return m_pNext
; }
133 wxFileConfigLineList
*Prev() const { return m_pPrev
; }
135 // get/change lines text
136 void SetText(const wxString
& str
) { m_strLine
= str
; }
137 const wxString
& Text() const { return m_strLine
; }
140 wxString m_strLine
; // line contents
141 wxFileConfigLineList
*m_pNext
, // next node
142 *m_pPrev
; // previous one
144 DECLARE_NO_COPY_CLASS(wxFileConfigLineList
)
147 // ----------------------------------------------------------------------------
148 // wxFileConfigEntry: a name/value pair
149 // ----------------------------------------------------------------------------
151 class wxFileConfigEntry
154 wxFileConfigGroup
*m_pParent
; // group that contains us
156 wxString m_strName
, // entry name
158 bool m_bImmutable
:1, // can be overriden locally?
159 m_bHasValue
:1; // set after first call to SetValue()
161 int m_nLine
; // used if m_pLine == NULL only
163 // pointer to our line in the linked list or NULL if it was found in global
164 // file (which we don't modify)
165 wxFileConfigLineList
*m_pLine
;
168 wxFileConfigEntry(wxFileConfigGroup
*pParent
,
169 const wxString
& strName
, int nLine
);
172 const wxString
& Name() const { return m_strName
; }
173 const wxString
& Value() const { return m_strValue
; }
174 wxFileConfigGroup
*Group() const { return m_pParent
; }
175 bool IsImmutable() const { return m_bImmutable
; }
176 bool IsLocal() const { return m_pLine
!= 0; }
177 int Line() const { return m_nLine
; }
178 wxFileConfigLineList
*
179 GetLine() const { return m_pLine
; }
181 // modify entry attributes
182 void SetValue(const wxString
& strValue
, bool bUser
= true);
183 void SetLine(wxFileConfigLineList
*pLine
);
185 DECLARE_NO_COPY_CLASS(wxFileConfigEntry
)
188 // ----------------------------------------------------------------------------
189 // wxFileConfigGroup: container of entries and other groups
190 // ----------------------------------------------------------------------------
192 class wxFileConfigGroup
195 wxFileConfig
*m_pConfig
; // config object we belong to
196 wxFileConfigGroup
*m_pParent
; // parent group (NULL for root group)
197 ArrayEntries m_aEntries
; // entries in this group
198 ArrayGroups m_aSubgroups
; // subgroups
199 wxString m_strName
; // group's name
200 wxFileConfigLineList
*m_pLine
; // pointer to our line in the linked list
201 wxFileConfigEntry
*m_pLastEntry
; // last entry/subgroup of this group in the
202 wxFileConfigGroup
*m_pLastGroup
; // local file (we insert new ones after it)
204 // DeleteSubgroupByName helper
205 bool DeleteSubgroup(wxFileConfigGroup
*pGroup
);
208 void UpdateGroupAndSubgroupsLines();
212 wxFileConfigGroup(wxFileConfigGroup
*pParent
, const wxString
& strName
, wxFileConfig
*);
214 // dtor deletes all entries and subgroups also
215 ~wxFileConfigGroup();
218 const wxString
& Name() const { return m_strName
; }
219 wxFileConfigGroup
*Parent() const { return m_pParent
; }
220 wxFileConfig
*Config() const { return m_pConfig
; }
222 const ArrayEntries
& Entries() const { return m_aEntries
; }
223 const ArrayGroups
& Groups() const { return m_aSubgroups
; }
224 bool IsEmpty() const { return Entries().IsEmpty() && Groups().IsEmpty(); }
226 // find entry/subgroup (NULL if not found)
227 wxFileConfigGroup
*FindSubgroup(const wxString
& name
) const;
228 wxFileConfigEntry
*FindEntry (const wxString
& name
) const;
230 // delete entry/subgroup, return false if doesn't exist
231 bool DeleteSubgroupByName(const wxString
& name
);
232 bool DeleteEntry(const wxString
& name
);
234 // create new entry/subgroup returning pointer to newly created element
235 wxFileConfigGroup
*AddSubgroup(const wxString
& strName
);
236 wxFileConfigEntry
*AddEntry (const wxString
& strName
, int nLine
= wxNOT_FOUND
);
238 void SetLine(wxFileConfigLineList
*pLine
);
240 // rename: no checks are done to ensure that the name is unique!
241 void Rename(const wxString
& newName
);
244 wxString
GetFullName() const;
246 // get the last line belonging to an entry/subgroup of this group
247 wxFileConfigLineList
*GetGroupLine(); // line which contains [group]
248 wxFileConfigLineList
*GetLastEntryLine(); // after which our subgroups start
249 wxFileConfigLineList
*GetLastGroupLine(); // after which the next group starts
251 // called by entries/subgroups when they're created/deleted
252 void SetLastEntry(wxFileConfigEntry
*pEntry
);
253 void SetLastGroup(wxFileConfigGroup
*pGroup
)
254 { m_pLastGroup
= pGroup
; }
256 DECLARE_NO_COPY_CLASS(wxFileConfigGroup
)
259 // ============================================================================
261 // ============================================================================
263 // ----------------------------------------------------------------------------
265 // ----------------------------------------------------------------------------
267 // this function modifies in place the given wxFileName object if it doesn't
268 // already have an extension
270 // note that it's slightly misnamed under Mac as there it doesn't add an
271 // extension but modifies the file name instead, so you shouldn't suppose that
272 // fn.HasExt() is true after it returns
273 static void AddConfFileExtIfNeeded(wxFileName
& fn
)
277 #if defined( __WXMAC__ )
278 fn
.SetName(fn
.GetName() + wxT(" Preferences"));
279 #elif defined( __UNIX__ )
280 fn
.SetExt(wxT(".conf"));
282 fn
.SetExt(wxT(".ini"));
287 wxString
wxFileConfig::GetGlobalDir()
289 return wxStandardPaths::Get().GetConfigDir();
292 wxString
wxFileConfig::GetLocalDir(int style
)
296 wxStandardPathsBase
& stdp
= wxStandardPaths::Get();
298 // it so happens that user data directory is a subdirectory of user config
299 // directory on all supported platforms, which explains why we use it here
300 return style
& wxCONFIG_USE_SUBDIR
? stdp
.GetUserDataDir()
301 : stdp
.GetUserConfigDir();
304 wxFileName
wxFileConfig::GetGlobalFile(const wxString
& szFile
)
306 wxFileName
fn(GetGlobalDir(), szFile
);
308 AddConfFileExtIfNeeded(fn
);
313 wxFileName
wxFileConfig::GetLocalFile(const wxString
& szFile
, int style
)
315 wxFileName
fn(GetLocalDir(style
), szFile
);
318 if ( !(style
& wxCONFIG_USE_SUBDIR
) )
320 // dot-files under Unix start with, well, a dot (but OTOH they usually
321 // don't have any specific extension)
322 fn
.SetName(wxT('.') + fn
.GetName());
324 else // we do append ".conf" extension to config files in subdirectories
327 AddConfFileExtIfNeeded(fn
);
333 // ----------------------------------------------------------------------------
335 // ----------------------------------------------------------------------------
336 IMPLEMENT_ABSTRACT_CLASS(wxFileConfig
, wxConfigBase
)
338 void wxFileConfig::Init()
341 m_pRootGroup
= new wxFileConfigGroup(NULL
, wxEmptyString
, this);
346 // It's not an error if (one of the) file(s) doesn't exist.
348 // parse the global file
349 if ( m_fnGlobalFile
.IsOk() && m_fnGlobalFile
.FileExists() )
351 wxTextFile
fileGlobal(m_fnGlobalFile
.GetFullPath());
353 if ( fileGlobal
.Open(*m_conv
/*ignored in ANSI build*/) )
355 Parse(fileGlobal
, false /* global */);
360 wxLogWarning(_("can't open global configuration file '%s'."), m_fnGlobalFile
.GetFullPath().c_str());
364 // parse the local file
365 if ( m_fnLocalFile
.IsOk() && m_fnLocalFile
.FileExists() )
367 wxTextFile
fileLocal(m_fnLocalFile
.GetFullPath());
368 if ( fileLocal
.Open(*m_conv
/*ignored in ANSI build*/) )
370 Parse(fileLocal
, true /* local */);
375 wxLogWarning(_("can't open user configuration file '%s'."), m_fnLocalFile
.GetFullPath().c_str() );
382 // constructor supports creation of wxFileConfig objects of any type
383 wxFileConfig::wxFileConfig(const wxString
& appName
, const wxString
& vendorName
,
384 const wxString
& strLocal
, const wxString
& strGlobal
,
386 const wxMBConv
& conv
)
387 : wxConfigBase(::GetAppName(appName
), vendorName
,
390 m_fnLocalFile(strLocal
),
391 m_fnGlobalFile(strGlobal
),
394 // Make up names for files if empty
395 if ( !m_fnLocalFile
.IsOk() && (style
& wxCONFIG_USE_LOCAL_FILE
) )
396 m_fnLocalFile
= GetLocalFile(GetAppName(), style
);
398 if ( !m_fnGlobalFile
.IsOk() && (style
& wxCONFIG_USE_GLOBAL_FILE
) )
399 m_fnGlobalFile
= GetGlobalFile(GetAppName());
401 // Check if styles are not supplied, but filenames are, in which case
402 // add the correct styles.
403 if ( m_fnLocalFile
.IsOk() )
404 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE
);
406 if ( m_fnGlobalFile
.IsOk() )
407 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE
);
409 // if the path is not absolute, prepend the standard directory to it
410 // unless explicitly asked not to
411 if ( !(style
& wxCONFIG_USE_RELATIVE_PATH
) )
413 if ( m_fnLocalFile
.IsOk() )
414 m_fnLocalFile
.MakeAbsolute(GetLocalDir(style
));
416 if ( m_fnGlobalFile
.IsOk() )
417 m_fnGlobalFile
.MakeAbsolute(GetGlobalDir());
427 wxFileConfig::wxFileConfig(wxInputStream
&inStream
, const wxMBConv
& conv
)
428 : m_conv(conv
.Clone())
430 // always local_file when this constructor is called (?)
431 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE
);
434 m_pRootGroup
= new wxFileConfigGroup(NULL
, wxEmptyString
, this);
439 // read the entire stream contents in memory
442 static const size_t chunkLen
= 1024;
444 wxMemoryBuffer
buf(chunkLen
);
447 inStream
.Read(buf
.GetAppendBuf(chunkLen
), chunkLen
);
448 buf
.UngetAppendBuf(inStream
.LastRead());
450 const wxStreamError err
= inStream
.GetLastError();
452 if ( err
!= wxSTREAM_NO_ERROR
&& err
!= wxSTREAM_EOF
)
454 wxLogError(_("Error reading config options."));
458 while ( !inStream
.Eof() );
462 str
= conv
.cMB2WC((char *)buf
.GetData(), buf
.GetDataLen(), &len
);
463 if ( !len
&& buf
.GetDataLen() )
465 wxLogError(_("Failed to read config options."));
467 #else // !wxUSE_UNICODE
468 // no need for conversion
469 str
.assign((char *)buf
.GetData(), buf
.GetDataLen());
470 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
474 // translate everything to the current (platform-dependent) line
475 // termination character
476 str
= wxTextBuffer::Translate(str
);
478 wxMemoryText memText
;
480 // Now we can add the text to the memory text. To do this we extract line
481 // by line from the translated string, until we've reached the end.
483 // VZ: all this is horribly inefficient, we should do the translation on
484 // the fly in one pass saving both memory and time (TODO)
486 const wxChar
*pEOL
= wxTextBuffer::GetEOL(wxTextBuffer::typeDefault
);
487 const size_t EOLLen
= wxStrlen(pEOL
);
489 int posLineStart
= str
.Find(pEOL
);
490 while ( posLineStart
!= -1 )
492 wxString
line(str
.Left(posLineStart
));
494 memText
.AddLine(line
);
496 str
= str
.Mid(posLineStart
+ EOLLen
);
498 posLineStart
= str
.Find(pEOL
);
501 // also add whatever we have left in the translated string.
503 memText
.AddLine(str
);
505 // Finally we can parse it all.
506 Parse(memText
, true /* local */);
512 #endif // wxUSE_STREAMS
514 void wxFileConfig::CleanUp()
518 wxFileConfigLineList
*pCur
= m_linesHead
;
519 while ( pCur
!= NULL
) {
520 wxFileConfigLineList
*pNext
= pCur
->Next();
526 wxFileConfig::~wxFileConfig()
535 // ----------------------------------------------------------------------------
536 // parse a config file
537 // ----------------------------------------------------------------------------
539 void wxFileConfig::Parse(const wxTextBuffer
& buffer
, bool bLocal
)
542 size_t nLineCount
= buffer
.GetLineCount();
544 for ( size_t n
= 0; n
< nLineCount
; n
++ )
546 wxString strLine
= buffer
[n
];
547 // FIXME-UTF8: rewrite using iterators, without this buffer
548 wxWxCharBuffer
buf(strLine
.c_str());
549 const wxChar
*pStart
;
552 // add the line to linked list
555 LineListAppend(strLine
);
557 // let the root group have its start line as well
560 m_pCurrentGroup
->SetLine(m_linesTail
);
565 // skip leading spaces
566 for ( pStart
= buf
; wxIsspace(*pStart
); pStart
++ )
569 // skip blank/comment lines
570 if ( *pStart
== wxT('\0')|| *pStart
== wxT(';') || *pStart
== wxT('#') )
573 if ( *pStart
== wxT('[') ) { // a new group
576 while ( *++pEnd
!= wxT(']') ) {
577 if ( *pEnd
== wxT('\\') ) {
578 // the next char is escaped, so skip it even if it is ']'
582 if ( *pEnd
== wxT('\n') || *pEnd
== wxT('\0') ) {
583 // we reached the end of line, break out of the loop
588 if ( *pEnd
!= wxT(']') ) {
589 wxLogError(_("file '%s': unexpected character %c at line %d."),
590 buffer
.GetName(), *pEnd
, n
+ 1);
591 continue; // skip this line
594 // group name here is always considered as abs path
597 strGroup
<< wxCONFIG_PATH_SEPARATOR
598 << FilterInEntryName(wxString(pStart
, pEnd
- pStart
));
600 // will create it if doesn't yet exist
605 if ( m_pCurrentGroup
->Parent() )
606 m_pCurrentGroup
->Parent()->SetLastGroup(m_pCurrentGroup
);
607 m_pCurrentGroup
->SetLine(m_linesTail
);
610 // check that there is nothing except comments left on this line
612 while ( *++pEnd
!= wxT('\0') && bCont
) {
621 // ignore whitespace ('\n' impossible here)
625 wxLogWarning(_("file '%s', line %d: '%s' ignored after group header."),
626 buffer
.GetName(), n
+ 1, pEnd
);
633 while ( *pEnd
&& *pEnd
!= wxT('=') /* && !wxIsspace(*pEnd)*/ ) {
634 if ( *pEnd
== wxT('\\') ) {
635 // next character may be space or not - still take it because it's
636 // quoted (unless there is nothing)
639 // the error message will be given below anyhow
647 wxString
strKey(FilterInEntryName(wxString(pStart
, pEnd
).Trim()));
650 while ( wxIsspace(*pEnd
) )
653 if ( *pEnd
++ != wxT('=') ) {
654 wxLogError(_("file '%s', line %d: '=' expected."),
655 buffer
.GetName(), n
+ 1);
658 wxFileConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
660 if ( pEntry
== NULL
) {
662 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
665 if ( bLocal
&& pEntry
->IsImmutable() ) {
666 // immutable keys can't be changed by user
667 wxLogWarning(_("file '%s', line %d: value for immutable key '%s' ignored."),
668 buffer
.GetName(), n
+ 1, strKey
.c_str());
671 // the condition below catches the cases (a) and (b) but not (c):
672 // (a) global key found second time in global file
673 // (b) key found second (or more) time in local file
674 // (c) key from global file now found in local one
675 // which is exactly what we want.
676 else if ( !bLocal
|| pEntry
->IsLocal() ) {
677 wxLogWarning(_("file '%s', line %d: key '%s' was first found at line %d."),
678 buffer
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
684 pEntry
->SetLine(m_linesTail
);
687 while ( wxIsspace(*pEnd
) )
690 wxString value
= pEnd
;
691 if ( !(GetStyle() & wxCONFIG_USE_NO_ESCAPE_CHARACTERS
) )
692 value
= FilterInValue(value
);
694 pEntry
->SetValue(value
, false);
700 // ----------------------------------------------------------------------------
702 // ----------------------------------------------------------------------------
704 void wxFileConfig::SetRootPath()
707 m_pCurrentGroup
= m_pRootGroup
;
711 wxFileConfig::DoSetPath(const wxString
& strPath
, bool createMissingComponents
)
713 wxArrayString aParts
;
715 if ( strPath
.empty() ) {
720 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
722 wxSplitPath(aParts
, strPath
);
725 // relative path, combine with current one
726 wxString strFullPath
= m_strPath
;
727 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
728 wxSplitPath(aParts
, strFullPath
);
731 // change current group
733 m_pCurrentGroup
= m_pRootGroup
;
734 for ( n
= 0; n
< aParts
.GetCount(); n
++ ) {
735 wxFileConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
736 if ( pNextGroup
== NULL
)
738 if ( !createMissingComponents
)
741 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
744 m_pCurrentGroup
= pNextGroup
;
747 // recombine path parts in one variable
749 for ( n
= 0; n
< aParts
.GetCount(); n
++ ) {
750 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
756 void wxFileConfig::SetPath(const wxString
& strPath
)
758 DoSetPath(strPath
, true /* create missing path components */);
761 // ----------------------------------------------------------------------------
763 // ----------------------------------------------------------------------------
765 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
768 return GetNextGroup(str
, lIndex
);
771 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
773 if ( size_t(lIndex
) < m_pCurrentGroup
->Groups().GetCount() ) {
774 str
= m_pCurrentGroup
->Groups()[(size_t)lIndex
++]->Name();
781 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
784 return GetNextEntry(str
, lIndex
);
787 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
789 if ( size_t(lIndex
) < m_pCurrentGroup
->Entries().GetCount() ) {
790 str
= m_pCurrentGroup
->Entries()[(size_t)lIndex
++]->Name();
797 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
799 size_t n
= m_pCurrentGroup
->Entries().GetCount();
801 wxFileConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
802 size_t nSubgroups
= m_pCurrentGroup
->Groups().GetCount();
803 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
804 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
805 n
+= GetNumberOfEntries(true);
806 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
813 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
815 size_t n
= m_pCurrentGroup
->Groups().GetCount();
817 wxFileConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
818 size_t nSubgroups
= m_pCurrentGroup
->Groups().GetCount();
819 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
820 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
821 n
+= GetNumberOfGroups(true);
822 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
829 // ----------------------------------------------------------------------------
830 // tests for existence
831 // ----------------------------------------------------------------------------
833 bool wxFileConfig::HasGroup(const wxString
& strName
) const
835 // special case: DoSetPath("") does work as it's equivalent to DoSetPath("/")
836 // but there is no group with empty name so treat this separately
837 if ( strName
.empty() )
840 const wxString pathOld
= GetPath();
842 wxFileConfig
*self
= wx_const_cast(wxFileConfig
*, this);
844 rc
= self
->DoSetPath(strName
, false /* don't create missing components */);
846 self
->SetPath(pathOld
);
851 bool wxFileConfig::HasEntry(const wxString
& entry
) const
853 // path is the part before the last "/"
854 wxString path
= entry
.BeforeLast(wxCONFIG_PATH_SEPARATOR
);
856 // except in the special case of "/keyname" when there is nothing before "/"
857 if ( path
.empty() && *entry
.c_str() == wxCONFIG_PATH_SEPARATOR
)
859 path
= wxCONFIG_PATH_SEPARATOR
;
862 // change to the path of the entry if necessary and remember the old path
863 // to restore it later
865 wxFileConfig
* const self
= wx_const_cast(wxFileConfig
*, this);
869 if ( pathOld
.empty() )
870 pathOld
= wxCONFIG_PATH_SEPARATOR
;
872 if ( !self
->DoSetPath(path
, false /* don't create if doesn't exist */) )
878 // check if the entry exists in this group
879 const bool exists
= m_pCurrentGroup
->FindEntry(
880 entry
.AfterLast(wxCONFIG_PATH_SEPARATOR
)) != NULL
;
882 // restore the old path if we changed it above
883 if ( !pathOld
.empty() )
885 self
->SetPath(pathOld
);
891 // ----------------------------------------------------------------------------
893 // ----------------------------------------------------------------------------
895 bool wxFileConfig::DoReadString(const wxString
& key
, wxString
* pStr
) const
897 wxConfigPathChanger
path(this, key
);
899 wxFileConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
900 if (pEntry
== NULL
) {
904 *pStr
= pEntry
->Value();
909 bool wxFileConfig::DoReadLong(const wxString
& key
, long *pl
) const
912 if ( !Read(key
, &str
) )
915 // extra spaces shouldn't prevent us from reading numeric values
918 return str
.ToLong(pl
);
923 bool wxFileConfig::DoReadBinary(const wxString
& key
, wxMemoryBuffer
* buf
) const
925 wxCHECK_MSG( buf
, false, _T("NULL buffer") );
928 if ( !Read(key
, &str
) )
931 *buf
= wxBase64Decode(str
.ToAscii());
935 #endif // wxUSE_BASE64
937 bool wxFileConfig::DoWriteString(const wxString
& key
, const wxString
& szValue
)
939 wxConfigPathChanger
path(this, key
);
940 wxString strName
= path
.Name();
942 wxLogTrace( FILECONF_TRACE_MASK
,
943 _T(" Writing String '%s' = '%s' to Group '%s'"),
948 if ( strName
.empty() )
950 // setting the value of a group is an error
952 wxASSERT_MSG( szValue
.empty(), wxT("can't set value of a group!") );
954 // ... except if it's empty in which case it's a way to force it's creation
956 wxLogTrace( FILECONF_TRACE_MASK
,
957 _T(" Creating group %s"),
958 m_pCurrentGroup
->Name().c_str() );
962 // this will add a line for this group if it didn't have it before
964 (void)m_pCurrentGroup
->GetGroupLine();
968 // writing an entry check that the name is reasonable
969 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
)
971 wxLogError( _("Config entry name cannot start with '%c'."),
972 wxCONFIG_IMMUTABLE_PREFIX
);
976 wxFileConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
980 wxLogTrace( FILECONF_TRACE_MASK
,
981 _T(" Adding Entry %s"),
983 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
986 wxLogTrace( FILECONF_TRACE_MASK
,
987 _T(" Setting value %s"),
989 pEntry
->SetValue(szValue
);
997 bool wxFileConfig::DoWriteLong(const wxString
& key
, long lValue
)
999 return Write(key
, wxString::Format(_T("%ld"), lValue
));
1004 bool wxFileConfig::DoWriteBinary(const wxString
& key
, const wxMemoryBuffer
& buf
)
1006 return Write(key
, wxBase64Encode(buf
));
1009 #endif // wxUSE_BASE64
1011 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
1013 if ( !IsDirty() || !m_fnLocalFile
.GetFullPath() )
1016 // set the umask if needed
1017 wxCHANGE_UMASK(m_umask
);
1019 wxTempFile
file(m_fnLocalFile
.GetFullPath());
1021 if ( !file
.IsOpened() )
1023 wxLogError(_("can't open user configuration file."));
1027 // write all strings to file
1029 filetext
.reserve(4096);
1030 for ( wxFileConfigLineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() )
1032 filetext
<< p
->Text() << wxTextFile::GetEOL();
1035 if ( !file
.Write(filetext
, *m_conv
) )
1037 wxLogError(_("can't write user configuration file."));
1041 if ( !file
.Commit() )
1043 wxLogError(_("Failed to update user configuration file."));
1050 #if defined(__WXMAC__)
1051 m_fnLocalFile
.MacSetTypeAndCreator('TEXT', 'ttxt');
1059 bool wxFileConfig::Save(wxOutputStream
& os
, const wxMBConv
& conv
)
1061 // save unconditionally, even if not dirty
1062 for ( wxFileConfigLineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() )
1064 wxString line
= p
->Text();
1065 line
+= wxTextFile::GetEOL();
1067 wxCharBuffer
buf(line
.mb_str(conv
));
1068 if ( !os
.Write(buf
, strlen(buf
)) )
1070 wxLogError(_("Error saving user configuration data."));
1081 #endif // wxUSE_STREAMS
1083 // ----------------------------------------------------------------------------
1084 // renaming groups/entries
1085 // ----------------------------------------------------------------------------
1087 bool wxFileConfig::RenameEntry(const wxString
& oldName
,
1088 const wxString
& newName
)
1090 wxASSERT_MSG( oldName
.find(wxCONFIG_PATH_SEPARATOR
) == wxString::npos
,
1091 _T("RenameEntry(): paths are not supported") );
1093 // check that the entry exists
1094 wxFileConfigEntry
*oldEntry
= m_pCurrentGroup
->FindEntry(oldName
);
1098 // check that the new entry doesn't already exist
1099 if ( m_pCurrentGroup
->FindEntry(newName
) )
1102 // delete the old entry, create the new one
1103 wxString value
= oldEntry
->Value();
1104 if ( !m_pCurrentGroup
->DeleteEntry(oldName
) )
1109 wxFileConfigEntry
*newEntry
= m_pCurrentGroup
->AddEntry(newName
);
1110 newEntry
->SetValue(value
);
1115 bool wxFileConfig::RenameGroup(const wxString
& oldName
,
1116 const wxString
& newName
)
1118 // check that the group exists
1119 wxFileConfigGroup
*group
= m_pCurrentGroup
->FindSubgroup(oldName
);
1123 // check that the new group doesn't already exist
1124 if ( m_pCurrentGroup
->FindSubgroup(newName
) )
1127 group
->Rename(newName
);
1134 // ----------------------------------------------------------------------------
1135 // delete groups/entries
1136 // ----------------------------------------------------------------------------
1138 bool wxFileConfig::DeleteEntry(const wxString
& key
, bool bGroupIfEmptyAlso
)
1140 wxConfigPathChanger
path(this, key
);
1142 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
1147 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
1148 if ( m_pCurrentGroup
!= m_pRootGroup
) {
1149 wxFileConfigGroup
*pGroup
= m_pCurrentGroup
;
1150 SetPath(wxT("..")); // changes m_pCurrentGroup!
1151 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
1153 //else: never delete the root group
1159 bool wxFileConfig::DeleteGroup(const wxString
& key
)
1161 wxConfigPathChanger
path(this, RemoveTrailingSeparator(key
));
1163 if ( !m_pCurrentGroup
->DeleteSubgroupByName(path
.Name()) )
1166 path
.UpdateIfDeleted();
1173 bool wxFileConfig::DeleteAll()
1177 if ( m_fnLocalFile
.IsOk() )
1179 if ( m_fnLocalFile
.FileExists() &&
1180 !wxRemoveFile(m_fnLocalFile
.GetFullPath()) )
1182 wxLogSysError(_("can't delete user configuration file '%s'"),
1183 m_fnLocalFile
.GetFullPath().c_str());
1193 // ----------------------------------------------------------------------------
1194 // linked list functions
1195 // ----------------------------------------------------------------------------
1197 // append a new line to the end of the list
1199 wxFileConfigLineList
*wxFileConfig::LineListAppend(const wxString
& str
)
1201 wxLogTrace( FILECONF_TRACE_MASK
,
1202 _T(" ** Adding Line '%s'"),
1204 wxLogTrace( FILECONF_TRACE_MASK
,
1206 ((m_linesHead
) ? (const wxChar
*)m_linesHead
->Text().c_str()
1208 wxLogTrace( FILECONF_TRACE_MASK
,
1210 ((m_linesTail
) ? (const wxChar
*)m_linesTail
->Text().c_str()
1213 wxFileConfigLineList
*pLine
= new wxFileConfigLineList(str
);
1215 if ( m_linesTail
== NULL
)
1218 m_linesHead
= pLine
;
1223 m_linesTail
->SetNext(pLine
);
1224 pLine
->SetPrev(m_linesTail
);
1227 m_linesTail
= pLine
;
1229 wxLogTrace( FILECONF_TRACE_MASK
,
1231 ((m_linesHead
) ? (const wxChar
*)m_linesHead
->Text().c_str()
1233 wxLogTrace( FILECONF_TRACE_MASK
,
1235 ((m_linesTail
) ? (const wxChar
*)m_linesTail
->Text().c_str()
1241 // insert a new line after the given one or in the very beginning if !pLine
1242 wxFileConfigLineList
*wxFileConfig::LineListInsert(const wxString
& str
,
1243 wxFileConfigLineList
*pLine
)
1245 wxLogTrace( FILECONF_TRACE_MASK
,
1246 _T(" ** Inserting Line '%s' after '%s'"),
1248 ((pLine
) ? (const wxChar
*)pLine
->Text().c_str()
1250 wxLogTrace( FILECONF_TRACE_MASK
,
1252 ((m_linesHead
) ? (const wxChar
*)m_linesHead
->Text().c_str()
1254 wxLogTrace( FILECONF_TRACE_MASK
,
1256 ((m_linesTail
) ? (const wxChar
*)m_linesTail
->Text().c_str()
1259 if ( pLine
== m_linesTail
)
1260 return LineListAppend(str
);
1262 wxFileConfigLineList
*pNewLine
= new wxFileConfigLineList(str
);
1263 if ( pLine
== NULL
)
1265 // prepend to the list
1266 pNewLine
->SetNext(m_linesHead
);
1267 m_linesHead
->SetPrev(pNewLine
);
1268 m_linesHead
= pNewLine
;
1272 // insert before pLine
1273 wxFileConfigLineList
*pNext
= pLine
->Next();
1274 pNewLine
->SetNext(pNext
);
1275 pNewLine
->SetPrev(pLine
);
1276 pNext
->SetPrev(pNewLine
);
1277 pLine
->SetNext(pNewLine
);
1280 wxLogTrace( FILECONF_TRACE_MASK
,
1282 ((m_linesHead
) ? (const wxChar
*)m_linesHead
->Text().c_str()
1284 wxLogTrace( FILECONF_TRACE_MASK
,
1286 ((m_linesTail
) ? (const wxChar
*)m_linesTail
->Text().c_str()
1292 void wxFileConfig::LineListRemove(wxFileConfigLineList
*pLine
)
1294 wxLogTrace( FILECONF_TRACE_MASK
,
1295 _T(" ** Removing Line '%s'"),
1296 pLine
->Text().c_str() );
1297 wxLogTrace( FILECONF_TRACE_MASK
,
1299 ((m_linesHead
) ? (const wxChar
*)m_linesHead
->Text().c_str()
1301 wxLogTrace( FILECONF_TRACE_MASK
,
1303 ((m_linesTail
) ? (const wxChar
*)m_linesTail
->Text().c_str()
1306 wxFileConfigLineList
*pPrev
= pLine
->Prev(),
1307 *pNext
= pLine
->Next();
1311 if ( pPrev
== NULL
)
1312 m_linesHead
= pNext
;
1314 pPrev
->SetNext(pNext
);
1318 if ( pNext
== NULL
)
1319 m_linesTail
= pPrev
;
1321 pNext
->SetPrev(pPrev
);
1323 if ( m_pRootGroup
->GetGroupLine() == pLine
)
1324 m_pRootGroup
->SetLine(m_linesHead
);
1326 wxLogTrace( FILECONF_TRACE_MASK
,
1328 ((m_linesHead
) ? (const wxChar
*)m_linesHead
->Text().c_str()
1330 wxLogTrace( FILECONF_TRACE_MASK
,
1332 ((m_linesTail
) ? (const wxChar
*)m_linesTail
->Text().c_str()
1338 bool wxFileConfig::LineListIsEmpty()
1340 return m_linesHead
== NULL
;
1343 // ============================================================================
1344 // wxFileConfig::wxFileConfigGroup
1345 // ============================================================================
1347 // ----------------------------------------------------------------------------
1349 // ----------------------------------------------------------------------------
1352 wxFileConfigGroup::wxFileConfigGroup(wxFileConfigGroup
*pParent
,
1353 const wxString
& strName
,
1354 wxFileConfig
*pConfig
)
1355 : m_aEntries(CompareEntries
),
1356 m_aSubgroups(CompareGroups
),
1359 m_pConfig
= pConfig
;
1360 m_pParent
= pParent
;
1363 m_pLastEntry
= NULL
;
1364 m_pLastGroup
= NULL
;
1367 // dtor deletes all children
1368 wxFileConfigGroup::~wxFileConfigGroup()
1371 size_t n
, nCount
= m_aEntries
.GetCount();
1372 for ( n
= 0; n
< nCount
; n
++ )
1373 delete m_aEntries
[n
];
1376 nCount
= m_aSubgroups
.GetCount();
1377 for ( n
= 0; n
< nCount
; n
++ )
1378 delete m_aSubgroups
[n
];
1381 // ----------------------------------------------------------------------------
1383 // ----------------------------------------------------------------------------
1385 void wxFileConfigGroup::SetLine(wxFileConfigLineList
*pLine
)
1387 // for a normal (i.e. not root) group this method shouldn't be called twice
1388 // unless we are resetting the line
1389 wxASSERT_MSG( !m_pParent
|| !m_pLine
|| !pLine
,
1390 _T("changing line for a non-root group?") );
1396 This is a bit complicated, so let me explain it in details. All lines that
1397 were read from the local file (the only one we will ever modify) are stored
1398 in a (doubly) linked list. Our problem is to know at which position in this
1399 list should we insert the new entries/subgroups. To solve it we keep three
1400 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
1402 m_pLine points to the line containing "[group_name]"
1403 m_pLastEntry points to the last entry of this group in the local file.
1404 m_pLastGroup subgroup
1406 Initially, they're NULL all three. When the group (an entry/subgroup) is read
1407 from the local file, the corresponding variable is set. However, if the group
1408 was read from the global file and then modified or created by the application
1409 these variables are still NULL and we need to create the corresponding lines.
1410 See the following functions (and comments preceding them) for the details of
1413 Also, when our last entry/group are deleted we need to find the new last
1414 element - the code in DeleteEntry/Subgroup does this by backtracking the list
1415 of lines until it either founds an entry/subgroup (and this is the new last
1416 element) or the m_pLine of the group, in which case there are no more entries
1417 (or subgroups) left and m_pLast<element> becomes NULL.
1419 NB: This last problem could be avoided for entries if we added new entries
1420 immediately after m_pLine, but in this case the entries would appear
1421 backwards in the config file (OTOH, it's not that important) and as we
1422 would still need to do it for the subgroups the code wouldn't have been
1423 significantly less complicated.
1426 // Return the line which contains "[our name]". If we're still not in the list,
1427 // add our line to it immediately after the last line of our parent group if we
1428 // have it or in the very beginning if we're the root group.
1429 wxFileConfigLineList
*wxFileConfigGroup::GetGroupLine()
1431 wxLogTrace( FILECONF_TRACE_MASK
,
1432 _T(" GetGroupLine() for Group '%s'"),
1437 wxLogTrace( FILECONF_TRACE_MASK
,
1438 _T(" Getting Line item pointer") );
1440 wxFileConfigGroup
*pParent
= Parent();
1442 // this group wasn't present in local config file, add it now
1445 wxLogTrace( FILECONF_TRACE_MASK
,
1446 _T(" checking parent '%s'"),
1447 pParent
->Name().c_str() );
1449 wxString strFullName
;
1451 // add 1 to the name because we don't want to start with '/'
1452 strFullName
<< wxT("[")
1453 << FilterOutEntryName(GetFullName().c_str() + 1)
1455 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
1456 pParent
->GetLastGroupLine());
1457 pParent
->SetLastGroup(this); // we're surely after all the others
1459 //else: this is the root group and so we return NULL because we don't
1460 // have any group line
1466 // Return the last line belonging to the subgroups of this group (after which
1467 // we can add a new subgroup), if we don't have any subgroups or entries our
1468 // last line is the group line (m_pLine) itself.
1469 wxFileConfigLineList
*wxFileConfigGroup::GetLastGroupLine()
1471 // if we have any subgroups, our last line is the last line of the last
1475 wxFileConfigLineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
1477 wxASSERT_MSG( pLine
, _T("last group must have !NULL associated line") );
1482 // no subgroups, so the last line is the line of thelast entry (if any)
1483 return GetLastEntryLine();
1486 // return the last line belonging to the entries of this group (after which
1487 // we can add a new entry), if we don't have any entries we will add the new
1488 // one immediately after the group line itself.
1489 wxFileConfigLineList
*wxFileConfigGroup::GetLastEntryLine()
1491 wxLogTrace( FILECONF_TRACE_MASK
,
1492 _T(" GetLastEntryLine() for Group '%s'"),
1497 wxFileConfigLineList
*pLine
= m_pLastEntry
->GetLine();
1499 wxASSERT_MSG( pLine
, _T("last entry must have !NULL associated line") );
1504 // no entries: insert after the group header, if any
1505 return GetGroupLine();
1508 void wxFileConfigGroup::SetLastEntry(wxFileConfigEntry
*pEntry
)
1510 m_pLastEntry
= pEntry
;
1514 // the only situation in which a group without its own line can have
1515 // an entry is when the first entry is added to the initially empty
1516 // root pseudo-group
1517 wxASSERT_MSG( !m_pParent
, _T("unexpected for non root group") );
1519 // let the group know that it does have a line in the file now
1520 m_pLine
= pEntry
->GetLine();
1524 // ----------------------------------------------------------------------------
1526 // ----------------------------------------------------------------------------
1528 void wxFileConfigGroup::UpdateGroupAndSubgroupsLines()
1530 // update the line of this group
1531 wxFileConfigLineList
*line
= GetGroupLine();
1532 wxCHECK_RET( line
, _T("a non root group must have a corresponding line!") );
1534 // +1: skip the leading '/'
1535 line
->SetText(wxString::Format(_T("[%s]"), GetFullName().c_str() + 1));
1538 // also update all subgroups as they have this groups name in their lines
1539 const size_t nCount
= m_aSubgroups
.GetCount();
1540 for ( size_t n
= 0; n
< nCount
; n
++ )
1542 m_aSubgroups
[n
]->UpdateGroupAndSubgroupsLines();
1546 void wxFileConfigGroup::Rename(const wxString
& newName
)
1548 wxCHECK_RET( m_pParent
, _T("the root group can't be renamed") );
1550 if ( newName
== m_strName
)
1553 // we need to remove the group from the parent and it back under the new
1554 // name to keep the parents array of subgroups alphabetically sorted
1555 m_pParent
->m_aSubgroups
.Remove(this);
1557 m_strName
= newName
;
1559 m_pParent
->m_aSubgroups
.Add(this);
1561 // update the group lines recursively
1562 UpdateGroupAndSubgroupsLines();
1565 wxString
wxFileConfigGroup::GetFullName() const
1569 fullname
= Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
1574 // ----------------------------------------------------------------------------
1576 // ----------------------------------------------------------------------------
1578 // use binary search because the array is sorted
1580 wxFileConfigGroup::FindEntry(const wxString
& name
) const
1584 hi
= m_aEntries
.GetCount();
1586 wxFileConfigEntry
*pEntry
;
1590 pEntry
= m_aEntries
[i
];
1592 #if wxCONFIG_CASE_SENSITIVE
1593 res
= pEntry
->Name().compare(name
);
1595 res
= pEntry
->Name().CmpNoCase(name
);
1610 wxFileConfigGroup::FindSubgroup(const wxString
& name
) const
1614 hi
= m_aSubgroups
.GetCount();
1616 wxFileConfigGroup
*pGroup
;
1620 pGroup
= m_aSubgroups
[i
];
1622 #if wxCONFIG_CASE_SENSITIVE
1623 res
= pGroup
->Name().compare(name
);
1625 res
= pGroup
->Name().CmpNoCase(name
);
1639 // ----------------------------------------------------------------------------
1640 // create a new item
1641 // ----------------------------------------------------------------------------
1643 // create a new entry and add it to the current group
1644 wxFileConfigEntry
*wxFileConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
1646 wxASSERT( FindEntry(strName
) == 0 );
1648 wxFileConfigEntry
*pEntry
= new wxFileConfigEntry(this, strName
, nLine
);
1650 m_aEntries
.Add(pEntry
);
1654 // create a new group and add it to the current group
1655 wxFileConfigGroup
*wxFileConfigGroup::AddSubgroup(const wxString
& strName
)
1657 wxASSERT( FindSubgroup(strName
) == 0 );
1659 wxFileConfigGroup
*pGroup
= new wxFileConfigGroup(this, strName
, m_pConfig
);
1661 m_aSubgroups
.Add(pGroup
);
1665 // ----------------------------------------------------------------------------
1667 // ----------------------------------------------------------------------------
1670 The delete operations are _very_ slow if we delete the last item of this
1671 group (see comments before GetXXXLineXXX functions for more details),
1672 so it's much better to start with the first entry/group if we want to
1673 delete several of them.
1676 bool wxFileConfigGroup::DeleteSubgroupByName(const wxString
& name
)
1678 wxFileConfigGroup
* const pGroup
= FindSubgroup(name
);
1680 return pGroup
? DeleteSubgroup(pGroup
) : false;
1683 // Delete the subgroup and remove all references to it from
1684 // other data structures.
1685 bool wxFileConfigGroup::DeleteSubgroup(wxFileConfigGroup
*pGroup
)
1687 wxCHECK_MSG( pGroup
, false, _T("deleting non existing group?") );
1689 wxLogTrace( FILECONF_TRACE_MASK
,
1690 _T("Deleting group '%s' from '%s'"),
1691 pGroup
->Name().c_str(),
1694 wxLogTrace( FILECONF_TRACE_MASK
,
1695 _T(" (m_pLine) = prev: %p, this %p, next %p"),
1696 m_pLine
? wx_static_cast(void*, m_pLine
->Prev()) : 0,
1697 wx_static_cast(void*, m_pLine
),
1698 m_pLine
? wx_static_cast(void*, m_pLine
->Next()) : 0 );
1699 wxLogTrace( FILECONF_TRACE_MASK
,
1701 m_pLine
? (const wxChar
*)m_pLine
->Text().c_str()
1704 // delete all entries...
1705 size_t nCount
= pGroup
->m_aEntries
.GetCount();
1707 wxLogTrace(FILECONF_TRACE_MASK
,
1708 _T("Removing %lu entries"), (unsigned long)nCount
);
1710 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ )
1712 wxFileConfigLineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1716 wxLogTrace( FILECONF_TRACE_MASK
,
1718 pLine
->Text().c_str() );
1719 m_pConfig
->LineListRemove(pLine
);
1723 // ...and subgroups of this subgroup
1724 nCount
= pGroup
->m_aSubgroups
.GetCount();
1726 wxLogTrace( FILECONF_TRACE_MASK
,
1727 _T("Removing %lu subgroups"), (unsigned long)nCount
);
1729 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ )
1731 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[0]);
1734 // and then finally the group itself
1735 wxFileConfigLineList
*pLine
= pGroup
->m_pLine
;
1738 wxLogTrace( FILECONF_TRACE_MASK
,
1739 _T(" Removing line for group '%s' : '%s'"),
1740 pGroup
->Name().c_str(),
1741 pLine
->Text().c_str() );
1742 wxLogTrace( FILECONF_TRACE_MASK
,
1743 _T(" Removing from group '%s' : '%s'"),
1745 ((m_pLine
) ? (const wxChar
*)m_pLine
->Text().c_str()
1748 // notice that we may do this test inside the previous "if"
1749 // because the last entry's line is surely !NULL
1750 if ( pGroup
== m_pLastGroup
)
1752 wxLogTrace( FILECONF_TRACE_MASK
,
1753 _T(" Removing last group") );
1755 // our last entry is being deleted, so find the last one which
1756 // stays by going back until we find a subgroup or reach the
1758 const size_t nSubgroups
= m_aSubgroups
.GetCount();
1760 m_pLastGroup
= NULL
;
1761 for ( wxFileConfigLineList
*pl
= pLine
->Prev();
1762 pl
&& !m_pLastGroup
;
1765 // does this line belong to our subgroup?
1766 for ( size_t n
= 0; n
< nSubgroups
; n
++ )
1768 // do _not_ call GetGroupLine! we don't want to add it to
1769 // the local file if it's not already there
1770 if ( m_aSubgroups
[n
]->m_pLine
== pl
)
1772 m_pLastGroup
= m_aSubgroups
[n
];
1777 if ( pl
== m_pLine
)
1782 m_pConfig
->LineListRemove(pLine
);
1786 wxLogTrace( FILECONF_TRACE_MASK
,
1787 _T(" No line entry for Group '%s'?"),
1788 pGroup
->Name().c_str() );
1791 m_aSubgroups
.Remove(pGroup
);
1797 bool wxFileConfigGroup::DeleteEntry(const wxString
& name
)
1799 wxFileConfigEntry
*pEntry
= FindEntry(name
);
1802 // entry doesn't exist, nothing to do
1806 wxFileConfigLineList
*pLine
= pEntry
->GetLine();
1807 if ( pLine
!= NULL
) {
1808 // notice that we may do this test inside the previous "if" because the
1809 // last entry's line is surely !NULL
1810 if ( pEntry
== m_pLastEntry
) {
1811 // our last entry is being deleted - find the last one which stays
1812 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1814 // go back until we find another entry or reach the group's line
1815 wxFileConfigEntry
*pNewLast
= NULL
;
1816 size_t n
, nEntries
= m_aEntries
.GetCount();
1817 wxFileConfigLineList
*pl
;
1818 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1819 // is it our subgroup?
1820 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1821 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1822 pNewLast
= m_aEntries
[n
];
1825 if ( pNewLast
!= NULL
) // found?
1829 if ( pl
== m_pLine
) {
1830 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1832 // we've reached the group line without finding any subgroups
1833 m_pLastEntry
= NULL
;
1836 m_pLastEntry
= pNewLast
;
1839 m_pConfig
->LineListRemove(pLine
);
1842 m_aEntries
.Remove(pEntry
);
1848 // ============================================================================
1849 // wxFileConfig::wxFileConfigEntry
1850 // ============================================================================
1852 // ----------------------------------------------------------------------------
1854 // ----------------------------------------------------------------------------
1855 wxFileConfigEntry::wxFileConfigEntry(wxFileConfigGroup
*pParent
,
1856 const wxString
& strName
,
1858 : m_strName(strName
)
1860 wxASSERT( !strName
.empty() );
1862 m_pParent
= pParent
;
1866 m_bHasValue
= false;
1868 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1870 m_strName
.erase(0, 1); // remove first character
1873 // ----------------------------------------------------------------------------
1875 // ----------------------------------------------------------------------------
1877 void wxFileConfigEntry::SetLine(wxFileConfigLineList
*pLine
)
1879 if ( m_pLine
!= NULL
) {
1880 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1881 Name().c_str(), m_pParent
->GetFullName().c_str());
1885 Group()->SetLastEntry(this);
1888 // second parameter is false if we read the value from file and prevents the
1889 // entry from being marked as 'dirty'
1890 void wxFileConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1892 if ( bUser
&& IsImmutable() )
1894 wxLogWarning( _("attempt to change immutable key '%s' ignored."),
1899 // do nothing if it's the same value: but don't test for it if m_bHasValue
1900 // hadn't been set yet or we'd never write empty values to the file
1901 if ( m_bHasValue
&& strValue
== m_strValue
)
1905 m_strValue
= strValue
;
1909 wxString strValFiltered
;
1911 if ( Group()->Config()->GetStyle() & wxCONFIG_USE_NO_ESCAPE_CHARACTERS
)
1913 strValFiltered
= strValue
;
1916 strValFiltered
= FilterOutValue(strValue
);
1920 strLine
<< FilterOutEntryName(m_strName
) << wxT('=') << strValFiltered
;
1924 // entry was read from the local config file, just modify the line
1925 m_pLine
->SetText(strLine
);
1927 else // this entry didn't exist in the local file
1929 // add a new line to the file: note that line returned by
1930 // GetLastEntryLine() may be NULL if we're in the root group and it
1931 // doesn't have any entries yet, but this is ok as passing NULL
1932 // line to LineListInsert() means to prepend new line to the list
1933 wxFileConfigLineList
*line
= Group()->GetLastEntryLine();
1934 m_pLine
= Group()->Config()->LineListInsert(strLine
, line
);
1936 Group()->SetLastEntry(this);
1941 // ============================================================================
1943 // ============================================================================
1945 // ----------------------------------------------------------------------------
1946 // compare functions for array sorting
1947 // ----------------------------------------------------------------------------
1949 int CompareEntries(wxFileConfigEntry
*p1
, wxFileConfigEntry
*p2
)
1951 #if wxCONFIG_CASE_SENSITIVE
1952 return p1
->Name().compare(p2
->Name());
1954 return p1
->Name().CmpNoCase(p2
->Name());
1958 int CompareGroups(wxFileConfigGroup
*p1
, wxFileConfigGroup
*p2
)
1960 #if wxCONFIG_CASE_SENSITIVE
1961 return p1
->Name().compare(p2
->Name());
1963 return p1
->Name().CmpNoCase(p2
->Name());
1967 // ----------------------------------------------------------------------------
1969 // ----------------------------------------------------------------------------
1971 // undo FilterOutValue
1972 static wxString
FilterInValue(const wxString
& str
)
1975 strResult
.Alloc(str
.Len());
1977 bool bQuoted
= !str
.empty() && str
[0] == '"';
1979 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1980 if ( str
[n
] == wxT('\\') ) {
1981 switch ( str
[++n
].GetValue() ) {
1983 strResult
+= wxT('\n');
1987 strResult
+= wxT('\r');
1991 strResult
+= wxT('\t');
1995 strResult
+= wxT('\\');
1999 strResult
+= wxT('"');
2004 if ( str
[n
] != wxT('"') || !bQuoted
)
2005 strResult
+= str
[n
];
2006 else if ( n
!= str
.Len() - 1 ) {
2007 wxLogWarning(_("unexpected \" at position %d in '%s'."),
2010 //else: it's the last quote of a quoted string, ok
2017 // quote the string before writing it to file
2018 static wxString
FilterOutValue(const wxString
& str
)
2024 strResult
.Alloc(str
.Len());
2026 // quoting is necessary to preserve spaces in the beginning of the string
2027 bool bQuote
= wxIsspace(str
[0]) || str
[0] == wxT('"');
2030 strResult
+= wxT('"');
2033 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
2034 switch ( str
[n
].GetValue() ) {
2056 //else: fall through
2059 strResult
+= str
[n
];
2060 continue; // nothing special to do
2063 // we get here only for special characters
2064 strResult
<< wxT('\\') << c
;
2068 strResult
+= wxT('"');
2073 // undo FilterOutEntryName
2074 static wxString
FilterInEntryName(const wxString
& str
)
2077 strResult
.Alloc(str
.Len());
2079 for ( const wxChar
*pc
= str
.c_str(); *pc
!= '\0'; pc
++ ) {
2080 if ( *pc
== wxT('\\') ) {
2081 // we need to test it here or we'd skip past the NUL in the loop line
2082 if ( *++pc
== _T('\0') )
2092 // sanitize entry or group name: insert '\\' before any special characters
2093 static wxString
FilterOutEntryName(const wxString
& str
)
2096 strResult
.Alloc(str
.Len());
2098 for ( const wxChar
*pc
= str
.c_str(); *pc
!= wxT('\0'); pc
++ ) {
2099 const wxChar c
= *pc
;
2101 // we explicitly allow some of "safe" chars and 8bit ASCII characters
2102 // which will probably never have special meaning and with which we can't
2103 // use isalnum() anyhow (in ASCII built, in Unicode it's just fine)
2105 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
2106 // should *not* be quoted
2109 ((unsigned char)c
< 127) &&
2111 !wxIsalnum(c
) && !wxStrchr(wxT("@_/-!.*%"), c
) )
2113 strResult
+= wxT('\\');
2122 // we can't put ?: in the ctor initializer list because it confuses some
2123 // broken compilers (Borland C++)
2124 static wxString
GetAppName(const wxString
& appName
)
2126 if ( !appName
&& wxTheApp
)
2127 return wxTheApp
->GetAppName();
2132 #endif // wxUSE_CONFIG