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
,
430 const wxMBConv
& conv
)
431 : wxConfigBase(::GetAppName(appName
), vendorName
,
434 m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
),
437 // Make up names for files if empty
438 if ( m_strLocalFile
.empty() && (style
& wxCONFIG_USE_LOCAL_FILE
) )
439 m_strLocalFile
= GetLocalFileName(GetAppName());
441 if ( m_strGlobalFile
.empty() && (style
& wxCONFIG_USE_GLOBAL_FILE
) )
442 m_strGlobalFile
= GetGlobalFileName(GetAppName());
444 // Check if styles are not supplied, but filenames are, in which case
445 // add the correct styles.
446 if ( !m_strLocalFile
.empty() )
447 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE
);
449 if ( !m_strGlobalFile
.empty() )
450 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE
);
452 // if the path is not absolute, prepend the standard directory to it
453 // UNLESS wxCONFIG_USE_RELATIVE_PATH style is set
454 if ( !(style
& wxCONFIG_USE_RELATIVE_PATH
) )
456 if ( !m_strLocalFile
.empty() && !wxIsAbsolutePath(m_strLocalFile
) )
458 const wxString strLocalOrig
= m_strLocalFile
;
459 m_strLocalFile
= GetLocalDir();
460 m_strLocalFile
<< strLocalOrig
;
463 if ( !m_strGlobalFile
.empty() && !wxIsAbsolutePath(m_strGlobalFile
) )
465 const wxString strGlobalOrig
= m_strGlobalFile
;
466 m_strGlobalFile
= GetGlobalDir();
467 m_strGlobalFile
<< strGlobalOrig
;
478 wxFileConfig::wxFileConfig(wxInputStream
&inStream
, const wxMBConv
& conv
)
481 // always local_file when this constructor is called (?)
482 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE
);
485 m_pRootGroup
= new wxFileConfigGroup(NULL
, wxEmptyString
, this);
490 // translate everything to the current (platform-dependent) line
491 // termination character
499 inStream
.Read(buf
, WXSIZEOF(buf
)-1); // leave room for the NULL
501 const wxStreamError err
= inStream
.GetLastError();
503 if ( err
!= wxSTREAM_NO_ERROR
&& err
!= wxSTREAM_EOF
)
505 wxLogError(_("Error reading config options."));
509 // FIXME: this is broken because if we have part of multibyte
510 // character in the buffer (and another part hasn't been
511 // read yet) we're going to lose data because of conversion
513 buf
[inStream
.LastRead()] = '\0';
514 strTmp
+= conv
.cMB2WX(buf
);
516 while ( !inStream
.Eof() );
518 strTrans
= wxTextBuffer::Translate(strTmp
);
521 wxMemoryText memText
;
523 // Now we can add the text to the memory text. To do this we extract line
524 // by line from the translated string, until we've reached the end.
526 // VZ: all this is horribly inefficient, we should do the translation on
527 // the fly in one pass saving both memory and time (TODO)
529 const wxChar
*pEOL
= wxTextBuffer::GetEOL(wxTextBuffer::typeDefault
);
530 const size_t EOLLen
= wxStrlen(pEOL
);
532 int posLineStart
= strTrans
.Find(pEOL
);
533 while ( posLineStart
!= -1 )
535 wxString
line(strTrans
.Left(posLineStart
));
537 memText
.AddLine(line
);
539 strTrans
= strTrans
.Mid(posLineStart
+ EOLLen
);
541 posLineStart
= strTrans
.Find(pEOL
);
544 // also add whatever we have left in the translated string.
545 if ( !strTrans
.empty() )
546 memText
.AddLine(strTrans
);
548 // Finally we can parse it all.
549 Parse(memText
, true /* local */);
555 #endif // wxUSE_STREAMS
557 void wxFileConfig::CleanUp()
561 wxFileConfigLineList
*pCur
= m_linesHead
;
562 while ( pCur
!= NULL
) {
563 wxFileConfigLineList
*pNext
= pCur
->Next();
569 wxFileConfig::~wxFileConfig()
576 // ----------------------------------------------------------------------------
577 // parse a config file
578 // ----------------------------------------------------------------------------
580 void wxFileConfig::Parse(const wxTextBuffer
& buffer
, bool bLocal
)
582 const wxChar
*pStart
;
586 size_t nLineCount
= buffer
.GetLineCount();
588 for ( size_t n
= 0; n
< nLineCount
; n
++ )
592 // add the line to linked list
595 LineListAppend(strLine
);
597 // let the root group have its start line as well
600 m_pCurrentGroup
->SetLine(m_linesTail
);
605 // skip leading spaces
606 for ( pStart
= strLine
; wxIsspace(*pStart
); pStart
++ )
609 // skip blank/comment lines
610 if ( *pStart
== wxT('\0')|| *pStart
== wxT(';') || *pStart
== wxT('#') )
613 if ( *pStart
== wxT('[') ) { // a new group
616 while ( *++pEnd
!= wxT(']') ) {
617 if ( *pEnd
== wxT('\\') ) {
618 // the next char is escaped, so skip it even if it is ']'
622 if ( *pEnd
== wxT('\n') || *pEnd
== wxT('\0') ) {
623 // we reached the end of line, break out of the loop
628 if ( *pEnd
!= wxT(']') ) {
629 wxLogError(_("file '%s': unexpected character %c at line %d."),
630 buffer
.GetName(), *pEnd
, n
+ 1);
631 continue; // skip this line
634 // group name here is always considered as abs path
637 strGroup
<< wxCONFIG_PATH_SEPARATOR
638 << FilterInEntryName(wxString(pStart
, pEnd
- pStart
));
640 // will create it if doesn't yet exist
645 if ( m_pCurrentGroup
->Parent() )
646 m_pCurrentGroup
->Parent()->SetLastGroup(m_pCurrentGroup
);
647 m_pCurrentGroup
->SetLine(m_linesTail
);
650 // check that there is nothing except comments left on this line
652 while ( *++pEnd
!= wxT('\0') && bCont
) {
661 // ignore whitespace ('\n' impossible here)
665 wxLogWarning(_("file '%s', line %d: '%s' ignored after group header."),
666 buffer
.GetName(), n
+ 1, pEnd
);
673 while ( *pEnd
&& *pEnd
!= wxT('=') /* && !wxIsspace(*pEnd)*/ ) {
674 if ( *pEnd
== wxT('\\') ) {
675 // next character may be space or not - still take it because it's
676 // quoted (unless there is nothing)
679 // the error message will be given below anyhow
687 wxString
strKey(FilterInEntryName(wxString(pStart
, pEnd
).Trim()));
690 while ( wxIsspace(*pEnd
) )
693 if ( *pEnd
++ != wxT('=') ) {
694 wxLogError(_("file '%s', line %d: '=' expected."),
695 buffer
.GetName(), n
+ 1);
698 wxFileConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
700 if ( pEntry
== NULL
) {
702 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
705 if ( bLocal
&& pEntry
->IsImmutable() ) {
706 // immutable keys can't be changed by user
707 wxLogWarning(_("file '%s', line %d: value for immutable key '%s' ignored."),
708 buffer
.GetName(), n
+ 1, strKey
.c_str());
711 // the condition below catches the cases (a) and (b) but not (c):
712 // (a) global key found second time in global file
713 // (b) key found second (or more) time in local file
714 // (c) key from global file now found in local one
715 // which is exactly what we want.
716 else if ( !bLocal
|| pEntry
->IsLocal() ) {
717 wxLogWarning(_("file '%s', line %d: key '%s' was first found at line %d."),
718 buffer
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
724 pEntry
->SetLine(m_linesTail
);
727 while ( wxIsspace(*pEnd
) )
730 wxString value
= pEnd
;
731 if ( !(GetStyle() & wxCONFIG_USE_NO_ESCAPE_CHARACTERS
) )
732 value
= FilterInValue(value
);
734 pEntry
->SetValue(value
, false);
740 // ----------------------------------------------------------------------------
742 // ----------------------------------------------------------------------------
744 void wxFileConfig::SetRootPath()
747 m_pCurrentGroup
= m_pRootGroup
;
751 wxFileConfig::DoSetPath(const wxString
& strPath
, bool createMissingComponents
)
753 wxArrayString aParts
;
755 if ( strPath
.empty() ) {
760 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
762 wxSplitPath(aParts
, strPath
);
765 // relative path, combine with current one
766 wxString strFullPath
= m_strPath
;
767 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
768 wxSplitPath(aParts
, strFullPath
);
771 // change current group
773 m_pCurrentGroup
= m_pRootGroup
;
774 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
775 wxFileConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
776 if ( pNextGroup
== NULL
)
778 if ( !createMissingComponents
)
781 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
784 m_pCurrentGroup
= pNextGroup
;
787 // recombine path parts in one variable
789 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
790 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
796 void wxFileConfig::SetPath(const wxString
& strPath
)
798 DoSetPath(strPath
, true /* create missing path components */);
801 // ----------------------------------------------------------------------------
803 // ----------------------------------------------------------------------------
805 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
808 return GetNextGroup(str
, lIndex
);
811 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
813 if ( size_t(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
814 str
= m_pCurrentGroup
->Groups()[(size_t)lIndex
++]->Name();
821 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
824 return GetNextEntry(str
, lIndex
);
827 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
829 if ( size_t(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
830 str
= m_pCurrentGroup
->Entries()[(size_t)lIndex
++]->Name();
837 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
839 size_t n
= m_pCurrentGroup
->Entries().Count();
841 wxFileConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
842 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
843 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
844 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
845 n
+= GetNumberOfEntries(true);
846 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
853 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
855 size_t n
= m_pCurrentGroup
->Groups().Count();
857 wxFileConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
858 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
859 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
860 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
861 n
+= GetNumberOfGroups(true);
862 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
869 // ----------------------------------------------------------------------------
870 // tests for existence
871 // ----------------------------------------------------------------------------
873 bool wxFileConfig::HasGroup(const wxString
& strName
) const
875 // special case: DoSetPath("") does work as it's equivalent to DoSetPath("/")
876 // but there is no group with empty name so treat this separately
877 if ( strName
.empty() )
880 const wxString pathOld
= GetPath();
882 wxFileConfig
*self
= wx_const_cast(wxFileConfig
*, this);
884 rc
= self
->DoSetPath(strName
, false /* don't create missing components */);
886 self
->SetPath(pathOld
);
891 bool wxFileConfig::HasEntry(const wxString
& strName
) const
893 wxConfigPathChanger
path(this, strName
);
895 wxFileConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
896 return pEntry
!= NULL
;
899 // ----------------------------------------------------------------------------
901 // ----------------------------------------------------------------------------
903 bool wxFileConfig::DoReadString(const wxString
& key
, wxString
* pStr
) const
905 wxConfigPathChanger
path(this, key
);
907 wxFileConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
908 if (pEntry
== NULL
) {
912 *pStr
= pEntry
->Value();
917 bool wxFileConfig::DoReadLong(const wxString
& key
, long *pl
) const
920 if ( !Read(key
, &str
) )
923 // extra spaces shouldn't prevent us from reading numeric values
926 return str
.ToLong(pl
);
929 bool wxFileConfig::DoWriteString(const wxString
& key
, const wxString
& szValue
)
931 wxConfigPathChanger
path(this, key
);
932 wxString strName
= path
.Name();
934 wxLogTrace( FILECONF_TRACE_MASK
,
935 _T(" Writing String '%s' = '%s' to Group '%s'"),
940 if ( strName
.empty() )
942 // setting the value of a group is an error
944 wxASSERT_MSG( szValue
.empty(), wxT("can't set value of a group!") );
946 // ... except if it's empty in which case it's a way to force it's creation
948 wxLogTrace( FILECONF_TRACE_MASK
,
949 _T(" Creating group %s"),
950 m_pCurrentGroup
->Name().c_str() );
954 // this will add a line for this group if it didn't have it before
956 (void)m_pCurrentGroup
->GetGroupLine();
960 // writing an entry check that the name is reasonable
961 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
)
963 wxLogError( _("Config entry name cannot start with '%c'."),
964 wxCONFIG_IMMUTABLE_PREFIX
);
968 wxFileConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
972 wxLogTrace( FILECONF_TRACE_MASK
,
973 _T(" Adding Entry %s"),
975 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
978 wxLogTrace( FILECONF_TRACE_MASK
,
979 _T(" Setting value %s"),
981 pEntry
->SetValue(szValue
);
989 bool wxFileConfig::DoWriteLong(const wxString
& key
, long lValue
)
991 return Write(key
, wxString::Format(_T("%ld"), lValue
));
994 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
996 if ( !IsDirty() || !m_strLocalFile
)
999 // set the umask if needed
1000 wxCHANGE_UMASK(m_umask
);
1002 wxTempFile
file(m_strLocalFile
);
1004 if ( !file
.IsOpened() )
1006 wxLogError(_("can't open user configuration file."));
1010 // write all strings to file
1011 for ( wxFileConfigLineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() )
1013 wxString line
= p
->Text();
1014 line
+= wxTextFile::GetEOL();
1015 if ( !file
.Write(line
, m_conv
) )
1017 wxLogError(_("can't write user configuration file."));
1022 if ( !file
.Commit() )
1024 wxLogError(_("Failed to update user configuration file."));
1031 #if defined(__WXMAC__)
1032 wxFileName(m_strLocalFile
).MacSetTypeAndCreator('TEXT', 'ttxt');
1040 bool wxFileConfig::Save(wxOutputStream
& os
, const wxMBConv
& conv
)
1042 // save unconditionally, even if not dirty
1043 for ( wxFileConfigLineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() )
1045 wxString line
= p
->Text();
1046 line
+= wxTextFile::GetEOL();
1048 wxCharBuffer
buf(line
.mb_str(conv
));
1049 if ( !os
.Write(buf
, strlen(buf
)) )
1051 wxLogError(_("Error saving user configuration data."));
1062 #endif // wxUSE_STREAMS
1064 // ----------------------------------------------------------------------------
1065 // renaming groups/entries
1066 // ----------------------------------------------------------------------------
1068 bool wxFileConfig::RenameEntry(const wxString
& oldName
,
1069 const wxString
& newName
)
1071 wxASSERT_MSG( !wxStrchr(oldName
, wxCONFIG_PATH_SEPARATOR
),
1072 _T("RenameEntry(): paths are not supported") );
1074 // check that the entry exists
1075 wxFileConfigEntry
*oldEntry
= m_pCurrentGroup
->FindEntry(oldName
);
1079 // check that the new entry doesn't already exist
1080 if ( m_pCurrentGroup
->FindEntry(newName
) )
1083 // delete the old entry, create the new one
1084 wxString value
= oldEntry
->Value();
1085 if ( !m_pCurrentGroup
->DeleteEntry(oldName
) )
1090 wxFileConfigEntry
*newEntry
= m_pCurrentGroup
->AddEntry(newName
);
1091 newEntry
->SetValue(value
);
1096 bool wxFileConfig::RenameGroup(const wxString
& oldName
,
1097 const wxString
& newName
)
1099 // check that the group exists
1100 wxFileConfigGroup
*group
= m_pCurrentGroup
->FindSubgroup(oldName
);
1104 // check that the new group doesn't already exist
1105 if ( m_pCurrentGroup
->FindSubgroup(newName
) )
1108 group
->Rename(newName
);
1115 // ----------------------------------------------------------------------------
1116 // delete groups/entries
1117 // ----------------------------------------------------------------------------
1119 bool wxFileConfig::DeleteEntry(const wxString
& key
, bool bGroupIfEmptyAlso
)
1121 wxConfigPathChanger
path(this, key
);
1123 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
1128 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
1129 if ( m_pCurrentGroup
!= m_pRootGroup
) {
1130 wxFileConfigGroup
*pGroup
= m_pCurrentGroup
;
1131 SetPath(wxT("..")); // changes m_pCurrentGroup!
1132 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
1134 //else: never delete the root group
1140 bool wxFileConfig::DeleteGroup(const wxString
& key
)
1142 wxConfigPathChanger
path(this, key
);
1144 if ( !m_pCurrentGroup
->DeleteSubgroupByName(path
.Name()) )
1152 bool wxFileConfig::DeleteAll()
1156 if ( !m_strLocalFile
.empty() )
1158 if ( wxFile::Exists(m_strLocalFile
) && wxRemove(m_strLocalFile
) == -1 )
1160 wxLogSysError(_("can't delete user configuration file '%s'"),
1161 m_strLocalFile
.c_str());
1171 // ----------------------------------------------------------------------------
1172 // linked list functions
1173 // ----------------------------------------------------------------------------
1175 // append a new line to the end of the list
1177 wxFileConfigLineList
*wxFileConfig::LineListAppend(const wxString
& str
)
1179 wxLogTrace( FILECONF_TRACE_MASK
,
1180 _T(" ** Adding Line '%s'"),
1182 wxLogTrace( FILECONF_TRACE_MASK
,
1184 ((m_linesHead
) ? m_linesHead
->Text().c_str() : wxEmptyString
) );
1185 wxLogTrace( FILECONF_TRACE_MASK
,
1187 ((m_linesTail
) ? m_linesTail
->Text().c_str() : wxEmptyString
) );
1189 wxFileConfigLineList
*pLine
= new wxFileConfigLineList(str
);
1191 if ( m_linesTail
== NULL
)
1194 m_linesHead
= pLine
;
1199 m_linesTail
->SetNext(pLine
);
1200 pLine
->SetPrev(m_linesTail
);
1203 m_linesTail
= pLine
;
1205 wxLogTrace( FILECONF_TRACE_MASK
,
1207 ((m_linesHead
) ? m_linesHead
->Text().c_str() : wxEmptyString
) );
1208 wxLogTrace( FILECONF_TRACE_MASK
,
1210 ((m_linesTail
) ? m_linesTail
->Text().c_str() : wxEmptyString
) );
1215 // insert a new line after the given one or in the very beginning if !pLine
1216 wxFileConfigLineList
*wxFileConfig::LineListInsert(const wxString
& str
,
1217 wxFileConfigLineList
*pLine
)
1219 wxLogTrace( FILECONF_TRACE_MASK
,
1220 _T(" ** Inserting Line '%s' after '%s'"),
1222 ((pLine
) ? pLine
->Text().c_str() : wxEmptyString
) );
1223 wxLogTrace( FILECONF_TRACE_MASK
,
1225 ((m_linesHead
) ? m_linesHead
->Text().c_str() : wxEmptyString
) );
1226 wxLogTrace( FILECONF_TRACE_MASK
,
1228 ((m_linesTail
) ? m_linesTail
->Text().c_str() : wxEmptyString
) );
1230 if ( pLine
== m_linesTail
)
1231 return LineListAppend(str
);
1233 wxFileConfigLineList
*pNewLine
= new wxFileConfigLineList(str
);
1234 if ( pLine
== NULL
)
1236 // prepend to the list
1237 pNewLine
->SetNext(m_linesHead
);
1238 m_linesHead
->SetPrev(pNewLine
);
1239 m_linesHead
= pNewLine
;
1243 // insert before pLine
1244 wxFileConfigLineList
*pNext
= pLine
->Next();
1245 pNewLine
->SetNext(pNext
);
1246 pNewLine
->SetPrev(pLine
);
1247 pNext
->SetPrev(pNewLine
);
1248 pLine
->SetNext(pNewLine
);
1251 wxLogTrace( FILECONF_TRACE_MASK
,
1253 ((m_linesHead
) ? m_linesHead
->Text().c_str() : wxEmptyString
) );
1254 wxLogTrace( FILECONF_TRACE_MASK
,
1256 ((m_linesTail
) ? m_linesTail
->Text().c_str() : wxEmptyString
) );
1261 void wxFileConfig::LineListRemove(wxFileConfigLineList
*pLine
)
1263 wxLogTrace( FILECONF_TRACE_MASK
,
1264 _T(" ** Removing Line '%s'"),
1265 pLine
->Text().c_str() );
1266 wxLogTrace( FILECONF_TRACE_MASK
,
1268 ((m_linesHead
) ? m_linesHead
->Text().c_str() : wxEmptyString
) );
1269 wxLogTrace( FILECONF_TRACE_MASK
,
1271 ((m_linesTail
) ? m_linesTail
->Text().c_str() : wxEmptyString
) );
1273 wxFileConfigLineList
*pPrev
= pLine
->Prev(),
1274 *pNext
= pLine
->Next();
1278 if ( pPrev
== NULL
)
1279 m_linesHead
= pNext
;
1281 pPrev
->SetNext(pNext
);
1285 if ( pNext
== NULL
)
1286 m_linesTail
= pPrev
;
1288 pNext
->SetPrev(pPrev
);
1290 if ( m_pRootGroup
->GetGroupLine() == pLine
)
1291 m_pRootGroup
->SetLine(m_linesHead
);
1293 wxLogTrace( FILECONF_TRACE_MASK
,
1295 ((m_linesHead
) ? m_linesHead
->Text().c_str() : wxEmptyString
) );
1296 wxLogTrace( FILECONF_TRACE_MASK
,
1298 ((m_linesTail
) ? m_linesTail
->Text().c_str() : wxEmptyString
) );
1303 bool wxFileConfig::LineListIsEmpty()
1305 return m_linesHead
== NULL
;
1308 // ============================================================================
1309 // wxFileConfig::wxFileConfigGroup
1310 // ============================================================================
1312 // ----------------------------------------------------------------------------
1314 // ----------------------------------------------------------------------------
1317 wxFileConfigGroup::wxFileConfigGroup(wxFileConfigGroup
*pParent
,
1318 const wxString
& strName
,
1319 wxFileConfig
*pConfig
)
1320 : m_aEntries(CompareEntries
),
1321 m_aSubgroups(CompareGroups
),
1324 m_pConfig
= pConfig
;
1325 m_pParent
= pParent
;
1328 m_pLastEntry
= NULL
;
1329 m_pLastGroup
= NULL
;
1332 // dtor deletes all children
1333 wxFileConfigGroup::~wxFileConfigGroup()
1336 size_t n
, nCount
= m_aEntries
.Count();
1337 for ( n
= 0; n
< nCount
; n
++ )
1338 delete m_aEntries
[n
];
1341 nCount
= m_aSubgroups
.Count();
1342 for ( n
= 0; n
< nCount
; n
++ )
1343 delete m_aSubgroups
[n
];
1346 // ----------------------------------------------------------------------------
1348 // ----------------------------------------------------------------------------
1350 void wxFileConfigGroup::SetLine(wxFileConfigLineList
*pLine
)
1352 // for a normal (i.e. not root) group this method shouldn't be called twice
1353 // unless we are resetting the line
1354 wxASSERT_MSG( !m_pParent
|| !m_pLine
|| !pLine
,
1355 _T("changing line for a non-root group?") );
1361 This is a bit complicated, so let me explain it in details. All lines that
1362 were read from the local file (the only one we will ever modify) are stored
1363 in a (doubly) linked list. Our problem is to know at which position in this
1364 list should we insert the new entries/subgroups. To solve it we keep three
1365 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
1367 m_pLine points to the line containing "[group_name]"
1368 m_pLastEntry points to the last entry of this group in the local file.
1369 m_pLastGroup subgroup
1371 Initially, they're NULL all three. When the group (an entry/subgroup) is read
1372 from the local file, the corresponding variable is set. However, if the group
1373 was read from the global file and then modified or created by the application
1374 these variables are still NULL and we need to create the corresponding lines.
1375 See the following functions (and comments preceding them) for the details of
1378 Also, when our last entry/group are deleted we need to find the new last
1379 element - the code in DeleteEntry/Subgroup does this by backtracking the list
1380 of lines until it either founds an entry/subgroup (and this is the new last
1381 element) or the m_pLine of the group, in which case there are no more entries
1382 (or subgroups) left and m_pLast<element> becomes NULL.
1384 NB: This last problem could be avoided for entries if we added new entries
1385 immediately after m_pLine, but in this case the entries would appear
1386 backwards in the config file (OTOH, it's not that important) and as we
1387 would still need to do it for the subgroups the code wouldn't have been
1388 significantly less complicated.
1391 // Return the line which contains "[our name]". If we're still not in the list,
1392 // add our line to it immediately after the last line of our parent group if we
1393 // have it or in the very beginning if we're the root group.
1394 wxFileConfigLineList
*wxFileConfigGroup::GetGroupLine()
1396 wxLogTrace( FILECONF_TRACE_MASK
,
1397 _T(" GetGroupLine() for Group '%s'"),
1402 wxLogTrace( FILECONF_TRACE_MASK
,
1403 _T(" Getting Line item pointer") );
1405 wxFileConfigGroup
*pParent
= Parent();
1407 // this group wasn't present in local config file, add it now
1410 wxLogTrace( FILECONF_TRACE_MASK
,
1411 _T(" checking parent '%s'"),
1412 pParent
->Name().c_str() );
1414 wxString strFullName
;
1416 // add 1 to the name because we don't want to start with '/'
1417 strFullName
<< wxT("[")
1418 << FilterOutEntryName(GetFullName().c_str() + 1)
1420 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
1421 pParent
->GetLastGroupLine());
1422 pParent
->SetLastGroup(this); // we're surely after all the others
1424 //else: this is the root group and so we return NULL because we don't
1425 // have any group line
1431 // Return the last line belonging to the subgroups of this group (after which
1432 // we can add a new subgroup), if we don't have any subgroups or entries our
1433 // last line is the group line (m_pLine) itself.
1434 wxFileConfigLineList
*wxFileConfigGroup::GetLastGroupLine()
1436 // if we have any subgroups, our last line is the last line of the last
1440 wxFileConfigLineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
1442 wxASSERT_MSG( pLine
, _T("last group must have !NULL associated line") );
1447 // no subgroups, so the last line is the line of thelast entry (if any)
1448 return GetLastEntryLine();
1451 // return the last line belonging to the entries of this group (after which
1452 // we can add a new entry), if we don't have any entries we will add the new
1453 // one immediately after the group line itself.
1454 wxFileConfigLineList
*wxFileConfigGroup::GetLastEntryLine()
1456 wxLogTrace( FILECONF_TRACE_MASK
,
1457 _T(" GetLastEntryLine() for Group '%s'"),
1462 wxFileConfigLineList
*pLine
= m_pLastEntry
->GetLine();
1464 wxASSERT_MSG( pLine
, _T("last entry must have !NULL associated line") );
1469 // no entries: insert after the group header, if any
1470 return GetGroupLine();
1473 void wxFileConfigGroup::SetLastEntry(wxFileConfigEntry
*pEntry
)
1475 m_pLastEntry
= pEntry
;
1479 // the only situation in which a group without its own line can have
1480 // an entry is when the first entry is added to the initially empty
1481 // root pseudo-group
1482 wxASSERT_MSG( !m_pParent
, _T("unexpected for non root group") );
1484 // let the group know that it does have a line in the file now
1485 m_pLine
= pEntry
->GetLine();
1489 // ----------------------------------------------------------------------------
1491 // ----------------------------------------------------------------------------
1493 void wxFileConfigGroup::UpdateGroupAndSubgroupsLines()
1495 // update the line of this group
1496 wxFileConfigLineList
*line
= GetGroupLine();
1497 wxCHECK_RET( line
, _T("a non root group must have a corresponding line!") );
1499 // +1: skip the leading '/'
1500 line
->SetText(wxString::Format(_T("[%s]"), GetFullName().c_str() + 1));
1503 // also update all subgroups as they have this groups name in their lines
1504 const size_t nCount
= m_aSubgroups
.Count();
1505 for ( size_t n
= 0; n
< nCount
; n
++ )
1507 m_aSubgroups
[n
]->UpdateGroupAndSubgroupsLines();
1511 void wxFileConfigGroup::Rename(const wxString
& newName
)
1513 wxCHECK_RET( m_pParent
, _T("the root group can't be renamed") );
1515 m_strName
= newName
;
1517 // update the group lines recursively
1518 UpdateGroupAndSubgroupsLines();
1521 wxString
wxFileConfigGroup::GetFullName() const
1525 fullname
= Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
1530 // ----------------------------------------------------------------------------
1532 // ----------------------------------------------------------------------------
1534 // use binary search because the array is sorted
1536 wxFileConfigGroup::FindEntry(const wxChar
*szName
) const
1540 hi
= m_aEntries
.Count();
1542 wxFileConfigEntry
*pEntry
;
1546 pEntry
= m_aEntries
[i
];
1548 #if wxCONFIG_CASE_SENSITIVE
1549 res
= wxStrcmp(pEntry
->Name(), szName
);
1551 res
= wxStricmp(pEntry
->Name(), szName
);
1566 wxFileConfigGroup::FindSubgroup(const wxChar
*szName
) const
1570 hi
= m_aSubgroups
.Count();
1572 wxFileConfigGroup
*pGroup
;
1576 pGroup
= m_aSubgroups
[i
];
1578 #if wxCONFIG_CASE_SENSITIVE
1579 res
= wxStrcmp(pGroup
->Name(), szName
);
1581 res
= wxStricmp(pGroup
->Name(), szName
);
1595 // ----------------------------------------------------------------------------
1596 // create a new item
1597 // ----------------------------------------------------------------------------
1599 // create a new entry and add it to the current group
1600 wxFileConfigEntry
*wxFileConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
1602 wxASSERT( FindEntry(strName
) == 0 );
1604 wxFileConfigEntry
*pEntry
= new wxFileConfigEntry(this, strName
, nLine
);
1606 m_aEntries
.Add(pEntry
);
1610 // create a new group and add it to the current group
1611 wxFileConfigGroup
*wxFileConfigGroup::AddSubgroup(const wxString
& strName
)
1613 wxASSERT( FindSubgroup(strName
) == 0 );
1615 wxFileConfigGroup
*pGroup
= new wxFileConfigGroup(this, strName
, m_pConfig
);
1617 m_aSubgroups
.Add(pGroup
);
1621 // ----------------------------------------------------------------------------
1623 // ----------------------------------------------------------------------------
1626 The delete operations are _very_ slow if we delete the last item of this
1627 group (see comments before GetXXXLineXXX functions for more details),
1628 so it's much better to start with the first entry/group if we want to
1629 delete several of them.
1632 bool wxFileConfigGroup::DeleteSubgroupByName(const wxChar
*szName
)
1634 wxFileConfigGroup
* const pGroup
= FindSubgroup(szName
);
1636 return pGroup
? DeleteSubgroup(pGroup
) : false;
1639 // Delete the subgroup and remove all references to it from
1640 // other data structures.
1641 bool wxFileConfigGroup::DeleteSubgroup(wxFileConfigGroup
*pGroup
)
1643 wxCHECK_MSG( pGroup
, false, _T("deleting non existing group?") );
1645 wxLogTrace( FILECONF_TRACE_MASK
,
1646 _T("Deleting group '%s' from '%s'"),
1647 pGroup
->Name().c_str(),
1650 wxLogTrace( FILECONF_TRACE_MASK
,
1651 _T(" (m_pLine) = prev: %p, this %p, next %p"),
1652 ((m_pLine
) ? m_pLine
->Prev() : 0),
1654 ((m_pLine
) ? m_pLine
->Next() : 0) );
1655 wxLogTrace( FILECONF_TRACE_MASK
,
1657 ((m_pLine
) ? m_pLine
->Text().c_str() : wxEmptyString
) );
1659 // delete all entries...
1660 size_t nCount
= pGroup
->m_aEntries
.Count();
1662 wxLogTrace(FILECONF_TRACE_MASK
,
1663 _T("Removing %lu entries"), (unsigned long)nCount
);
1665 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ )
1667 wxFileConfigLineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1671 wxLogTrace( FILECONF_TRACE_MASK
,
1673 pLine
->Text().c_str() );
1674 m_pConfig
->LineListRemove(pLine
);
1678 // ...and subgroups of this subgroup
1679 nCount
= pGroup
->m_aSubgroups
.Count();
1681 wxLogTrace( FILECONF_TRACE_MASK
,
1682 _T("Removing %lu subgroups"), (unsigned long)nCount
);
1684 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ )
1686 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[0]);
1689 // and then finally the group itself
1690 wxFileConfigLineList
*pLine
= pGroup
->m_pLine
;
1693 wxLogTrace( FILECONF_TRACE_MASK
,
1694 _T(" Removing line for group '%s' : '%s'"),
1695 pGroup
->Name().c_str(),
1696 pLine
->Text().c_str() );
1697 wxLogTrace( FILECONF_TRACE_MASK
,
1698 _T(" Removing from group '%s' : '%s'"),
1700 ((m_pLine
) ? m_pLine
->Text().c_str() : wxEmptyString
) );
1702 // notice that we may do this test inside the previous "if"
1703 // because the last entry's line is surely !NULL
1704 if ( pGroup
== m_pLastGroup
)
1706 wxLogTrace( FILECONF_TRACE_MASK
,
1707 _T(" Removing last group") );
1709 // our last entry is being deleted, so find the last one which
1710 // stays by going back until we find a subgroup or reach the
1712 const size_t nSubgroups
= m_aSubgroups
.Count();
1714 m_pLastGroup
= NULL
;
1715 for ( wxFileConfigLineList
*pl
= pLine
->Prev();
1716 pl
&& pl
!= m_pLine
&& !m_pLastGroup
;
1719 // does this line belong to our subgroup?
1720 for ( size_t n
= 0; n
< nSubgroups
; n
++ )
1722 // do _not_ call GetGroupLine! we don't want to add it to
1723 // the local file if it's not already there
1724 if ( m_aSubgroups
[n
]->m_pLine
== pl
)
1726 m_pLastGroup
= m_aSubgroups
[n
];
1733 m_pConfig
->LineListRemove(pLine
);
1737 wxLogTrace( FILECONF_TRACE_MASK
,
1738 _T(" No line entry for Group '%s'?"),
1739 pGroup
->Name().c_str() );
1742 m_aSubgroups
.Remove(pGroup
);
1748 bool wxFileConfigGroup::DeleteEntry(const wxChar
*szName
)
1750 wxFileConfigEntry
*pEntry
= FindEntry(szName
);
1753 // entry doesn't exist, nothing to do
1757 wxFileConfigLineList
*pLine
= pEntry
->GetLine();
1758 if ( pLine
!= NULL
) {
1759 // notice that we may do this test inside the previous "if" because the
1760 // last entry's line is surely !NULL
1761 if ( pEntry
== m_pLastEntry
) {
1762 // our last entry is being deleted - find the last one which stays
1763 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1765 // go back until we find another entry or reach the group's line
1766 wxFileConfigEntry
*pNewLast
= NULL
;
1767 size_t n
, nEntries
= m_aEntries
.Count();
1768 wxFileConfigLineList
*pl
;
1769 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1770 // is it our subgroup?
1771 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1772 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1773 pNewLast
= m_aEntries
[n
];
1776 if ( pNewLast
!= NULL
) // found?
1780 if ( pl
== m_pLine
) {
1781 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1783 // we've reached the group line without finding any subgroups
1784 m_pLastEntry
= NULL
;
1787 m_pLastEntry
= pNewLast
;
1790 m_pConfig
->LineListRemove(pLine
);
1793 m_aEntries
.Remove(pEntry
);
1799 // ============================================================================
1800 // wxFileConfig::wxFileConfigEntry
1801 // ============================================================================
1803 // ----------------------------------------------------------------------------
1805 // ----------------------------------------------------------------------------
1806 wxFileConfigEntry::wxFileConfigEntry(wxFileConfigGroup
*pParent
,
1807 const wxString
& strName
,
1809 : m_strName(strName
)
1811 wxASSERT( !strName
.empty() );
1813 m_pParent
= pParent
;
1817 m_bHasValue
= false;
1819 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1821 m_strName
.erase(0, 1); // remove first character
1824 // ----------------------------------------------------------------------------
1826 // ----------------------------------------------------------------------------
1828 void wxFileConfigEntry::SetLine(wxFileConfigLineList
*pLine
)
1830 if ( m_pLine
!= NULL
) {
1831 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1832 Name().c_str(), m_pParent
->GetFullName().c_str());
1836 Group()->SetLastEntry(this);
1839 // second parameter is false if we read the value from file and prevents the
1840 // entry from being marked as 'dirty'
1841 void wxFileConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1843 if ( bUser
&& IsImmutable() )
1845 wxLogWarning( _("attempt to change immutable key '%s' ignored."),
1850 // do nothing if it's the same value: but don't test for it if m_bHasValue
1851 // hadn't been set yet or we'd never write empty values to the file
1852 if ( m_bHasValue
&& strValue
== m_strValue
)
1856 m_strValue
= strValue
;
1860 wxString strValFiltered
;
1862 if ( Group()->Config()->GetStyle() & wxCONFIG_USE_NO_ESCAPE_CHARACTERS
)
1864 strValFiltered
= strValue
;
1867 strValFiltered
= FilterOutValue(strValue
);
1871 strLine
<< FilterOutEntryName(m_strName
) << wxT('=') << strValFiltered
;
1875 // entry was read from the local config file, just modify the line
1876 m_pLine
->SetText(strLine
);
1878 else // this entry didn't exist in the local file
1880 // add a new line to the file: note that line returned by
1881 // GetLastEntryLine() may be NULL if we're in the root group and it
1882 // doesn't have any entries yet, but this is ok as passing NULL
1883 // line to LineListInsert() means to prepend new line to the list
1884 wxFileConfigLineList
*line
= Group()->GetLastEntryLine();
1885 m_pLine
= Group()->Config()->LineListInsert(strLine
, line
);
1887 Group()->SetLastEntry(this);
1892 // ============================================================================
1894 // ============================================================================
1896 // ----------------------------------------------------------------------------
1897 // compare functions for array sorting
1898 // ----------------------------------------------------------------------------
1900 int CompareEntries(wxFileConfigEntry
*p1
, wxFileConfigEntry
*p2
)
1902 #if wxCONFIG_CASE_SENSITIVE
1903 return wxStrcmp(p1
->Name(), p2
->Name());
1905 return wxStricmp(p1
->Name(), p2
->Name());
1909 int CompareGroups(wxFileConfigGroup
*p1
, wxFileConfigGroup
*p2
)
1911 #if wxCONFIG_CASE_SENSITIVE
1912 return wxStrcmp(p1
->Name(), p2
->Name());
1914 return wxStricmp(p1
->Name(), p2
->Name());
1918 // ----------------------------------------------------------------------------
1920 // ----------------------------------------------------------------------------
1922 // undo FilterOutValue
1923 static wxString
FilterInValue(const wxString
& str
)
1926 strResult
.Alloc(str
.Len());
1928 bool bQuoted
= !str
.empty() && str
[0] == '"';
1930 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1931 if ( str
[n
] == wxT('\\') ) {
1932 switch ( str
[++n
] ) {
1934 strResult
+= wxT('\n');
1938 strResult
+= wxT('\r');
1942 strResult
+= wxT('\t');
1946 strResult
+= wxT('\\');
1950 strResult
+= wxT('"');
1955 if ( str
[n
] != wxT('"') || !bQuoted
)
1956 strResult
+= str
[n
];
1957 else if ( n
!= str
.Len() - 1 ) {
1958 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1961 //else: it's the last quote of a quoted string, ok
1968 // quote the string before writing it to file
1969 static wxString
FilterOutValue(const wxString
& str
)
1975 strResult
.Alloc(str
.Len());
1977 // quoting is necessary to preserve spaces in the beginning of the string
1978 bool bQuote
= wxIsspace(str
[0]) || str
[0] == wxT('"');
1981 strResult
+= wxT('"');
1984 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
2007 //else: fall through
2010 strResult
+= str
[n
];
2011 continue; // nothing special to do
2014 // we get here only for special characters
2015 strResult
<< wxT('\\') << c
;
2019 strResult
+= wxT('"');
2024 // undo FilterOutEntryName
2025 static wxString
FilterInEntryName(const wxString
& str
)
2028 strResult
.Alloc(str
.Len());
2030 for ( const wxChar
*pc
= str
.c_str(); *pc
!= '\0'; pc
++ ) {
2031 if ( *pc
== wxT('\\') ) {
2032 // we need to test it here or we'd skip past the NUL in the loop line
2033 if ( *++pc
== _T('\0') )
2043 // sanitize entry or group name: insert '\\' before any special characters
2044 static wxString
FilterOutEntryName(const wxString
& str
)
2047 strResult
.Alloc(str
.Len());
2049 for ( const wxChar
*pc
= str
.c_str(); *pc
!= wxT('\0'); pc
++ ) {
2050 const wxChar c
= *pc
;
2052 // we explicitly allow some of "safe" chars and 8bit ASCII characters
2053 // which will probably never have special meaning and with which we can't
2054 // use isalnum() anyhow (in ASCII built, in Unicode it's just fine)
2056 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
2057 // should *not* be quoted
2060 ((unsigned char)c
< 127) &&
2062 !wxIsalnum(c
) && !wxStrchr(wxT("@_/-!.*%"), c
) )
2064 strResult
+= wxT('\\');
2073 // we can't put ?: in the ctor initializer list because it confuses some
2074 // broken compilers (Borland C++)
2075 static wxString
GetAppName(const wxString
& appName
)
2077 if ( !appName
&& wxTheApp
)
2078 return wxTheApp
->GetAppName();
2083 #endif // wxUSE_CONFIG