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 #include "wx/wxprec.h"
23 #if wxUSE_CONFIG && wxUSE_FILECONFIG
26 #include "wx/string.h"
31 #include "wx/dynarray.h"
34 #include "wx/textfile.h"
35 #include "wx/memtext.h"
36 #include "wx/config.h"
37 #include "wx/fileconf.h"
38 #include "wx/filefn.h"
41 #include "wx/stream.h"
42 #endif // wxUSE_STREAMS
44 #include "wx/utils.h" // for wxGetHomeDir
46 #if defined(__WXMAC__)
47 #include "wx/mac/private.h" // includes mac headers
48 #include "wx/filename.h" // for MacSetTypeAndCreator
51 #if defined(__WXMSW__)
52 #include "wx/msw/private.h"
62 // ----------------------------------------------------------------------------
64 // ----------------------------------------------------------------------------
65 #define CONST_CAST ((wxFileConfig *)this)->
67 // ----------------------------------------------------------------------------
69 // ----------------------------------------------------------------------------
75 #define FILECONF_TRACE_MASK _T("fileconf")
77 // ----------------------------------------------------------------------------
78 // global functions declarations
79 // ----------------------------------------------------------------------------
81 // compare functions for sorting the arrays
82 static int LINKAGEMODE
CompareEntries(wxFileConfigEntry
*p1
, wxFileConfigEntry
*p2
);
83 static int LINKAGEMODE
CompareGroups(wxFileConfigGroup
*p1
, wxFileConfigGroup
*p2
);
86 static wxString
FilterInValue(const wxString
& str
);
87 static wxString
FilterOutValue(const wxString
& str
);
89 static wxString
FilterInEntryName(const wxString
& str
);
90 static wxString
FilterOutEntryName(const wxString
& str
);
92 // get the name to use in wxFileConfig ctor
93 static wxString
GetAppName(const wxString
& appname
);
95 // ============================================================================
97 // ============================================================================
99 // ----------------------------------------------------------------------------
100 // "template" array types
101 // ----------------------------------------------------------------------------
103 #ifdef WXMAKINGDLL_BASE
104 WX_DEFINE_SORTED_USER_EXPORTED_ARRAY(wxFileConfigEntry
*, ArrayEntries
,
106 WX_DEFINE_SORTED_USER_EXPORTED_ARRAY(wxFileConfigGroup
*, ArrayGroups
,
109 WX_DEFINE_SORTED_ARRAY(wxFileConfigEntry
*, ArrayEntries
);
110 WX_DEFINE_SORTED_ARRAY(wxFileConfigGroup
*, ArrayGroups
);
113 // ----------------------------------------------------------------------------
114 // wxFileConfigLineList
115 // ----------------------------------------------------------------------------
117 // we store all lines of the local config file as a linked list in memory
118 class wxFileConfigLineList
121 void SetNext(wxFileConfigLineList
*pNext
) { m_pNext
= pNext
; }
122 void SetPrev(wxFileConfigLineList
*pPrev
) { m_pPrev
= pPrev
; }
125 wxFileConfigLineList(const wxString
& str
,
126 wxFileConfigLineList
*pNext
= NULL
) : m_strLine(str
)
127 { SetNext(pNext
); SetPrev(NULL
); }
129 // next/prev nodes in the linked list
130 wxFileConfigLineList
*Next() const { return m_pNext
; }
131 wxFileConfigLineList
*Prev() const { return m_pPrev
; }
133 // get/change lines text
134 void SetText(const wxString
& str
) { m_strLine
= str
; }
135 const wxString
& Text() const { return m_strLine
; }
138 wxString m_strLine
; // line contents
139 wxFileConfigLineList
*m_pNext
, // next node
140 *m_pPrev
; // previous one
142 DECLARE_NO_COPY_CLASS(wxFileConfigLineList
)
145 // ----------------------------------------------------------------------------
146 // wxFileConfigEntry: a name/value pair
147 // ----------------------------------------------------------------------------
149 class wxFileConfigEntry
152 wxFileConfigGroup
*m_pParent
; // group that contains us
154 wxString m_strName
, // entry name
156 bool m_bImmutable
:1, // can be overriden locally?
157 m_bHasValue
:1; // set after first call to SetValue()
159 int m_nLine
; // used if m_pLine == NULL only
161 // pointer to our line in the linked list or NULL if it was found in global
162 // file (which we don't modify)
163 wxFileConfigLineList
*m_pLine
;
166 wxFileConfigEntry(wxFileConfigGroup
*pParent
,
167 const wxString
& strName
, int nLine
);
170 const wxString
& Name() const { return m_strName
; }
171 const wxString
& Value() const { return m_strValue
; }
172 wxFileConfigGroup
*Group() const { return m_pParent
; }
173 bool IsImmutable() const { return m_bImmutable
; }
174 bool IsLocal() const { return m_pLine
!= 0; }
175 int Line() const { return m_nLine
; }
176 wxFileConfigLineList
*
177 GetLine() const { return m_pLine
; }
179 // modify entry attributes
180 void SetValue(const wxString
& strValue
, bool bUser
= true);
181 void SetLine(wxFileConfigLineList
*pLine
);
183 DECLARE_NO_COPY_CLASS(wxFileConfigEntry
)
186 // ----------------------------------------------------------------------------
187 // wxFileConfigGroup: container of entries and other groups
188 // ----------------------------------------------------------------------------
190 class wxFileConfigGroup
193 wxFileConfig
*m_pConfig
; // config object we belong to
194 wxFileConfigGroup
*m_pParent
; // parent group (NULL for root group)
195 ArrayEntries m_aEntries
; // entries in this group
196 ArrayGroups m_aSubgroups
; // subgroups
197 wxString m_strName
; // group's name
198 wxFileConfigLineList
*m_pLine
; // pointer to our line in the linked list
199 wxFileConfigEntry
*m_pLastEntry
; // last entry/subgroup of this group in the
200 wxFileConfigGroup
*m_pLastGroup
; // local file (we insert new ones after it)
202 // DeleteSubgroupByName helper
203 bool DeleteSubgroup(wxFileConfigGroup
*pGroup
);
206 void UpdateGroupAndSubgroupsLines();
210 wxFileConfigGroup(wxFileConfigGroup
*pParent
, const wxString
& strName
, wxFileConfig
*);
212 // dtor deletes all entries and subgroups also
213 ~wxFileConfigGroup();
216 const wxString
& Name() const { return m_strName
; }
217 wxFileConfigGroup
*Parent() const { return m_pParent
; }
218 wxFileConfig
*Config() const { return m_pConfig
; }
220 const ArrayEntries
& Entries() const { return m_aEntries
; }
221 const ArrayGroups
& Groups() const { return m_aSubgroups
; }
222 bool IsEmpty() const { return Entries().IsEmpty() && Groups().IsEmpty(); }
224 // find entry/subgroup (NULL if not found)
225 wxFileConfigGroup
*FindSubgroup(const wxChar
*szName
) const;
226 wxFileConfigEntry
*FindEntry (const wxChar
*szName
) const;
228 // delete entry/subgroup, return false if doesn't exist
229 bool DeleteSubgroupByName(const wxChar
*szName
);
230 bool DeleteEntry(const wxChar
*szName
);
232 // create new entry/subgroup returning pointer to newly created element
233 wxFileConfigGroup
*AddSubgroup(const wxString
& strName
);
234 wxFileConfigEntry
*AddEntry (const wxString
& strName
, int nLine
= wxNOT_FOUND
);
236 void SetLine(wxFileConfigLineList
*pLine
);
238 // rename: no checks are done to ensure that the name is unique!
239 void Rename(const wxString
& newName
);
242 wxString
GetFullName() const;
244 // get the last line belonging to an entry/subgroup of this group
245 wxFileConfigLineList
*GetGroupLine(); // line which contains [group]
246 wxFileConfigLineList
*GetLastEntryLine(); // after which our subgroups start
247 wxFileConfigLineList
*GetLastGroupLine(); // after which the next group starts
249 // called by entries/subgroups when they're created/deleted
250 void SetLastEntry(wxFileConfigEntry
*pEntry
);
251 void SetLastGroup(wxFileConfigGroup
*pGroup
)
252 { m_pLastGroup
= pGroup
; }
254 DECLARE_NO_COPY_CLASS(wxFileConfigGroup
)
257 // ============================================================================
259 // ============================================================================
261 // ----------------------------------------------------------------------------
263 // ----------------------------------------------------------------------------
264 wxString
wxFileConfig::GetGlobalDir()
268 #ifdef __VMS__ // Note if __VMS is defined __UNIX is also defined
269 strDir
= wxT("sys$manager:");
270 #elif defined(__WXMAC__)
271 strDir
= wxMacFindFolder( (short) kOnSystemDisk
, kPreferencesFolderType
, kDontCreateFolder
) ;
272 #elif defined( __UNIX__ )
273 strDir
= wxT("/etc/");
274 #elif defined(__OS2__)
275 ULONG aulSysInfo
[QSV_MAX
] = {0};
279 rc
= DosQuerySysInfo( 1L, QSV_MAX
, (PVOID
)aulSysInfo
, sizeof(ULONG
)*QSV_MAX
);
282 drive
= aulSysInfo
[QSV_BOOT_DRIVE
- 1];
283 strDir
.Printf(wxT("%c:\\OS2\\"), 'A'+drive
-1);
285 #elif defined(__WXSTUBS__)
286 wxASSERT_MSG( false, wxT("TODO") ) ;
287 #elif defined(__DOS__)
288 // There's no such thing as global cfg dir in MS-DOS, let's return
289 // current directory (FIXME_MGL?)
291 #elif defined(__WXWINCE__)
292 strDir
= wxT("\\Windows\\");
295 wxChar szWinDir
[MAX_PATH
];
296 ::GetWindowsDirectory(szWinDir
, MAX_PATH
);
300 #endif // Unix/Windows
305 wxString
wxFileConfig::GetLocalDir()
309 #if defined(__WXMAC__) || defined(__DOS__)
310 // no local dir concept on Mac OS 9 or MS-DOS
311 strDir
<< GetGlobalDir() ;
313 wxGetHomeDir(&strDir
);
317 (strDir
.Last() != wxT('/'))
319 && (strDir
.Last() != wxT(']'))
324 if (strDir
.Last() != wxT('\\'))
332 wxString
wxFileConfig::GetGlobalFileName(const wxChar
*szFile
)
334 wxString str
= GetGlobalDir();
337 if ( wxStrchr(szFile
, wxT('.')) == NULL
)
338 #if defined( __WXMAC__ )
339 str
<< wxT(" Preferences") ;
340 #elif defined( __UNIX__ )
349 wxString
wxFileConfig::GetLocalFileName(const wxChar
*szFile
)
352 // On VMS I saw the problem that the home directory was appended
353 // twice for the configuration file. Does that also happen for
355 wxString str
= wxT( '.' );
357 wxString str
= GetLocalDir();
360 #if defined( __UNIX__ ) && !defined( __VMS ) && !defined( __WXMAC__ )
366 #if defined(__WINDOWS__) || defined(__DOS__)
367 if ( wxStrchr(szFile
, wxT('.')) == NULL
)
372 str
<< wxT(" Preferences") ;
378 // ----------------------------------------------------------------------------
380 // ----------------------------------------------------------------------------
382 void wxFileConfig::Init()
385 m_pRootGroup
= new wxFileConfigGroup(NULL
, wxEmptyString
, this);
390 // It's not an error if (one of the) file(s) doesn't exist.
392 // parse the global file
393 if ( !m_strGlobalFile
.empty() && wxFile::Exists(m_strGlobalFile
) )
395 wxTextFile
fileGlobal(m_strGlobalFile
);
397 if ( fileGlobal
.Open(m_conv
/*ignored in ANSI build*/) )
399 Parse(fileGlobal
, false /* global */);
404 wxLogWarning(_("can't open global configuration file '%s'."), m_strGlobalFile
.c_str());
408 // parse the local file
409 if ( !m_strLocalFile
.empty() && wxFile::Exists(m_strLocalFile
) )
411 wxTextFile
fileLocal(m_strLocalFile
);
412 if ( fileLocal
.Open(m_conv
/*ignored in ANSI build*/) )
414 Parse(fileLocal
, true /* local */);
419 wxLogWarning(_("can't open user configuration file '%s'."), m_strLocalFile
.c_str() );
426 // constructor supports creation of wxFileConfig objects of any type
427 wxFileConfig::wxFileConfig(const wxString
& appName
, const wxString
& vendorName
,
428 const wxString
& strLocal
, const wxString
& strGlobal
,
429 long style
, wxMBConv
& conv
)
430 : wxConfigBase(::GetAppName(appName
), vendorName
,
433 m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
),
436 // Make up names for files if empty
437 if ( m_strLocalFile
.empty() && (style
& wxCONFIG_USE_LOCAL_FILE
) )
438 m_strLocalFile
= GetLocalFileName(GetAppName());
440 if ( m_strGlobalFile
.empty() && (style
& wxCONFIG_USE_GLOBAL_FILE
) )
441 m_strGlobalFile
= GetGlobalFileName(GetAppName());
443 // Check if styles are not supplied, but filenames are, in which case
444 // add the correct styles.
445 if ( !m_strLocalFile
.empty() )
446 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE
);
448 if ( !m_strGlobalFile
.empty() )
449 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE
);
451 // if the path is not absolute, prepend the standard directory to it
452 // UNLESS wxCONFIG_USE_RELATIVE_PATH style is set
453 if ( !(style
& wxCONFIG_USE_RELATIVE_PATH
) )
455 if ( !m_strLocalFile
.empty() && !wxIsAbsolutePath(m_strLocalFile
) )
457 const wxString strLocalOrig
= m_strLocalFile
;
458 m_strLocalFile
= GetLocalDir();
459 m_strLocalFile
<< strLocalOrig
;
462 if ( !m_strGlobalFile
.empty() && !wxIsAbsolutePath(m_strGlobalFile
) )
464 const wxString strGlobalOrig
= m_strGlobalFile
;
465 m_strGlobalFile
= GetGlobalDir();
466 m_strGlobalFile
<< strGlobalOrig
;
477 wxFileConfig::wxFileConfig(wxInputStream
&inStream
, wxMBConv
& conv
)
480 // always local_file when this constructor is called (?)
481 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE
);
484 m_pRootGroup
= new wxFileConfigGroup(NULL
, wxEmptyString
, this);
489 // translate everything to the current (platform-dependent) line
490 // termination character
498 inStream
.Read(buf
, WXSIZEOF(buf
)-1); // leave room for the NULL
500 const wxStreamError err
= inStream
.GetLastError();
502 if ( err
!= wxSTREAM_NO_ERROR
&& err
!= wxSTREAM_EOF
)
504 wxLogError(_("Error reading config options."));
508 // FIXME: this is broken because if we have part of multibyte
509 // character in the buffer (and another part hasn't been
510 // read yet) we're going to lose data because of conversion
512 buf
[inStream
.LastRead()] = '\0';
513 strTmp
+= conv
.cMB2WX(buf
);
515 while ( !inStream
.Eof() );
517 strTrans
= wxTextBuffer::Translate(strTmp
);
520 wxMemoryText memText
;
522 // Now we can add the text to the memory text. To do this we extract line
523 // by line from the translated string, until we've reached the end.
525 // VZ: all this is horribly inefficient, we should do the translation on
526 // the fly in one pass saving both memory and time (TODO)
528 const wxChar
*pEOL
= wxTextBuffer::GetEOL(wxTextBuffer::typeDefault
);
529 const size_t EOLLen
= wxStrlen(pEOL
);
531 int posLineStart
= strTrans
.Find(pEOL
);
532 while ( posLineStart
!= -1 )
534 wxString
line(strTrans
.Left(posLineStart
));
536 memText
.AddLine(line
);
538 strTrans
= strTrans
.Mid(posLineStart
+ EOLLen
);
540 posLineStart
= strTrans
.Find(pEOL
);
543 // also add whatever we have left in the translated string.
544 if ( !strTrans
.empty() )
545 memText
.AddLine(strTrans
);
547 // Finally we can parse it all.
548 Parse(memText
, true /* local */);
554 #endif // wxUSE_STREAMS
556 void wxFileConfig::CleanUp()
560 wxFileConfigLineList
*pCur
= m_linesHead
;
561 while ( pCur
!= NULL
) {
562 wxFileConfigLineList
*pNext
= pCur
->Next();
568 wxFileConfig::~wxFileConfig()
575 // ----------------------------------------------------------------------------
576 // parse a config file
577 // ----------------------------------------------------------------------------
579 void wxFileConfig::Parse(const wxTextBuffer
& buffer
, bool bLocal
)
581 const wxChar
*pStart
;
585 size_t nLineCount
= buffer
.GetLineCount();
587 for ( size_t n
= 0; n
< nLineCount
; n
++ )
591 // add the line to linked list
594 LineListAppend(strLine
);
596 // let the root group have its start line as well
599 m_pCurrentGroup
->SetLine(m_linesTail
);
604 // skip leading spaces
605 for ( pStart
= strLine
; wxIsspace(*pStart
); pStart
++ )
608 // skip blank/comment lines
609 if ( *pStart
== wxT('\0')|| *pStart
== wxT(';') || *pStart
== wxT('#') )
612 if ( *pStart
== wxT('[') ) { // a new group
615 while ( *++pEnd
!= wxT(']') ) {
616 if ( *pEnd
== wxT('\\') ) {
617 // the next char is escaped, so skip it even if it is ']'
621 if ( *pEnd
== wxT('\n') || *pEnd
== wxT('\0') ) {
622 // we reached the end of line, break out of the loop
627 if ( *pEnd
!= wxT(']') ) {
628 wxLogError(_("file '%s': unexpected character %c at line %d."),
629 buffer
.GetName(), *pEnd
, n
+ 1);
630 continue; // skip this line
633 // group name here is always considered as abs path
636 strGroup
<< wxCONFIG_PATH_SEPARATOR
637 << FilterInEntryName(wxString(pStart
, pEnd
- pStart
));
639 // will create it if doesn't yet exist
644 if ( m_pCurrentGroup
->Parent() )
645 m_pCurrentGroup
->Parent()->SetLastGroup(m_pCurrentGroup
);
646 m_pCurrentGroup
->SetLine(m_linesTail
);
649 // check that there is nothing except comments left on this line
651 while ( *++pEnd
!= wxT('\0') && bCont
) {
660 // ignore whitespace ('\n' impossible here)
664 wxLogWarning(_("file '%s', line %d: '%s' ignored after group header."),
665 buffer
.GetName(), n
+ 1, pEnd
);
672 while ( *pEnd
&& *pEnd
!= wxT('=') /* && !wxIsspace(*pEnd)*/ ) {
673 if ( *pEnd
== wxT('\\') ) {
674 // next character may be space or not - still take it because it's
675 // quoted (unless there is nothing)
678 // the error message will be given below anyhow
686 wxString
strKey(FilterInEntryName(wxString(pStart
, pEnd
).Trim()));
689 while ( wxIsspace(*pEnd
) )
692 if ( *pEnd
++ != wxT('=') ) {
693 wxLogError(_("file '%s', line %d: '=' expected."),
694 buffer
.GetName(), n
+ 1);
697 wxFileConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
699 if ( pEntry
== NULL
) {
701 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
704 if ( bLocal
&& pEntry
->IsImmutable() ) {
705 // immutable keys can't be changed by user
706 wxLogWarning(_("file '%s', line %d: value for immutable key '%s' ignored."),
707 buffer
.GetName(), n
+ 1, strKey
.c_str());
710 // the condition below catches the cases (a) and (b) but not (c):
711 // (a) global key found second time in global file
712 // (b) key found second (or more) time in local file
713 // (c) key from global file now found in local one
714 // which is exactly what we want.
715 else if ( !bLocal
|| pEntry
->IsLocal() ) {
716 wxLogWarning(_("file '%s', line %d: key '%s' was first found at line %d."),
717 buffer
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
723 pEntry
->SetLine(m_linesTail
);
726 while ( wxIsspace(*pEnd
) )
729 wxString value
= pEnd
;
730 if ( !(GetStyle() & wxCONFIG_USE_NO_ESCAPE_CHARACTERS
) )
731 value
= FilterInValue(value
);
733 pEntry
->SetValue(value
, false);
739 // ----------------------------------------------------------------------------
741 // ----------------------------------------------------------------------------
743 void wxFileConfig::SetRootPath()
746 m_pCurrentGroup
= m_pRootGroup
;
750 wxFileConfig::DoSetPath(const wxString
& strPath
, bool createMissingComponents
)
752 wxArrayString aParts
;
754 if ( strPath
.empty() ) {
759 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
761 wxSplitPath(aParts
, strPath
);
764 // relative path, combine with current one
765 wxString strFullPath
= m_strPath
;
766 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
767 wxSplitPath(aParts
, strFullPath
);
770 // change current group
772 m_pCurrentGroup
= m_pRootGroup
;
773 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
774 wxFileConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
775 if ( pNextGroup
== NULL
)
777 if ( !createMissingComponents
)
780 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
783 m_pCurrentGroup
= pNextGroup
;
786 // recombine path parts in one variable
788 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
789 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
795 void wxFileConfig::SetPath(const wxString
& strPath
)
797 DoSetPath(strPath
, true /* create missing path components */);
800 // ----------------------------------------------------------------------------
802 // ----------------------------------------------------------------------------
804 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
807 return GetNextGroup(str
, lIndex
);
810 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
812 if ( size_t(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
813 str
= m_pCurrentGroup
->Groups()[(size_t)lIndex
++]->Name();
820 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
823 return GetNextEntry(str
, lIndex
);
826 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
828 if ( size_t(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
829 str
= m_pCurrentGroup
->Entries()[(size_t)lIndex
++]->Name();
836 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
838 size_t n
= m_pCurrentGroup
->Entries().Count();
840 wxFileConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
841 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
842 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
843 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
844 n
+= GetNumberOfEntries(true);
845 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
852 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
854 size_t n
= m_pCurrentGroup
->Groups().Count();
856 wxFileConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
857 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
858 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
859 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
860 n
+= GetNumberOfGroups(true);
861 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
868 // ----------------------------------------------------------------------------
869 // tests for existence
870 // ----------------------------------------------------------------------------
872 bool wxFileConfig::HasGroup(const wxString
& strName
) const
874 // special case: DoSetPath("") does work as it's equivalent to DoSetPath("/")
875 // but there is no group with empty name so treat this separately
876 if ( strName
.empty() )
879 const wxString pathOld
= GetPath();
881 wxFileConfig
*self
= wx_const_cast(wxFileConfig
*, this);
883 rc
= self
->DoSetPath(strName
, false /* don't create missing components */);
885 self
->SetPath(pathOld
);
890 bool wxFileConfig::HasEntry(const wxString
& strName
) const
892 wxConfigPathChanger
path(this, strName
);
894 wxFileConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
895 return pEntry
!= NULL
;
898 // ----------------------------------------------------------------------------
900 // ----------------------------------------------------------------------------
902 bool wxFileConfig::DoReadString(const wxString
& key
, wxString
* pStr
) const
904 wxConfigPathChanger
path(this, key
);
906 wxFileConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
907 if (pEntry
== NULL
) {
911 *pStr
= pEntry
->Value();
916 bool wxFileConfig::DoReadLong(const wxString
& key
, long *pl
) const
919 if ( !Read(key
, &str
) )
922 // extra spaces shouldn't prevent us from reading numeric values
925 return str
.ToLong(pl
);
928 bool wxFileConfig::DoWriteString(const wxString
& key
, const wxString
& szValue
)
930 wxConfigPathChanger
path(this, key
);
931 wxString strName
= path
.Name();
933 wxLogTrace( FILECONF_TRACE_MASK
,
934 _T(" Writing String '%s' = '%s' to Group '%s'"),
939 if ( strName
.empty() )
941 // setting the value of a group is an error
943 wxASSERT_MSG( szValue
.empty(), wxT("can't set value of a group!") );
945 // ... except if it's empty in which case it's a way to force it's creation
947 wxLogTrace( FILECONF_TRACE_MASK
,
948 _T(" Creating group %s"),
949 m_pCurrentGroup
->Name().c_str() );
953 // this will add a line for this group if it didn't have it before
955 (void)m_pCurrentGroup
->GetGroupLine();
959 // writing an entry check that the name is reasonable
960 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
)
962 wxLogError( _("Config entry name cannot start with '%c'."),
963 wxCONFIG_IMMUTABLE_PREFIX
);
967 wxFileConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
971 wxLogTrace( FILECONF_TRACE_MASK
,
972 _T(" Adding Entry %s"),
974 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
977 wxLogTrace( FILECONF_TRACE_MASK
,
978 _T(" Setting value %s"),
980 pEntry
->SetValue(szValue
);
988 bool wxFileConfig::DoWriteLong(const wxString
& key
, long lValue
)
990 return Write(key
, wxString::Format(_T("%ld"), lValue
));
993 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
995 if ( !IsDirty() || !m_strLocalFile
)
998 // set the umask if needed
999 wxCHANGE_UMASK(m_umask
);
1001 wxTempFile
file(m_strLocalFile
);
1003 if ( !file
.IsOpened() )
1005 wxLogError(_("can't open user configuration file."));
1009 // write all strings to file
1010 for ( wxFileConfigLineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() )
1012 wxString line
= p
->Text();
1013 line
+= wxTextFile::GetEOL();
1014 if ( !file
.Write(line
, m_conv
) )
1016 wxLogError(_("can't write user configuration file."));
1021 if ( !file
.Commit() )
1023 wxLogError(_("Failed to update user configuration file."));
1030 #if defined(__WXMAC__)
1031 wxFileName(m_strLocalFile
).MacSetTypeAndCreator('TEXT', 'ttxt');
1039 bool wxFileConfig::Save(wxOutputStream
& os
, wxMBConv
& conv
)
1041 // save unconditionally, even if not dirty
1042 for ( wxFileConfigLineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() )
1044 wxString line
= p
->Text();
1045 line
+= wxTextFile::GetEOL();
1047 wxCharBuffer
buf(line
.mb_str(conv
));
1048 if ( !os
.Write(buf
, strlen(buf
)) )
1050 wxLogError(_("Error saving user configuration data."));
1061 #endif // wxUSE_STREAMS
1063 // ----------------------------------------------------------------------------
1064 // renaming groups/entries
1065 // ----------------------------------------------------------------------------
1067 bool wxFileConfig::RenameEntry(const wxString
& oldName
,
1068 const wxString
& newName
)
1070 wxASSERT_MSG( !wxStrchr(oldName
, wxCONFIG_PATH_SEPARATOR
),
1071 _T("RenameEntry(): paths are not supported") );
1073 // check that the entry exists
1074 wxFileConfigEntry
*oldEntry
= m_pCurrentGroup
->FindEntry(oldName
);
1078 // check that the new entry doesn't already exist
1079 if ( m_pCurrentGroup
->FindEntry(newName
) )
1082 // delete the old entry, create the new one
1083 wxString value
= oldEntry
->Value();
1084 if ( !m_pCurrentGroup
->DeleteEntry(oldName
) )
1089 wxFileConfigEntry
*newEntry
= m_pCurrentGroup
->AddEntry(newName
);
1090 newEntry
->SetValue(value
);
1095 bool wxFileConfig::RenameGroup(const wxString
& oldName
,
1096 const wxString
& newName
)
1098 // check that the group exists
1099 wxFileConfigGroup
*group
= m_pCurrentGroup
->FindSubgroup(oldName
);
1103 // check that the new group doesn't already exist
1104 if ( m_pCurrentGroup
->FindSubgroup(newName
) )
1107 group
->Rename(newName
);
1114 // ----------------------------------------------------------------------------
1115 // delete groups/entries
1116 // ----------------------------------------------------------------------------
1118 bool wxFileConfig::DeleteEntry(const wxString
& key
, bool bGroupIfEmptyAlso
)
1120 wxConfigPathChanger
path(this, key
);
1122 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
1127 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
1128 if ( m_pCurrentGroup
!= m_pRootGroup
) {
1129 wxFileConfigGroup
*pGroup
= m_pCurrentGroup
;
1130 SetPath(wxT("..")); // changes m_pCurrentGroup!
1131 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
1133 //else: never delete the root group
1139 bool wxFileConfig::DeleteGroup(const wxString
& key
)
1141 wxConfigPathChanger
path(this, key
);
1143 if ( !m_pCurrentGroup
->DeleteSubgroupByName(path
.Name()) )
1151 bool wxFileConfig::DeleteAll()
1155 if ( !m_strLocalFile
.empty() )
1157 if ( wxFile::Exists(m_strLocalFile
) && wxRemove(m_strLocalFile
) == -1 )
1159 wxLogSysError(_("can't delete user configuration file '%s'"),
1160 m_strLocalFile
.c_str());
1170 // ----------------------------------------------------------------------------
1171 // linked list functions
1172 // ----------------------------------------------------------------------------
1174 // append a new line to the end of the list
1176 wxFileConfigLineList
*wxFileConfig::LineListAppend(const wxString
& str
)
1178 wxLogTrace( FILECONF_TRACE_MASK
,
1179 _T(" ** Adding Line '%s'"),
1181 wxLogTrace( FILECONF_TRACE_MASK
,
1183 ((m_linesHead
) ? m_linesHead
->Text().c_str() : wxEmptyString
) );
1184 wxLogTrace( FILECONF_TRACE_MASK
,
1186 ((m_linesTail
) ? m_linesTail
->Text().c_str() : wxEmptyString
) );
1188 wxFileConfigLineList
*pLine
= new wxFileConfigLineList(str
);
1190 if ( m_linesTail
== NULL
)
1193 m_linesHead
= pLine
;
1198 m_linesTail
->SetNext(pLine
);
1199 pLine
->SetPrev(m_linesTail
);
1202 m_linesTail
= pLine
;
1204 wxLogTrace( FILECONF_TRACE_MASK
,
1206 ((m_linesHead
) ? m_linesHead
->Text().c_str() : wxEmptyString
) );
1207 wxLogTrace( FILECONF_TRACE_MASK
,
1209 ((m_linesTail
) ? m_linesTail
->Text().c_str() : wxEmptyString
) );
1214 // insert a new line after the given one or in the very beginning if !pLine
1215 wxFileConfigLineList
*wxFileConfig::LineListInsert(const wxString
& str
,
1216 wxFileConfigLineList
*pLine
)
1218 wxLogTrace( FILECONF_TRACE_MASK
,
1219 _T(" ** Inserting Line '%s' after '%s'"),
1221 ((pLine
) ? pLine
->Text().c_str() : wxEmptyString
) );
1222 wxLogTrace( FILECONF_TRACE_MASK
,
1224 ((m_linesHead
) ? m_linesHead
->Text().c_str() : wxEmptyString
) );
1225 wxLogTrace( FILECONF_TRACE_MASK
,
1227 ((m_linesTail
) ? m_linesTail
->Text().c_str() : wxEmptyString
) );
1229 if ( pLine
== m_linesTail
)
1230 return LineListAppend(str
);
1232 wxFileConfigLineList
*pNewLine
= new wxFileConfigLineList(str
);
1233 if ( pLine
== NULL
)
1235 // prepend to the list
1236 pNewLine
->SetNext(m_linesHead
);
1237 m_linesHead
->SetPrev(pNewLine
);
1238 m_linesHead
= pNewLine
;
1242 // insert before pLine
1243 wxFileConfigLineList
*pNext
= pLine
->Next();
1244 pNewLine
->SetNext(pNext
);
1245 pNewLine
->SetPrev(pLine
);
1246 pNext
->SetPrev(pNewLine
);
1247 pLine
->SetNext(pNewLine
);
1250 wxLogTrace( FILECONF_TRACE_MASK
,
1252 ((m_linesHead
) ? m_linesHead
->Text().c_str() : wxEmptyString
) );
1253 wxLogTrace( FILECONF_TRACE_MASK
,
1255 ((m_linesTail
) ? m_linesTail
->Text().c_str() : wxEmptyString
) );
1260 void wxFileConfig::LineListRemove(wxFileConfigLineList
*pLine
)
1262 wxLogTrace( FILECONF_TRACE_MASK
,
1263 _T(" ** Removing Line '%s'"),
1264 pLine
->Text().c_str() );
1265 wxLogTrace( FILECONF_TRACE_MASK
,
1267 ((m_linesHead
) ? m_linesHead
->Text().c_str() : wxEmptyString
) );
1268 wxLogTrace( FILECONF_TRACE_MASK
,
1270 ((m_linesTail
) ? m_linesTail
->Text().c_str() : wxEmptyString
) );
1272 wxFileConfigLineList
*pPrev
= pLine
->Prev(),
1273 *pNext
= pLine
->Next();
1277 if ( pPrev
== NULL
)
1278 m_linesHead
= pNext
;
1280 pPrev
->SetNext(pNext
);
1284 if ( pNext
== NULL
)
1285 m_linesTail
= pPrev
;
1287 pNext
->SetPrev(pPrev
);
1289 if ( m_pRootGroup
->GetGroupLine() == pLine
)
1290 m_pRootGroup
->SetLine(m_linesHead
);
1292 wxLogTrace( FILECONF_TRACE_MASK
,
1294 ((m_linesHead
) ? m_linesHead
->Text().c_str() : wxEmptyString
) );
1295 wxLogTrace( FILECONF_TRACE_MASK
,
1297 ((m_linesTail
) ? m_linesTail
->Text().c_str() : wxEmptyString
) );
1302 bool wxFileConfig::LineListIsEmpty()
1304 return m_linesHead
== NULL
;
1307 // ============================================================================
1308 // wxFileConfig::wxFileConfigGroup
1309 // ============================================================================
1311 // ----------------------------------------------------------------------------
1313 // ----------------------------------------------------------------------------
1316 wxFileConfigGroup::wxFileConfigGroup(wxFileConfigGroup
*pParent
,
1317 const wxString
& strName
,
1318 wxFileConfig
*pConfig
)
1319 : m_aEntries(CompareEntries
),
1320 m_aSubgroups(CompareGroups
),
1323 m_pConfig
= pConfig
;
1324 m_pParent
= pParent
;
1327 m_pLastEntry
= NULL
;
1328 m_pLastGroup
= NULL
;
1331 // dtor deletes all children
1332 wxFileConfigGroup::~wxFileConfigGroup()
1335 size_t n
, nCount
= m_aEntries
.Count();
1336 for ( n
= 0; n
< nCount
; n
++ )
1337 delete m_aEntries
[n
];
1340 nCount
= m_aSubgroups
.Count();
1341 for ( n
= 0; n
< nCount
; n
++ )
1342 delete m_aSubgroups
[n
];
1345 // ----------------------------------------------------------------------------
1347 // ----------------------------------------------------------------------------
1349 void wxFileConfigGroup::SetLine(wxFileConfigLineList
*pLine
)
1351 // for a normal (i.e. not root) group this method shouldn't be called twice
1352 // unless we are resetting the line
1353 wxASSERT_MSG( !m_pParent
|| !m_pLine
|| !pLine
,
1354 _T("changing line for a non-root group?") );
1360 This is a bit complicated, so let me explain it in details. All lines that
1361 were read from the local file (the only one we will ever modify) are stored
1362 in a (doubly) linked list. Our problem is to know at which position in this
1363 list should we insert the new entries/subgroups. To solve it we keep three
1364 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
1366 m_pLine points to the line containing "[group_name]"
1367 m_pLastEntry points to the last entry of this group in the local file.
1368 m_pLastGroup subgroup
1370 Initially, they're NULL all three. When the group (an entry/subgroup) is read
1371 from the local file, the corresponding variable is set. However, if the group
1372 was read from the global file and then modified or created by the application
1373 these variables are still NULL and we need to create the corresponding lines.
1374 See the following functions (and comments preceding them) for the details of
1377 Also, when our last entry/group are deleted we need to find the new last
1378 element - the code in DeleteEntry/Subgroup does this by backtracking the list
1379 of lines until it either founds an entry/subgroup (and this is the new last
1380 element) or the m_pLine of the group, in which case there are no more entries
1381 (or subgroups) left and m_pLast<element> becomes NULL.
1383 NB: This last problem could be avoided for entries if we added new entries
1384 immediately after m_pLine, but in this case the entries would appear
1385 backwards in the config file (OTOH, it's not that important) and as we
1386 would still need to do it for the subgroups the code wouldn't have been
1387 significantly less complicated.
1390 // Return the line which contains "[our name]". If we're still not in the list,
1391 // add our line to it immediately after the last line of our parent group if we
1392 // have it or in the very beginning if we're the root group.
1393 wxFileConfigLineList
*wxFileConfigGroup::GetGroupLine()
1395 wxLogTrace( FILECONF_TRACE_MASK
,
1396 _T(" GetGroupLine() for Group '%s'"),
1401 wxLogTrace( FILECONF_TRACE_MASK
,
1402 _T(" Getting Line item pointer") );
1404 wxFileConfigGroup
*pParent
= Parent();
1406 // this group wasn't present in local config file, add it now
1409 wxLogTrace( FILECONF_TRACE_MASK
,
1410 _T(" checking parent '%s'"),
1411 pParent
->Name().c_str() );
1413 wxString strFullName
;
1415 // add 1 to the name because we don't want to start with '/'
1416 strFullName
<< wxT("[")
1417 << FilterOutEntryName(GetFullName().c_str() + 1)
1419 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
1420 pParent
->GetLastGroupLine());
1421 pParent
->SetLastGroup(this); // we're surely after all the others
1423 //else: this is the root group and so we return NULL because we don't
1424 // have any group line
1430 // Return the last line belonging to the subgroups of this group (after which
1431 // we can add a new subgroup), if we don't have any subgroups or entries our
1432 // last line is the group line (m_pLine) itself.
1433 wxFileConfigLineList
*wxFileConfigGroup::GetLastGroupLine()
1435 // if we have any subgroups, our last line is the last line of the last
1439 wxFileConfigLineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
1441 wxASSERT_MSG( pLine
, _T("last group must have !NULL associated line") );
1446 // no subgroups, so the last line is the line of thelast entry (if any)
1447 return GetLastEntryLine();
1450 // return the last line belonging to the entries of this group (after which
1451 // we can add a new entry), if we don't have any entries we will add the new
1452 // one immediately after the group line itself.
1453 wxFileConfigLineList
*wxFileConfigGroup::GetLastEntryLine()
1455 wxLogTrace( FILECONF_TRACE_MASK
,
1456 _T(" GetLastEntryLine() for Group '%s'"),
1461 wxFileConfigLineList
*pLine
= m_pLastEntry
->GetLine();
1463 wxASSERT_MSG( pLine
, _T("last entry must have !NULL associated line") );
1468 // no entries: insert after the group header, if any
1469 return GetGroupLine();
1472 void wxFileConfigGroup::SetLastEntry(wxFileConfigEntry
*pEntry
)
1474 m_pLastEntry
= pEntry
;
1478 // the only situation in which a group without its own line can have
1479 // an entry is when the first entry is added to the initially empty
1480 // root pseudo-group
1481 wxASSERT_MSG( !m_pParent
, _T("unexpected for non root group") );
1483 // let the group know that it does have a line in the file now
1484 m_pLine
= pEntry
->GetLine();
1488 // ----------------------------------------------------------------------------
1490 // ----------------------------------------------------------------------------
1492 void wxFileConfigGroup::UpdateGroupAndSubgroupsLines()
1494 // update the line of this group
1495 wxFileConfigLineList
*line
= GetGroupLine();
1496 wxCHECK_RET( line
, _T("a non root group must have a corresponding line!") );
1498 // +1: skip the leading '/'
1499 line
->SetText(wxString::Format(_T("[%s]"), GetFullName().c_str() + 1));
1502 // also update all subgroups as they have this groups name in their lines
1503 const size_t nCount
= m_aSubgroups
.Count();
1504 for ( size_t n
= 0; n
< nCount
; n
++ )
1506 m_aSubgroups
[n
]->UpdateGroupAndSubgroupsLines();
1510 void wxFileConfigGroup::Rename(const wxString
& newName
)
1512 wxCHECK_RET( m_pParent
, _T("the root group can't be renamed") );
1514 m_strName
= newName
;
1516 // update the group lines recursively
1517 UpdateGroupAndSubgroupsLines();
1520 wxString
wxFileConfigGroup::GetFullName() const
1524 fullname
= Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
1529 // ----------------------------------------------------------------------------
1531 // ----------------------------------------------------------------------------
1533 // use binary search because the array is sorted
1535 wxFileConfigGroup::FindEntry(const wxChar
*szName
) const
1539 hi
= m_aEntries
.Count();
1541 wxFileConfigEntry
*pEntry
;
1545 pEntry
= m_aEntries
[i
];
1547 #if wxCONFIG_CASE_SENSITIVE
1548 res
= wxStrcmp(pEntry
->Name(), szName
);
1550 res
= wxStricmp(pEntry
->Name(), szName
);
1565 wxFileConfigGroup::FindSubgroup(const wxChar
*szName
) const
1569 hi
= m_aSubgroups
.Count();
1571 wxFileConfigGroup
*pGroup
;
1575 pGroup
= m_aSubgroups
[i
];
1577 #if wxCONFIG_CASE_SENSITIVE
1578 res
= wxStrcmp(pGroup
->Name(), szName
);
1580 res
= wxStricmp(pGroup
->Name(), szName
);
1594 // ----------------------------------------------------------------------------
1595 // create a new item
1596 // ----------------------------------------------------------------------------
1598 // create a new entry and add it to the current group
1599 wxFileConfigEntry
*wxFileConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
1601 wxASSERT( FindEntry(strName
) == 0 );
1603 wxFileConfigEntry
*pEntry
= new wxFileConfigEntry(this, strName
, nLine
);
1605 m_aEntries
.Add(pEntry
);
1609 // create a new group and add it to the current group
1610 wxFileConfigGroup
*wxFileConfigGroup::AddSubgroup(const wxString
& strName
)
1612 wxASSERT( FindSubgroup(strName
) == 0 );
1614 wxFileConfigGroup
*pGroup
= new wxFileConfigGroup(this, strName
, m_pConfig
);
1616 m_aSubgroups
.Add(pGroup
);
1620 // ----------------------------------------------------------------------------
1622 // ----------------------------------------------------------------------------
1625 The delete operations are _very_ slow if we delete the last item of this
1626 group (see comments before GetXXXLineXXX functions for more details),
1627 so it's much better to start with the first entry/group if we want to
1628 delete several of them.
1631 bool wxFileConfigGroup::DeleteSubgroupByName(const wxChar
*szName
)
1633 wxFileConfigGroup
* const pGroup
= FindSubgroup(szName
);
1635 return pGroup
? DeleteSubgroup(pGroup
) : false;
1638 // Delete the subgroup and remove all references to it from
1639 // other data structures.
1640 bool wxFileConfigGroup::DeleteSubgroup(wxFileConfigGroup
*pGroup
)
1642 wxCHECK_MSG( pGroup
, false, _T("deleting non existing group?") );
1644 wxLogTrace( FILECONF_TRACE_MASK
,
1645 _T("Deleting group '%s' from '%s'"),
1646 pGroup
->Name().c_str(),
1649 wxLogTrace( FILECONF_TRACE_MASK
,
1650 _T(" (m_pLine) = prev: %p, this %p, next %p"),
1651 ((m_pLine
) ? m_pLine
->Prev() : 0),
1653 ((m_pLine
) ? m_pLine
->Next() : 0) );
1654 wxLogTrace( FILECONF_TRACE_MASK
,
1656 ((m_pLine
) ? m_pLine
->Text().c_str() : wxEmptyString
) );
1658 // delete all entries...
1659 size_t nCount
= pGroup
->m_aEntries
.Count();
1661 wxLogTrace(FILECONF_TRACE_MASK
,
1662 _T("Removing %lu entries"), (unsigned long)nCount
);
1664 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ )
1666 wxFileConfigLineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1670 wxLogTrace( FILECONF_TRACE_MASK
,
1672 pLine
->Text().c_str() );
1673 m_pConfig
->LineListRemove(pLine
);
1677 // ...and subgroups of this subgroup
1678 nCount
= pGroup
->m_aSubgroups
.Count();
1680 wxLogTrace( FILECONF_TRACE_MASK
,
1681 _T("Removing %lu subgroups"), (unsigned long)nCount
);
1683 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ )
1685 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[0]);
1688 // and then finally the group itself
1689 wxFileConfigLineList
*pLine
= pGroup
->m_pLine
;
1692 wxLogTrace( FILECONF_TRACE_MASK
,
1693 _T(" Removing line for group '%s' : '%s'"),
1694 pGroup
->Name().c_str(),
1695 pLine
->Text().c_str() );
1696 wxLogTrace( FILECONF_TRACE_MASK
,
1697 _T(" Removing from group '%s' : '%s'"),
1699 ((m_pLine
) ? m_pLine
->Text().c_str() : wxEmptyString
) );
1701 // notice that we may do this test inside the previous "if"
1702 // because the last entry's line is surely !NULL
1703 if ( pGroup
== m_pLastGroup
)
1705 wxLogTrace( FILECONF_TRACE_MASK
,
1706 _T(" Removing last group") );
1708 // our last entry is being deleted, so find the last one which
1709 // stays by going back until we find a subgroup or reach the
1711 const size_t nSubgroups
= m_aSubgroups
.Count();
1713 m_pLastGroup
= NULL
;
1714 for ( wxFileConfigLineList
*pl
= pLine
->Prev();
1715 pl
&& pl
!= m_pLine
&& !m_pLastGroup
;
1718 // does this line belong to our subgroup?
1719 for ( size_t n
= 0; n
< nSubgroups
; n
++ )
1721 // do _not_ call GetGroupLine! we don't want to add it to
1722 // the local file if it's not already there
1723 if ( m_aSubgroups
[n
]->m_pLine
== pl
)
1725 m_pLastGroup
= m_aSubgroups
[n
];
1732 m_pConfig
->LineListRemove(pLine
);
1736 wxLogTrace( FILECONF_TRACE_MASK
,
1737 _T(" No line entry for Group '%s'?"),
1738 pGroup
->Name().c_str() );
1741 m_aSubgroups
.Remove(pGroup
);
1747 bool wxFileConfigGroup::DeleteEntry(const wxChar
*szName
)
1749 wxFileConfigEntry
*pEntry
= FindEntry(szName
);
1752 // entry doesn't exist, nothing to do
1756 wxFileConfigLineList
*pLine
= pEntry
->GetLine();
1757 if ( pLine
!= NULL
) {
1758 // notice that we may do this test inside the previous "if" because the
1759 // last entry's line is surely !NULL
1760 if ( pEntry
== m_pLastEntry
) {
1761 // our last entry is being deleted - find the last one which stays
1762 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1764 // go back until we find another entry or reach the group's line
1765 wxFileConfigEntry
*pNewLast
= NULL
;
1766 size_t n
, nEntries
= m_aEntries
.Count();
1767 wxFileConfigLineList
*pl
;
1768 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1769 // is it our subgroup?
1770 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1771 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1772 pNewLast
= m_aEntries
[n
];
1775 if ( pNewLast
!= NULL
) // found?
1779 if ( pl
== m_pLine
) {
1780 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1782 // we've reached the group line without finding any subgroups
1783 m_pLastEntry
= NULL
;
1786 m_pLastEntry
= pNewLast
;
1789 m_pConfig
->LineListRemove(pLine
);
1792 m_aEntries
.Remove(pEntry
);
1798 // ============================================================================
1799 // wxFileConfig::wxFileConfigEntry
1800 // ============================================================================
1802 // ----------------------------------------------------------------------------
1804 // ----------------------------------------------------------------------------
1805 wxFileConfigEntry::wxFileConfigEntry(wxFileConfigGroup
*pParent
,
1806 const wxString
& strName
,
1808 : m_strName(strName
)
1810 wxASSERT( !strName
.empty() );
1812 m_pParent
= pParent
;
1816 m_bHasValue
= false;
1818 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1820 m_strName
.erase(0, 1); // remove first character
1823 // ----------------------------------------------------------------------------
1825 // ----------------------------------------------------------------------------
1827 void wxFileConfigEntry::SetLine(wxFileConfigLineList
*pLine
)
1829 if ( m_pLine
!= NULL
) {
1830 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1831 Name().c_str(), m_pParent
->GetFullName().c_str());
1835 Group()->SetLastEntry(this);
1838 // second parameter is false if we read the value from file and prevents the
1839 // entry from being marked as 'dirty'
1840 void wxFileConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1842 if ( bUser
&& IsImmutable() )
1844 wxLogWarning( _("attempt to change immutable key '%s' ignored."),
1849 // do nothing if it's the same value: but don't test for it if m_bHasValue
1850 // hadn't been set yet or we'd never write empty values to the file
1851 if ( m_bHasValue
&& strValue
== m_strValue
)
1855 m_strValue
= strValue
;
1859 wxString strValFiltered
;
1861 if ( Group()->Config()->GetStyle() & wxCONFIG_USE_NO_ESCAPE_CHARACTERS
)
1863 strValFiltered
= strValue
;
1866 strValFiltered
= FilterOutValue(strValue
);
1870 strLine
<< FilterOutEntryName(m_strName
) << wxT('=') << strValFiltered
;
1874 // entry was read from the local config file, just modify the line
1875 m_pLine
->SetText(strLine
);
1877 else // this entry didn't exist in the local file
1879 // add a new line to the file: note that line returned by
1880 // GetLastEntryLine() may be NULL if we're in the root group and it
1881 // doesn't have any entries yet, but this is ok as passing NULL
1882 // line to LineListInsert() means to prepend new line to the list
1883 wxFileConfigLineList
*line
= Group()->GetLastEntryLine();
1884 m_pLine
= Group()->Config()->LineListInsert(strLine
, line
);
1886 Group()->SetLastEntry(this);
1891 // ============================================================================
1893 // ============================================================================
1895 // ----------------------------------------------------------------------------
1896 // compare functions for array sorting
1897 // ----------------------------------------------------------------------------
1899 int CompareEntries(wxFileConfigEntry
*p1
, wxFileConfigEntry
*p2
)
1901 #if wxCONFIG_CASE_SENSITIVE
1902 return wxStrcmp(p1
->Name(), p2
->Name());
1904 return wxStricmp(p1
->Name(), p2
->Name());
1908 int CompareGroups(wxFileConfigGroup
*p1
, wxFileConfigGroup
*p2
)
1910 #if wxCONFIG_CASE_SENSITIVE
1911 return wxStrcmp(p1
->Name(), p2
->Name());
1913 return wxStricmp(p1
->Name(), p2
->Name());
1917 // ----------------------------------------------------------------------------
1919 // ----------------------------------------------------------------------------
1921 // undo FilterOutValue
1922 static wxString
FilterInValue(const wxString
& str
)
1925 strResult
.Alloc(str
.Len());
1927 bool bQuoted
= !str
.empty() && str
[0] == '"';
1929 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1930 if ( str
[n
] == wxT('\\') ) {
1931 switch ( str
[++n
] ) {
1933 strResult
+= wxT('\n');
1937 strResult
+= wxT('\r');
1941 strResult
+= wxT('\t');
1945 strResult
+= wxT('\\');
1949 strResult
+= wxT('"');
1954 if ( str
[n
] != wxT('"') || !bQuoted
)
1955 strResult
+= str
[n
];
1956 else if ( n
!= str
.Len() - 1 ) {
1957 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1960 //else: it's the last quote of a quoted string, ok
1967 // quote the string before writing it to file
1968 static wxString
FilterOutValue(const wxString
& str
)
1974 strResult
.Alloc(str
.Len());
1976 // quoting is necessary to preserve spaces in the beginning of the string
1977 bool bQuote
= wxIsspace(str
[0]) || str
[0] == wxT('"');
1980 strResult
+= wxT('"');
1983 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
2006 //else: fall through
2009 strResult
+= str
[n
];
2010 continue; // nothing special to do
2013 // we get here only for special characters
2014 strResult
<< wxT('\\') << c
;
2018 strResult
+= wxT('"');
2023 // undo FilterOutEntryName
2024 static wxString
FilterInEntryName(const wxString
& str
)
2027 strResult
.Alloc(str
.Len());
2029 for ( const wxChar
*pc
= str
.c_str(); *pc
!= '\0'; pc
++ ) {
2030 if ( *pc
== wxT('\\') ) {
2031 // we need to test it here or we'd skip past the NUL in the loop line
2032 if ( *++pc
== _T('\0') )
2042 // sanitize entry or group name: insert '\\' before any special characters
2043 static wxString
FilterOutEntryName(const wxString
& str
)
2046 strResult
.Alloc(str
.Len());
2048 for ( const wxChar
*pc
= str
.c_str(); *pc
!= wxT('\0'); pc
++ ) {
2049 const wxChar c
= *pc
;
2051 // we explicitly allow some of "safe" chars and 8bit ASCII characters
2052 // which will probably never have special meaning and with which we can't
2053 // use isalnum() anyhow (in ASCII built, in Unicode it's just fine)
2055 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
2056 // should *not* be quoted
2059 ((unsigned char)c
< 127) &&
2061 !wxIsalnum(c
) && !wxStrchr(wxT("@_/-!.*%"), c
) )
2063 strResult
+= wxT('\\');
2072 // we can't put ?: in the ctor initializer list because it confuses some
2073 // broken compilers (Borland C++)
2074 static wxString
GetAppName(const wxString
& appName
)
2076 if ( !appName
&& wxTheApp
)
2077 return wxTheApp
->GetAppName();
2082 #endif // wxUSE_CONFIG