1 ///////////////////////////////////////////////////////////////////////////////
3 // Purpose: implementation of wxFileConfig derivation of wxConfig
4 // Author: Vadim Zeitlin
6 // Created: 07.04.98 (adapted from appconf.cpp)
8 // Copyright: (c) 1997 Karsten Ballüder & Vadim Zeitlin
9 // Ballueder@usa.net <zeitlin@dptmaths.ens-cachan.fr>
10 // Licence: wxWindows license
11 ///////////////////////////////////////////////////////////////////////////////
14 #pragma implementation "fileconf.h"
17 // ----------------------------------------------------------------------------
19 // ----------------------------------------------------------------------------
21 #include "wx/wxprec.h"
30 #include "wx/string.h"
35 #include "wx/dynarray.h"
38 #include "wx/textfile.h"
39 #include "wx/config.h"
40 #include "wx/fileconf.h"
42 #include "wx/utils.h" // for wxGetHomeDir
44 // _WINDOWS_ is defined when windows.h is included,
45 // __WXMSW__ is defined for MS Windows compilation
46 #if defined(__WXMSW__) && !defined(_WINDOWS_)
57 // ----------------------------------------------------------------------------
59 // ----------------------------------------------------------------------------
60 #define CONST_CAST ((wxFileConfig *)this)->
62 // ----------------------------------------------------------------------------
64 // ----------------------------------------------------------------------------
69 // ----------------------------------------------------------------------------
70 // global functions declarations
71 // ----------------------------------------------------------------------------
73 // compare functions for sorting the arrays
74 static int LINKAGEMODE
CompareEntries(ConfigEntry
*p1
, ConfigEntry
*p2
);
75 static int LINKAGEMODE
CompareGroups(ConfigGroup
*p1
, ConfigGroup
*p2
);
78 static wxString
FilterInValue(const wxString
& str
);
79 static wxString
FilterOutValue(const wxString
& str
);
81 static wxString
FilterInEntryName(const wxString
& str
);
82 static wxString
FilterOutEntryName(const wxString
& str
);
84 // get the name to use in wxFileConfig ctor
85 static wxString
GetAppName(const wxString
& appname
);
87 // ============================================================================
89 // ============================================================================
91 // ----------------------------------------------------------------------------
93 // ----------------------------------------------------------------------------
94 wxString
wxFileConfig::GetGlobalDir()
98 #ifdef __VMS__ // Note if __VMS is defined __UNIX is also defined
99 strDir
= wxT("sys$manager:");
100 #elif defined( __UNIX__ )
101 strDir
= wxT("/etc/");
102 #elif defined(__WXPM__)
103 ULONG aulSysInfo
[QSV_MAX
] = {0};
107 rc
= DosQuerySysInfo( 1L, QSV_MAX
, (PVOID
)aulSysInfo
, sizeof(ULONG
)*QSV_MAX
);
110 drive
= aulSysInfo
[QSV_BOOT_DRIVE
- 1];
114 strDir
= "A:\\OS2\\";
117 strDir
= "B:\\OS2\\";
120 strDir
= "C:\\OS2\\";
123 strDir
= "D:\\OS2\\";
126 strDir
= "E:\\OS2\\";
129 strDir
= "F:\\OS2\\";
132 strDir
= "G:\\OS2\\";
135 strDir
= "H:\\OS2\\";
138 strDir
= "I:\\OS2\\";
141 strDir
= "J:\\OS2\\";
144 strDir
= "K:\\OS2\\";
147 strDir
= "L:\\OS2\\";
150 strDir
= "M:\\OS2\\";
153 strDir
= "N:\\OS2\\";
156 strDir
= "O:\\OS2\\";
159 strDir
= "P:\\OS2\\";
162 strDir
= "Q:\\OS2\\";
165 strDir
= "R:\\OS2\\";
168 strDir
= "S:\\OS2\\";
171 strDir
= "T:\\OS2\\";
174 strDir
= "U:\\OS2\\";
177 strDir
= "V:\\OS2\\";
180 strDir
= "W:\\OS2\\";
183 strDir
= "X:\\OS2\\";
186 strDir
= "Y:\\OS2\\";
189 strDir
= "Z:\\OS2\\";
193 #elif defined(__WXSTUBS__)
194 wxASSERT_MSG( FALSE
, wxT("TODO") ) ;
195 #elif defined(__WXMAC__)
200 if ( FindFolder( (short) kOnSystemDisk
, kPreferencesFolderType
, kDontCreateFolder
, &vRefNum
, &dirID
) == noErr
)
203 if ( FSMakeFSSpec( vRefNum
, dirID
, "\p" , &file
) == noErr
)
205 strDir
= wxMacFSSpec2UnixFilename( &file
) + "/" ;
210 wxChar szWinDir
[MAX_PATH
];
211 ::GetWindowsDirectory(szWinDir
, MAX_PATH
);
215 #endif // Unix/Windows
220 wxString
wxFileConfig::GetLocalDir()
225 wxGetHomeDir(&strDir
);
229 if (strDir
.Last() != wxT('/')) strDir
<< wxT('/');
231 if (strDir
.Last() != wxT('\\')) strDir
<< wxT('\\');
235 // no local dir concept on mac
236 return GetGlobalDir() ;
242 wxString
wxFileConfig::GetGlobalFileName(const wxChar
*szFile
)
244 wxString str
= GetGlobalDir();
247 if ( wxStrchr(szFile
, wxT('.')) == NULL
)
250 #elif defined( __WXMAC__ )
251 str
<< " Preferences";
259 wxString
wxFileConfig::GetLocalFileName(const wxChar
*szFile
)
261 #ifdef __VMS__ // On VMS I saw the problem that the home directory was appended
262 // twice for the configuration file. Does that also happen for other
264 wxString str
= wxT( ' ' );
266 wxString str
= GetLocalDir();
276 if ( wxStrchr(szFile
, wxT('.')) == NULL
)
282 str
<< " Preferences";
287 // ----------------------------------------------------------------------------
289 // ----------------------------------------------------------------------------
291 void wxFileConfig::Init()
294 m_pRootGroup
= new ConfigGroup(NULL
, "", this);
299 // it's not an error if (one of the) file(s) doesn't exist
301 // parse the global file
302 if ( !m_strGlobalFile
.IsEmpty() && wxFile::Exists(m_strGlobalFile
) ) {
303 wxTextFile
fileGlobal(m_strGlobalFile
);
305 if ( fileGlobal
.Open() ) {
306 Parse(fileGlobal
, FALSE
/* global */);
310 wxLogWarning(_("can't open global configuration file '%s'."),
311 m_strGlobalFile
.c_str());
314 // parse the local file
315 if ( !m_strLocalFile
.IsEmpty() && wxFile::Exists(m_strLocalFile
) ) {
316 wxTextFile
fileLocal(m_strLocalFile
);
317 if ( fileLocal
.Open() ) {
318 Parse(fileLocal
, TRUE
/* local */);
322 wxLogWarning(_("can't open user configuration file '%s'."),
323 m_strLocalFile
.c_str());
327 // constructor supports creation of wxFileConfig objects of any type
328 wxFileConfig::wxFileConfig(const wxString
& appName
, const wxString
& vendorName
,
329 const wxString
& strLocal
, const wxString
& strGlobal
,
331 : wxConfigBase(::GetAppName(appName
), vendorName
,
334 m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
336 // Make up names for files if empty
337 if ( m_strLocalFile
.IsEmpty() && (style
& wxCONFIG_USE_LOCAL_FILE
) )
339 m_strLocalFile
= GetLocalFileName(GetAppName());
342 if ( m_strGlobalFile
.IsEmpty() && (style
& wxCONFIG_USE_GLOBAL_FILE
) )
344 m_strGlobalFile
= GetGlobalFileName(GetAppName());
347 // Check if styles are not supplied, but filenames are, in which case
348 // add the correct styles.
349 if ( !m_strLocalFile
.IsEmpty() )
350 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE
);
352 if ( !m_strGlobalFile
.IsEmpty() )
353 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE
);
355 // if the path is not absolute, prepend the standard directory to it
356 // UNLESS wxCONFIG_USE_RELATIVE_PATH style is set
357 if ( !(style
& wxCONFIG_USE_RELATIVE_PATH
) )
359 if ( !m_strLocalFile
.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile
) )
361 wxString strLocal
= m_strLocalFile
;
362 m_strLocalFile
= GetLocalDir();
363 m_strLocalFile
<< strLocal
;
366 if ( !m_strGlobalFile
.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile
) )
368 wxString strGlobal
= m_strGlobalFile
;
369 m_strGlobalFile
= GetGlobalDir();
370 m_strGlobalFile
<< strGlobal
;
377 void wxFileConfig::CleanUp()
381 LineList
*pCur
= m_linesHead
;
382 while ( pCur
!= NULL
) {
383 LineList
*pNext
= pCur
->Next();
389 wxFileConfig::~wxFileConfig()
396 // ----------------------------------------------------------------------------
397 // parse a config file
398 // ----------------------------------------------------------------------------
400 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
402 const wxChar
*pStart
;
406 size_t nLineCount
= file
.GetLineCount();
407 for ( size_t n
= 0; n
< nLineCount
; n
++ ) {
410 // add the line to linked list
412 LineListAppend(strLine
);
414 // skip leading spaces
415 for ( pStart
= strLine
; wxIsspace(*pStart
); pStart
++ )
418 // skip blank/comment lines
419 if ( *pStart
== wxT('\0')|| *pStart
== wxT(';') || *pStart
== wxT('#') )
422 if ( *pStart
== wxT('[') ) { // a new group
425 while ( *++pEnd
!= wxT(']') ) {
426 if ( *pEnd
== wxT('\n') || *pEnd
== wxT('\0') )
430 if ( *pEnd
!= wxT(']') ) {
431 wxLogError(_("file '%s': unexpected character %c at line %d."),
432 file
.GetName(), *pEnd
, n
+ 1);
433 continue; // skip this line
436 // group name here is always considered as abs path
439 strGroup
<< wxCONFIG_PATH_SEPARATOR
440 << FilterInEntryName(wxString(pStart
, pEnd
- pStart
));
442 // will create it if doesn't yet exist
446 m_pCurrentGroup
->SetLine(m_linesTail
);
448 // check that there is nothing except comments left on this line
450 while ( *++pEnd
!= wxT('\0') && bCont
) {
459 // ignore whitespace ('\n' impossible here)
463 wxLogWarning(_("file '%s', line %d: '%s' "
464 "ignored after group header."),
465 file
.GetName(), n
+ 1, pEnd
);
471 const wxChar
*pEnd
= pStart
;
472 while ( *pEnd
!= wxT('=') && !wxIsspace(*pEnd
) ) {
473 if ( *pEnd
== wxT('\\') ) {
474 // next character may be space or not - still take it because it's
482 wxString
strKey(FilterInEntryName(wxString(pStart
, pEnd
)));
485 while ( isspace(*pEnd
) )
488 if ( *pEnd
++ != wxT('=') ) {
489 wxLogError(_("file '%s', line %d: '=' expected."),
490 file
.GetName(), n
+ 1);
493 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
495 if ( pEntry
== NULL
) {
497 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
500 pEntry
->SetLine(m_linesTail
);
503 if ( bLocal
&& pEntry
->IsImmutable() ) {
504 // immutable keys can't be changed by user
505 wxLogWarning(_("file '%s', line %d: value for "
506 "immutable key '%s' ignored."),
507 file
.GetName(), n
+ 1, strKey
.c_str());
510 // the condition below catches the cases (a) and (b) but not (c):
511 // (a) global key found second time in global file
512 // (b) key found second (or more) time in local file
513 // (c) key from global file now found in local one
514 // which is exactly what we want.
515 else if ( !bLocal
|| pEntry
->IsLocal() ) {
516 wxLogWarning(_("file '%s', line %d: key '%s' was first "
517 "found at line %d."),
518 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
521 pEntry
->SetLine(m_linesTail
);
526 while ( wxIsspace(*pEnd
) )
529 pEntry
->SetValue(FilterInValue(pEnd
), FALSE
/* read from file */);
535 // ----------------------------------------------------------------------------
537 // ----------------------------------------------------------------------------
539 void wxFileConfig::SetRootPath()
542 m_pCurrentGroup
= m_pRootGroup
;
545 void wxFileConfig::SetPath(const wxString
& strPath
)
547 wxArrayString aParts
;
549 if ( strPath
.IsEmpty() ) {
554 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
556 wxSplitPath(aParts
, strPath
);
559 // relative path, combine with current one
560 wxString strFullPath
= m_strPath
;
561 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
562 wxSplitPath(aParts
, strFullPath
);
565 // change current group
567 m_pCurrentGroup
= m_pRootGroup
;
568 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
569 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
570 if ( pNextGroup
== NULL
)
571 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
572 m_pCurrentGroup
= pNextGroup
;
575 // recombine path parts in one variable
577 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
578 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
582 // ----------------------------------------------------------------------------
584 // ----------------------------------------------------------------------------
586 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
589 return GetNextGroup(str
, lIndex
);
592 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
594 if ( size_t(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
595 str
= m_pCurrentGroup
->Groups()[(size_t)lIndex
++]->Name();
602 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
605 return GetNextEntry(str
, lIndex
);
608 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
610 if ( size_t(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
611 str
= m_pCurrentGroup
->Entries()[(size_t)lIndex
++]->Name();
618 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
620 size_t n
= m_pCurrentGroup
->Entries().Count();
622 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
623 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
624 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
625 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
626 n
+= GetNumberOfEntries(TRUE
);
627 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
634 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
636 size_t n
= m_pCurrentGroup
->Groups().Count();
638 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
639 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
640 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
641 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
642 n
+= GetNumberOfGroups(TRUE
);
643 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
650 // ----------------------------------------------------------------------------
651 // tests for existence
652 // ----------------------------------------------------------------------------
654 bool wxFileConfig::HasGroup(const wxString
& strName
) const
656 wxConfigPathChanger
path(this, strName
);
658 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
659 return pGroup
!= NULL
;
662 bool wxFileConfig::HasEntry(const wxString
& strName
) const
664 wxConfigPathChanger
path(this, strName
);
666 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
667 return pEntry
!= NULL
;
670 // ----------------------------------------------------------------------------
672 // ----------------------------------------------------------------------------
674 bool wxFileConfig::Read(const wxString
& key
,
675 wxString
* pStr
) const
677 wxConfigPathChanger
path(this, key
);
679 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
680 if (pEntry
== NULL
) {
684 *pStr
= ExpandEnvVars(pEntry
->Value());
689 bool wxFileConfig::Read(const wxString
& key
,
690 wxString
* pStr
, const wxString
& defVal
) const
692 wxConfigPathChanger
path(this, key
);
694 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
695 if (pEntry
== NULL
) {
696 if( IsRecordingDefaults() )
697 ((wxFileConfig
*)this)->Write(key
,defVal
);
698 *pStr
= ExpandEnvVars(defVal
);
702 *pStr
= ExpandEnvVars(pEntry
->Value());
707 bool wxFileConfig::Read(const wxString
& key
, long *pl
) const
710 if ( Read(key
, & str
) ) {
719 bool wxFileConfig::Write(const wxString
& key
, const wxString
& szValue
)
721 wxConfigPathChanger
path(this, key
);
723 wxString strName
= path
.Name();
724 if ( strName
.IsEmpty() ) {
725 // setting the value of a group is an error
726 wxASSERT_MSG( wxIsEmpty(szValue
), wxT("can't set value of a group!") );
728 // ... except if it's empty in which case it's a way to force it's creation
729 m_pCurrentGroup
->SetDirty();
731 // this will add a line for this group if it didn't have it before
732 (void)m_pCurrentGroup
->GetGroupLine();
737 // check that the name is reasonable
738 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
739 wxLogError(_("Config entry name cannot start with '%c'."),
740 wxCONFIG_IMMUTABLE_PREFIX
);
744 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
745 if ( pEntry
== NULL
)
746 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
748 pEntry
->SetValue(szValue
);
754 bool wxFileConfig::Write(const wxString
& key
, long lValue
)
756 // ltoa() is not ANSI :-(
758 buf
.Printf(wxT("%ld"), lValue
);
759 return Write(key
, buf
);
762 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
764 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() || !m_strLocalFile
)
767 wxTempFile
file(m_strLocalFile
);
769 if ( !file
.IsOpened() ) {
770 wxLogError(_("can't open user configuration file."));
774 // write all strings to file
775 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
776 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
777 wxLogError(_("can't write user configuration file."));
783 return file
.Commit();
785 bool ret
= file
.Commit();
790 wxUnixFilename2FSSpec( m_strLocalFile
, &spec
) ;
792 if ( FSpGetFInfo( &spec
, &finfo
) == noErr
)
794 finfo
.fdType
= 'TEXT' ;
795 finfo
.fdCreator
= 'ttxt' ;
796 FSpSetFInfo( &spec
, &finfo
) ;
803 // ----------------------------------------------------------------------------
804 // renaming groups/entries
805 // ----------------------------------------------------------------------------
807 bool wxFileConfig::RenameEntry(const wxString
& oldName
,
808 const wxString
& newName
)
810 // check that the entry exists
811 ConfigEntry
*oldEntry
= m_pCurrentGroup
->FindEntry(oldName
);
815 // check that the new entry doesn't already exist
816 if ( m_pCurrentGroup
->FindEntry(newName
) )
819 // delete the old entry, create the new one
820 wxString value
= oldEntry
->Value();
821 if ( !m_pCurrentGroup
->DeleteEntry(oldName
) )
824 ConfigEntry
*newEntry
= m_pCurrentGroup
->AddEntry(newName
);
825 newEntry
->SetValue(value
);
830 bool wxFileConfig::RenameGroup(const wxString
& oldName
,
831 const wxString
& newName
)
833 // check that the group exists
834 ConfigGroup
*group
= m_pCurrentGroup
->FindSubgroup(oldName
);
838 // check that the new group doesn't already exist
839 if ( m_pCurrentGroup
->FindSubgroup(newName
) )
842 group
->Rename(newName
);
847 // ----------------------------------------------------------------------------
848 // delete groups/entries
849 // ----------------------------------------------------------------------------
851 bool wxFileConfig::DeleteEntry(const wxString
& key
, bool bGroupIfEmptyAlso
)
853 wxConfigPathChanger
path(this, key
);
855 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
858 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
859 if ( m_pCurrentGroup
!= m_pRootGroup
) {
860 ConfigGroup
*pGroup
= m_pCurrentGroup
;
861 SetPath(wxT("..")); // changes m_pCurrentGroup!
862 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
864 //else: never delete the root group
870 bool wxFileConfig::DeleteGroup(const wxString
& key
)
872 wxConfigPathChanger
path(this, key
);
874 return m_pCurrentGroup
->DeleteSubgroupByName(path
.Name());
877 bool wxFileConfig::DeleteAll()
881 if ( remove(m_strLocalFile
.fn_str()) == -1 )
882 wxLogSysError(_("can't delete user configuration file '%s'"), m_strLocalFile
.c_str());
884 m_strLocalFile
= m_strGlobalFile
= wxT("");
890 // ----------------------------------------------------------------------------
891 // linked list functions
892 // ----------------------------------------------------------------------------
894 // append a new line to the end of the list
895 LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
897 LineList
*pLine
= new LineList(str
);
899 if ( m_linesTail
== NULL
) {
905 m_linesTail
->SetNext(pLine
);
906 pLine
->SetPrev(m_linesTail
);
913 // insert a new line after the given one or in the very beginning if !pLine
914 LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
917 if ( pLine
== m_linesTail
)
918 return LineListAppend(str
);
920 LineList
*pNewLine
= new LineList(str
);
921 if ( pLine
== NULL
) {
922 // prepend to the list
923 pNewLine
->SetNext(m_linesHead
);
924 m_linesHead
->SetPrev(pNewLine
);
925 m_linesHead
= pNewLine
;
928 // insert before pLine
929 LineList
*pNext
= pLine
->Next();
930 pNewLine
->SetNext(pNext
);
931 pNewLine
->SetPrev(pLine
);
932 pNext
->SetPrev(pNewLine
);
933 pLine
->SetNext(pNewLine
);
939 void wxFileConfig::LineListRemove(LineList
*pLine
)
941 LineList
*pPrev
= pLine
->Prev(),
942 *pNext
= pLine
->Next();
948 pPrev
->SetNext(pNext
);
954 pNext
->SetPrev(pPrev
);
959 bool wxFileConfig::LineListIsEmpty()
961 return m_linesHead
== NULL
;
964 // ============================================================================
965 // wxFileConfig::ConfigGroup
966 // ============================================================================
968 // ----------------------------------------------------------------------------
970 // ----------------------------------------------------------------------------
973 ConfigGroup::ConfigGroup(ConfigGroup
*pParent
,
974 const wxString
& strName
,
975 wxFileConfig
*pConfig
)
976 : m_aEntries(CompareEntries
),
977 m_aSubgroups(CompareGroups
),
989 // dtor deletes all children
990 ConfigGroup::~ConfigGroup()
993 size_t n
, nCount
= m_aEntries
.Count();
994 for ( n
= 0; n
< nCount
; n
++ )
995 delete m_aEntries
[n
];
998 nCount
= m_aSubgroups
.Count();
999 for ( n
= 0; n
< nCount
; n
++ )
1000 delete m_aSubgroups
[n
];
1003 // ----------------------------------------------------------------------------
1005 // ----------------------------------------------------------------------------
1007 void ConfigGroup::SetLine(LineList
*pLine
)
1009 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
1015 This is a bit complicated, so let me explain it in details. All lines that
1016 were read from the local file (the only one we will ever modify) are stored
1017 in a (doubly) linked list. Our problem is to know at which position in this
1018 list should we insert the new entries/subgroups. To solve it we keep three
1019 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
1021 m_pLine points to the line containing "[group_name]"
1022 m_pLastEntry points to the last entry of this group in the local file.
1023 m_pLastGroup subgroup
1025 Initially, they're NULL all three. When the group (an entry/subgroup) is read
1026 from the local file, the corresponding variable is set. However, if the group
1027 was read from the global file and then modified or created by the application
1028 these variables are still NULL and we need to create the corresponding lines.
1029 See the following functions (and comments preceding them) for the details of
1032 Also, when our last entry/group are deleted we need to find the new last
1033 element - the code in DeleteEntry/Subgroup does this by backtracking the list
1034 of lines until it either founds an entry/subgroup (and this is the new last
1035 element) or the m_pLine of the group, in which case there are no more entries
1036 (or subgroups) left and m_pLast<element> becomes NULL.
1038 NB: This last problem could be avoided for entries if we added new entries
1039 immediately after m_pLine, but in this case the entries would appear
1040 backwards in the config file (OTOH, it's not that important) and as we
1041 would still need to do it for the subgroups the code wouldn't have been
1042 significantly less complicated.
1045 // Return the line which contains "[our name]". If we're still not in the list,
1046 // add our line to it immediately after the last line of our parent group if we
1047 // have it or in the very beginning if we're the root group.
1048 LineList
*ConfigGroup::GetGroupLine()
1050 if ( m_pLine
== NULL
) {
1051 ConfigGroup
*pParent
= Parent();
1053 // this group wasn't present in local config file, add it now
1054 if ( pParent
!= NULL
) {
1055 wxString strFullName
;
1056 strFullName
<< wxT("[")
1058 << FilterOutEntryName(GetFullName().c_str() + 1)
1060 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
1061 pParent
->GetLastGroupLine());
1062 pParent
->SetLastGroup(this); // we're surely after all the others
1065 // we return NULL, so that LineListInsert() will insert us in the
1073 // Return the last line belonging to the subgroups of this group (after which
1074 // we can add a new subgroup), if we don't have any subgroups or entries our
1075 // last line is the group line (m_pLine) itself.
1076 LineList
*ConfigGroup::GetLastGroupLine()
1078 // if we have any subgroups, our last line is the last line of the last
1080 if ( m_pLastGroup
!= NULL
) {
1081 LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
1083 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
1087 // no subgroups, so the last line is the line of thelast entry (if any)
1088 return GetLastEntryLine();
1091 // return the last line belonging to the entries of this group (after which
1092 // we can add a new entry), if we don't have any entries we will add the new
1093 // one immediately after the group line itself.
1094 LineList
*ConfigGroup::GetLastEntryLine()
1096 if ( m_pLastEntry
!= NULL
) {
1097 LineList
*pLine
= m_pLastEntry
->GetLine();
1099 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
1103 // no entries: insert after the group header
1104 return GetGroupLine();
1107 // ----------------------------------------------------------------------------
1109 // ----------------------------------------------------------------------------
1111 void ConfigGroup::Rename(const wxString
& newName
)
1113 m_strName
= newName
;
1115 LineList
*line
= GetGroupLine();
1116 wxString strFullName
;
1117 strFullName
<< wxT("[") << (GetFullName().c_str() + 1) << wxT("]"); // +1: no '/'
1118 line
->SetText(strFullName
);
1123 wxString
ConfigGroup::GetFullName() const
1126 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
1131 // ----------------------------------------------------------------------------
1133 // ----------------------------------------------------------------------------
1135 // use binary search because the array is sorted
1137 ConfigGroup::FindEntry(const wxChar
*szName
) const
1141 hi
= m_aEntries
.Count();
1143 ConfigEntry
*pEntry
;
1147 pEntry
= m_aEntries
[i
];
1149 #if wxCONFIG_CASE_SENSITIVE
1150 res
= wxStrcmp(pEntry
->Name(), szName
);
1152 res
= wxStricmp(pEntry
->Name(), szName
);
1167 ConfigGroup::FindSubgroup(const wxChar
*szName
) const
1171 hi
= m_aSubgroups
.Count();
1173 ConfigGroup
*pGroup
;
1177 pGroup
= m_aSubgroups
[i
];
1179 #if wxCONFIG_CASE_SENSITIVE
1180 res
= wxStrcmp(pGroup
->Name(), szName
);
1182 res
= wxStricmp(pGroup
->Name(), szName
);
1196 // ----------------------------------------------------------------------------
1197 // create a new item
1198 // ----------------------------------------------------------------------------
1200 // create a new entry and add it to the current group
1202 ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
1204 wxASSERT( FindEntry(strName
) == NULL
);
1206 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
1207 m_aEntries
.Add(pEntry
);
1212 // create a new group and add it to the current group
1214 ConfigGroup::AddSubgroup(const wxString
& strName
)
1216 wxASSERT( FindSubgroup(strName
) == NULL
);
1218 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
1219 m_aSubgroups
.Add(pGroup
);
1224 // ----------------------------------------------------------------------------
1226 // ----------------------------------------------------------------------------
1229 The delete operations are _very_ slow if we delete the last item of this
1230 group (see comments before GetXXXLineXXX functions for more details),
1231 so it's much better to start with the first entry/group if we want to
1232 delete several of them.
1235 bool ConfigGroup::DeleteSubgroupByName(const wxChar
*szName
)
1237 return DeleteSubgroup(FindSubgroup(szName
));
1240 // doesn't delete the subgroup itself, but does remove references to it from
1241 // all other data structures (and normally the returned pointer should be
1242 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1243 bool ConfigGroup::DeleteSubgroup(ConfigGroup
*pGroup
)
1245 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1247 // delete all entries
1248 size_t nCount
= pGroup
->m_aEntries
.Count();
1249 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1250 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1251 if ( pLine
!= NULL
)
1252 m_pConfig
->LineListRemove(pLine
);
1255 // and subgroups of this sungroup
1256 nCount
= pGroup
->m_aSubgroups
.Count();
1257 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1258 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[0]);
1261 LineList
*pLine
= pGroup
->m_pLine
;
1262 if ( pLine
!= NULL
) {
1263 // notice that we may do this test inside the previous "if" because the
1264 // last entry's line is surely !NULL
1265 if ( pGroup
== m_pLastGroup
) {
1266 // our last entry is being deleted - find the last one which stays
1267 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1269 // go back until we find a subgroup or reach the group's line
1270 ConfigGroup
*pNewLast
= NULL
;
1271 size_t n
, nSubgroups
= m_aSubgroups
.Count();
1273 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1274 // is it our subgroup?
1275 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1276 // do _not_ call GetGroupLine! we don't want to add it to the local
1277 // file if it's not already there
1278 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1279 pNewLast
= m_aSubgroups
[n
];
1282 if ( pNewLast
!= NULL
) // found?
1286 if ( pl
== m_pLine
) {
1287 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1289 // we've reached the group line without finding any subgroups
1290 m_pLastGroup
= NULL
;
1293 m_pLastGroup
= pNewLast
;
1296 m_pConfig
->LineListRemove(pLine
);
1301 m_aSubgroups
.Remove(pGroup
);
1307 bool ConfigGroup::DeleteEntry(const wxChar
*szName
)
1309 ConfigEntry
*pEntry
= FindEntry(szName
);
1310 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1312 LineList
*pLine
= pEntry
->GetLine();
1313 if ( pLine
!= NULL
) {
1314 // notice that we may do this test inside the previous "if" because the
1315 // last entry's line is surely !NULL
1316 if ( pEntry
== m_pLastEntry
) {
1317 // our last entry is being deleted - find the last one which stays
1318 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1320 // go back until we find another entry or reach the group's line
1321 ConfigEntry
*pNewLast
= NULL
;
1322 size_t n
, nEntries
= m_aEntries
.Count();
1324 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1325 // is it our subgroup?
1326 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1327 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1328 pNewLast
= m_aEntries
[n
];
1331 if ( pNewLast
!= NULL
) // found?
1335 if ( pl
== m_pLine
) {
1336 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1338 // we've reached the group line without finding any subgroups
1339 m_pLastEntry
= NULL
;
1342 m_pLastEntry
= pNewLast
;
1345 m_pConfig
->LineListRemove(pLine
);
1348 // we must be written back for the changes to be saved
1351 m_aEntries
.Remove(pEntry
);
1357 // ----------------------------------------------------------------------------
1359 // ----------------------------------------------------------------------------
1360 void ConfigGroup::SetDirty()
1363 if ( Parent() != NULL
) // propagate upwards
1364 Parent()->SetDirty();
1367 // ============================================================================
1368 // wxFileConfig::ConfigEntry
1369 // ============================================================================
1371 // ----------------------------------------------------------------------------
1373 // ----------------------------------------------------------------------------
1374 ConfigEntry::ConfigEntry(ConfigGroup
*pParent
,
1375 const wxString
& strName
,
1377 : m_strName(strName
)
1379 wxASSERT( !strName
.IsEmpty() );
1381 m_pParent
= pParent
;
1387 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1389 m_strName
.erase(0, 1); // remove first character
1392 // ----------------------------------------------------------------------------
1394 // ----------------------------------------------------------------------------
1396 void ConfigEntry::SetLine(LineList
*pLine
)
1398 if ( m_pLine
!= NULL
) {
1399 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1400 Name().c_str(), m_pParent
->GetFullName().c_str());
1404 Group()->SetLastEntry(this);
1407 // second parameter is FALSE if we read the value from file and prevents the
1408 // entry from being marked as 'dirty'
1409 void ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1411 if ( bUser
&& IsImmutable() ) {
1412 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1417 // do nothing if it's the same value
1418 if ( strValue
== m_strValue
)
1421 m_strValue
= strValue
;
1424 wxString strVal
= FilterOutValue(strValue
);
1426 strLine
<< FilterOutEntryName(m_strName
) << wxT('=') << strVal
;
1428 if ( m_pLine
!= NULL
) {
1429 // entry was read from the local config file, just modify the line
1430 m_pLine
->SetText(strLine
);
1433 // add a new line to the file
1434 wxASSERT( m_nLine
== wxNOT_FOUND
); // consistency check
1436 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1437 Group()->GetLastEntryLine());
1438 Group()->SetLastEntry(this);
1445 void ConfigEntry::SetDirty()
1448 Group()->SetDirty();
1451 // ============================================================================
1453 // ============================================================================
1455 // ----------------------------------------------------------------------------
1456 // compare functions for array sorting
1457 // ----------------------------------------------------------------------------
1459 int CompareEntries(ConfigEntry
*p1
,
1462 #if wxCONFIG_CASE_SENSITIVE
1463 return wxStrcmp(p1
->Name(), p2
->Name());
1465 return wxStricmp(p1
->Name(), p2
->Name());
1469 int CompareGroups(ConfigGroup
*p1
,
1472 #if wxCONFIG_CASE_SENSITIVE
1473 return wxStrcmp(p1
->Name(), p2
->Name());
1475 return wxStricmp(p1
->Name(), p2
->Name());
1479 // ----------------------------------------------------------------------------
1481 // ----------------------------------------------------------------------------
1483 // undo FilterOutValue
1484 static wxString
FilterInValue(const wxString
& str
)
1487 strResult
.Alloc(str
.Len());
1489 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1491 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1492 if ( str
[n
] == wxT('\\') ) {
1493 switch ( str
[++n
] ) {
1495 strResult
+= wxT('\n');
1499 strResult
+= wxT('\r');
1503 strResult
+= wxT('\t');
1507 strResult
+= wxT('\\');
1511 strResult
+= wxT('"');
1516 if ( str
[n
] != wxT('"') || !bQuoted
)
1517 strResult
+= str
[n
];
1518 else if ( n
!= str
.Len() - 1 ) {
1519 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1522 //else: it's the last quote of a quoted string, ok
1529 // quote the string before writing it to file
1530 static wxString
FilterOutValue(const wxString
& str
)
1536 strResult
.Alloc(str
.Len());
1538 // quoting is necessary to preserve spaces in the beginning of the string
1539 bool bQuote
= wxIsspace(str
[0]) || str
[0] == wxT('"');
1542 strResult
+= wxT('"');
1545 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
1568 //else: fall through
1571 strResult
+= str
[n
];
1572 continue; // nothing special to do
1575 // we get here only for special characters
1576 strResult
<< wxT('\\') << c
;
1580 strResult
+= wxT('"');
1585 // undo FilterOutEntryName
1586 static wxString
FilterInEntryName(const wxString
& str
)
1589 strResult
.Alloc(str
.Len());
1591 for ( const wxChar
*pc
= str
.c_str(); *pc
!= '\0'; pc
++ ) {
1592 if ( *pc
== wxT('\\') )
1601 // sanitize entry or group name: insert '\\' before any special characters
1602 static wxString
FilterOutEntryName(const wxString
& str
)
1605 strResult
.Alloc(str
.Len());
1607 for ( const wxChar
*pc
= str
.c_str(); *pc
!= wxT('\0'); pc
++ ) {
1610 // we explicitly allow some of "safe" chars and 8bit ASCII characters
1611 // which will probably never have special meaning
1612 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
1613 // should *not* be quoted
1614 if ( !wxIsalnum(c
) && !wxStrchr(wxT("@_/-!.*%"), c
) && ((c
& 0x80) == 0) )
1615 strResult
+= wxT('\\');
1623 // we can't put ?: in the ctor initializer list because it confuses some
1624 // broken compilers (Borland C++)
1625 static wxString
GetAppName(const wxString
& appName
)
1627 if ( !appName
&& wxTheApp
)
1628 return wxTheApp
->GetAppName();
1633 #endif // wxUSE_CONFIG