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__)
206 if ( FindFolder( (short) kOnSystemDisk
, kPreferencesFolderType
, kDontCreateFolder
, &vRefNum
, &dirID
) == noErr
)
209 if ( FSMakeFSSpec( vRefNum
, dirID
, "\p" , &file
) == noErr
)
211 strDir
= wxMacFSSpec2UnixFilename( &file
) + "/" ;
216 wxChar szWinDir
[MAX_PATH
];
217 ::GetWindowsDirectory(szWinDir
, MAX_PATH
);
221 #endif // Unix/Windows
226 wxString
wxFileConfig::GetLocalDir()
231 wxGetHomeDir(&strDir
);
235 if (strDir
.Last() != wxT('/')) strDir
<< wxT('/');
237 if (strDir
.Last() != wxT('\\')) strDir
<< wxT('\\');
241 // no local dir concept on mac
242 return GetGlobalDir() ;
248 wxString
wxFileConfig::GetGlobalFileName(const wxChar
*szFile
)
250 wxString str
= GetGlobalDir();
253 if ( wxStrchr(szFile
, wxT('.')) == NULL
)
256 #elif defined( __WXMAC__ )
257 str
<< " Preferences";
265 wxString
wxFileConfig::GetLocalFileName(const wxChar
*szFile
)
267 #ifdef __VMS__ // On VMS I saw the problem that the home directory was appended
268 // twice for the configuration file. Does that also happen for other
270 wxString str
= wxT( ' ' );
272 wxString str
= GetLocalDir();
282 if ( wxStrchr(szFile
, wxT('.')) == NULL
)
288 str
<< " Preferences";
293 // ----------------------------------------------------------------------------
295 // ----------------------------------------------------------------------------
297 void wxFileConfig::Init()
300 m_pRootGroup
= new ConfigGroup(NULL
, "", this);
305 // it's not an error if (one of the) file(s) doesn't exist
307 // parse the global file
308 if ( !m_strGlobalFile
.IsEmpty() && wxFile::Exists(m_strGlobalFile
) ) {
309 wxTextFile
fileGlobal(m_strGlobalFile
);
311 if ( fileGlobal
.Open() ) {
312 Parse(fileGlobal
, FALSE
/* global */);
316 wxLogWarning(_("can't open global configuration file '%s'."),
317 m_strGlobalFile
.c_str());
320 // parse the local file
321 if ( !m_strLocalFile
.IsEmpty() && wxFile::Exists(m_strLocalFile
) ) {
322 wxTextFile
fileLocal(m_strLocalFile
);
323 if ( fileLocal
.Open() ) {
324 Parse(fileLocal
, TRUE
/* local */);
328 wxLogWarning(_("can't open user configuration file '%s'."),
329 m_strLocalFile
.c_str());
333 // constructor supports creation of wxFileConfig objects of any type
334 wxFileConfig::wxFileConfig(const wxString
& appName
, const wxString
& vendorName
,
335 const wxString
& strLocal
, const wxString
& strGlobal
,
337 : wxConfigBase(::GetAppName(appName
), vendorName
,
340 m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
342 // Make up names for files if empty
343 if ( m_strLocalFile
.IsEmpty() && (style
& wxCONFIG_USE_LOCAL_FILE
) )
345 m_strLocalFile
= GetLocalFileName(GetAppName());
348 if ( m_strGlobalFile
.IsEmpty() && (style
& wxCONFIG_USE_GLOBAL_FILE
) )
350 m_strGlobalFile
= GetGlobalFileName(GetAppName());
353 // Check if styles are not supplied, but filenames are, in which case
354 // add the correct styles.
355 if ( !m_strLocalFile
.IsEmpty() )
356 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE
);
358 if ( !m_strGlobalFile
.IsEmpty() )
359 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE
);
361 // if the path is not absolute, prepend the standard directory to it
362 // UNLESS wxCONFIG_USE_RELATIVE_PATH style is set
363 if ( !(style
& wxCONFIG_USE_RELATIVE_PATH
) )
365 if ( !m_strLocalFile
.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile
) )
367 wxString strLocal
= m_strLocalFile
;
368 m_strLocalFile
= GetLocalDir();
369 m_strLocalFile
<< strLocal
;
372 if ( !m_strGlobalFile
.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile
) )
374 wxString strGlobal
= m_strGlobalFile
;
375 m_strGlobalFile
= GetGlobalDir();
376 m_strGlobalFile
<< strGlobal
;
385 void wxFileConfig::CleanUp()
389 LineList
*pCur
= m_linesHead
;
390 while ( pCur
!= NULL
) {
391 LineList
*pNext
= pCur
->Next();
397 wxFileConfig::~wxFileConfig()
404 // ----------------------------------------------------------------------------
405 // parse a config file
406 // ----------------------------------------------------------------------------
408 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
410 const wxChar
*pStart
;
414 size_t nLineCount
= file
.GetLineCount();
415 for ( size_t n
= 0; n
< nLineCount
; n
++ ) {
418 // add the line to linked list
420 LineListAppend(strLine
);
422 // skip leading spaces
423 for ( pStart
= strLine
; wxIsspace(*pStart
); pStart
++ )
426 // skip blank/comment lines
427 if ( *pStart
== wxT('\0')|| *pStart
== wxT(';') || *pStart
== wxT('#') )
430 if ( *pStart
== wxT('[') ) { // a new group
433 while ( *++pEnd
!= wxT(']') ) {
434 if ( *pEnd
== wxT('\n') || *pEnd
== wxT('\0') )
438 if ( *pEnd
!= wxT(']') ) {
439 wxLogError(_("file '%s': unexpected character %c at line %d."),
440 file
.GetName(), *pEnd
, n
+ 1);
441 continue; // skip this line
444 // group name here is always considered as abs path
447 strGroup
<< wxCONFIG_PATH_SEPARATOR
448 << FilterInEntryName(wxString(pStart
, pEnd
- pStart
));
450 // will create it if doesn't yet exist
454 m_pCurrentGroup
->SetLine(m_linesTail
);
456 // check that there is nothing except comments left on this line
458 while ( *++pEnd
!= wxT('\0') && bCont
) {
467 // ignore whitespace ('\n' impossible here)
471 wxLogWarning(_("file '%s', line %d: '%s' ignored after group header."),
472 file
.GetName(), n
+ 1, pEnd
);
478 const wxChar
*pEnd
= pStart
;
479 while ( *pEnd
&& *pEnd
!= wxT('=') && !wxIsspace(*pEnd
) ) {
480 if ( *pEnd
== wxT('\\') ) {
481 // next character may be space or not - still take it because it's
482 // quoted (unless there is nothing)
485 // the error message will be given below anyhow
493 wxString
strKey(FilterInEntryName(wxString(pStart
, pEnd
)));
496 while ( wxIsspace(*pEnd
) )
499 if ( *pEnd
++ != wxT('=') ) {
500 wxLogError(_("file '%s', line %d: '=' expected."),
501 file
.GetName(), n
+ 1);
504 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
506 if ( pEntry
== NULL
) {
508 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
511 pEntry
->SetLine(m_linesTail
);
514 if ( bLocal
&& pEntry
->IsImmutable() ) {
515 // immutable keys can't be changed by user
516 wxLogWarning(_("file '%s', line %d: value for immutable key '%s' ignored."),
517 file
.GetName(), n
+ 1, strKey
.c_str());
520 // the condition below catches the cases (a) and (b) but not (c):
521 // (a) global key found second time in global file
522 // (b) key found second (or more) time in local file
523 // (c) key from global file now found in local one
524 // which is exactly what we want.
525 else if ( !bLocal
|| pEntry
->IsLocal() ) {
526 wxLogWarning(_("file '%s', line %d: key '%s' was first found at line %d."),
527 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
530 pEntry
->SetLine(m_linesTail
);
535 while ( wxIsspace(*pEnd
) )
538 pEntry
->SetValue(FilterInValue(pEnd
), FALSE
/* read from file */);
544 // ----------------------------------------------------------------------------
546 // ----------------------------------------------------------------------------
548 void wxFileConfig::SetRootPath()
551 m_pCurrentGroup
= m_pRootGroup
;
554 void wxFileConfig::SetPath(const wxString
& strPath
)
556 wxArrayString aParts
;
558 if ( strPath
.IsEmpty() ) {
563 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
565 wxSplitPath(aParts
, strPath
);
568 // relative path, combine with current one
569 wxString strFullPath
= m_strPath
;
570 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
571 wxSplitPath(aParts
, strFullPath
);
574 // change current group
576 m_pCurrentGroup
= m_pRootGroup
;
577 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
578 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
579 if ( pNextGroup
== NULL
)
580 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
581 m_pCurrentGroup
= pNextGroup
;
584 // recombine path parts in one variable
586 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
587 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
591 // ----------------------------------------------------------------------------
593 // ----------------------------------------------------------------------------
595 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
598 return GetNextGroup(str
, lIndex
);
601 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
603 if ( size_t(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
604 str
= m_pCurrentGroup
->Groups()[(size_t)lIndex
++]->Name();
611 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
614 return GetNextEntry(str
, lIndex
);
617 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
619 if ( size_t(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
620 str
= m_pCurrentGroup
->Entries()[(size_t)lIndex
++]->Name();
627 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
629 size_t n
= m_pCurrentGroup
->Entries().Count();
631 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
632 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
633 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
634 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
635 n
+= GetNumberOfEntries(TRUE
);
636 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
643 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
645 size_t n
= m_pCurrentGroup
->Groups().Count();
647 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
648 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
649 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
650 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
651 n
+= GetNumberOfGroups(TRUE
);
652 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
659 // ----------------------------------------------------------------------------
660 // tests for existence
661 // ----------------------------------------------------------------------------
663 bool wxFileConfig::HasGroup(const wxString
& strName
) const
665 wxConfigPathChanger
path(this, strName
);
667 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
668 return pGroup
!= NULL
;
671 bool wxFileConfig::HasEntry(const wxString
& strName
) const
673 wxConfigPathChanger
path(this, strName
);
675 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
676 return pEntry
!= NULL
;
679 // ----------------------------------------------------------------------------
681 // ----------------------------------------------------------------------------
683 bool wxFileConfig::Read(const wxString
& key
,
684 wxString
* pStr
) const
686 wxConfigPathChanger
path(this, key
);
688 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
689 if (pEntry
== NULL
) {
693 *pStr
= ExpandEnvVars(pEntry
->Value());
697 bool wxFileConfig::Read(const wxString
& key
,
698 wxString
* pStr
, const wxString
& defVal
) const
700 wxConfigPathChanger
path(this, key
);
702 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
704 if (pEntry
== NULL
) {
705 if( IsRecordingDefaults() )
706 ((wxFileConfig
*)this)->Write(key
,defVal
);
707 *pStr
= ExpandEnvVars(defVal
);
711 *pStr
= ExpandEnvVars(pEntry
->Value());
718 bool wxFileConfig::Read(const wxString
& key
, long *pl
) const
721 if ( !Read(key
, & str
) )
730 bool wxFileConfig::Write(const wxString
& key
, const wxString
& szValue
)
732 wxConfigPathChanger
path(this, key
);
734 wxString strName
= path
.Name();
735 if ( strName
.IsEmpty() ) {
736 // setting the value of a group is an error
737 wxASSERT_MSG( wxIsEmpty(szValue
), wxT("can't set value of a group!") );
739 // ... except if it's empty in which case it's a way to force it's creation
740 m_pCurrentGroup
->SetDirty();
742 // this will add a line for this group if it didn't have it before
743 (void)m_pCurrentGroup
->GetGroupLine();
748 // check that the name is reasonable
749 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
750 wxLogError(_("Config entry name cannot start with '%c'."),
751 wxCONFIG_IMMUTABLE_PREFIX
);
755 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
756 if ( pEntry
== NULL
)
757 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
759 pEntry
->SetValue(szValue
);
765 bool wxFileConfig::Write(const wxString
& key
, long lValue
)
767 // ltoa() is not ANSI :-(
769 buf
.Printf(wxT("%ld"), lValue
);
770 return Write(key
, buf
);
773 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
775 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() || !m_strLocalFile
)
779 // set the umask if needed
783 umaskOld
= umask((mode_t
)m_umask
);
787 wxTempFile
file(m_strLocalFile
);
789 if ( !file
.IsOpened() ) {
790 wxLogError(_("can't open user configuration file."));
794 // write all strings to file
795 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
796 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
797 wxLogError(_("can't write user configuration file."));
802 bool ret
= file
.Commit();
809 wxUnixFilename2FSSpec( m_strLocalFile
, &spec
) ;
811 if ( FSpGetFInfo( &spec
, &finfo
) == noErr
)
813 finfo
.fdType
= 'TEXT' ;
814 finfo
.fdCreator
= 'ttxt' ;
815 FSpSetFInfo( &spec
, &finfo
) ;
821 // restore the old umask if we changed it
824 (void)umask(umaskOld
);
831 // ----------------------------------------------------------------------------
832 // renaming groups/entries
833 // ----------------------------------------------------------------------------
835 bool wxFileConfig::RenameEntry(const wxString
& oldName
,
836 const wxString
& newName
)
838 // check that the entry exists
839 ConfigEntry
*oldEntry
= m_pCurrentGroup
->FindEntry(oldName
);
843 // check that the new entry doesn't already exist
844 if ( m_pCurrentGroup
->FindEntry(newName
) )
847 // delete the old entry, create the new one
848 wxString value
= oldEntry
->Value();
849 if ( !m_pCurrentGroup
->DeleteEntry(oldName
) )
852 ConfigEntry
*newEntry
= m_pCurrentGroup
->AddEntry(newName
);
853 newEntry
->SetValue(value
);
858 bool wxFileConfig::RenameGroup(const wxString
& oldName
,
859 const wxString
& newName
)
861 // check that the group exists
862 ConfigGroup
*group
= m_pCurrentGroup
->FindSubgroup(oldName
);
866 // check that the new group doesn't already exist
867 if ( m_pCurrentGroup
->FindSubgroup(newName
) )
870 group
->Rename(newName
);
875 // ----------------------------------------------------------------------------
876 // delete groups/entries
877 // ----------------------------------------------------------------------------
879 bool wxFileConfig::DeleteEntry(const wxString
& key
, bool bGroupIfEmptyAlso
)
881 wxConfigPathChanger
path(this, key
);
883 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
886 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
887 if ( m_pCurrentGroup
!= m_pRootGroup
) {
888 ConfigGroup
*pGroup
= m_pCurrentGroup
;
889 SetPath(wxT("..")); // changes m_pCurrentGroup!
890 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
892 //else: never delete the root group
898 bool wxFileConfig::DeleteGroup(const wxString
& key
)
900 wxConfigPathChanger
path(this, key
);
902 return m_pCurrentGroup
->DeleteSubgroupByName(path
.Name());
905 bool wxFileConfig::DeleteAll()
909 if ( wxRemove(m_strLocalFile
) == -1 )
910 wxLogSysError(_("can't delete user configuration file '%s'"), m_strLocalFile
.c_str());
912 m_strLocalFile
= m_strGlobalFile
= wxT("");
918 // ----------------------------------------------------------------------------
919 // linked list functions
920 // ----------------------------------------------------------------------------
922 // append a new line to the end of the list
923 LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
925 LineList
*pLine
= new LineList(str
);
927 if ( m_linesTail
== NULL
) {
933 m_linesTail
->SetNext(pLine
);
934 pLine
->SetPrev(m_linesTail
);
941 // insert a new line after the given one or in the very beginning if !pLine
942 LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
945 if ( pLine
== m_linesTail
)
946 return LineListAppend(str
);
948 LineList
*pNewLine
= new LineList(str
);
949 if ( pLine
== NULL
) {
950 // prepend to the list
951 pNewLine
->SetNext(m_linesHead
);
952 m_linesHead
->SetPrev(pNewLine
);
953 m_linesHead
= pNewLine
;
956 // insert before pLine
957 LineList
*pNext
= pLine
->Next();
958 pNewLine
->SetNext(pNext
);
959 pNewLine
->SetPrev(pLine
);
960 pNext
->SetPrev(pNewLine
);
961 pLine
->SetNext(pNewLine
);
967 void wxFileConfig::LineListRemove(LineList
*pLine
)
969 LineList
*pPrev
= pLine
->Prev(),
970 *pNext
= pLine
->Next();
976 pPrev
->SetNext(pNext
);
982 pNext
->SetPrev(pPrev
);
987 bool wxFileConfig::LineListIsEmpty()
989 return m_linesHead
== NULL
;
992 // ============================================================================
993 // wxFileConfig::ConfigGroup
994 // ============================================================================
996 // ----------------------------------------------------------------------------
998 // ----------------------------------------------------------------------------
1001 ConfigGroup::ConfigGroup(ConfigGroup
*pParent
,
1002 const wxString
& strName
,
1003 wxFileConfig
*pConfig
)
1004 : m_aEntries(CompareEntries
),
1005 m_aSubgroups(CompareGroups
),
1008 m_pConfig
= pConfig
;
1009 m_pParent
= pParent
;
1013 m_pLastEntry
= NULL
;
1014 m_pLastGroup
= NULL
;
1017 // dtor deletes all children
1018 ConfigGroup::~ConfigGroup()
1021 size_t n
, nCount
= m_aEntries
.Count();
1022 for ( n
= 0; n
< nCount
; n
++ )
1023 delete m_aEntries
[n
];
1026 nCount
= m_aSubgroups
.Count();
1027 for ( n
= 0; n
< nCount
; n
++ )
1028 delete m_aSubgroups
[n
];
1031 // ----------------------------------------------------------------------------
1033 // ----------------------------------------------------------------------------
1035 void ConfigGroup::SetLine(LineList
*pLine
)
1037 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
1043 This is a bit complicated, so let me explain it in details. All lines that
1044 were read from the local file (the only one we will ever modify) are stored
1045 in a (doubly) linked list. Our problem is to know at which position in this
1046 list should we insert the new entries/subgroups. To solve it we keep three
1047 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
1049 m_pLine points to the line containing "[group_name]"
1050 m_pLastEntry points to the last entry of this group in the local file.
1051 m_pLastGroup subgroup
1053 Initially, they're NULL all three. When the group (an entry/subgroup) is read
1054 from the local file, the corresponding variable is set. However, if the group
1055 was read from the global file and then modified or created by the application
1056 these variables are still NULL and we need to create the corresponding lines.
1057 See the following functions (and comments preceding them) for the details of
1060 Also, when our last entry/group are deleted we need to find the new last
1061 element - the code in DeleteEntry/Subgroup does this by backtracking the list
1062 of lines until it either founds an entry/subgroup (and this is the new last
1063 element) or the m_pLine of the group, in which case there are no more entries
1064 (or subgroups) left and m_pLast<element> becomes NULL.
1066 NB: This last problem could be avoided for entries if we added new entries
1067 immediately after m_pLine, but in this case the entries would appear
1068 backwards in the config file (OTOH, it's not that important) and as we
1069 would still need to do it for the subgroups the code wouldn't have been
1070 significantly less complicated.
1073 // Return the line which contains "[our name]". If we're still not in the list,
1074 // add our line to it immediately after the last line of our parent group if we
1075 // have it or in the very beginning if we're the root group.
1076 LineList
*ConfigGroup::GetGroupLine()
1078 if ( m_pLine
== NULL
) {
1079 ConfigGroup
*pParent
= Parent();
1081 // this group wasn't present in local config file, add it now
1082 if ( pParent
!= NULL
) {
1083 wxString strFullName
;
1084 strFullName
<< wxT("[")
1086 << FilterOutEntryName(GetFullName().c_str() + 1)
1088 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
1089 pParent
->GetLastGroupLine());
1090 pParent
->SetLastGroup(this); // we're surely after all the others
1093 // we return NULL, so that LineListInsert() will insert us in the
1101 // Return the last line belonging to the subgroups of this group (after which
1102 // we can add a new subgroup), if we don't have any subgroups or entries our
1103 // last line is the group line (m_pLine) itself.
1104 LineList
*ConfigGroup::GetLastGroupLine()
1106 // if we have any subgroups, our last line is the last line of the last
1108 if ( m_pLastGroup
!= NULL
) {
1109 LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
1111 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
1115 // no subgroups, so the last line is the line of thelast entry (if any)
1116 return GetLastEntryLine();
1119 // return the last line belonging to the entries of this group (after which
1120 // we can add a new entry), if we don't have any entries we will add the new
1121 // one immediately after the group line itself.
1122 LineList
*ConfigGroup::GetLastEntryLine()
1124 if ( m_pLastEntry
!= NULL
) {
1125 LineList
*pLine
= m_pLastEntry
->GetLine();
1127 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
1131 // no entries: insert after the group header
1132 return GetGroupLine();
1135 // ----------------------------------------------------------------------------
1137 // ----------------------------------------------------------------------------
1139 void ConfigGroup::Rename(const wxString
& newName
)
1141 m_strName
= newName
;
1143 LineList
*line
= GetGroupLine();
1144 wxString strFullName
;
1145 strFullName
<< wxT("[") << (GetFullName().c_str() + 1) << wxT("]"); // +1: no '/'
1146 line
->SetText(strFullName
);
1151 wxString
ConfigGroup::GetFullName() const
1154 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
1159 // ----------------------------------------------------------------------------
1161 // ----------------------------------------------------------------------------
1163 // use binary search because the array is sorted
1165 ConfigGroup::FindEntry(const wxChar
*szName
) const
1169 hi
= m_aEntries
.Count();
1171 ConfigEntry
*pEntry
;
1175 pEntry
= m_aEntries
[i
];
1177 #if wxCONFIG_CASE_SENSITIVE
1178 res
= wxStrcmp(pEntry
->Name(), szName
);
1180 res
= wxStricmp(pEntry
->Name(), szName
);
1195 ConfigGroup::FindSubgroup(const wxChar
*szName
) const
1199 hi
= m_aSubgroups
.Count();
1201 ConfigGroup
*pGroup
;
1205 pGroup
= m_aSubgroups
[i
];
1207 #if wxCONFIG_CASE_SENSITIVE
1208 res
= wxStrcmp(pGroup
->Name(), szName
);
1210 res
= wxStricmp(pGroup
->Name(), szName
);
1224 // ----------------------------------------------------------------------------
1225 // create a new item
1226 // ----------------------------------------------------------------------------
1228 // create a new entry and add it to the current group
1230 ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
1232 wxASSERT( FindEntry(strName
) == NULL
);
1234 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
1235 m_aEntries
.Add(pEntry
);
1240 // create a new group and add it to the current group
1242 ConfigGroup::AddSubgroup(const wxString
& strName
)
1244 wxASSERT( FindSubgroup(strName
) == NULL
);
1246 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
1247 m_aSubgroups
.Add(pGroup
);
1252 // ----------------------------------------------------------------------------
1254 // ----------------------------------------------------------------------------
1257 The delete operations are _very_ slow if we delete the last item of this
1258 group (see comments before GetXXXLineXXX functions for more details),
1259 so it's much better to start with the first entry/group if we want to
1260 delete several of them.
1263 bool ConfigGroup::DeleteSubgroupByName(const wxChar
*szName
)
1265 return DeleteSubgroup(FindSubgroup(szName
));
1268 // doesn't delete the subgroup itself, but does remove references to it from
1269 // all other data structures (and normally the returned pointer should be
1270 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1271 bool ConfigGroup::DeleteSubgroup(ConfigGroup
*pGroup
)
1273 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1275 // delete all entries
1276 size_t nCount
= pGroup
->m_aEntries
.Count();
1277 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1278 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1279 if ( pLine
!= NULL
)
1280 m_pConfig
->LineListRemove(pLine
);
1283 // and subgroups of this sungroup
1284 nCount
= pGroup
->m_aSubgroups
.Count();
1285 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1286 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[0]);
1289 LineList
*pLine
= pGroup
->m_pLine
;
1290 if ( pLine
!= NULL
) {
1291 // notice that we may do this test inside the previous "if" because the
1292 // last entry's line is surely !NULL
1293 if ( pGroup
== m_pLastGroup
) {
1294 // our last entry is being deleted - find the last one which stays
1295 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1297 // go back until we find a subgroup or reach the group's line
1298 ConfigGroup
*pNewLast
= NULL
;
1299 size_t n
, nSubgroups
= m_aSubgroups
.Count();
1301 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1302 // is it our subgroup?
1303 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1304 // do _not_ call GetGroupLine! we don't want to add it to the local
1305 // file if it's not already there
1306 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1307 pNewLast
= m_aSubgroups
[n
];
1310 if ( pNewLast
!= NULL
) // found?
1314 if ( pl
== m_pLine
) {
1315 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1317 // we've reached the group line without finding any subgroups
1318 m_pLastGroup
= NULL
;
1321 m_pLastGroup
= pNewLast
;
1324 m_pConfig
->LineListRemove(pLine
);
1329 m_aSubgroups
.Remove(pGroup
);
1335 bool ConfigGroup::DeleteEntry(const wxChar
*szName
)
1337 ConfigEntry
*pEntry
= FindEntry(szName
);
1338 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1340 LineList
*pLine
= pEntry
->GetLine();
1341 if ( pLine
!= NULL
) {
1342 // notice that we may do this test inside the previous "if" because the
1343 // last entry's line is surely !NULL
1344 if ( pEntry
== m_pLastEntry
) {
1345 // our last entry is being deleted - find the last one which stays
1346 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1348 // go back until we find another entry or reach the group's line
1349 ConfigEntry
*pNewLast
= NULL
;
1350 size_t n
, nEntries
= m_aEntries
.Count();
1352 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1353 // is it our subgroup?
1354 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1355 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1356 pNewLast
= m_aEntries
[n
];
1359 if ( pNewLast
!= NULL
) // found?
1363 if ( pl
== m_pLine
) {
1364 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1366 // we've reached the group line without finding any subgroups
1367 m_pLastEntry
= NULL
;
1370 m_pLastEntry
= pNewLast
;
1373 m_pConfig
->LineListRemove(pLine
);
1376 // we must be written back for the changes to be saved
1379 m_aEntries
.Remove(pEntry
);
1385 // ----------------------------------------------------------------------------
1387 // ----------------------------------------------------------------------------
1388 void ConfigGroup::SetDirty()
1391 if ( Parent() != NULL
) // propagate upwards
1392 Parent()->SetDirty();
1395 // ============================================================================
1396 // wxFileConfig::ConfigEntry
1397 // ============================================================================
1399 // ----------------------------------------------------------------------------
1401 // ----------------------------------------------------------------------------
1402 ConfigEntry::ConfigEntry(ConfigGroup
*pParent
,
1403 const wxString
& strName
,
1405 : m_strName(strName
)
1407 wxASSERT( !strName
.IsEmpty() );
1409 m_pParent
= pParent
;
1415 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1417 m_strName
.erase(0, 1); // remove first character
1420 // ----------------------------------------------------------------------------
1422 // ----------------------------------------------------------------------------
1424 void ConfigEntry::SetLine(LineList
*pLine
)
1426 if ( m_pLine
!= NULL
) {
1427 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1428 Name().c_str(), m_pParent
->GetFullName().c_str());
1432 Group()->SetLastEntry(this);
1435 // second parameter is FALSE if we read the value from file and prevents the
1436 // entry from being marked as 'dirty'
1437 void ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1439 if ( bUser
&& IsImmutable() ) {
1440 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1445 // do nothing if it's the same value
1446 if ( strValue
== m_strValue
)
1449 m_strValue
= strValue
;
1452 wxString strVal
= FilterOutValue(strValue
);
1454 strLine
<< FilterOutEntryName(m_strName
) << wxT('=') << strVal
;
1456 if ( m_pLine
!= NULL
) {
1457 // entry was read from the local config file, just modify the line
1458 m_pLine
->SetText(strLine
);
1461 // add a new line to the file
1462 wxASSERT( m_nLine
== wxNOT_FOUND
); // consistency check
1464 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1465 Group()->GetLastEntryLine());
1466 Group()->SetLastEntry(this);
1473 void ConfigEntry::SetDirty()
1476 Group()->SetDirty();
1479 // ============================================================================
1481 // ============================================================================
1483 // ----------------------------------------------------------------------------
1484 // compare functions for array sorting
1485 // ----------------------------------------------------------------------------
1487 int CompareEntries(ConfigEntry
*p1
,
1490 #if wxCONFIG_CASE_SENSITIVE
1491 return wxStrcmp(p1
->Name(), p2
->Name());
1493 return wxStricmp(p1
->Name(), p2
->Name());
1497 int CompareGroups(ConfigGroup
*p1
,
1500 #if wxCONFIG_CASE_SENSITIVE
1501 return wxStrcmp(p1
->Name(), p2
->Name());
1503 return wxStricmp(p1
->Name(), p2
->Name());
1507 // ----------------------------------------------------------------------------
1509 // ----------------------------------------------------------------------------
1511 // undo FilterOutValue
1512 static wxString
FilterInValue(const wxString
& str
)
1515 strResult
.Alloc(str
.Len());
1517 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1519 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1520 if ( str
[n
] == wxT('\\') ) {
1521 switch ( str
[++n
] ) {
1523 strResult
+= wxT('\n');
1527 strResult
+= wxT('\r');
1531 strResult
+= wxT('\t');
1535 strResult
+= wxT('\\');
1539 strResult
+= wxT('"');
1544 if ( str
[n
] != wxT('"') || !bQuoted
)
1545 strResult
+= str
[n
];
1546 else if ( n
!= str
.Len() - 1 ) {
1547 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1550 //else: it's the last quote of a quoted string, ok
1557 // quote the string before writing it to file
1558 static wxString
FilterOutValue(const wxString
& str
)
1564 strResult
.Alloc(str
.Len());
1566 // quoting is necessary to preserve spaces in the beginning of the string
1567 bool bQuote
= wxIsspace(str
[0]) || str
[0] == wxT('"');
1570 strResult
+= wxT('"');
1573 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
1596 //else: fall through
1599 strResult
+= str
[n
];
1600 continue; // nothing special to do
1603 // we get here only for special characters
1604 strResult
<< wxT('\\') << c
;
1608 strResult
+= wxT('"');
1613 // undo FilterOutEntryName
1614 static wxString
FilterInEntryName(const wxString
& str
)
1617 strResult
.Alloc(str
.Len());
1619 for ( const wxChar
*pc
= str
.c_str(); *pc
!= '\0'; pc
++ ) {
1620 if ( *pc
== wxT('\\') )
1629 // sanitize entry or group name: insert '\\' before any special characters
1630 static wxString
FilterOutEntryName(const wxString
& str
)
1633 strResult
.Alloc(str
.Len());
1635 for ( const wxChar
*pc
= str
.c_str(); *pc
!= wxT('\0'); pc
++ ) {
1638 // we explicitly allow some of "safe" chars and 8bit ASCII characters
1639 // which will probably never have special meaning
1640 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
1641 // should *not* be quoted
1642 if ( !wxIsalnum(c
) && !wxStrchr(wxT("@_/-!.*%"), c
) && ((c
& 0x80) == 0) )
1643 strResult
+= wxT('\\');
1651 // we can't put ?: in the ctor initializer list because it confuses some
1652 // broken compilers (Borland C++)
1653 static wxString
GetAppName(const wxString
& appName
)
1655 if ( !appName
&& wxTheApp
)
1656 return wxTheApp
->GetAppName();
1661 #endif // wxUSE_CONFIG