1 ///////////////////////////////////////////////////////////////////////////////
3 // Purpose: implementation of wxFileConfig derivation of wxConfig
4 // Author: Vadim Zeitlin
6 // Created: 07.04.98 (adapted from appconf.cpp)
8 // Copyright: (c) 1997 Karsten Ballüder & Vadim Zeitlin
9 // Ballueder@usa.net <zeitlin@dptmaths.ens-cachan.fr>
10 // Licence: wxWindows license
11 ///////////////////////////////////////////////////////////////////////////////
14 #pragma implementation "fileconf.h"
17 // ----------------------------------------------------------------------------
19 // ----------------------------------------------------------------------------
21 #include "wx/wxprec.h"
30 #include "wx/string.h"
35 #include "wx/dynarray.h"
38 #include "wx/textfile.h"
39 #include "wx/config.h"
40 #include "wx/fileconf.h"
42 #include "wx/utils.h" // for wxGetHomeDir
44 // _WINDOWS_ is defined when windows.h is included,
45 // __WXMSW__ is defined for MS Windows compilation
46 #if defined(__WXMSW__) && !defined(_WINDOWS_)
57 // ----------------------------------------------------------------------------
59 // ----------------------------------------------------------------------------
60 #define CONST_CAST ((wxFileConfig *)this)->
62 // ----------------------------------------------------------------------------
64 // ----------------------------------------------------------------------------
69 // ----------------------------------------------------------------------------
70 // global functions declarations
71 // ----------------------------------------------------------------------------
73 // compare functions for sorting the arrays
74 static int LINKAGEMODE
CompareEntries(ConfigEntry
*p1
, ConfigEntry
*p2
);
75 static int LINKAGEMODE
CompareGroups(ConfigGroup
*p1
, ConfigGroup
*p2
);
78 static wxString
FilterInValue(const wxString
& str
);
79 static wxString
FilterOutValue(const wxString
& str
);
81 static wxString
FilterInEntryName(const wxString
& str
);
82 static wxString
FilterOutEntryName(const wxString
& str
);
84 // get the name to use in wxFileConfig ctor
85 static wxString
GetAppName(const wxString
& appname
);
87 // ============================================================================
89 // ============================================================================
91 // ----------------------------------------------------------------------------
93 // ----------------------------------------------------------------------------
94 wxString
wxFileConfig::GetGlobalDir()
98 #ifdef __VMS__ // Note if __VMS is defined __UNIX is also defined
99 strDir
= wxT("sys$manager:");
100 #elif defined( __UNIX__ )
101 strDir
= wxT("/etc/");
102 #elif defined(__WXPM__)
103 ULONG aulSysInfo
[QSV_MAX
] = {0};
107 rc
= DosQuerySysInfo( 1L, QSV_MAX
, (PVOID
)aulSysInfo
, sizeof(ULONG
)*QSV_MAX
);
110 drive
= aulSysInfo
[QSV_BOOT_DRIVE
- 1];
114 strDir
= "A:\\OS2\\";
117 strDir
= "B:\\OS2\\";
120 strDir
= "C:\\OS2\\";
123 strDir
= "D:\\OS2\\";
126 strDir
= "E:\\OS2\\";
129 strDir
= "F:\\OS2\\";
132 strDir
= "G:\\OS2\\";
135 strDir
= "H:\\OS2\\";
138 strDir
= "I:\\OS2\\";
141 strDir
= "J:\\OS2\\";
144 strDir
= "K:\\OS2\\";
147 strDir
= "L:\\OS2\\";
150 strDir
= "M:\\OS2\\";
153 strDir
= "N:\\OS2\\";
156 strDir
= "O:\\OS2\\";
159 strDir
= "P:\\OS2\\";
162 strDir
= "Q:\\OS2\\";
165 strDir
= "R:\\OS2\\";
168 strDir
= "S:\\OS2\\";
171 strDir
= "T:\\OS2\\";
174 strDir
= "U:\\OS2\\";
177 strDir
= "V:\\OS2\\";
180 strDir
= "W:\\OS2\\";
183 strDir
= "X:\\OS2\\";
186 strDir
= "Y:\\OS2\\";
189 strDir
= "Z:\\OS2\\";
193 #elif defined(__WXSTUBS__)
194 wxASSERT_MSG( FALSE
, wxT("TODO") ) ;
195 #elif defined(__WXMAC__)
200 if ( FindFolder( (short) kOnSystemDisk
, kPreferencesFolderType
, kDontCreateFolder
, &vRefNum
, &dirID
) == noErr
)
203 if ( FSMakeFSSpec( vRefNum
, dirID
, "\p" , &file
) == noErr
)
205 strDir
= wxMacFSSpec2UnixFilename( &file
) + "/" ;
210 wxChar szWinDir
[MAX_PATH
];
211 ::GetWindowsDirectory(szWinDir
, MAX_PATH
);
215 #endif // Unix/Windows
220 wxString
wxFileConfig::GetLocalDir()
225 wxGetHomeDir(&strDir
);
229 if (strDir
.Last() != wxT('/')) strDir
<< wxT('/');
231 if (strDir
.Last() != wxT('\\')) strDir
<< wxT('\\');
235 // no local dir concept on mac
236 return GetGlobalDir() ;
242 wxString
wxFileConfig::GetGlobalFileName(const wxChar
*szFile
)
244 wxString str
= GetGlobalDir();
247 if ( wxStrchr(szFile
, wxT('.')) == NULL
)
250 #elif defined( __WXMAC__ )
251 str
<< " Preferences";
259 wxString
wxFileConfig::GetLocalFileName(const wxChar
*szFile
)
261 #ifdef __VMS__ // On VMS I saw the problem that the home directory was appended
262 // twice for the configuration file. Does that also happen for other
264 wxString str
= wxT( ' ' );
266 wxString str
= GetLocalDir();
276 if ( wxStrchr(szFile
, wxT('.')) == NULL
)
282 str
<< " Preferences";
287 // ----------------------------------------------------------------------------
289 // ----------------------------------------------------------------------------
291 void wxFileConfig::Init()
294 m_pRootGroup
= new ConfigGroup(NULL
, "", this);
299 // it's not an error if (one of the) file(s) doesn't exist
301 // parse the global file
302 if ( !m_strGlobalFile
.IsEmpty() && wxFile::Exists(m_strGlobalFile
) ) {
303 wxTextFile
fileGlobal(m_strGlobalFile
);
305 if ( fileGlobal
.Open() ) {
306 Parse(fileGlobal
, FALSE
/* global */);
310 wxLogWarning(_("can't open global configuration file '%s'."),
311 m_strGlobalFile
.c_str());
314 // parse the local file
315 if ( !m_strLocalFile
.IsEmpty() && wxFile::Exists(m_strLocalFile
) ) {
316 wxTextFile
fileLocal(m_strLocalFile
);
317 if ( fileLocal
.Open() ) {
318 Parse(fileLocal
, TRUE
/* local */);
322 wxLogWarning(_("can't open user configuration file '%s'."),
323 m_strLocalFile
.c_str());
327 // constructor supports creation of wxFileConfig objects of any type
328 wxFileConfig::wxFileConfig(const wxString
& appName
, const wxString
& vendorName
,
329 const wxString
& strLocal
, const wxString
& strGlobal
,
331 : wxConfigBase(::GetAppName(appName
), vendorName
,
334 m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
336 // Make up names for files if empty
337 if ( m_strLocalFile
.IsEmpty() && (style
& wxCONFIG_USE_LOCAL_FILE
) )
339 m_strLocalFile
= GetLocalFileName(GetAppName());
342 if ( m_strGlobalFile
.IsEmpty() && (style
& wxCONFIG_USE_GLOBAL_FILE
) )
344 m_strGlobalFile
= GetGlobalFileName(GetAppName());
347 // Check if styles are not supplied, but filenames are, in which case
348 // add the correct styles.
349 if ( !m_strLocalFile
.IsEmpty() )
350 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE
);
352 if ( !m_strGlobalFile
.IsEmpty() )
353 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE
);
355 // if the path is not absolute, prepend the standard directory to it
356 // UNLESS wxCONFIG_USE_RELATIVE_PATH style is set
357 if ( !(style
& wxCONFIG_USE_RELATIVE_PATH
) )
359 if ( !m_strLocalFile
.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile
) )
361 wxString strLocal
= m_strLocalFile
;
362 m_strLocalFile
= GetLocalDir();
363 m_strLocalFile
<< strLocal
;
366 if ( !m_strGlobalFile
.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile
) )
368 wxString strGlobal
= m_strGlobalFile
;
369 m_strGlobalFile
= GetGlobalDir();
370 m_strGlobalFile
<< strGlobal
;
377 void wxFileConfig::CleanUp()
381 LineList
*pCur
= m_linesHead
;
382 while ( pCur
!= NULL
) {
383 LineList
*pNext
= pCur
->Next();
389 wxFileConfig::~wxFileConfig()
396 // ----------------------------------------------------------------------------
397 // parse a config file
398 // ----------------------------------------------------------------------------
400 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
402 const wxChar
*pStart
;
406 size_t nLineCount
= file
.GetLineCount();
407 for ( size_t n
= 0; n
< nLineCount
; n
++ ) {
410 // add the line to linked list
412 LineListAppend(strLine
);
414 // skip leading spaces
415 for ( pStart
= strLine
; wxIsspace(*pStart
); pStart
++ )
418 // skip blank/comment lines
419 if ( *pStart
== wxT('\0')|| *pStart
== wxT(';') || *pStart
== wxT('#') )
422 if ( *pStart
== wxT('[') ) { // a new group
425 while ( *++pEnd
!= wxT(']') ) {
426 if ( *pEnd
== wxT('\n') || *pEnd
== wxT('\0') )
430 if ( *pEnd
!= wxT(']') ) {
431 wxLogError(_("file '%s': unexpected character %c at line %d."),
432 file
.GetName(), *pEnd
, n
+ 1);
433 continue; // skip this line
436 // group name here is always considered as abs path
439 strGroup
<< wxCONFIG_PATH_SEPARATOR
440 << FilterInEntryName(wxString(pStart
, pEnd
- pStart
));
442 // will create it if doesn't yet exist
446 m_pCurrentGroup
->SetLine(m_linesTail
);
448 // check that there is nothing except comments left on this line
450 while ( *++pEnd
!= wxT('\0') && bCont
) {
459 // ignore whitespace ('\n' impossible here)
463 wxLogWarning(_("file '%s', line %d: '%s' "
464 "ignored after group header."),
465 file
.GetName(), n
+ 1, pEnd
);
471 const wxChar
*pEnd
= pStart
;
472 while ( *pEnd
&& *pEnd
!= wxT('=') && !wxIsspace(*pEnd
) ) {
473 if ( *pEnd
== wxT('\\') ) {
474 // next character may be space or not - still take it because it's
475 // quoted (unless there is nothing)
478 // the error message will be given below anyhow
486 wxString
strKey(FilterInEntryName(wxString(pStart
, pEnd
)));
489 while ( isspace(*pEnd
) )
492 if ( *pEnd
++ != wxT('=') ) {
493 wxLogError(_("file '%s', line %d: '=' expected."),
494 file
.GetName(), n
+ 1);
497 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
499 if ( pEntry
== NULL
) {
501 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
504 pEntry
->SetLine(m_linesTail
);
507 if ( bLocal
&& pEntry
->IsImmutable() ) {
508 // immutable keys can't be changed by user
509 wxLogWarning(_("file '%s', line %d: value for "
510 "immutable key '%s' ignored."),
511 file
.GetName(), n
+ 1, strKey
.c_str());
514 // the condition below catches the cases (a) and (b) but not (c):
515 // (a) global key found second time in global file
516 // (b) key found second (or more) time in local file
517 // (c) key from global file now found in local one
518 // which is exactly what we want.
519 else if ( !bLocal
|| pEntry
->IsLocal() ) {
520 wxLogWarning(_("file '%s', line %d: key '%s' was first "
521 "found at line %d."),
522 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
525 pEntry
->SetLine(m_linesTail
);
530 while ( wxIsspace(*pEnd
) )
533 pEntry
->SetValue(FilterInValue(pEnd
), FALSE
/* read from file */);
539 // ----------------------------------------------------------------------------
541 // ----------------------------------------------------------------------------
543 void wxFileConfig::SetRootPath()
546 m_pCurrentGroup
= m_pRootGroup
;
549 void wxFileConfig::SetPath(const wxString
& strPath
)
551 wxArrayString aParts
;
553 if ( strPath
.IsEmpty() ) {
558 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
560 wxSplitPath(aParts
, strPath
);
563 // relative path, combine with current one
564 wxString strFullPath
= m_strPath
;
565 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
566 wxSplitPath(aParts
, strFullPath
);
569 // change current group
571 m_pCurrentGroup
= m_pRootGroup
;
572 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
573 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
574 if ( pNextGroup
== NULL
)
575 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
576 m_pCurrentGroup
= pNextGroup
;
579 // recombine path parts in one variable
581 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
582 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
586 // ----------------------------------------------------------------------------
588 // ----------------------------------------------------------------------------
590 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
593 return GetNextGroup(str
, lIndex
);
596 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
598 if ( size_t(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
599 str
= m_pCurrentGroup
->Groups()[(size_t)lIndex
++]->Name();
606 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
609 return GetNextEntry(str
, lIndex
);
612 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
614 if ( size_t(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
615 str
= m_pCurrentGroup
->Entries()[(size_t)lIndex
++]->Name();
622 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
624 size_t n
= m_pCurrentGroup
->Entries().Count();
626 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
627 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
628 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
629 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
630 n
+= GetNumberOfEntries(TRUE
);
631 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
638 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
640 size_t n
= m_pCurrentGroup
->Groups().Count();
642 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
643 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
644 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
645 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
646 n
+= GetNumberOfGroups(TRUE
);
647 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
654 // ----------------------------------------------------------------------------
655 // tests for existence
656 // ----------------------------------------------------------------------------
658 bool wxFileConfig::HasGroup(const wxString
& strName
) const
660 wxConfigPathChanger
path(this, strName
);
662 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
663 return pGroup
!= NULL
;
666 bool wxFileConfig::HasEntry(const wxString
& strName
) const
668 wxConfigPathChanger
path(this, strName
);
670 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
671 return pEntry
!= NULL
;
674 // ----------------------------------------------------------------------------
676 // ----------------------------------------------------------------------------
678 bool wxFileConfig::Read(const wxString
& key
,
679 wxString
* pStr
) const
681 wxConfigPathChanger
path(this, key
);
683 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
684 if (pEntry
== NULL
) {
688 *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());
699 if (pEntry
== NULL
) {
700 if( IsRecordingDefaults() )
701 ((wxFileConfig
*)this)->Write(key
,defVal
);
702 *pStr
= ExpandEnvVars(defVal
);
706 *pStr
= ExpandEnvVars(pEntry
->Value());
711 bool wxFileConfig::Read(const wxString
& key
, long *pl
) const
714 if ( Read(key
, & str
) ) {
723 bool wxFileConfig::Write(const wxString
& key
, const wxString
& szValue
)
725 wxConfigPathChanger
path(this, key
);
727 wxString strName
= path
.Name();
728 if ( strName
.IsEmpty() ) {
729 // setting the value of a group is an error
730 wxASSERT_MSG( wxIsEmpty(szValue
), wxT("can't set value of a group!") );
732 // ... except if it's empty in which case it's a way to force it's creation
733 m_pCurrentGroup
->SetDirty();
735 // this will add a line for this group if it didn't have it before
736 (void)m_pCurrentGroup
->GetGroupLine();
741 // check that the name is reasonable
742 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
743 wxLogError(_("Config entry name cannot start with '%c'."),
744 wxCONFIG_IMMUTABLE_PREFIX
);
748 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
749 if ( pEntry
== NULL
)
750 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
752 pEntry
->SetValue(szValue
);
758 bool wxFileConfig::Write(const wxString
& key
, long lValue
)
760 // ltoa() is not ANSI :-(
762 buf
.Printf(wxT("%ld"), lValue
);
763 return Write(key
, buf
);
766 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
768 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() || !m_strLocalFile
)
771 wxTempFile
file(m_strLocalFile
);
773 if ( !file
.IsOpened() ) {
774 wxLogError(_("can't open user configuration file."));
778 // write all strings to file
779 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
780 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
781 wxLogError(_("can't write user configuration file."));
787 return file
.Commit();
789 bool ret
= file
.Commit();
794 wxUnixFilename2FSSpec( m_strLocalFile
, &spec
) ;
796 if ( FSpGetFInfo( &spec
, &finfo
) == noErr
)
798 finfo
.fdType
= 'TEXT' ;
799 finfo
.fdCreator
= 'ttxt' ;
800 FSpSetFInfo( &spec
, &finfo
) ;
807 // ----------------------------------------------------------------------------
808 // renaming groups/entries
809 // ----------------------------------------------------------------------------
811 bool wxFileConfig::RenameEntry(const wxString
& oldName
,
812 const wxString
& newName
)
814 // check that the entry exists
815 ConfigEntry
*oldEntry
= m_pCurrentGroup
->FindEntry(oldName
);
819 // check that the new entry doesn't already exist
820 if ( m_pCurrentGroup
->FindEntry(newName
) )
823 // delete the old entry, create the new one
824 wxString value
= oldEntry
->Value();
825 if ( !m_pCurrentGroup
->DeleteEntry(oldName
) )
828 ConfigEntry
*newEntry
= m_pCurrentGroup
->AddEntry(newName
);
829 newEntry
->SetValue(value
);
834 bool wxFileConfig::RenameGroup(const wxString
& oldName
,
835 const wxString
& newName
)
837 // check that the group exists
838 ConfigGroup
*group
= m_pCurrentGroup
->FindSubgroup(oldName
);
842 // check that the new group doesn't already exist
843 if ( m_pCurrentGroup
->FindSubgroup(newName
) )
846 group
->Rename(newName
);
851 // ----------------------------------------------------------------------------
852 // delete groups/entries
853 // ----------------------------------------------------------------------------
855 bool wxFileConfig::DeleteEntry(const wxString
& key
, bool bGroupIfEmptyAlso
)
857 wxConfigPathChanger
path(this, key
);
859 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
862 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
863 if ( m_pCurrentGroup
!= m_pRootGroup
) {
864 ConfigGroup
*pGroup
= m_pCurrentGroup
;
865 SetPath(wxT("..")); // changes m_pCurrentGroup!
866 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
868 //else: never delete the root group
874 bool wxFileConfig::DeleteGroup(const wxString
& key
)
876 wxConfigPathChanger
path(this, key
);
878 return m_pCurrentGroup
->DeleteSubgroupByName(path
.Name());
881 bool wxFileConfig::DeleteAll()
885 if ( remove(m_strLocalFile
.fn_str()) == -1 )
886 wxLogSysError(_("can't delete user configuration file '%s'"), m_strLocalFile
.c_str());
888 m_strLocalFile
= m_strGlobalFile
= wxT("");
894 // ----------------------------------------------------------------------------
895 // linked list functions
896 // ----------------------------------------------------------------------------
898 // append a new line to the end of the list
899 LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
901 LineList
*pLine
= new LineList(str
);
903 if ( m_linesTail
== NULL
) {
909 m_linesTail
->SetNext(pLine
);
910 pLine
->SetPrev(m_linesTail
);
917 // insert a new line after the given one or in the very beginning if !pLine
918 LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
921 if ( pLine
== m_linesTail
)
922 return LineListAppend(str
);
924 LineList
*pNewLine
= new LineList(str
);
925 if ( pLine
== NULL
) {
926 // prepend to the list
927 pNewLine
->SetNext(m_linesHead
);
928 m_linesHead
->SetPrev(pNewLine
);
929 m_linesHead
= pNewLine
;
932 // insert before pLine
933 LineList
*pNext
= pLine
->Next();
934 pNewLine
->SetNext(pNext
);
935 pNewLine
->SetPrev(pLine
);
936 pNext
->SetPrev(pNewLine
);
937 pLine
->SetNext(pNewLine
);
943 void wxFileConfig::LineListRemove(LineList
*pLine
)
945 LineList
*pPrev
= pLine
->Prev(),
946 *pNext
= pLine
->Next();
952 pPrev
->SetNext(pNext
);
958 pNext
->SetPrev(pPrev
);
963 bool wxFileConfig::LineListIsEmpty()
965 return m_linesHead
== NULL
;
968 // ============================================================================
969 // wxFileConfig::ConfigGroup
970 // ============================================================================
972 // ----------------------------------------------------------------------------
974 // ----------------------------------------------------------------------------
977 ConfigGroup::ConfigGroup(ConfigGroup
*pParent
,
978 const wxString
& strName
,
979 wxFileConfig
*pConfig
)
980 : m_aEntries(CompareEntries
),
981 m_aSubgroups(CompareGroups
),
993 // dtor deletes all children
994 ConfigGroup::~ConfigGroup()
997 size_t n
, nCount
= m_aEntries
.Count();
998 for ( n
= 0; n
< nCount
; n
++ )
999 delete m_aEntries
[n
];
1002 nCount
= m_aSubgroups
.Count();
1003 for ( n
= 0; n
< nCount
; n
++ )
1004 delete m_aSubgroups
[n
];
1007 // ----------------------------------------------------------------------------
1009 // ----------------------------------------------------------------------------
1011 void ConfigGroup::SetLine(LineList
*pLine
)
1013 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
1019 This is a bit complicated, so let me explain it in details. All lines that
1020 were read from the local file (the only one we will ever modify) are stored
1021 in a (doubly) linked list. Our problem is to know at which position in this
1022 list should we insert the new entries/subgroups. To solve it we keep three
1023 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
1025 m_pLine points to the line containing "[group_name]"
1026 m_pLastEntry points to the last entry of this group in the local file.
1027 m_pLastGroup subgroup
1029 Initially, they're NULL all three. When the group (an entry/subgroup) is read
1030 from the local file, the corresponding variable is set. However, if the group
1031 was read from the global file and then modified or created by the application
1032 these variables are still NULL and we need to create the corresponding lines.
1033 See the following functions (and comments preceding them) for the details of
1036 Also, when our last entry/group are deleted we need to find the new last
1037 element - the code in DeleteEntry/Subgroup does this by backtracking the list
1038 of lines until it either founds an entry/subgroup (and this is the new last
1039 element) or the m_pLine of the group, in which case there are no more entries
1040 (or subgroups) left and m_pLast<element> becomes NULL.
1042 NB: This last problem could be avoided for entries if we added new entries
1043 immediately after m_pLine, but in this case the entries would appear
1044 backwards in the config file (OTOH, it's not that important) and as we
1045 would still need to do it for the subgroups the code wouldn't have been
1046 significantly less complicated.
1049 // Return the line which contains "[our name]". If we're still not in the list,
1050 // add our line to it immediately after the last line of our parent group if we
1051 // have it or in the very beginning if we're the root group.
1052 LineList
*ConfigGroup::GetGroupLine()
1054 if ( m_pLine
== NULL
) {
1055 ConfigGroup
*pParent
= Parent();
1057 // this group wasn't present in local config file, add it now
1058 if ( pParent
!= NULL
) {
1059 wxString strFullName
;
1060 strFullName
<< wxT("[")
1062 << FilterOutEntryName(GetFullName().c_str() + 1)
1064 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
1065 pParent
->GetLastGroupLine());
1066 pParent
->SetLastGroup(this); // we're surely after all the others
1069 // we return NULL, so that LineListInsert() will insert us in the
1077 // Return the last line belonging to the subgroups of this group (after which
1078 // we can add a new subgroup), if we don't have any subgroups or entries our
1079 // last line is the group line (m_pLine) itself.
1080 LineList
*ConfigGroup::GetLastGroupLine()
1082 // if we have any subgroups, our last line is the last line of the last
1084 if ( m_pLastGroup
!= NULL
) {
1085 LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
1087 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
1091 // no subgroups, so the last line is the line of thelast entry (if any)
1092 return GetLastEntryLine();
1095 // return the last line belonging to the entries of this group (after which
1096 // we can add a new entry), if we don't have any entries we will add the new
1097 // one immediately after the group line itself.
1098 LineList
*ConfigGroup::GetLastEntryLine()
1100 if ( m_pLastEntry
!= NULL
) {
1101 LineList
*pLine
= m_pLastEntry
->GetLine();
1103 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
1107 // no entries: insert after the group header
1108 return GetGroupLine();
1111 // ----------------------------------------------------------------------------
1113 // ----------------------------------------------------------------------------
1115 void ConfigGroup::Rename(const wxString
& newName
)
1117 m_strName
= newName
;
1119 LineList
*line
= GetGroupLine();
1120 wxString strFullName
;
1121 strFullName
<< wxT("[") << (GetFullName().c_str() + 1) << wxT("]"); // +1: no '/'
1122 line
->SetText(strFullName
);
1127 wxString
ConfigGroup::GetFullName() const
1130 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
1135 // ----------------------------------------------------------------------------
1137 // ----------------------------------------------------------------------------
1139 // use binary search because the array is sorted
1141 ConfigGroup::FindEntry(const wxChar
*szName
) const
1145 hi
= m_aEntries
.Count();
1147 ConfigEntry
*pEntry
;
1151 pEntry
= m_aEntries
[i
];
1153 #if wxCONFIG_CASE_SENSITIVE
1154 res
= wxStrcmp(pEntry
->Name(), szName
);
1156 res
= wxStricmp(pEntry
->Name(), szName
);
1171 ConfigGroup::FindSubgroup(const wxChar
*szName
) const
1175 hi
= m_aSubgroups
.Count();
1177 ConfigGroup
*pGroup
;
1181 pGroup
= m_aSubgroups
[i
];
1183 #if wxCONFIG_CASE_SENSITIVE
1184 res
= wxStrcmp(pGroup
->Name(), szName
);
1186 res
= wxStricmp(pGroup
->Name(), szName
);
1200 // ----------------------------------------------------------------------------
1201 // create a new item
1202 // ----------------------------------------------------------------------------
1204 // create a new entry and add it to the current group
1206 ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
1208 wxASSERT( FindEntry(strName
) == NULL
);
1210 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
1211 m_aEntries
.Add(pEntry
);
1216 // create a new group and add it to the current group
1218 ConfigGroup::AddSubgroup(const wxString
& strName
)
1220 wxASSERT( FindSubgroup(strName
) == NULL
);
1222 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
1223 m_aSubgroups
.Add(pGroup
);
1228 // ----------------------------------------------------------------------------
1230 // ----------------------------------------------------------------------------
1233 The delete operations are _very_ slow if we delete the last item of this
1234 group (see comments before GetXXXLineXXX functions for more details),
1235 so it's much better to start with the first entry/group if we want to
1236 delete several of them.
1239 bool ConfigGroup::DeleteSubgroupByName(const wxChar
*szName
)
1241 return DeleteSubgroup(FindSubgroup(szName
));
1244 // doesn't delete the subgroup itself, but does remove references to it from
1245 // all other data structures (and normally the returned pointer should be
1246 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1247 bool ConfigGroup::DeleteSubgroup(ConfigGroup
*pGroup
)
1249 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1251 // delete all entries
1252 size_t nCount
= pGroup
->m_aEntries
.Count();
1253 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1254 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1255 if ( pLine
!= NULL
)
1256 m_pConfig
->LineListRemove(pLine
);
1259 // and subgroups of this sungroup
1260 nCount
= pGroup
->m_aSubgroups
.Count();
1261 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1262 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[0]);
1265 LineList
*pLine
= pGroup
->m_pLine
;
1266 if ( pLine
!= NULL
) {
1267 // notice that we may do this test inside the previous "if" because the
1268 // last entry's line is surely !NULL
1269 if ( pGroup
== m_pLastGroup
) {
1270 // our last entry is being deleted - find the last one which stays
1271 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1273 // go back until we find a subgroup or reach the group's line
1274 ConfigGroup
*pNewLast
= NULL
;
1275 size_t n
, nSubgroups
= m_aSubgroups
.Count();
1277 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1278 // is it our subgroup?
1279 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1280 // do _not_ call GetGroupLine! we don't want to add it to the local
1281 // file if it's not already there
1282 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1283 pNewLast
= m_aSubgroups
[n
];
1286 if ( pNewLast
!= NULL
) // found?
1290 if ( pl
== m_pLine
) {
1291 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1293 // we've reached the group line without finding any subgroups
1294 m_pLastGroup
= NULL
;
1297 m_pLastGroup
= pNewLast
;
1300 m_pConfig
->LineListRemove(pLine
);
1305 m_aSubgroups
.Remove(pGroup
);
1311 bool ConfigGroup::DeleteEntry(const wxChar
*szName
)
1313 ConfigEntry
*pEntry
= FindEntry(szName
);
1314 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1316 LineList
*pLine
= pEntry
->GetLine();
1317 if ( pLine
!= NULL
) {
1318 // notice that we may do this test inside the previous "if" because the
1319 // last entry's line is surely !NULL
1320 if ( pEntry
== m_pLastEntry
) {
1321 // our last entry is being deleted - find the last one which stays
1322 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1324 // go back until we find another entry or reach the group's line
1325 ConfigEntry
*pNewLast
= NULL
;
1326 size_t n
, nEntries
= m_aEntries
.Count();
1328 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1329 // is it our subgroup?
1330 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1331 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1332 pNewLast
= m_aEntries
[n
];
1335 if ( pNewLast
!= NULL
) // found?
1339 if ( pl
== m_pLine
) {
1340 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1342 // we've reached the group line without finding any subgroups
1343 m_pLastEntry
= NULL
;
1346 m_pLastEntry
= pNewLast
;
1349 m_pConfig
->LineListRemove(pLine
);
1352 // we must be written back for the changes to be saved
1355 m_aEntries
.Remove(pEntry
);
1361 // ----------------------------------------------------------------------------
1363 // ----------------------------------------------------------------------------
1364 void ConfigGroup::SetDirty()
1367 if ( Parent() != NULL
) // propagate upwards
1368 Parent()->SetDirty();
1371 // ============================================================================
1372 // wxFileConfig::ConfigEntry
1373 // ============================================================================
1375 // ----------------------------------------------------------------------------
1377 // ----------------------------------------------------------------------------
1378 ConfigEntry::ConfigEntry(ConfigGroup
*pParent
,
1379 const wxString
& strName
,
1381 : m_strName(strName
)
1383 wxASSERT( !strName
.IsEmpty() );
1385 m_pParent
= pParent
;
1391 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1393 m_strName
.erase(0, 1); // remove first character
1396 // ----------------------------------------------------------------------------
1398 // ----------------------------------------------------------------------------
1400 void ConfigEntry::SetLine(LineList
*pLine
)
1402 if ( m_pLine
!= NULL
) {
1403 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1404 Name().c_str(), m_pParent
->GetFullName().c_str());
1408 Group()->SetLastEntry(this);
1411 // second parameter is FALSE if we read the value from file and prevents the
1412 // entry from being marked as 'dirty'
1413 void ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1415 if ( bUser
&& IsImmutable() ) {
1416 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1421 // do nothing if it's the same value
1422 if ( strValue
== m_strValue
)
1425 m_strValue
= strValue
;
1428 wxString strVal
= FilterOutValue(strValue
);
1430 strLine
<< FilterOutEntryName(m_strName
) << wxT('=') << strVal
;
1432 if ( m_pLine
!= NULL
) {
1433 // entry was read from the local config file, just modify the line
1434 m_pLine
->SetText(strLine
);
1437 // add a new line to the file
1438 wxASSERT( m_nLine
== wxNOT_FOUND
); // consistency check
1440 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1441 Group()->GetLastEntryLine());
1442 Group()->SetLastEntry(this);
1449 void ConfigEntry::SetDirty()
1452 Group()->SetDirty();
1455 // ============================================================================
1457 // ============================================================================
1459 // ----------------------------------------------------------------------------
1460 // compare functions for array sorting
1461 // ----------------------------------------------------------------------------
1463 int CompareEntries(ConfigEntry
*p1
,
1466 #if wxCONFIG_CASE_SENSITIVE
1467 return wxStrcmp(p1
->Name(), p2
->Name());
1469 return wxStricmp(p1
->Name(), p2
->Name());
1473 int CompareGroups(ConfigGroup
*p1
,
1476 #if wxCONFIG_CASE_SENSITIVE
1477 return wxStrcmp(p1
->Name(), p2
->Name());
1479 return wxStricmp(p1
->Name(), p2
->Name());
1483 // ----------------------------------------------------------------------------
1485 // ----------------------------------------------------------------------------
1487 // undo FilterOutValue
1488 static wxString
FilterInValue(const wxString
& str
)
1491 strResult
.Alloc(str
.Len());
1493 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1495 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1496 if ( str
[n
] == wxT('\\') ) {
1497 switch ( str
[++n
] ) {
1499 strResult
+= wxT('\n');
1503 strResult
+= wxT('\r');
1507 strResult
+= wxT('\t');
1511 strResult
+= wxT('\\');
1515 strResult
+= wxT('"');
1520 if ( str
[n
] != wxT('"') || !bQuoted
)
1521 strResult
+= str
[n
];
1522 else if ( n
!= str
.Len() - 1 ) {
1523 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1526 //else: it's the last quote of a quoted string, ok
1533 // quote the string before writing it to file
1534 static wxString
FilterOutValue(const wxString
& str
)
1540 strResult
.Alloc(str
.Len());
1542 // quoting is necessary to preserve spaces in the beginning of the string
1543 bool bQuote
= wxIsspace(str
[0]) || str
[0] == wxT('"');
1546 strResult
+= wxT('"');
1549 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
1572 //else: fall through
1575 strResult
+= str
[n
];
1576 continue; // nothing special to do
1579 // we get here only for special characters
1580 strResult
<< wxT('\\') << c
;
1584 strResult
+= wxT('"');
1589 // undo FilterOutEntryName
1590 static wxString
FilterInEntryName(const wxString
& str
)
1593 strResult
.Alloc(str
.Len());
1595 for ( const wxChar
*pc
= str
.c_str(); *pc
!= '\0'; pc
++ ) {
1596 if ( *pc
== wxT('\\') )
1605 // sanitize entry or group name: insert '\\' before any special characters
1606 static wxString
FilterOutEntryName(const wxString
& str
)
1609 strResult
.Alloc(str
.Len());
1611 for ( const wxChar
*pc
= str
.c_str(); *pc
!= wxT('\0'); pc
++ ) {
1614 // we explicitly allow some of "safe" chars and 8bit ASCII characters
1615 // which will probably never have special meaning
1616 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
1617 // should *not* be quoted
1618 if ( !wxIsalnum(c
) && !wxStrchr(wxT("@_/-!.*%"), c
) && ((c
& 0x80) == 0) )
1619 strResult
+= wxT('\\');
1627 // we can't put ?: in the ctor initializer list because it confuses some
1628 // broken compilers (Borland C++)
1629 static wxString
GetAppName(const wxString
& appName
)
1631 if ( !appName
&& wxTheApp
)
1632 return wxTheApp
->GetAppName();
1637 #endif // wxUSE_CONFIG