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_)
52 #define LINKAGEMODE _Optlink
60 // ----------------------------------------------------------------------------
62 // ----------------------------------------------------------------------------
63 #define CONST_CAST ((wxFileConfig *)this)->
65 // ----------------------------------------------------------------------------
67 // ----------------------------------------------------------------------------
72 // ----------------------------------------------------------------------------
73 // global functions declarations
74 // ----------------------------------------------------------------------------
76 // compare functions for sorting the arrays
77 static int LINKAGEMODE
CompareEntries(ConfigEntry
*p1
, ConfigEntry
*p2
);
78 static int LINKAGEMODE
CompareGroups(ConfigGroup
*p1
, ConfigGroup
*p2
);
81 static wxString
FilterInValue(const wxString
& str
);
82 static wxString
FilterOutValue(const wxString
& str
);
84 static wxString
FilterInEntryName(const wxString
& str
);
85 static wxString
FilterOutEntryName(const wxString
& str
);
87 // get the name to use in wxFileConfig ctor
88 static wxString
GetAppName(const wxString
& appname
);
90 // ============================================================================
92 // ============================================================================
94 // ----------------------------------------------------------------------------
96 // ----------------------------------------------------------------------------
97 wxString
wxFileConfig::GetGlobalDir()
102 strDir
= wxT("/etc/");
103 #elif defined(__WXPM__)
104 ULONG aulSysInfo
[QSV_MAX
] = {0};
108 rc
= DosQuerySysInfo( 1L, QSV_MAX
, (PVOID
)aulSysInfo
, sizeof(ULONG
)*QSV_MAX
);
111 drive
= aulSysInfo
[QSV_BOOT_DRIVE
- 1];
115 strDir
= "A:\\OS2\\";
118 strDir
= "B:\\OS2\\";
121 strDir
= "C:\\OS2\\";
124 strDir
= "D:\\OS2\\";
127 strDir
= "E:\\OS2\\";
130 strDir
= "F:\\OS2\\";
133 strDir
= "G:\\OS2\\";
136 strDir
= "H:\\OS2\\";
139 strDir
= "I:\\OS2\\";
142 strDir
= "J:\\OS2\\";
145 strDir
= "K:\\OS2\\";
148 strDir
= "L:\\OS2\\";
151 strDir
= "M:\\OS2\\";
154 strDir
= "N:\\OS2\\";
157 strDir
= "O:\\OS2\\";
160 strDir
= "P:\\OS2\\";
163 strDir
= "Q:\\OS2\\";
166 strDir
= "R:\\OS2\\";
169 strDir
= "S:\\OS2\\";
172 strDir
= "T:\\OS2\\";
175 strDir
= "U:\\OS2\\";
178 strDir
= "V:\\OS2\\";
181 strDir
= "W:\\OS2\\";
184 strDir
= "X:\\OS2\\";
187 strDir
= "Y:\\OS2\\";
190 strDir
= "Z:\\OS2\\";
194 #elif defined(__WXSTUBS__)
195 wxASSERT_MSG( FALSE
, wxT("TODO") ) ;
196 #elif defined(__WXMAC__)
201 if ( FindFolder( (short) kOnSystemDisk
, kPreferencesFolderType
, kDontCreateFolder
, &vRefNum
, &dirID
) == noErr
)
204 if ( FSMakeFSSpec( vRefNum
, dirID
, "\p" , &file
) == noErr
)
206 strDir
= wxMacFSSpec2UnixFilename( &file
) + "/" ;
211 wxChar szWinDir
[MAX_PATH
];
212 ::GetWindowsDirectory(szWinDir
, MAX_PATH
);
216 #endif // Unix/Windows
221 wxString
wxFileConfig::GetLocalDir()
226 wxGetHomeDir(&strDir
);
229 if (strDir
.Last() != wxT('/')) strDir
<< wxT('/');
231 if (strDir
.Last() != wxT('\\')) strDir
<< wxT('\\');
234 // no local dir concept on mac
235 return GetGlobalDir() ;
241 wxString
wxFileConfig::GetGlobalFileName(const wxChar
*szFile
)
243 wxString str
= GetGlobalDir();
246 if ( wxStrchr(szFile
, wxT('.')) == NULL
)
249 #elif defined( __WXMAC__ )
250 str
<< " Preferences";
258 wxString
wxFileConfig::GetLocalFileName(const wxChar
*szFile
)
260 wxString str
= GetLocalDir();
269 if ( wxStrchr(szFile
, wxT('.')) == NULL
)
275 str
<< " Preferences";
280 // ----------------------------------------------------------------------------
282 // ----------------------------------------------------------------------------
284 void wxFileConfig::Init()
287 m_pRootGroup
= new ConfigGroup(NULL
, "", this);
292 // it's not an error if (one of the) file(s) doesn't exist
294 // parse the global file
295 if ( !m_strGlobalFile
.IsEmpty() && wxFile::Exists(m_strGlobalFile
) ) {
296 wxTextFile
fileGlobal(m_strGlobalFile
);
298 if ( fileGlobal
.Open() ) {
299 Parse(fileGlobal
, FALSE
/* global */);
303 wxLogWarning(_("can't open global configuration file '%s'."),
304 m_strGlobalFile
.c_str());
307 // parse the local file
308 if ( !m_strLocalFile
.IsEmpty() && wxFile::Exists(m_strLocalFile
) ) {
309 wxTextFile
fileLocal(m_strLocalFile
);
310 if ( fileLocal
.Open() ) {
311 Parse(fileLocal
, TRUE
/* local */);
315 wxLogWarning(_("can't open user configuration file '%s'."),
316 m_strLocalFile
.c_str());
320 // constructor supports creation of wxFileConfig objects of any type
321 wxFileConfig::wxFileConfig(const wxString
& appName
, const wxString
& vendorName
,
322 const wxString
& strLocal
, const wxString
& strGlobal
,
324 : wxConfigBase(::GetAppName(appName
), vendorName
,
327 m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
329 // Make up names for files if empty
330 if ( m_strLocalFile
.IsEmpty() && (style
& wxCONFIG_USE_LOCAL_FILE
) )
332 m_strLocalFile
= GetLocalFileName(GetAppName());
335 if ( m_strGlobalFile
.IsEmpty() && (style
& wxCONFIG_USE_GLOBAL_FILE
) )
337 m_strGlobalFile
= GetGlobalFileName(GetAppName());
340 // Check if styles are not supplied, but filenames are, in which case
341 // add the correct styles.
342 if ( !m_strLocalFile
.IsEmpty() )
343 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE
);
345 if ( !m_strGlobalFile
.IsEmpty() )
346 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE
);
348 // if the path is not absolute, prepend the standard directory to it
349 // UNLESS wxCONFIG_USE_RELATIVE_PATH style is set
350 if ( !(style
& wxCONFIG_USE_RELATIVE_PATH
) )
352 if ( !m_strLocalFile
.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile
) )
354 wxString strLocal
= m_strLocalFile
;
355 m_strLocalFile
= GetLocalDir();
356 m_strLocalFile
<< strLocal
;
359 if ( !m_strGlobalFile
.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile
) )
361 wxString strGlobal
= m_strGlobalFile
;
362 m_strGlobalFile
= GetGlobalDir();
363 m_strGlobalFile
<< strGlobal
;
370 void wxFileConfig::CleanUp()
374 LineList
*pCur
= m_linesHead
;
375 while ( pCur
!= NULL
) {
376 LineList
*pNext
= pCur
->Next();
382 wxFileConfig::~wxFileConfig()
389 // ----------------------------------------------------------------------------
390 // parse a config file
391 // ----------------------------------------------------------------------------
393 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
395 const wxChar
*pStart
;
399 size_t nLineCount
= file
.GetLineCount();
400 for ( size_t n
= 0; n
< nLineCount
; n
++ ) {
403 // add the line to linked list
405 LineListAppend(strLine
);
407 // skip leading spaces
408 for ( pStart
= strLine
; wxIsspace(*pStart
); pStart
++ )
411 // skip blank/comment lines
412 if ( *pStart
== wxT('\0')|| *pStart
== wxT(';') || *pStart
== wxT('#') )
415 if ( *pStart
== wxT('[') ) { // a new group
418 while ( *++pEnd
!= wxT(']') ) {
419 if ( *pEnd
== wxT('\n') || *pEnd
== wxT('\0') )
423 if ( *pEnd
!= wxT(']') ) {
424 wxLogError(_("file '%s': unexpected character %c at line %d."),
425 file
.GetName(), *pEnd
, n
+ 1);
426 continue; // skip this line
429 // group name here is always considered as abs path
432 strGroup
<< wxCONFIG_PATH_SEPARATOR
433 << FilterInEntryName(wxString(pStart
, pEnd
- pStart
));
435 // will create it if doesn't yet exist
439 m_pCurrentGroup
->SetLine(m_linesTail
);
441 // check that there is nothing except comments left on this line
443 while ( *++pEnd
!= wxT('\0') && bCont
) {
452 // ignore whitespace ('\n' impossible here)
456 wxLogWarning(_("file '%s', line %d: '%s' "
457 "ignored after group header."),
458 file
.GetName(), n
+ 1, pEnd
);
464 const wxChar
*pEnd
= pStart
;
465 while ( *pEnd
!= wxT('=') && !wxIsspace(*pEnd
) ) {
466 if ( *pEnd
== wxT('\\') ) {
467 // next character may be space or not - still take it because it's
475 wxString
strKey(FilterInEntryName(wxString(pStart
, pEnd
)));
478 while ( isspace(*pEnd
) )
481 if ( *pEnd
++ != wxT('=') ) {
482 wxLogError(_("file '%s', line %d: '=' expected."),
483 file
.GetName(), n
+ 1);
486 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
488 if ( pEntry
== NULL
) {
490 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
493 pEntry
->SetLine(m_linesTail
);
496 if ( bLocal
&& pEntry
->IsImmutable() ) {
497 // immutable keys can't be changed by user
498 wxLogWarning(_("file '%s', line %d: value for "
499 "immutable key '%s' ignored."),
500 file
.GetName(), n
+ 1, strKey
.c_str());
503 // the condition below catches the cases (a) and (b) but not (c):
504 // (a) global key found second time in global file
505 // (b) key found second (or more) time in local file
506 // (c) key from global file now found in local one
507 // which is exactly what we want.
508 else if ( !bLocal
|| pEntry
->IsLocal() ) {
509 wxLogWarning(_("file '%s', line %d: key '%s' was first "
510 "found at line %d."),
511 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
514 pEntry
->SetLine(m_linesTail
);
519 while ( wxIsspace(*pEnd
) )
522 pEntry
->SetValue(FilterInValue(pEnd
), FALSE
/* read from file */);
528 // ----------------------------------------------------------------------------
530 // ----------------------------------------------------------------------------
532 void wxFileConfig::SetRootPath()
535 m_pCurrentGroup
= m_pRootGroup
;
538 void wxFileConfig::SetPath(const wxString
& strPath
)
540 wxArrayString aParts
;
542 if ( strPath
.IsEmpty() ) {
547 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
549 wxSplitPath(aParts
, strPath
);
552 // relative path, combine with current one
553 wxString strFullPath
= m_strPath
;
554 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
555 wxSplitPath(aParts
, strFullPath
);
558 // change current group
560 m_pCurrentGroup
= m_pRootGroup
;
561 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
562 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
563 if ( pNextGroup
== NULL
)
564 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
565 m_pCurrentGroup
= pNextGroup
;
568 // recombine path parts in one variable
570 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
571 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
575 // ----------------------------------------------------------------------------
577 // ----------------------------------------------------------------------------
579 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
582 return GetNextGroup(str
, lIndex
);
585 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
587 if ( size_t(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
588 str
= m_pCurrentGroup
->Groups()[lIndex
++]->Name();
595 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
598 return GetNextEntry(str
, lIndex
);
601 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
603 if ( size_t(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
604 str
= m_pCurrentGroup
->Entries()[lIndex
++]->Name();
611 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
613 size_t n
= m_pCurrentGroup
->Entries().Count();
615 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
616 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
617 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
618 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
619 n
+= GetNumberOfEntries(TRUE
);
620 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
627 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
629 size_t n
= m_pCurrentGroup
->Groups().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
+= GetNumberOfGroups(TRUE
);
636 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
643 // ----------------------------------------------------------------------------
644 // tests for existence
645 // ----------------------------------------------------------------------------
647 bool wxFileConfig::HasGroup(const wxString
& strName
) const
649 wxConfigPathChanger
path(this, strName
);
651 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
652 return pGroup
!= NULL
;
655 bool wxFileConfig::HasEntry(const wxString
& strName
) const
657 wxConfigPathChanger
path(this, strName
);
659 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
660 return pEntry
!= NULL
;
663 // ----------------------------------------------------------------------------
665 // ----------------------------------------------------------------------------
667 bool wxFileConfig::Read(const wxString
& key
,
668 wxString
* pStr
) const
670 wxConfigPathChanger
path(this, key
);
672 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
673 if (pEntry
== NULL
) {
677 *pStr
= ExpandEnvVars(pEntry
->Value());
682 bool wxFileConfig::Read(const wxString
& key
,
683 wxString
* pStr
, const wxString
& defVal
) const
685 wxConfigPathChanger
path(this, key
);
687 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
688 if (pEntry
== NULL
) {
689 if( IsRecordingDefaults() )
690 ((wxFileConfig
*)this)->Write(key
,defVal
);
691 *pStr
= ExpandEnvVars(defVal
);
695 *pStr
= ExpandEnvVars(pEntry
->Value());
700 bool wxFileConfig::Read(const wxString
& key
, long *pl
) const
703 if ( Read(key
, & str
) ) {
712 bool wxFileConfig::Write(const wxString
& key
, const wxString
& szValue
)
714 wxConfigPathChanger
path(this, key
);
716 wxString strName
= path
.Name();
717 if ( strName
.IsEmpty() ) {
718 // setting the value of a group is an error
719 wxASSERT_MSG( wxIsEmpty(szValue
), wxT("can't set value of a group!") );
721 // ... except if it's empty in which case it's a way to force it's creation
722 m_pCurrentGroup
->SetDirty();
724 // this will add a line for this group if it didn't have it before
725 (void)m_pCurrentGroup
->GetGroupLine();
730 // check that the name is reasonable
731 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
732 wxLogError(_("Config entry name cannot start with '%c'."),
733 wxCONFIG_IMMUTABLE_PREFIX
);
737 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
738 if ( pEntry
== NULL
)
739 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
741 pEntry
->SetValue(szValue
);
747 bool wxFileConfig::Write(const wxString
& key
, long lValue
)
749 // ltoa() is not ANSI :-(
751 buf
.Printf(wxT("%ld"), lValue
);
752 return Write(key
, buf
);
755 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
757 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() )
760 wxTempFile
file(m_strLocalFile
);
762 if ( !file
.IsOpened() ) {
763 wxLogError(_("can't open user configuration file."));
767 // write all strings to file
768 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
769 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
770 wxLogError(_("can't write user configuration file."));
776 return file
.Commit();
778 bool ret
= file
.Commit();
783 wxUnixFilename2FSSpec( m_strLocalFile
, &spec
) ;
785 if ( FSpGetFInfo( &spec
, &finfo
) == noErr
)
787 finfo
.fdType
= 'TEXT' ;
788 finfo
.fdCreator
= 'ttxt' ;
789 FSpSetFInfo( &spec
, &finfo
) ;
796 // ----------------------------------------------------------------------------
797 // renaming groups/entries
798 // ----------------------------------------------------------------------------
800 bool wxFileConfig::RenameEntry(const wxString
& oldName
,
801 const wxString
& newName
)
803 // check that the entry exists
804 ConfigEntry
*oldEntry
= m_pCurrentGroup
->FindEntry(oldName
);
808 // check that the new entry doesn't already exist
809 if ( m_pCurrentGroup
->FindEntry(newName
) )
812 // delete the old entry, create the new one
813 wxString value
= oldEntry
->Value();
814 if ( !m_pCurrentGroup
->DeleteEntry(oldName
) )
817 ConfigEntry
*newEntry
= m_pCurrentGroup
->AddEntry(newName
);
818 newEntry
->SetValue(value
);
823 bool wxFileConfig::RenameGroup(const wxString
& oldName
,
824 const wxString
& newName
)
826 // check that the group exists
827 ConfigGroup
*group
= m_pCurrentGroup
->FindSubgroup(oldName
);
831 // check that the new group doesn't already exist
832 if ( m_pCurrentGroup
->FindSubgroup(newName
) )
835 group
->Rename(newName
);
840 // ----------------------------------------------------------------------------
841 // delete groups/entries
842 // ----------------------------------------------------------------------------
844 bool wxFileConfig::DeleteEntry(const wxString
& key
, bool bGroupIfEmptyAlso
)
846 wxConfigPathChanger
path(this, key
);
848 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
851 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
852 if ( m_pCurrentGroup
!= m_pRootGroup
) {
853 ConfigGroup
*pGroup
= m_pCurrentGroup
;
854 SetPath(wxT("..")); // changes m_pCurrentGroup!
855 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
857 //else: never delete the root group
863 bool wxFileConfig::DeleteGroup(const wxString
& key
)
865 wxConfigPathChanger
path(this, key
);
867 return m_pCurrentGroup
->DeleteSubgroupByName(path
.Name());
870 bool wxFileConfig::DeleteAll()
874 if ( remove(m_strLocalFile
.fn_str()) == -1 )
875 wxLogSysError(_("can't delete user configuration file '%s'"), m_strLocalFile
.c_str());
877 m_strLocalFile
= m_strGlobalFile
= wxT("");
883 // ----------------------------------------------------------------------------
884 // linked list functions
885 // ----------------------------------------------------------------------------
887 // append a new line to the end of the list
888 LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
890 LineList
*pLine
= new LineList(str
);
892 if ( m_linesTail
== NULL
) {
898 m_linesTail
->SetNext(pLine
);
899 pLine
->SetPrev(m_linesTail
);
906 // insert a new line after the given one or in the very beginning if !pLine
907 LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
910 if ( pLine
== m_linesTail
)
911 return LineListAppend(str
);
913 LineList
*pNewLine
= new LineList(str
);
914 if ( pLine
== NULL
) {
915 // prepend to the list
916 pNewLine
->SetNext(m_linesHead
);
917 m_linesHead
->SetPrev(pNewLine
);
918 m_linesHead
= pNewLine
;
921 // insert before pLine
922 LineList
*pNext
= pLine
->Next();
923 pNewLine
->SetNext(pNext
);
924 pNewLine
->SetPrev(pLine
);
925 pNext
->SetPrev(pNewLine
);
926 pLine
->SetNext(pNewLine
);
932 void wxFileConfig::LineListRemove(LineList
*pLine
)
934 LineList
*pPrev
= pLine
->Prev(),
935 *pNext
= pLine
->Next();
941 pPrev
->SetNext(pNext
);
947 pNext
->SetPrev(pPrev
);
952 bool wxFileConfig::LineListIsEmpty()
954 return m_linesHead
== NULL
;
957 // ============================================================================
958 // wxFileConfig::ConfigGroup
959 // ============================================================================
961 // ----------------------------------------------------------------------------
963 // ----------------------------------------------------------------------------
966 ConfigGroup::ConfigGroup(ConfigGroup
*pParent
,
967 const wxString
& strName
,
968 wxFileConfig
*pConfig
)
969 : m_aEntries(CompareEntries
),
970 m_aSubgroups(CompareGroups
),
982 // dtor deletes all children
983 ConfigGroup::~ConfigGroup()
986 size_t n
, nCount
= m_aEntries
.Count();
987 for ( n
= 0; n
< nCount
; n
++ )
988 delete m_aEntries
[n
];
991 nCount
= m_aSubgroups
.Count();
992 for ( n
= 0; n
< nCount
; n
++ )
993 delete m_aSubgroups
[n
];
996 // ----------------------------------------------------------------------------
998 // ----------------------------------------------------------------------------
1000 void ConfigGroup::SetLine(LineList
*pLine
)
1002 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
1008 This is a bit complicated, so let me explain it in details. All lines that
1009 were read from the local file (the only one we will ever modify) are stored
1010 in a (doubly) linked list. Our problem is to know at which position in this
1011 list should we insert the new entries/subgroups. To solve it we keep three
1012 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
1014 m_pLine points to the line containing "[group_name]"
1015 m_pLastEntry points to the last entry of this group in the local file.
1016 m_pLastGroup subgroup
1018 Initially, they're NULL all three. When the group (an entry/subgroup) is read
1019 from the local file, the corresponding variable is set. However, if the group
1020 was read from the global file and then modified or created by the application
1021 these variables are still NULL and we need to create the corresponding lines.
1022 See the following functions (and comments preceding them) for the details of
1025 Also, when our last entry/group are deleted we need to find the new last
1026 element - the code in DeleteEntry/Subgroup does this by backtracking the list
1027 of lines until it either founds an entry/subgroup (and this is the new last
1028 element) or the m_pLine of the group, in which case there are no more entries
1029 (or subgroups) left and m_pLast<element> becomes NULL.
1031 NB: This last problem could be avoided for entries if we added new entries
1032 immediately after m_pLine, but in this case the entries would appear
1033 backwards in the config file (OTOH, it's not that important) and as we
1034 would still need to do it for the subgroups the code wouldn't have been
1035 significantly less complicated.
1038 // Return the line which contains "[our name]". If we're still not in the list,
1039 // add our line to it immediately after the last line of our parent group if we
1040 // have it or in the very beginning if we're the root group.
1041 LineList
*ConfigGroup::GetGroupLine()
1043 if ( m_pLine
== NULL
) {
1044 ConfigGroup
*pParent
= Parent();
1046 // this group wasn't present in local config file, add it now
1047 if ( pParent
!= NULL
) {
1048 wxString strFullName
;
1049 strFullName
<< wxT("[")
1051 << FilterOutEntryName(GetFullName().c_str() + 1)
1053 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
1054 pParent
->GetLastGroupLine());
1055 pParent
->SetLastGroup(this); // we're surely after all the others
1058 // we return NULL, so that LineListInsert() will insert us in the
1066 // Return the last line belonging to the subgroups of this group (after which
1067 // we can add a new subgroup), if we don't have any subgroups or entries our
1068 // last line is the group line (m_pLine) itself.
1069 LineList
*ConfigGroup::GetLastGroupLine()
1071 // if we have any subgroups, our last line is the last line of the last
1073 if ( m_pLastGroup
!= NULL
) {
1074 LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
1076 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
1080 // no subgroups, so the last line is the line of thelast entry (if any)
1081 return GetLastEntryLine();
1084 // return the last line belonging to the entries of this group (after which
1085 // we can add a new entry), if we don't have any entries we will add the new
1086 // one immediately after the group line itself.
1087 LineList
*ConfigGroup::GetLastEntryLine()
1089 if ( m_pLastEntry
!= NULL
) {
1090 LineList
*pLine
= m_pLastEntry
->GetLine();
1092 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
1096 // no entries: insert after the group header
1097 return GetGroupLine();
1100 // ----------------------------------------------------------------------------
1102 // ----------------------------------------------------------------------------
1104 void ConfigGroup::Rename(const wxString
& newName
)
1106 m_strName
= newName
;
1108 LineList
*line
= GetGroupLine();
1109 wxString strFullName
;
1110 strFullName
<< wxT("[") << (GetFullName().c_str() + 1) << wxT("]"); // +1: no '/'
1111 line
->SetText(strFullName
);
1116 wxString
ConfigGroup::GetFullName() const
1119 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
1124 // ----------------------------------------------------------------------------
1126 // ----------------------------------------------------------------------------
1128 // use binary search because the array is sorted
1130 ConfigGroup::FindEntry(const wxChar
*szName
) const
1134 hi
= m_aEntries
.Count();
1136 ConfigEntry
*pEntry
;
1140 pEntry
= m_aEntries
[i
];
1142 #if wxCONFIG_CASE_SENSITIVE
1143 res
= wxStrcmp(pEntry
->Name(), szName
);
1145 res
= wxStricmp(pEntry
->Name(), szName
);
1160 ConfigGroup::FindSubgroup(const wxChar
*szName
) const
1164 hi
= m_aSubgroups
.Count();
1166 ConfigGroup
*pGroup
;
1170 pGroup
= m_aSubgroups
[i
];
1172 #if wxCONFIG_CASE_SENSITIVE
1173 res
= wxStrcmp(pGroup
->Name(), szName
);
1175 res
= wxStricmp(pGroup
->Name(), szName
);
1189 // ----------------------------------------------------------------------------
1190 // create a new item
1191 // ----------------------------------------------------------------------------
1193 // create a new entry and add it to the current group
1195 ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
1197 wxASSERT( FindEntry(strName
) == NULL
);
1199 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
1200 m_aEntries
.Add(pEntry
);
1205 // create a new group and add it to the current group
1207 ConfigGroup::AddSubgroup(const wxString
& strName
)
1209 wxASSERT( FindSubgroup(strName
) == NULL
);
1211 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
1212 m_aSubgroups
.Add(pGroup
);
1217 // ----------------------------------------------------------------------------
1219 // ----------------------------------------------------------------------------
1222 The delete operations are _very_ slow if we delete the last item of this
1223 group (see comments before GetXXXLineXXX functions for more details),
1224 so it's much better to start with the first entry/group if we want to
1225 delete several of them.
1228 bool ConfigGroup::DeleteSubgroupByName(const wxChar
*szName
)
1230 return DeleteSubgroup(FindSubgroup(szName
));
1233 // doesn't delete the subgroup itself, but does remove references to it from
1234 // all other data structures (and normally the returned pointer should be
1235 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1236 bool ConfigGroup::DeleteSubgroup(ConfigGroup
*pGroup
)
1238 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1240 // delete all entries
1241 size_t nCount
= pGroup
->m_aEntries
.Count();
1242 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1243 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1244 if ( pLine
!= NULL
)
1245 m_pConfig
->LineListRemove(pLine
);
1248 // and subgroups of this sungroup
1249 nCount
= pGroup
->m_aSubgroups
.Count();
1250 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1251 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[0]);
1254 LineList
*pLine
= pGroup
->m_pLine
;
1255 if ( pLine
!= NULL
) {
1256 // notice that we may do this test inside the previous "if" because the
1257 // last entry's line is surely !NULL
1258 if ( pGroup
== m_pLastGroup
) {
1259 // our last entry is being deleted - find the last one which stays
1260 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1262 // go back until we find a subgroup or reach the group's line
1263 ConfigGroup
*pNewLast
= NULL
;
1264 size_t n
, nSubgroups
= m_aSubgroups
.Count();
1266 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1267 // is it our subgroup?
1268 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1269 // do _not_ call GetGroupLine! we don't want to add it to the local
1270 // file if it's not already there
1271 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1272 pNewLast
= m_aSubgroups
[n
];
1275 if ( pNewLast
!= NULL
) // found?
1279 if ( pl
== m_pLine
) {
1280 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1282 // we've reached the group line without finding any subgroups
1283 m_pLastGroup
= NULL
;
1286 m_pLastGroup
= pNewLast
;
1289 m_pConfig
->LineListRemove(pLine
);
1294 m_aSubgroups
.Remove(pGroup
);
1300 bool ConfigGroup::DeleteEntry(const wxChar
*szName
)
1302 ConfigEntry
*pEntry
= FindEntry(szName
);
1303 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1305 LineList
*pLine
= pEntry
->GetLine();
1306 if ( pLine
!= NULL
) {
1307 // notice that we may do this test inside the previous "if" because the
1308 // last entry's line is surely !NULL
1309 if ( pEntry
== m_pLastEntry
) {
1310 // our last entry is being deleted - find the last one which stays
1311 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1313 // go back until we find another entry or reach the group's line
1314 ConfigEntry
*pNewLast
= NULL
;
1315 size_t n
, nEntries
= m_aEntries
.Count();
1317 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1318 // is it our subgroup?
1319 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1320 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1321 pNewLast
= m_aEntries
[n
];
1324 if ( pNewLast
!= NULL
) // found?
1328 if ( pl
== m_pLine
) {
1329 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1331 // we've reached the group line without finding any subgroups
1332 m_pLastEntry
= NULL
;
1335 m_pLastEntry
= pNewLast
;
1338 m_pConfig
->LineListRemove(pLine
);
1341 // we must be written back for the changes to be saved
1344 m_aEntries
.Remove(pEntry
);
1350 // ----------------------------------------------------------------------------
1352 // ----------------------------------------------------------------------------
1353 void ConfigGroup::SetDirty()
1356 if ( Parent() != NULL
) // propagate upwards
1357 Parent()->SetDirty();
1360 // ============================================================================
1361 // wxFileConfig::ConfigEntry
1362 // ============================================================================
1364 // ----------------------------------------------------------------------------
1366 // ----------------------------------------------------------------------------
1367 ConfigEntry::ConfigEntry(ConfigGroup
*pParent
,
1368 const wxString
& strName
,
1370 : m_strName(strName
)
1372 wxASSERT( !strName
.IsEmpty() );
1374 m_pParent
= pParent
;
1380 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1382 m_strName
.erase(0, 1); // remove first character
1385 // ----------------------------------------------------------------------------
1387 // ----------------------------------------------------------------------------
1389 void ConfigEntry::SetLine(LineList
*pLine
)
1391 if ( m_pLine
!= NULL
) {
1392 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1393 Name().c_str(), m_pParent
->GetFullName().c_str());
1397 Group()->SetLastEntry(this);
1400 // second parameter is FALSE if we read the value from file and prevents the
1401 // entry from being marked as 'dirty'
1402 void ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1404 if ( bUser
&& IsImmutable() ) {
1405 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1410 // do nothing if it's the same value
1411 if ( strValue
== m_strValue
)
1414 m_strValue
= strValue
;
1417 wxString strVal
= FilterOutValue(strValue
);
1419 strLine
<< FilterOutEntryName(m_strName
) << wxT('=') << strVal
;
1421 if ( m_pLine
!= NULL
) {
1422 // entry was read from the local config file, just modify the line
1423 m_pLine
->SetText(strLine
);
1426 // add a new line to the file
1427 wxASSERT( m_nLine
== wxNOT_FOUND
); // consistency check
1429 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1430 Group()->GetLastEntryLine());
1431 Group()->SetLastEntry(this);
1438 void ConfigEntry::SetDirty()
1441 Group()->SetDirty();
1444 // ============================================================================
1446 // ============================================================================
1448 // ----------------------------------------------------------------------------
1449 // compare functions for array sorting
1450 // ----------------------------------------------------------------------------
1452 int CompareEntries(ConfigEntry
*p1
,
1455 #if wxCONFIG_CASE_SENSITIVE
1456 return wxStrcmp(p1
->Name(), p2
->Name());
1458 return wxStricmp(p1
->Name(), p2
->Name());
1462 int CompareGroups(ConfigGroup
*p1
,
1465 #if wxCONFIG_CASE_SENSITIVE
1466 return wxStrcmp(p1
->Name(), p2
->Name());
1468 return wxStricmp(p1
->Name(), p2
->Name());
1472 // ----------------------------------------------------------------------------
1474 // ----------------------------------------------------------------------------
1476 // undo FilterOutValue
1477 static wxString
FilterInValue(const wxString
& str
)
1480 strResult
.Alloc(str
.Len());
1482 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1484 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1485 if ( str
[n
] == wxT('\\') ) {
1486 switch ( str
[++n
] ) {
1488 strResult
+= wxT('\n');
1492 strResult
+= wxT('\r');
1496 strResult
+= wxT('\t');
1500 strResult
+= wxT('\\');
1504 strResult
+= wxT('"');
1509 if ( str
[n
] != wxT('"') || !bQuoted
)
1510 strResult
+= str
[n
];
1511 else if ( n
!= str
.Len() - 1 ) {
1512 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1515 //else: it's the last quote of a quoted string, ok
1522 // quote the string before writing it to file
1523 static wxString
FilterOutValue(const wxString
& str
)
1529 strResult
.Alloc(str
.Len());
1531 // quoting is necessary to preserve spaces in the beginning of the string
1532 bool bQuote
= wxIsspace(str
[0]) || str
[0] == wxT('"');
1535 strResult
+= wxT('"');
1538 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
1561 //else: fall through
1564 strResult
+= str
[n
];
1565 continue; // nothing special to do
1568 // we get here only for special characters
1569 strResult
<< wxT('\\') << c
;
1573 strResult
+= wxT('"');
1578 // undo FilterOutEntryName
1579 static wxString
FilterInEntryName(const wxString
& str
)
1582 strResult
.Alloc(str
.Len());
1584 for ( const wxChar
*pc
= str
.c_str(); *pc
!= '\0'; pc
++ ) {
1585 if ( *pc
== wxT('\\') )
1594 // sanitize entry or group name: insert '\\' before any special characters
1595 static wxString
FilterOutEntryName(const wxString
& str
)
1598 strResult
.Alloc(str
.Len());
1600 for ( const wxChar
*pc
= str
.c_str(); *pc
!= wxT('\0'); pc
++ ) {
1603 // we explicitly allow some of "safe" chars and 8bit ASCII characters
1604 // which will probably never have special meaning
1605 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
1606 // should *not* be quoted
1607 if ( !wxIsalnum(c
) && !wxStrchr(wxT("@_/-!.*%"), c
) && ((c
& 0x80) == 0) )
1608 strResult
+= wxT('\\');
1616 // we can't put ?: in the ctor initializer list because it confuses some
1617 // broken compilers (Borland C++)
1618 static wxString
GetAppName(const wxString
& appName
)
1620 if ( !appName
&& wxTheApp
)
1621 return wxTheApp
->GetAppName();
1626 #endif // wxUSE_CONFIG