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(']'))
237 if (strDir
.Last() != wxT('/')) strDir
<< wxT('/');
239 if (strDir
.Last() != wxT('\\')) strDir
<< wxT('\\');
242 // no local dir concept on mac
243 return GetGlobalDir() ;
249 wxString
wxFileConfig::GetGlobalFileName(const wxChar
*szFile
)
251 wxString str
= GetGlobalDir();
254 if ( wxStrchr(szFile
, wxT('.')) == NULL
)
257 #elif defined( __WXMAC__ )
258 str
<< " Preferences";
266 wxString
wxFileConfig::GetLocalFileName(const wxChar
*szFile
)
268 #ifdef __VMS__ // On VMS I saw the problem that the home directory was appended
269 // twice for the configuration file. Does that also happen for other
271 wxString str
= wxT( '.' );
273 wxString str
= GetLocalDir();
276 #if defined( __UNIX__ ) && !defined( __VMS )
283 if ( wxStrchr(szFile
, wxT('.')) == NULL
)
289 str
<< " Preferences";
294 // ----------------------------------------------------------------------------
296 // ----------------------------------------------------------------------------
298 void wxFileConfig::Init()
301 m_pRootGroup
= new ConfigGroup(NULL
, "", this);
306 // it's not an error if (one of the) file(s) doesn't exist
308 // parse the global file
309 if ( !m_strGlobalFile
.IsEmpty() && wxFile::Exists(m_strGlobalFile
) ) {
310 wxTextFile
fileGlobal(m_strGlobalFile
);
312 if ( fileGlobal
.Open() ) {
313 Parse(fileGlobal
, FALSE
/* global */);
317 wxLogWarning(_("can't open global configuration file '%s'."),
318 m_strGlobalFile
.c_str());
321 // parse the local file
322 if ( !m_strLocalFile
.IsEmpty() && wxFile::Exists(m_strLocalFile
) ) {
323 wxTextFile
fileLocal(m_strLocalFile
);
324 if ( fileLocal
.Open() ) {
325 Parse(fileLocal
, TRUE
/* local */);
329 wxLogWarning(_("can't open user configuration file '%s'."),
330 m_strLocalFile
.c_str());
334 // constructor supports creation of wxFileConfig objects of any type
335 wxFileConfig::wxFileConfig(const wxString
& appName
, const wxString
& vendorName
,
336 const wxString
& strLocal
, const wxString
& strGlobal
,
338 : wxConfigBase(::GetAppName(appName
), vendorName
,
341 m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
343 // Make up names for files if empty
344 if ( m_strLocalFile
.IsEmpty() && (style
& wxCONFIG_USE_LOCAL_FILE
) )
346 m_strLocalFile
= GetLocalFileName(GetAppName());
349 if ( m_strGlobalFile
.IsEmpty() && (style
& wxCONFIG_USE_GLOBAL_FILE
) )
351 m_strGlobalFile
= GetGlobalFileName(GetAppName());
354 // Check if styles are not supplied, but filenames are, in which case
355 // add the correct styles.
356 if ( !m_strLocalFile
.IsEmpty() )
357 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE
);
359 if ( !m_strGlobalFile
.IsEmpty() )
360 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE
);
362 // if the path is not absolute, prepend the standard directory to it
363 // UNLESS wxCONFIG_USE_RELATIVE_PATH style is set
364 if ( !(style
& wxCONFIG_USE_RELATIVE_PATH
) )
366 if ( !m_strLocalFile
.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile
) )
368 wxString strLocal
= m_strLocalFile
;
369 m_strLocalFile
= GetLocalDir();
370 m_strLocalFile
<< strLocal
;
373 if ( !m_strGlobalFile
.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile
) )
375 wxString strGlobal
= m_strGlobalFile
;
376 m_strGlobalFile
= GetGlobalDir();
377 m_strGlobalFile
<< strGlobal
;
386 void wxFileConfig::CleanUp()
390 LineList
*pCur
= m_linesHead
;
391 while ( pCur
!= NULL
) {
392 LineList
*pNext
= pCur
->Next();
398 wxFileConfig::~wxFileConfig()
405 // ----------------------------------------------------------------------------
406 // parse a config file
407 // ----------------------------------------------------------------------------
409 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
411 const wxChar
*pStart
;
415 size_t nLineCount
= file
.GetLineCount();
416 for ( size_t n
= 0; n
< nLineCount
; n
++ ) {
419 // add the line to linked list
421 LineListAppend(strLine
);
423 // skip leading spaces
424 for ( pStart
= strLine
; wxIsspace(*pStart
); pStart
++ )
427 // skip blank/comment lines
428 if ( *pStart
== wxT('\0')|| *pStart
== wxT(';') || *pStart
== wxT('#') )
431 if ( *pStart
== wxT('[') ) { // a new group
434 while ( *++pEnd
!= wxT(']') ) {
435 if ( *pEnd
== wxT('\\') ) {
436 // the next char is escaped, so skip it even if it is ']'
440 if ( *pEnd
== wxT('\n') || *pEnd
== wxT('\0') ) {
441 // we reached the end of line, break out of the loop
446 if ( *pEnd
!= wxT(']') ) {
447 wxLogError(_("file '%s': unexpected character %c at line %d."),
448 file
.GetName(), *pEnd
, n
+ 1);
449 continue; // skip this line
452 // group name here is always considered as abs path
455 strGroup
<< wxCONFIG_PATH_SEPARATOR
456 << FilterInEntryName(wxString(pStart
, pEnd
- pStart
));
458 // will create it if doesn't yet exist
462 m_pCurrentGroup
->SetLine(m_linesTail
);
464 // check that there is nothing except comments left on this line
466 while ( *++pEnd
!= wxT('\0') && bCont
) {
475 // ignore whitespace ('\n' impossible here)
479 wxLogWarning(_("file '%s', line %d: '%s' ignored after group header."),
480 file
.GetName(), n
+ 1, pEnd
);
486 const wxChar
*pEnd
= pStart
;
487 while ( *pEnd
&& *pEnd
!= wxT('=') && !wxIsspace(*pEnd
) ) {
488 if ( *pEnd
== wxT('\\') ) {
489 // next character may be space or not - still take it because it's
490 // quoted (unless there is nothing)
493 // the error message will be given below anyhow
501 wxString
strKey(FilterInEntryName(wxString(pStart
, pEnd
)));
504 while ( wxIsspace(*pEnd
) )
507 if ( *pEnd
++ != wxT('=') ) {
508 wxLogError(_("file '%s', line %d: '=' expected."),
509 file
.GetName(), n
+ 1);
512 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
514 if ( pEntry
== NULL
) {
516 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
519 pEntry
->SetLine(m_linesTail
);
522 if ( bLocal
&& pEntry
->IsImmutable() ) {
523 // immutable keys can't be changed by user
524 wxLogWarning(_("file '%s', line %d: value for immutable key '%s' ignored."),
525 file
.GetName(), n
+ 1, strKey
.c_str());
528 // the condition below catches the cases (a) and (b) but not (c):
529 // (a) global key found second time in global file
530 // (b) key found second (or more) time in local file
531 // (c) key from global file now found in local one
532 // which is exactly what we want.
533 else if ( !bLocal
|| pEntry
->IsLocal() ) {
534 wxLogWarning(_("file '%s', line %d: key '%s' was first found at line %d."),
535 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
538 pEntry
->SetLine(m_linesTail
);
543 while ( wxIsspace(*pEnd
) )
546 pEntry
->SetValue(FilterInValue(pEnd
), FALSE
/* read from file */);
552 // ----------------------------------------------------------------------------
554 // ----------------------------------------------------------------------------
556 void wxFileConfig::SetRootPath()
559 m_pCurrentGroup
= m_pRootGroup
;
562 void wxFileConfig::SetPath(const wxString
& strPath
)
564 wxArrayString aParts
;
566 if ( strPath
.IsEmpty() ) {
571 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
573 wxSplitPath(aParts
, strPath
);
576 // relative path, combine with current one
577 wxString strFullPath
= m_strPath
;
578 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
579 wxSplitPath(aParts
, strFullPath
);
582 // change current group
584 m_pCurrentGroup
= m_pRootGroup
;
585 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
586 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
587 if ( pNextGroup
== NULL
)
588 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
589 m_pCurrentGroup
= pNextGroup
;
592 // recombine path parts in one variable
594 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
595 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
599 // ----------------------------------------------------------------------------
601 // ----------------------------------------------------------------------------
603 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
606 return GetNextGroup(str
, lIndex
);
609 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
611 if ( size_t(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
612 str
= m_pCurrentGroup
->Groups()[(size_t)lIndex
++]->Name();
619 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
622 return GetNextEntry(str
, lIndex
);
625 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
627 if ( size_t(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
628 str
= m_pCurrentGroup
->Entries()[(size_t)lIndex
++]->Name();
635 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
637 size_t n
= m_pCurrentGroup
->Entries().Count();
639 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
640 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
641 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
642 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
643 n
+= GetNumberOfEntries(TRUE
);
644 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
651 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
653 size_t n
= m_pCurrentGroup
->Groups().Count();
655 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
656 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
657 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
658 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
659 n
+= GetNumberOfGroups(TRUE
);
660 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
667 // ----------------------------------------------------------------------------
668 // tests for existence
669 // ----------------------------------------------------------------------------
671 bool wxFileConfig::HasGroup(const wxString
& strName
) const
673 wxConfigPathChanger
path(this, strName
);
675 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
676 return pGroup
!= NULL
;
679 bool wxFileConfig::HasEntry(const wxString
& strName
) const
681 wxConfigPathChanger
path(this, strName
);
683 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
684 return pEntry
!= NULL
;
687 // ----------------------------------------------------------------------------
689 // ----------------------------------------------------------------------------
691 bool wxFileConfig::Read(const wxString
& key
,
692 wxString
* pStr
) const
694 wxConfigPathChanger
path(this, key
);
696 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
697 if (pEntry
== NULL
) {
701 *pStr
= ExpandEnvVars(pEntry
->Value());
705 bool wxFileConfig::Read(const wxString
& key
,
706 wxString
* pStr
, const wxString
& defVal
) const
708 wxConfigPathChanger
path(this, key
);
710 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
712 if (pEntry
== NULL
) {
713 if( IsRecordingDefaults() )
714 ((wxFileConfig
*)this)->Write(key
,defVal
);
715 *pStr
= ExpandEnvVars(defVal
);
719 *pStr
= ExpandEnvVars(pEntry
->Value());
726 bool wxFileConfig::Read(const wxString
& key
, long *pl
) const
729 if ( !Read(key
, & str
) )
738 bool wxFileConfig::Write(const wxString
& key
, const wxString
& szValue
)
740 wxConfigPathChanger
path(this, key
);
742 wxString strName
= path
.Name();
743 if ( strName
.IsEmpty() ) {
744 // setting the value of a group is an error
745 wxASSERT_MSG( wxIsEmpty(szValue
), wxT("can't set value of a group!") );
747 // ... except if it's empty in which case it's a way to force it's creation
748 m_pCurrentGroup
->SetDirty();
750 // this will add a line for this group if it didn't have it before
751 (void)m_pCurrentGroup
->GetGroupLine();
756 // check that the name is reasonable
757 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
758 wxLogError(_("Config entry name cannot start with '%c'."),
759 wxCONFIG_IMMUTABLE_PREFIX
);
763 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
764 if ( pEntry
== NULL
)
765 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
767 pEntry
->SetValue(szValue
);
773 bool wxFileConfig::Write(const wxString
& key
, long lValue
)
775 // ltoa() is not ANSI :-(
777 buf
.Printf(wxT("%ld"), lValue
);
778 return Write(key
, buf
);
781 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
783 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() || !m_strLocalFile
)
787 // set the umask if needed
791 umaskOld
= umask((mode_t
)m_umask
);
795 wxTempFile
file(m_strLocalFile
);
797 if ( !file
.IsOpened() ) {
798 wxLogError(_("can't open user configuration file."));
802 // write all strings to file
803 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
804 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
805 wxLogError(_("can't write user configuration file."));
810 bool ret
= file
.Commit();
817 wxUnixFilename2FSSpec( m_strLocalFile
, &spec
) ;
819 if ( FSpGetFInfo( &spec
, &finfo
) == noErr
)
821 finfo
.fdType
= 'TEXT' ;
822 finfo
.fdCreator
= 'ttxt' ;
823 FSpSetFInfo( &spec
, &finfo
) ;
829 // restore the old umask if we changed it
832 (void)umask(umaskOld
);
839 // ----------------------------------------------------------------------------
840 // renaming groups/entries
841 // ----------------------------------------------------------------------------
843 bool wxFileConfig::RenameEntry(const wxString
& oldName
,
844 const wxString
& newName
)
846 // check that the entry exists
847 ConfigEntry
*oldEntry
= m_pCurrentGroup
->FindEntry(oldName
);
851 // check that the new entry doesn't already exist
852 if ( m_pCurrentGroup
->FindEntry(newName
) )
855 // delete the old entry, create the new one
856 wxString value
= oldEntry
->Value();
857 if ( !m_pCurrentGroup
->DeleteEntry(oldName
) )
860 ConfigEntry
*newEntry
= m_pCurrentGroup
->AddEntry(newName
);
861 newEntry
->SetValue(value
);
866 bool wxFileConfig::RenameGroup(const wxString
& oldName
,
867 const wxString
& newName
)
869 // check that the group exists
870 ConfigGroup
*group
= m_pCurrentGroup
->FindSubgroup(oldName
);
874 // check that the new group doesn't already exist
875 if ( m_pCurrentGroup
->FindSubgroup(newName
) )
878 group
->Rename(newName
);
883 // ----------------------------------------------------------------------------
884 // delete groups/entries
885 // ----------------------------------------------------------------------------
887 bool wxFileConfig::DeleteEntry(const wxString
& key
, bool bGroupIfEmptyAlso
)
889 wxConfigPathChanger
path(this, key
);
891 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
894 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
895 if ( m_pCurrentGroup
!= m_pRootGroup
) {
896 ConfigGroup
*pGroup
= m_pCurrentGroup
;
897 SetPath(wxT("..")); // changes m_pCurrentGroup!
898 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
900 //else: never delete the root group
906 bool wxFileConfig::DeleteGroup(const wxString
& key
)
908 wxConfigPathChanger
path(this, key
);
910 return m_pCurrentGroup
->DeleteSubgroupByName(path
.Name());
913 bool wxFileConfig::DeleteAll()
917 if ( wxRemove(m_strLocalFile
) == -1 )
918 wxLogSysError(_("can't delete user configuration file '%s'"), m_strLocalFile
.c_str());
920 m_strLocalFile
= m_strGlobalFile
= wxT("");
926 // ----------------------------------------------------------------------------
927 // linked list functions
928 // ----------------------------------------------------------------------------
930 // append a new line to the end of the list
931 LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
933 LineList
*pLine
= new LineList(str
);
935 if ( m_linesTail
== NULL
) {
941 m_linesTail
->SetNext(pLine
);
942 pLine
->SetPrev(m_linesTail
);
949 // insert a new line after the given one or in the very beginning if !pLine
950 LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
953 if ( pLine
== m_linesTail
)
954 return LineListAppend(str
);
956 LineList
*pNewLine
= new LineList(str
);
957 if ( pLine
== NULL
) {
958 // prepend to the list
959 pNewLine
->SetNext(m_linesHead
);
960 m_linesHead
->SetPrev(pNewLine
);
961 m_linesHead
= pNewLine
;
964 // insert before pLine
965 LineList
*pNext
= pLine
->Next();
966 pNewLine
->SetNext(pNext
);
967 pNewLine
->SetPrev(pLine
);
968 pNext
->SetPrev(pNewLine
);
969 pLine
->SetNext(pNewLine
);
975 void wxFileConfig::LineListRemove(LineList
*pLine
)
977 LineList
*pPrev
= pLine
->Prev(),
978 *pNext
= pLine
->Next();
984 pPrev
->SetNext(pNext
);
990 pNext
->SetPrev(pPrev
);
995 bool wxFileConfig::LineListIsEmpty()
997 return m_linesHead
== NULL
;
1000 // ============================================================================
1001 // wxFileConfig::ConfigGroup
1002 // ============================================================================
1004 // ----------------------------------------------------------------------------
1006 // ----------------------------------------------------------------------------
1009 ConfigGroup::ConfigGroup(ConfigGroup
*pParent
,
1010 const wxString
& strName
,
1011 wxFileConfig
*pConfig
)
1012 : m_aEntries(CompareEntries
),
1013 m_aSubgroups(CompareGroups
),
1016 m_pConfig
= pConfig
;
1017 m_pParent
= pParent
;
1021 m_pLastEntry
= NULL
;
1022 m_pLastGroup
= NULL
;
1025 // dtor deletes all children
1026 ConfigGroup::~ConfigGroup()
1029 size_t n
, nCount
= m_aEntries
.Count();
1030 for ( n
= 0; n
< nCount
; n
++ )
1031 delete m_aEntries
[n
];
1034 nCount
= m_aSubgroups
.Count();
1035 for ( n
= 0; n
< nCount
; n
++ )
1036 delete m_aSubgroups
[n
];
1039 // ----------------------------------------------------------------------------
1041 // ----------------------------------------------------------------------------
1043 void ConfigGroup::SetLine(LineList
*pLine
)
1045 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
1051 This is a bit complicated, so let me explain it in details. All lines that
1052 were read from the local file (the only one we will ever modify) are stored
1053 in a (doubly) linked list. Our problem is to know at which position in this
1054 list should we insert the new entries/subgroups. To solve it we keep three
1055 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
1057 m_pLine points to the line containing "[group_name]"
1058 m_pLastEntry points to the last entry of this group in the local file.
1059 m_pLastGroup subgroup
1061 Initially, they're NULL all three. When the group (an entry/subgroup) is read
1062 from the local file, the corresponding variable is set. However, if the group
1063 was read from the global file and then modified or created by the application
1064 these variables are still NULL and we need to create the corresponding lines.
1065 See the following functions (and comments preceding them) for the details of
1068 Also, when our last entry/group are deleted we need to find the new last
1069 element - the code in DeleteEntry/Subgroup does this by backtracking the list
1070 of lines until it either founds an entry/subgroup (and this is the new last
1071 element) or the m_pLine of the group, in which case there are no more entries
1072 (or subgroups) left and m_pLast<element> becomes NULL.
1074 NB: This last problem could be avoided for entries if we added new entries
1075 immediately after m_pLine, but in this case the entries would appear
1076 backwards in the config file (OTOH, it's not that important) and as we
1077 would still need to do it for the subgroups the code wouldn't have been
1078 significantly less complicated.
1081 // Return the line which contains "[our name]". If we're still not in the list,
1082 // add our line to it immediately after the last line of our parent group if we
1083 // have it or in the very beginning if we're the root group.
1084 LineList
*ConfigGroup::GetGroupLine()
1086 if ( m_pLine
== NULL
) {
1087 ConfigGroup
*pParent
= Parent();
1089 // this group wasn't present in local config file, add it now
1090 if ( pParent
!= NULL
) {
1091 wxString strFullName
;
1092 strFullName
<< wxT("[")
1094 << FilterOutEntryName(GetFullName().c_str() + 1)
1096 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
1097 pParent
->GetLastGroupLine());
1098 pParent
->SetLastGroup(this); // we're surely after all the others
1101 // we return NULL, so that LineListInsert() will insert us in the
1109 // Return the last line belonging to the subgroups of this group (after which
1110 // we can add a new subgroup), if we don't have any subgroups or entries our
1111 // last line is the group line (m_pLine) itself.
1112 LineList
*ConfigGroup::GetLastGroupLine()
1114 // if we have any subgroups, our last line is the last line of the last
1116 if ( m_pLastGroup
!= NULL
) {
1117 LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
1119 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
1123 // no subgroups, so the last line is the line of thelast entry (if any)
1124 return GetLastEntryLine();
1127 // return the last line belonging to the entries of this group (after which
1128 // we can add a new entry), if we don't have any entries we will add the new
1129 // one immediately after the group line itself.
1130 LineList
*ConfigGroup::GetLastEntryLine()
1132 if ( m_pLastEntry
!= NULL
) {
1133 LineList
*pLine
= m_pLastEntry
->GetLine();
1135 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
1139 // no entries: insert after the group header
1140 return GetGroupLine();
1143 // ----------------------------------------------------------------------------
1145 // ----------------------------------------------------------------------------
1147 void ConfigGroup::Rename(const wxString
& newName
)
1149 m_strName
= newName
;
1151 LineList
*line
= GetGroupLine();
1152 wxString strFullName
;
1153 strFullName
<< wxT("[") << (GetFullName().c_str() + 1) << wxT("]"); // +1: no '/'
1154 line
->SetText(strFullName
);
1159 wxString
ConfigGroup::GetFullName() const
1162 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
1167 // ----------------------------------------------------------------------------
1169 // ----------------------------------------------------------------------------
1171 // use binary search because the array is sorted
1173 ConfigGroup::FindEntry(const wxChar
*szName
) const
1177 hi
= m_aEntries
.Count();
1179 ConfigEntry
*pEntry
;
1183 pEntry
= m_aEntries
[i
];
1185 #if wxCONFIG_CASE_SENSITIVE
1186 res
= wxStrcmp(pEntry
->Name(), szName
);
1188 res
= wxStricmp(pEntry
->Name(), szName
);
1203 ConfigGroup::FindSubgroup(const wxChar
*szName
) const
1207 hi
= m_aSubgroups
.Count();
1209 ConfigGroup
*pGroup
;
1213 pGroup
= m_aSubgroups
[i
];
1215 #if wxCONFIG_CASE_SENSITIVE
1216 res
= wxStrcmp(pGroup
->Name(), szName
);
1218 res
= wxStricmp(pGroup
->Name(), szName
);
1232 // ----------------------------------------------------------------------------
1233 // create a new item
1234 // ----------------------------------------------------------------------------
1236 // create a new entry and add it to the current group
1238 ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
1240 wxASSERT( FindEntry(strName
) == NULL
);
1242 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
1243 m_aEntries
.Add(pEntry
);
1248 // create a new group and add it to the current group
1250 ConfigGroup::AddSubgroup(const wxString
& strName
)
1252 wxASSERT( FindSubgroup(strName
) == NULL
);
1254 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
1255 m_aSubgroups
.Add(pGroup
);
1260 // ----------------------------------------------------------------------------
1262 // ----------------------------------------------------------------------------
1265 The delete operations are _very_ slow if we delete the last item of this
1266 group (see comments before GetXXXLineXXX functions for more details),
1267 so it's much better to start with the first entry/group if we want to
1268 delete several of them.
1271 bool ConfigGroup::DeleteSubgroupByName(const wxChar
*szName
)
1273 return DeleteSubgroup(FindSubgroup(szName
));
1276 // doesn't delete the subgroup itself, but does remove references to it from
1277 // all other data structures (and normally the returned pointer should be
1278 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1279 bool ConfigGroup::DeleteSubgroup(ConfigGroup
*pGroup
)
1281 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1283 // delete all entries
1284 size_t nCount
= pGroup
->m_aEntries
.Count();
1285 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1286 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1287 if ( pLine
!= NULL
)
1288 m_pConfig
->LineListRemove(pLine
);
1291 // and subgroups of this sungroup
1292 nCount
= pGroup
->m_aSubgroups
.Count();
1293 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1294 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[0]);
1297 LineList
*pLine
= pGroup
->m_pLine
;
1298 if ( pLine
!= NULL
) {
1299 // notice that we may do this test inside the previous "if" because the
1300 // last entry's line is surely !NULL
1301 if ( pGroup
== m_pLastGroup
) {
1302 // our last entry is being deleted - find the last one which stays
1303 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1305 // go back until we find a subgroup or reach the group's line
1306 ConfigGroup
*pNewLast
= NULL
;
1307 size_t n
, nSubgroups
= m_aSubgroups
.Count();
1309 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1310 // is it our subgroup?
1311 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1312 // do _not_ call GetGroupLine! we don't want to add it to the local
1313 // file if it's not already there
1314 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1315 pNewLast
= m_aSubgroups
[n
];
1318 if ( pNewLast
!= NULL
) // found?
1322 if ( pl
== m_pLine
) {
1323 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1325 // we've reached the group line without finding any subgroups
1326 m_pLastGroup
= NULL
;
1329 m_pLastGroup
= pNewLast
;
1332 m_pConfig
->LineListRemove(pLine
);
1337 m_aSubgroups
.Remove(pGroup
);
1343 bool ConfigGroup::DeleteEntry(const wxChar
*szName
)
1345 ConfigEntry
*pEntry
= FindEntry(szName
);
1346 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1348 LineList
*pLine
= pEntry
->GetLine();
1349 if ( pLine
!= NULL
) {
1350 // notice that we may do this test inside the previous "if" because the
1351 // last entry's line is surely !NULL
1352 if ( pEntry
== m_pLastEntry
) {
1353 // our last entry is being deleted - find the last one which stays
1354 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1356 // go back until we find another entry or reach the group's line
1357 ConfigEntry
*pNewLast
= NULL
;
1358 size_t n
, nEntries
= m_aEntries
.Count();
1360 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1361 // is it our subgroup?
1362 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1363 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1364 pNewLast
= m_aEntries
[n
];
1367 if ( pNewLast
!= NULL
) // found?
1371 if ( pl
== m_pLine
) {
1372 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1374 // we've reached the group line without finding any subgroups
1375 m_pLastEntry
= NULL
;
1378 m_pLastEntry
= pNewLast
;
1381 m_pConfig
->LineListRemove(pLine
);
1384 // we must be written back for the changes to be saved
1387 m_aEntries
.Remove(pEntry
);
1393 // ----------------------------------------------------------------------------
1395 // ----------------------------------------------------------------------------
1396 void ConfigGroup::SetDirty()
1399 if ( Parent() != NULL
) // propagate upwards
1400 Parent()->SetDirty();
1403 // ============================================================================
1404 // wxFileConfig::ConfigEntry
1405 // ============================================================================
1407 // ----------------------------------------------------------------------------
1409 // ----------------------------------------------------------------------------
1410 ConfigEntry::ConfigEntry(ConfigGroup
*pParent
,
1411 const wxString
& strName
,
1413 : m_strName(strName
)
1415 wxASSERT( !strName
.IsEmpty() );
1417 m_pParent
= pParent
;
1423 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1425 m_strName
.erase(0, 1); // remove first character
1428 // ----------------------------------------------------------------------------
1430 // ----------------------------------------------------------------------------
1432 void ConfigEntry::SetLine(LineList
*pLine
)
1434 if ( m_pLine
!= NULL
) {
1435 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1436 Name().c_str(), m_pParent
->GetFullName().c_str());
1440 Group()->SetLastEntry(this);
1443 // second parameter is FALSE if we read the value from file and prevents the
1444 // entry from being marked as 'dirty'
1445 void ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1447 if ( bUser
&& IsImmutable() ) {
1448 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1453 // do nothing if it's the same value
1454 if ( strValue
== m_strValue
)
1457 m_strValue
= strValue
;
1460 wxString strVal
= FilterOutValue(strValue
);
1462 strLine
<< FilterOutEntryName(m_strName
) << wxT('=') << strVal
;
1464 if ( m_pLine
!= NULL
) {
1465 // entry was read from the local config file, just modify the line
1466 m_pLine
->SetText(strLine
);
1469 // add a new line to the file
1470 wxASSERT( m_nLine
== wxNOT_FOUND
); // consistency check
1472 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1473 Group()->GetLastEntryLine());
1474 Group()->SetLastEntry(this);
1481 void ConfigEntry::SetDirty()
1484 Group()->SetDirty();
1487 // ============================================================================
1489 // ============================================================================
1491 // ----------------------------------------------------------------------------
1492 // compare functions for array sorting
1493 // ----------------------------------------------------------------------------
1495 int CompareEntries(ConfigEntry
*p1
,
1498 #if wxCONFIG_CASE_SENSITIVE
1499 return wxStrcmp(p1
->Name(), p2
->Name());
1501 return wxStricmp(p1
->Name(), p2
->Name());
1505 int CompareGroups(ConfigGroup
*p1
,
1508 #if wxCONFIG_CASE_SENSITIVE
1509 return wxStrcmp(p1
->Name(), p2
->Name());
1511 return wxStricmp(p1
->Name(), p2
->Name());
1515 // ----------------------------------------------------------------------------
1517 // ----------------------------------------------------------------------------
1519 // undo FilterOutValue
1520 static wxString
FilterInValue(const wxString
& str
)
1523 strResult
.Alloc(str
.Len());
1525 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1527 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1528 if ( str
[n
] == wxT('\\') ) {
1529 switch ( str
[++n
] ) {
1531 strResult
+= wxT('\n');
1535 strResult
+= wxT('\r');
1539 strResult
+= wxT('\t');
1543 strResult
+= wxT('\\');
1547 strResult
+= wxT('"');
1552 if ( str
[n
] != wxT('"') || !bQuoted
)
1553 strResult
+= str
[n
];
1554 else if ( n
!= str
.Len() - 1 ) {
1555 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1558 //else: it's the last quote of a quoted string, ok
1565 // quote the string before writing it to file
1566 static wxString
FilterOutValue(const wxString
& str
)
1572 strResult
.Alloc(str
.Len());
1574 // quoting is necessary to preserve spaces in the beginning of the string
1575 bool bQuote
= wxIsspace(str
[0]) || str
[0] == wxT('"');
1578 strResult
+= wxT('"');
1581 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
1604 //else: fall through
1607 strResult
+= str
[n
];
1608 continue; // nothing special to do
1611 // we get here only for special characters
1612 strResult
<< wxT('\\') << c
;
1616 strResult
+= wxT('"');
1621 // undo FilterOutEntryName
1622 static wxString
FilterInEntryName(const wxString
& str
)
1625 strResult
.Alloc(str
.Len());
1627 for ( const wxChar
*pc
= str
.c_str(); *pc
!= '\0'; pc
++ ) {
1628 if ( *pc
== wxT('\\') )
1637 // sanitize entry or group name: insert '\\' before any special characters
1638 static wxString
FilterOutEntryName(const wxString
& str
)
1641 strResult
.Alloc(str
.Len());
1643 for ( const wxChar
*pc
= str
.c_str(); *pc
!= wxT('\0'); pc
++ ) {
1646 // we explicitly allow some of "safe" chars and 8bit ASCII characters
1647 // which will probably never have special meaning
1648 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
1649 // should *not* be quoted
1650 if ( !wxIsalnum(c
) && !wxStrchr(wxT("@_/-!.*%"), c
) && ((c
& 0x80) == 0) )
1651 strResult
+= wxT('\\');
1659 // we can't put ?: in the ctor initializer list because it confuses some
1660 // broken compilers (Borland C++)
1661 static wxString
GetAppName(const wxString
& appName
)
1663 if ( !appName
&& wxTheApp
)
1664 return wxTheApp
->GetAppName();
1669 #endif // wxUSE_CONFIG