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 // headers needed for umask()
59 #include <sys/types.h>
63 // ----------------------------------------------------------------------------
65 // ----------------------------------------------------------------------------
66 #define CONST_CAST ((wxFileConfig *)this)->
68 // ----------------------------------------------------------------------------
70 // ----------------------------------------------------------------------------
75 // ----------------------------------------------------------------------------
76 // global functions declarations
77 // ----------------------------------------------------------------------------
79 // compare functions for sorting the arrays
80 static int LINKAGEMODE
CompareEntries(ConfigEntry
*p1
, ConfigEntry
*p2
);
81 static int LINKAGEMODE
CompareGroups(ConfigGroup
*p1
, ConfigGroup
*p2
);
84 static wxString
FilterInValue(const wxString
& str
);
85 static wxString
FilterOutValue(const wxString
& str
);
87 static wxString
FilterInEntryName(const wxString
& str
);
88 static wxString
FilterOutEntryName(const wxString
& str
);
90 // get the name to use in wxFileConfig ctor
91 static wxString
GetAppName(const wxString
& appname
);
93 // ============================================================================
95 // ============================================================================
97 // ----------------------------------------------------------------------------
99 // ----------------------------------------------------------------------------
100 wxString
wxFileConfig::GetGlobalDir()
104 #ifdef __VMS__ // Note if __VMS is defined __UNIX is also defined
105 strDir
= wxT("sys$manager:");
106 #elif defined( __UNIX__ )
107 strDir
= wxT("/etc/");
108 #elif defined(__WXPM__)
109 ULONG aulSysInfo
[QSV_MAX
] = {0};
113 rc
= DosQuerySysInfo( 1L, QSV_MAX
, (PVOID
)aulSysInfo
, sizeof(ULONG
)*QSV_MAX
);
116 drive
= aulSysInfo
[QSV_BOOT_DRIVE
- 1];
120 strDir
= "A:\\OS2\\";
123 strDir
= "B:\\OS2\\";
126 strDir
= "C:\\OS2\\";
129 strDir
= "D:\\OS2\\";
132 strDir
= "E:\\OS2\\";
135 strDir
= "F:\\OS2\\";
138 strDir
= "G:\\OS2\\";
141 strDir
= "H:\\OS2\\";
144 strDir
= "I:\\OS2\\";
147 strDir
= "J:\\OS2\\";
150 strDir
= "K:\\OS2\\";
153 strDir
= "L:\\OS2\\";
156 strDir
= "M:\\OS2\\";
159 strDir
= "N:\\OS2\\";
162 strDir
= "O:\\OS2\\";
165 strDir
= "P:\\OS2\\";
168 strDir
= "Q:\\OS2\\";
171 strDir
= "R:\\OS2\\";
174 strDir
= "S:\\OS2\\";
177 strDir
= "T:\\OS2\\";
180 strDir
= "U:\\OS2\\";
183 strDir
= "V:\\OS2\\";
186 strDir
= "W:\\OS2\\";
189 strDir
= "X:\\OS2\\";
192 strDir
= "Y:\\OS2\\";
195 strDir
= "Z:\\OS2\\";
199 #elif defined(__WXSTUBS__)
200 wxASSERT_MSG( FALSE
, wxT("TODO") ) ;
201 #elif defined(__WXMAC__)
202 strDir
= wxMacFindFolder( (short) kOnSystemDisk
, kPreferencesFolderType
, kDontCreateFolder
) ;
204 wxChar szWinDir
[MAX_PATH
];
205 ::GetWindowsDirectory(szWinDir
, MAX_PATH
);
209 #endif // Unix/Windows
214 wxString
wxFileConfig::GetLocalDir()
219 wxGetHomeDir(&strDir
);
223 if (strDir
.Last() != wxT(']'))
225 if (strDir
.Last() != wxT('/')) strDir
<< wxT('/');
227 if (strDir
.Last() != wxT('\\')) strDir
<< wxT('\\');
230 // no local dir concept on mac
231 return GetGlobalDir() ;
237 wxString
wxFileConfig::GetGlobalFileName(const wxChar
*szFile
)
239 wxString str
= GetGlobalDir();
242 if ( wxStrchr(szFile
, wxT('.')) == NULL
)
245 #elif defined( __WXMAC__ )
246 str
<< " Preferences";
254 wxString
wxFileConfig::GetLocalFileName(const wxChar
*szFile
)
256 #ifdef __VMS__ // On VMS I saw the problem that the home directory was appended
257 // twice for the configuration file. Does that also happen for other
259 wxString str
= wxT( '.' );
261 wxString str
= GetLocalDir();
264 #if defined( __UNIX__ ) && !defined( __VMS )
271 if ( wxStrchr(szFile
, wxT('.')) == NULL
)
277 str
<< " Preferences";
282 // ----------------------------------------------------------------------------
284 // ----------------------------------------------------------------------------
286 void wxFileConfig::Init()
289 m_pRootGroup
= new ConfigGroup(NULL
, "", this);
294 // it's not an error if (one of the) file(s) doesn't exist
296 // parse the global file
297 if ( !m_strGlobalFile
.IsEmpty() && wxFile::Exists(m_strGlobalFile
) ) {
298 wxTextFile
fileGlobal(m_strGlobalFile
);
300 if ( fileGlobal
.Open() ) {
301 Parse(fileGlobal
, FALSE
/* global */);
305 wxLogWarning(_("can't open global configuration file '%s'."),
306 m_strGlobalFile
.c_str());
309 // parse the local file
310 if ( !m_strLocalFile
.IsEmpty() && wxFile::Exists(m_strLocalFile
) ) {
311 wxTextFile
fileLocal(m_strLocalFile
);
312 if ( fileLocal
.Open() ) {
313 Parse(fileLocal
, TRUE
/* local */);
317 wxLogWarning(_("can't open user configuration file '%s'."),
318 m_strLocalFile
.c_str());
322 // constructor supports creation of wxFileConfig objects of any type
323 wxFileConfig::wxFileConfig(const wxString
& appName
, const wxString
& vendorName
,
324 const wxString
& strLocal
, const wxString
& strGlobal
,
326 : wxConfigBase(::GetAppName(appName
), vendorName
,
329 m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
331 // Make up names for files if empty
332 if ( m_strLocalFile
.IsEmpty() && (style
& wxCONFIG_USE_LOCAL_FILE
) )
334 m_strLocalFile
= GetLocalFileName(GetAppName());
337 if ( m_strGlobalFile
.IsEmpty() && (style
& wxCONFIG_USE_GLOBAL_FILE
) )
339 m_strGlobalFile
= GetGlobalFileName(GetAppName());
342 // Check if styles are not supplied, but filenames are, in which case
343 // add the correct styles.
344 if ( !m_strLocalFile
.IsEmpty() )
345 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE
);
347 if ( !m_strGlobalFile
.IsEmpty() )
348 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE
);
350 // if the path is not absolute, prepend the standard directory to it
351 // UNLESS wxCONFIG_USE_RELATIVE_PATH style is set
352 if ( !(style
& wxCONFIG_USE_RELATIVE_PATH
) )
354 if ( !m_strLocalFile
.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile
) )
356 wxString strLocal
= m_strLocalFile
;
357 m_strLocalFile
= GetLocalDir();
358 m_strLocalFile
<< strLocal
;
361 if ( !m_strGlobalFile
.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile
) )
363 wxString strGlobal
= m_strGlobalFile
;
364 m_strGlobalFile
= GetGlobalDir();
365 m_strGlobalFile
<< strGlobal
;
374 void wxFileConfig::CleanUp()
378 LineList
*pCur
= m_linesHead
;
379 while ( pCur
!= NULL
) {
380 LineList
*pNext
= pCur
->Next();
386 wxFileConfig::~wxFileConfig()
393 // ----------------------------------------------------------------------------
394 // parse a config file
395 // ----------------------------------------------------------------------------
397 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
399 const wxChar
*pStart
;
403 size_t nLineCount
= file
.GetLineCount();
404 for ( size_t n
= 0; n
< nLineCount
; n
++ ) {
407 // add the line to linked list
409 LineListAppend(strLine
);
411 // skip leading spaces
412 for ( pStart
= strLine
; wxIsspace(*pStart
); pStart
++ )
415 // skip blank/comment lines
416 if ( *pStart
== wxT('\0')|| *pStart
== wxT(';') || *pStart
== wxT('#') )
419 if ( *pStart
== wxT('[') ) { // a new group
422 while ( *++pEnd
!= wxT(']') ) {
423 if ( *pEnd
== wxT('\\') ) {
424 // the next char is escaped, so skip it even if it is ']'
428 if ( *pEnd
== wxT('\n') || *pEnd
== wxT('\0') ) {
429 // we reached the end of line, break out of the loop
434 if ( *pEnd
!= wxT(']') ) {
435 wxLogError(_("file '%s': unexpected character %c at line %d."),
436 file
.GetName(), *pEnd
, n
+ 1);
437 continue; // skip this line
440 // group name here is always considered as abs path
443 strGroup
<< wxCONFIG_PATH_SEPARATOR
444 << FilterInEntryName(wxString(pStart
, pEnd
- pStart
));
446 // will create it if doesn't yet exist
450 m_pCurrentGroup
->SetLine(m_linesTail
);
452 // check that there is nothing except comments left on this line
454 while ( *++pEnd
!= wxT('\0') && bCont
) {
463 // ignore whitespace ('\n' impossible here)
467 wxLogWarning(_("file '%s', line %d: '%s' ignored after group header."),
468 file
.GetName(), n
+ 1, pEnd
);
474 const wxChar
*pEnd
= pStart
;
475 while ( *pEnd
&& *pEnd
!= wxT('=') && !wxIsspace(*pEnd
) ) {
476 if ( *pEnd
== wxT('\\') ) {
477 // next character may be space or not - still take it because it's
478 // quoted (unless there is nothing)
481 // the error message will be given below anyhow
489 wxString
strKey(FilterInEntryName(wxString(pStart
, pEnd
)));
492 while ( wxIsspace(*pEnd
) )
495 if ( *pEnd
++ != wxT('=') ) {
496 wxLogError(_("file '%s', line %d: '=' expected."),
497 file
.GetName(), n
+ 1);
500 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
502 if ( pEntry
== NULL
) {
504 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
507 pEntry
->SetLine(m_linesTail
);
510 if ( bLocal
&& pEntry
->IsImmutable() ) {
511 // immutable keys can't be changed by user
512 wxLogWarning(_("file '%s', line %d: value for immutable key '%s' ignored."),
513 file
.GetName(), n
+ 1, strKey
.c_str());
516 // the condition below catches the cases (a) and (b) but not (c):
517 // (a) global key found second time in global file
518 // (b) key found second (or more) time in local file
519 // (c) key from global file now found in local one
520 // which is exactly what we want.
521 else if ( !bLocal
|| pEntry
->IsLocal() ) {
522 wxLogWarning(_("file '%s', line %d: key '%s' was first found at line %d."),
523 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
526 pEntry
->SetLine(m_linesTail
);
531 while ( wxIsspace(*pEnd
) )
534 pEntry
->SetValue(FilterInValue(pEnd
), FALSE
/* read from file */);
540 // ----------------------------------------------------------------------------
542 // ----------------------------------------------------------------------------
544 void wxFileConfig::SetRootPath()
547 m_pCurrentGroup
= m_pRootGroup
;
550 void wxFileConfig::SetPath(const wxString
& strPath
)
552 wxArrayString aParts
;
554 if ( strPath
.IsEmpty() ) {
559 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
561 wxSplitPath(aParts
, strPath
);
564 // relative path, combine with current one
565 wxString strFullPath
= m_strPath
;
566 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
567 wxSplitPath(aParts
, strFullPath
);
570 // change current group
572 m_pCurrentGroup
= m_pRootGroup
;
573 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
574 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
575 if ( pNextGroup
== NULL
)
576 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
577 m_pCurrentGroup
= pNextGroup
;
580 // recombine path parts in one variable
582 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
583 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
587 // ----------------------------------------------------------------------------
589 // ----------------------------------------------------------------------------
591 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
594 return GetNextGroup(str
, lIndex
);
597 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
599 if ( size_t(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
600 str
= m_pCurrentGroup
->Groups()[(size_t)lIndex
++]->Name();
607 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
610 return GetNextEntry(str
, lIndex
);
613 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
615 if ( size_t(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
616 str
= m_pCurrentGroup
->Entries()[(size_t)lIndex
++]->Name();
623 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
625 size_t n
= m_pCurrentGroup
->Entries().Count();
627 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
628 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
629 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
630 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
631 n
+= GetNumberOfEntries(TRUE
);
632 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
639 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
641 size_t n
= m_pCurrentGroup
->Groups().Count();
643 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
644 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
645 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
646 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
647 n
+= GetNumberOfGroups(TRUE
);
648 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
655 // ----------------------------------------------------------------------------
656 // tests for existence
657 // ----------------------------------------------------------------------------
659 bool wxFileConfig::HasGroup(const wxString
& strName
) const
661 wxConfigPathChanger
path(this, strName
);
663 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
664 return pGroup
!= NULL
;
667 bool wxFileConfig::HasEntry(const wxString
& strName
) const
669 wxConfigPathChanger
path(this, strName
);
671 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
672 return pEntry
!= NULL
;
675 // ----------------------------------------------------------------------------
677 // ----------------------------------------------------------------------------
679 bool wxFileConfig::Read(const wxString
& key
,
680 wxString
* pStr
) const
682 wxConfigPathChanger
path(this, key
);
684 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
685 if (pEntry
== NULL
) {
689 *pStr
= ExpandEnvVars(pEntry
->Value());
693 bool wxFileConfig::Read(const wxString
& key
,
694 wxString
* pStr
, const wxString
& defVal
) const
696 wxConfigPathChanger
path(this, key
);
698 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
700 if (pEntry
== NULL
) {
701 if( IsRecordingDefaults() )
702 ((wxFileConfig
*)this)->Write(key
,defVal
);
703 *pStr
= ExpandEnvVars(defVal
);
707 *pStr
= ExpandEnvVars(pEntry
->Value());
714 bool wxFileConfig::Read(const wxString
& key
, long *pl
) const
717 if ( !Read(key
, & str
) )
726 bool wxFileConfig::Write(const wxString
& key
, const wxString
& szValue
)
728 wxConfigPathChanger
path(this, key
);
730 wxString strName
= path
.Name();
731 if ( strName
.IsEmpty() ) {
732 // setting the value of a group is an error
733 wxASSERT_MSG( wxIsEmpty(szValue
), wxT("can't set value of a group!") );
735 // ... except if it's empty in which case it's a way to force it's creation
736 m_pCurrentGroup
->SetDirty();
738 // this will add a line for this group if it didn't have it before
739 (void)m_pCurrentGroup
->GetGroupLine();
744 // check that the name is reasonable
745 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
746 wxLogError(_("Config entry name cannot start with '%c'."),
747 wxCONFIG_IMMUTABLE_PREFIX
);
751 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
752 if ( pEntry
== NULL
)
753 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
755 pEntry
->SetValue(szValue
);
761 bool wxFileConfig::Write(const wxString
& key
, long lValue
)
763 // ltoa() is not ANSI :-(
765 buf
.Printf(wxT("%ld"), lValue
);
766 return Write(key
, buf
);
769 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
771 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() || !m_strLocalFile
)
775 // set the umask if needed
779 umaskOld
= umask((mode_t
)m_umask
);
783 wxTempFile
file(m_strLocalFile
);
785 if ( !file
.IsOpened() ) {
786 wxLogError(_("can't open user configuration file."));
790 // write all strings to file
791 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
792 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
793 wxLogError(_("can't write user configuration file."));
798 bool ret
= file
.Commit();
805 wxUnixFilename2FSSpec( m_strLocalFile
, &spec
) ;
807 if ( FSpGetFInfo( &spec
, &finfo
) == noErr
)
809 finfo
.fdType
= 'TEXT' ;
810 finfo
.fdCreator
= 'ttxt' ;
811 FSpSetFInfo( &spec
, &finfo
) ;
817 // restore the old umask if we changed it
820 (void)umask(umaskOld
);
827 // ----------------------------------------------------------------------------
828 // renaming groups/entries
829 // ----------------------------------------------------------------------------
831 bool wxFileConfig::RenameEntry(const wxString
& oldName
,
832 const wxString
& newName
)
834 // check that the entry exists
835 ConfigEntry
*oldEntry
= m_pCurrentGroup
->FindEntry(oldName
);
839 // check that the new entry doesn't already exist
840 if ( m_pCurrentGroup
->FindEntry(newName
) )
843 // delete the old entry, create the new one
844 wxString value
= oldEntry
->Value();
845 if ( !m_pCurrentGroup
->DeleteEntry(oldName
) )
848 ConfigEntry
*newEntry
= m_pCurrentGroup
->AddEntry(newName
);
849 newEntry
->SetValue(value
);
854 bool wxFileConfig::RenameGroup(const wxString
& oldName
,
855 const wxString
& newName
)
857 // check that the group exists
858 ConfigGroup
*group
= m_pCurrentGroup
->FindSubgroup(oldName
);
862 // check that the new group doesn't already exist
863 if ( m_pCurrentGroup
->FindSubgroup(newName
) )
866 group
->Rename(newName
);
871 // ----------------------------------------------------------------------------
872 // delete groups/entries
873 // ----------------------------------------------------------------------------
875 bool wxFileConfig::DeleteEntry(const wxString
& key
, bool bGroupIfEmptyAlso
)
877 wxConfigPathChanger
path(this, key
);
879 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
882 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
883 if ( m_pCurrentGroup
!= m_pRootGroup
) {
884 ConfigGroup
*pGroup
= m_pCurrentGroup
;
885 SetPath(wxT("..")); // changes m_pCurrentGroup!
886 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
888 //else: never delete the root group
894 bool wxFileConfig::DeleteGroup(const wxString
& key
)
896 wxConfigPathChanger
path(this, key
);
898 return m_pCurrentGroup
->DeleteSubgroupByName(path
.Name());
901 bool wxFileConfig::DeleteAll()
905 if ( wxRemove(m_strLocalFile
) == -1 )
906 wxLogSysError(_("can't delete user configuration file '%s'"), m_strLocalFile
.c_str());
908 m_strLocalFile
= m_strGlobalFile
= wxT("");
914 // ----------------------------------------------------------------------------
915 // linked list functions
916 // ----------------------------------------------------------------------------
918 // append a new line to the end of the list
919 LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
921 LineList
*pLine
= new LineList(str
);
923 if ( m_linesTail
== NULL
) {
929 m_linesTail
->SetNext(pLine
);
930 pLine
->SetPrev(m_linesTail
);
937 // insert a new line after the given one or in the very beginning if !pLine
938 LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
941 if ( pLine
== m_linesTail
)
942 return LineListAppend(str
);
944 LineList
*pNewLine
= new LineList(str
);
945 if ( pLine
== NULL
) {
946 // prepend to the list
947 pNewLine
->SetNext(m_linesHead
);
948 m_linesHead
->SetPrev(pNewLine
);
949 m_linesHead
= pNewLine
;
952 // insert before pLine
953 LineList
*pNext
= pLine
->Next();
954 pNewLine
->SetNext(pNext
);
955 pNewLine
->SetPrev(pLine
);
956 pNext
->SetPrev(pNewLine
);
957 pLine
->SetNext(pNewLine
);
963 void wxFileConfig::LineListRemove(LineList
*pLine
)
965 LineList
*pPrev
= pLine
->Prev(),
966 *pNext
= pLine
->Next();
972 pPrev
->SetNext(pNext
);
978 pNext
->SetPrev(pPrev
);
983 bool wxFileConfig::LineListIsEmpty()
985 return m_linesHead
== NULL
;
988 // ============================================================================
989 // wxFileConfig::ConfigGroup
990 // ============================================================================
992 // ----------------------------------------------------------------------------
994 // ----------------------------------------------------------------------------
997 ConfigGroup::ConfigGroup(ConfigGroup
*pParent
,
998 const wxString
& strName
,
999 wxFileConfig
*pConfig
)
1000 : m_aEntries(CompareEntries
),
1001 m_aSubgroups(CompareGroups
),
1004 m_pConfig
= pConfig
;
1005 m_pParent
= pParent
;
1009 m_pLastEntry
= NULL
;
1010 m_pLastGroup
= NULL
;
1013 // dtor deletes all children
1014 ConfigGroup::~ConfigGroup()
1017 size_t n
, nCount
= m_aEntries
.Count();
1018 for ( n
= 0; n
< nCount
; n
++ )
1019 delete m_aEntries
[n
];
1022 nCount
= m_aSubgroups
.Count();
1023 for ( n
= 0; n
< nCount
; n
++ )
1024 delete m_aSubgroups
[n
];
1027 // ----------------------------------------------------------------------------
1029 // ----------------------------------------------------------------------------
1031 void ConfigGroup::SetLine(LineList
*pLine
)
1033 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
1039 This is a bit complicated, so let me explain it in details. All lines that
1040 were read from the local file (the only one we will ever modify) are stored
1041 in a (doubly) linked list. Our problem is to know at which position in this
1042 list should we insert the new entries/subgroups. To solve it we keep three
1043 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
1045 m_pLine points to the line containing "[group_name]"
1046 m_pLastEntry points to the last entry of this group in the local file.
1047 m_pLastGroup subgroup
1049 Initially, they're NULL all three. When the group (an entry/subgroup) is read
1050 from the local file, the corresponding variable is set. However, if the group
1051 was read from the global file and then modified or created by the application
1052 these variables are still NULL and we need to create the corresponding lines.
1053 See the following functions (and comments preceding them) for the details of
1056 Also, when our last entry/group are deleted we need to find the new last
1057 element - the code in DeleteEntry/Subgroup does this by backtracking the list
1058 of lines until it either founds an entry/subgroup (and this is the new last
1059 element) or the m_pLine of the group, in which case there are no more entries
1060 (or subgroups) left and m_pLast<element> becomes NULL.
1062 NB: This last problem could be avoided for entries if we added new entries
1063 immediately after m_pLine, but in this case the entries would appear
1064 backwards in the config file (OTOH, it's not that important) and as we
1065 would still need to do it for the subgroups the code wouldn't have been
1066 significantly less complicated.
1069 // Return the line which contains "[our name]". If we're still not in the list,
1070 // add our line to it immediately after the last line of our parent group if we
1071 // have it or in the very beginning if we're the root group.
1072 LineList
*ConfigGroup::GetGroupLine()
1074 if ( m_pLine
== NULL
) {
1075 ConfigGroup
*pParent
= Parent();
1077 // this group wasn't present in local config file, add it now
1078 if ( pParent
!= NULL
) {
1079 wxString strFullName
;
1080 strFullName
<< wxT("[")
1082 << FilterOutEntryName(GetFullName().c_str() + 1)
1084 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
1085 pParent
->GetLastGroupLine());
1086 pParent
->SetLastGroup(this); // we're surely after all the others
1089 // we return NULL, so that LineListInsert() will insert us in the
1097 // Return the last line belonging to the subgroups of this group (after which
1098 // we can add a new subgroup), if we don't have any subgroups or entries our
1099 // last line is the group line (m_pLine) itself.
1100 LineList
*ConfigGroup::GetLastGroupLine()
1102 // if we have any subgroups, our last line is the last line of the last
1104 if ( m_pLastGroup
!= NULL
) {
1105 LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
1107 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
1111 // no subgroups, so the last line is the line of thelast entry (if any)
1112 return GetLastEntryLine();
1115 // return the last line belonging to the entries of this group (after which
1116 // we can add a new entry), if we don't have any entries we will add the new
1117 // one immediately after the group line itself.
1118 LineList
*ConfigGroup::GetLastEntryLine()
1120 if ( m_pLastEntry
!= NULL
) {
1121 LineList
*pLine
= m_pLastEntry
->GetLine();
1123 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
1127 // no entries: insert after the group header
1128 return GetGroupLine();
1131 // ----------------------------------------------------------------------------
1133 // ----------------------------------------------------------------------------
1135 void ConfigGroup::Rename(const wxString
& newName
)
1137 m_strName
= newName
;
1139 LineList
*line
= GetGroupLine();
1140 wxString strFullName
;
1141 strFullName
<< wxT("[") << (GetFullName().c_str() + 1) << wxT("]"); // +1: no '/'
1142 line
->SetText(strFullName
);
1147 wxString
ConfigGroup::GetFullName() const
1150 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
1155 // ----------------------------------------------------------------------------
1157 // ----------------------------------------------------------------------------
1159 // use binary search because the array is sorted
1161 ConfigGroup::FindEntry(const wxChar
*szName
) const
1165 hi
= m_aEntries
.Count();
1167 ConfigEntry
*pEntry
;
1171 pEntry
= m_aEntries
[i
];
1173 #if wxCONFIG_CASE_SENSITIVE
1174 res
= wxStrcmp(pEntry
->Name(), szName
);
1176 res
= wxStricmp(pEntry
->Name(), szName
);
1191 ConfigGroup::FindSubgroup(const wxChar
*szName
) const
1195 hi
= m_aSubgroups
.Count();
1197 ConfigGroup
*pGroup
;
1201 pGroup
= m_aSubgroups
[i
];
1203 #if wxCONFIG_CASE_SENSITIVE
1204 res
= wxStrcmp(pGroup
->Name(), szName
);
1206 res
= wxStricmp(pGroup
->Name(), szName
);
1220 // ----------------------------------------------------------------------------
1221 // create a new item
1222 // ----------------------------------------------------------------------------
1224 // create a new entry and add it to the current group
1226 ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
1228 wxASSERT( FindEntry(strName
) == NULL
);
1230 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
1231 m_aEntries
.Add(pEntry
);
1236 // create a new group and add it to the current group
1238 ConfigGroup::AddSubgroup(const wxString
& strName
)
1240 wxASSERT( FindSubgroup(strName
) == NULL
);
1242 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
1243 m_aSubgroups
.Add(pGroup
);
1248 // ----------------------------------------------------------------------------
1250 // ----------------------------------------------------------------------------
1253 The delete operations are _very_ slow if we delete the last item of this
1254 group (see comments before GetXXXLineXXX functions for more details),
1255 so it's much better to start with the first entry/group if we want to
1256 delete several of them.
1259 bool ConfigGroup::DeleteSubgroupByName(const wxChar
*szName
)
1261 return DeleteSubgroup(FindSubgroup(szName
));
1264 // doesn't delete the subgroup itself, but does remove references to it from
1265 // all other data structures (and normally the returned pointer should be
1266 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1267 bool ConfigGroup::DeleteSubgroup(ConfigGroup
*pGroup
)
1269 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1271 // delete all entries
1272 size_t nCount
= pGroup
->m_aEntries
.Count();
1273 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1274 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1275 if ( pLine
!= NULL
)
1276 m_pConfig
->LineListRemove(pLine
);
1279 // and subgroups of this sungroup
1280 nCount
= pGroup
->m_aSubgroups
.Count();
1281 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1282 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[0]);
1285 LineList
*pLine
= pGroup
->m_pLine
;
1286 if ( pLine
!= NULL
) {
1287 // notice that we may do this test inside the previous "if" because the
1288 // last entry's line is surely !NULL
1289 if ( pGroup
== m_pLastGroup
) {
1290 // our last entry is being deleted - find the last one which stays
1291 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1293 // go back until we find a subgroup or reach the group's line
1294 ConfigGroup
*pNewLast
= NULL
;
1295 size_t n
, nSubgroups
= m_aSubgroups
.Count();
1297 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1298 // is it our subgroup?
1299 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1300 // do _not_ call GetGroupLine! we don't want to add it to the local
1301 // file if it's not already there
1302 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1303 pNewLast
= m_aSubgroups
[n
];
1306 if ( pNewLast
!= NULL
) // found?
1310 if ( pl
== m_pLine
) {
1311 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1313 // we've reached the group line without finding any subgroups
1314 m_pLastGroup
= NULL
;
1317 m_pLastGroup
= pNewLast
;
1320 m_pConfig
->LineListRemove(pLine
);
1325 m_aSubgroups
.Remove(pGroup
);
1331 bool ConfigGroup::DeleteEntry(const wxChar
*szName
)
1333 ConfigEntry
*pEntry
= FindEntry(szName
);
1334 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1336 LineList
*pLine
= pEntry
->GetLine();
1337 if ( pLine
!= NULL
) {
1338 // notice that we may do this test inside the previous "if" because the
1339 // last entry's line is surely !NULL
1340 if ( pEntry
== m_pLastEntry
) {
1341 // our last entry is being deleted - find the last one which stays
1342 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1344 // go back until we find another entry or reach the group's line
1345 ConfigEntry
*pNewLast
= NULL
;
1346 size_t n
, nEntries
= m_aEntries
.Count();
1348 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1349 // is it our subgroup?
1350 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1351 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1352 pNewLast
= m_aEntries
[n
];
1355 if ( pNewLast
!= NULL
) // found?
1359 if ( pl
== m_pLine
) {
1360 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1362 // we've reached the group line without finding any subgroups
1363 m_pLastEntry
= NULL
;
1366 m_pLastEntry
= pNewLast
;
1369 m_pConfig
->LineListRemove(pLine
);
1372 // we must be written back for the changes to be saved
1375 m_aEntries
.Remove(pEntry
);
1381 // ----------------------------------------------------------------------------
1383 // ----------------------------------------------------------------------------
1384 void ConfigGroup::SetDirty()
1387 if ( Parent() != NULL
) // propagate upwards
1388 Parent()->SetDirty();
1391 // ============================================================================
1392 // wxFileConfig::ConfigEntry
1393 // ============================================================================
1395 // ----------------------------------------------------------------------------
1397 // ----------------------------------------------------------------------------
1398 ConfigEntry::ConfigEntry(ConfigGroup
*pParent
,
1399 const wxString
& strName
,
1401 : m_strName(strName
)
1403 wxASSERT( !strName
.IsEmpty() );
1405 m_pParent
= pParent
;
1411 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1413 m_strName
.erase(0, 1); // remove first character
1416 // ----------------------------------------------------------------------------
1418 // ----------------------------------------------------------------------------
1420 void ConfigEntry::SetLine(LineList
*pLine
)
1422 if ( m_pLine
!= NULL
) {
1423 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1424 Name().c_str(), m_pParent
->GetFullName().c_str());
1428 Group()->SetLastEntry(this);
1431 // second parameter is FALSE if we read the value from file and prevents the
1432 // entry from being marked as 'dirty'
1433 void ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1435 if ( bUser
&& IsImmutable() ) {
1436 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1441 // do nothing if it's the same value
1442 if ( strValue
== m_strValue
)
1445 m_strValue
= strValue
;
1448 wxString strVal
= FilterOutValue(strValue
);
1450 strLine
<< FilterOutEntryName(m_strName
) << wxT('=') << strVal
;
1452 if ( m_pLine
!= NULL
) {
1453 // entry was read from the local config file, just modify the line
1454 m_pLine
->SetText(strLine
);
1457 // add a new line to the file
1458 wxASSERT( m_nLine
== wxNOT_FOUND
); // consistency check
1460 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1461 Group()->GetLastEntryLine());
1462 Group()->SetLastEntry(this);
1469 void ConfigEntry::SetDirty()
1472 Group()->SetDirty();
1475 // ============================================================================
1477 // ============================================================================
1479 // ----------------------------------------------------------------------------
1480 // compare functions for array sorting
1481 // ----------------------------------------------------------------------------
1483 int CompareEntries(ConfigEntry
*p1
,
1486 #if wxCONFIG_CASE_SENSITIVE
1487 return wxStrcmp(p1
->Name(), p2
->Name());
1489 return wxStricmp(p1
->Name(), p2
->Name());
1493 int CompareGroups(ConfigGroup
*p1
,
1496 #if wxCONFIG_CASE_SENSITIVE
1497 return wxStrcmp(p1
->Name(), p2
->Name());
1499 return wxStricmp(p1
->Name(), p2
->Name());
1503 // ----------------------------------------------------------------------------
1505 // ----------------------------------------------------------------------------
1507 // undo FilterOutValue
1508 static wxString
FilterInValue(const wxString
& str
)
1511 strResult
.Alloc(str
.Len());
1513 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1515 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1516 if ( str
[n
] == wxT('\\') ) {
1517 switch ( str
[++n
] ) {
1519 strResult
+= wxT('\n');
1523 strResult
+= wxT('\r');
1527 strResult
+= wxT('\t');
1531 strResult
+= wxT('\\');
1535 strResult
+= wxT('"');
1540 if ( str
[n
] != wxT('"') || !bQuoted
)
1541 strResult
+= str
[n
];
1542 else if ( n
!= str
.Len() - 1 ) {
1543 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1546 //else: it's the last quote of a quoted string, ok
1553 // quote the string before writing it to file
1554 static wxString
FilterOutValue(const wxString
& str
)
1560 strResult
.Alloc(str
.Len());
1562 // quoting is necessary to preserve spaces in the beginning of the string
1563 bool bQuote
= wxIsspace(str
[0]) || str
[0] == wxT('"');
1566 strResult
+= wxT('"');
1569 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
1592 //else: fall through
1595 strResult
+= str
[n
];
1596 continue; // nothing special to do
1599 // we get here only for special characters
1600 strResult
<< wxT('\\') << c
;
1604 strResult
+= wxT('"');
1609 // undo FilterOutEntryName
1610 static wxString
FilterInEntryName(const wxString
& str
)
1613 strResult
.Alloc(str
.Len());
1615 for ( const wxChar
*pc
= str
.c_str(); *pc
!= '\0'; pc
++ ) {
1616 if ( *pc
== wxT('\\') )
1625 // sanitize entry or group name: insert '\\' before any special characters
1626 static wxString
FilterOutEntryName(const wxString
& str
)
1629 strResult
.Alloc(str
.Len());
1631 for ( const wxChar
*pc
= str
.c_str(); *pc
!= wxT('\0'); pc
++ ) {
1634 // we explicitly allow some of "safe" chars and 8bit ASCII characters
1635 // which will probably never have special meaning
1636 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
1637 // should *not* be quoted
1638 if ( !wxIsalnum(c
) && !wxStrchr(wxT("@_/-!.*%"), c
) && ((c
& 0x80) == 0) )
1639 strResult
+= wxT('\\');
1647 // we can't put ?: in the ctor initializer list because it confuses some
1648 // broken compilers (Borland C++)
1649 static wxString
GetAppName(const wxString
& appName
)
1651 if ( !appName
&& wxTheApp
)
1652 return wxTheApp
->GetAppName();
1657 #endif // wxUSE_CONFIG