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
= _T("/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
, _T("TODO") ) ;
196 #elif defined(__WXMAC__)
197 wxASSERT_MSG( FALSE
, _T("TODO") ) ;
199 wxChar szWinDir
[MAX_PATH
];
200 ::GetWindowsDirectory(szWinDir
, MAX_PATH
);
204 #endif // Unix/Windows
209 wxString
wxFileConfig::GetLocalDir()
213 wxGetHomeDir(&strDir
);
216 if (strDir
.Last() != _T('/')) strDir
<< _T('/');
218 if (strDir
.Last() != _T('\\')) strDir
<< _T('\\');
224 wxString
wxFileConfig::GetGlobalFileName(const wxChar
*szFile
)
226 wxString str
= GetGlobalDir();
229 if ( wxStrchr(szFile
, _T('.')) == NULL
)
239 wxString
wxFileConfig::GetLocalFileName(const wxChar
*szFile
)
241 wxString str
= GetLocalDir();
250 if ( wxStrchr(szFile
, _T('.')) == NULL
)
257 // ----------------------------------------------------------------------------
259 // ----------------------------------------------------------------------------
261 void wxFileConfig::Init()
264 m_pRootGroup
= new ConfigGroup(NULL
, "", this);
269 // it's not an error if (one of the) file(s) doesn't exist
271 // parse the global file
272 if ( !m_strGlobalFile
.IsEmpty() && wxFile::Exists(m_strGlobalFile
) ) {
273 wxTextFile
fileGlobal(m_strGlobalFile
);
275 if ( fileGlobal
.Open() ) {
276 Parse(fileGlobal
, FALSE
/* global */);
280 wxLogWarning(_("can't open global configuration file '%s'."),
281 m_strGlobalFile
.c_str());
284 // parse the local file
285 if ( !m_strLocalFile
.IsEmpty() && wxFile::Exists(m_strLocalFile
) ) {
286 wxTextFile
fileLocal(m_strLocalFile
);
287 if ( fileLocal
.Open() ) {
288 Parse(fileLocal
, TRUE
/* local */);
292 wxLogWarning(_("can't open user configuration file '%s'."),
293 m_strLocalFile
.c_str());
297 // constructor supports creation of wxFileConfig objects of any type
298 wxFileConfig::wxFileConfig(const wxString
& appName
, const wxString
& vendorName
,
299 const wxString
& strLocal
, const wxString
& strGlobal
,
301 : wxConfigBase(::GetAppName(appName
), vendorName
,
304 m_strLocalFile(strLocal
), m_strGlobalFile(strGlobal
)
306 // Make up names for files if empty
307 if ( m_strLocalFile
.IsEmpty() && (style
& wxCONFIG_USE_LOCAL_FILE
) )
309 m_strLocalFile
= GetLocalFileName(GetAppName());
312 if ( m_strGlobalFile
.IsEmpty() && (style
& wxCONFIG_USE_GLOBAL_FILE
) )
314 m_strGlobalFile
= GetGlobalFileName(GetAppName());
317 // Check if styles are not supplied, but filenames are, in which case
318 // add the correct styles.
319 if ( !m_strLocalFile
.IsEmpty() )
320 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE
);
322 if ( !m_strGlobalFile
.IsEmpty() )
323 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE
);
325 // if the path is not absolute, prepend the standard directory to it
326 // UNLESS wxCONFIG_USE_RELATIVE_PATH style is set
327 if ( !(style
& wxCONFIG_USE_RELATIVE_PATH
) ){
328 if ( !m_strLocalFile
.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile
) )
330 wxString strLocal
= m_strLocalFile
;
331 m_strLocalFile
= GetLocalDir();
332 m_strLocalFile
<< strLocal
;
335 if ( !m_strGlobalFile
.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile
) )
337 wxString strGlobal
= m_strGlobalFile
;
338 m_strGlobalFile
= GetGlobalDir();
339 m_strGlobalFile
<< strGlobal
;
346 void wxFileConfig::CleanUp()
350 LineList
*pCur
= m_linesHead
;
351 while ( pCur
!= NULL
) {
352 LineList
*pNext
= pCur
->Next();
358 wxFileConfig::~wxFileConfig()
365 // ----------------------------------------------------------------------------
366 // parse a config file
367 // ----------------------------------------------------------------------------
369 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
371 const wxChar
*pStart
;
375 size_t nLineCount
= file
.GetLineCount();
376 for ( size_t n
= 0; n
< nLineCount
; n
++ ) {
379 // add the line to linked list
381 LineListAppend(strLine
);
383 // skip leading spaces
384 for ( pStart
= strLine
; wxIsspace(*pStart
); pStart
++ )
387 // skip blank/comment lines
388 if ( *pStart
== _T('\0')|| *pStart
== _T(';') || *pStart
== _T('#') )
391 if ( *pStart
== _T('[') ) { // a new group
394 while ( *++pEnd
!= _T(']') ) {
395 if ( *pEnd
== _T('\n') || *pEnd
== _T('\0') )
399 if ( *pEnd
!= _T(']') ) {
400 wxLogError(_("file '%s': unexpected character %c at line %d."),
401 file
.GetName(), *pEnd
, n
+ 1);
402 continue; // skip this line
405 // group name here is always considered as abs path
408 strGroup
<< wxCONFIG_PATH_SEPARATOR
409 << FilterInEntryName(wxString(pStart
, pEnd
- pStart
));
411 // will create it if doesn't yet exist
415 m_pCurrentGroup
->SetLine(m_linesTail
);
417 // check that there is nothing except comments left on this line
419 while ( *++pEnd
!= _T('\0') && bCont
) {
428 // ignore whitespace ('\n' impossible here)
432 wxLogWarning(_("file '%s', line %d: '%s' "
433 "ignored after group header."),
434 file
.GetName(), n
+ 1, pEnd
);
440 const wxChar
*pEnd
= pStart
;
441 while ( *pEnd
!= _T('=') && !wxIsspace(*pEnd
) ) {
442 if ( *pEnd
== _T('\\') ) {
443 // next character may be space or not - still take it because it's
451 wxString
strKey(FilterInEntryName(wxString(pStart
, pEnd
)));
454 while ( isspace(*pEnd
) )
457 if ( *pEnd
++ != _T('=') ) {
458 wxLogError(_("file '%s', line %d: '=' expected."),
459 file
.GetName(), n
+ 1);
462 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
464 if ( pEntry
== NULL
) {
466 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
469 pEntry
->SetLine(m_linesTail
);
472 if ( bLocal
&& pEntry
->IsImmutable() ) {
473 // immutable keys can't be changed by user
474 wxLogWarning(_("file '%s', line %d: value for "
475 "immutable key '%s' ignored."),
476 file
.GetName(), n
+ 1, strKey
.c_str());
479 // the condition below catches the cases (a) and (b) but not (c):
480 // (a) global key found second time in global file
481 // (b) key found second (or more) time in local file
482 // (c) key from global file now found in local one
483 // which is exactly what we want.
484 else if ( !bLocal
|| pEntry
->IsLocal() ) {
485 wxLogWarning(_("file '%s', line %d: key '%s' was first "
486 "found at line %d."),
487 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
490 pEntry
->SetLine(m_linesTail
);
495 while ( wxIsspace(*pEnd
) )
498 pEntry
->SetValue(FilterInValue(pEnd
), FALSE
/* read from file */);
504 // ----------------------------------------------------------------------------
506 // ----------------------------------------------------------------------------
508 void wxFileConfig::SetRootPath()
511 m_pCurrentGroup
= m_pRootGroup
;
514 void wxFileConfig::SetPath(const wxString
& strPath
)
516 wxArrayString aParts
;
518 if ( strPath
.IsEmpty() ) {
523 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
525 wxSplitPath(aParts
, strPath
);
528 // relative path, combine with current one
529 wxString strFullPath
= m_strPath
;
530 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
531 wxSplitPath(aParts
, strFullPath
);
534 // change current group
536 m_pCurrentGroup
= m_pRootGroup
;
537 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
538 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
539 if ( pNextGroup
== NULL
)
540 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
541 m_pCurrentGroup
= pNextGroup
;
544 // recombine path parts in one variable
546 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
547 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
551 // ----------------------------------------------------------------------------
553 // ----------------------------------------------------------------------------
555 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
558 return GetNextGroup(str
, lIndex
);
561 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
563 if ( size_t(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
564 str
= m_pCurrentGroup
->Groups()[lIndex
++]->Name();
571 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
574 return GetNextEntry(str
, lIndex
);
577 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
579 if ( size_t(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
580 str
= m_pCurrentGroup
->Entries()[lIndex
++]->Name();
587 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
589 size_t n
= m_pCurrentGroup
->Entries().Count();
591 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
592 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
593 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
594 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
595 n
+= GetNumberOfEntries(TRUE
);
596 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
603 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
605 size_t n
= m_pCurrentGroup
->Groups().Count();
607 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
608 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
609 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
610 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
611 n
+= GetNumberOfGroups(TRUE
);
612 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
619 // ----------------------------------------------------------------------------
620 // tests for existence
621 // ----------------------------------------------------------------------------
623 bool wxFileConfig::HasGroup(const wxString
& strName
) const
625 wxConfigPathChanger
path(this, strName
);
627 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
628 return pGroup
!= NULL
;
631 bool wxFileConfig::HasEntry(const wxString
& strName
) const
633 wxConfigPathChanger
path(this, strName
);
635 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
636 return pEntry
!= NULL
;
639 // ----------------------------------------------------------------------------
641 // ----------------------------------------------------------------------------
643 bool wxFileConfig::Read(const wxString
& key
,
644 wxString
* pStr
) const
646 wxConfigPathChanger
path(this, key
);
648 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
649 if (pEntry
== NULL
) {
653 *pStr
= ExpandEnvVars(pEntry
->Value());
658 bool wxFileConfig::Read(const wxString
& key
,
659 wxString
* pStr
, const wxString
& defVal
) const
661 wxConfigPathChanger
path(this, key
);
663 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
664 if (pEntry
== NULL
) {
665 if( IsRecordingDefaults() )
666 ((wxFileConfig
*)this)->Write(key
,defVal
);
667 *pStr
= ExpandEnvVars(defVal
);
671 *pStr
= ExpandEnvVars(pEntry
->Value());
676 bool wxFileConfig::Read(const wxString
& key
, long *pl
) const
679 if ( Read(key
, & str
) ) {
688 bool wxFileConfig::Write(const wxString
& key
, const wxString
& szValue
)
690 wxConfigPathChanger
path(this, key
);
692 wxString strName
= path
.Name();
693 if ( strName
.IsEmpty() ) {
694 // setting the value of a group is an error
695 wxASSERT_MSG( wxIsEmpty(szValue
), _T("can't set value of a group!") );
697 // ... except if it's empty in which case it's a way to force it's creation
698 m_pCurrentGroup
->SetDirty();
700 // this will add a line for this group if it didn't have it before
701 (void)m_pCurrentGroup
->GetGroupLine();
706 // check that the name is reasonable
707 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
708 wxLogError(_("Config entry name cannot start with '%c'."),
709 wxCONFIG_IMMUTABLE_PREFIX
);
713 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
714 if ( pEntry
== NULL
)
715 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
717 pEntry
->SetValue(szValue
);
723 bool wxFileConfig::Write(const wxString
& key
, long lValue
)
725 // ltoa() is not ANSI :-(
727 buf
.Printf(_T("%ld"), lValue
);
728 return Write(key
, buf
);
731 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
733 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() )
736 wxTempFile
file(m_strLocalFile
);
738 if ( !file
.IsOpened() ) {
739 wxLogError(_("can't open user configuration file."));
743 // write all strings to file
744 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
745 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
746 wxLogError(_("can't write user configuration file."));
751 return file
.Commit();
754 // ----------------------------------------------------------------------------
755 // renaming groups/entries
756 // ----------------------------------------------------------------------------
758 bool wxFileConfig::RenameEntry(const wxString
& oldName
,
759 const wxString
& newName
)
761 // check that the entry exists
762 ConfigEntry
*oldEntry
= m_pCurrentGroup
->FindEntry(oldName
);
766 // check that the new entry doesn't already exist
767 if ( m_pCurrentGroup
->FindEntry(newName
) )
770 // delete the old entry, create the new one
771 wxString value
= oldEntry
->Value();
772 if ( !m_pCurrentGroup
->DeleteEntry(oldName
) )
775 ConfigEntry
*newEntry
= m_pCurrentGroup
->AddEntry(newName
);
776 newEntry
->SetValue(value
);
781 bool wxFileConfig::RenameGroup(const wxString
& oldName
,
782 const wxString
& newName
)
784 // check that the group exists
785 ConfigGroup
*group
= m_pCurrentGroup
->FindSubgroup(oldName
);
789 // check that the new group doesn't already exist
790 if ( m_pCurrentGroup
->FindSubgroup(newName
) )
793 group
->Rename(newName
);
798 // ----------------------------------------------------------------------------
799 // delete groups/entries
800 // ----------------------------------------------------------------------------
802 bool wxFileConfig::DeleteEntry(const wxString
& key
, bool bGroupIfEmptyAlso
)
804 wxConfigPathChanger
path(this, key
);
806 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
809 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
810 if ( m_pCurrentGroup
!= m_pRootGroup
) {
811 ConfigGroup
*pGroup
= m_pCurrentGroup
;
812 SetPath(_T("..")); // changes m_pCurrentGroup!
813 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
815 //else: never delete the root group
821 bool wxFileConfig::DeleteGroup(const wxString
& key
)
823 wxConfigPathChanger
path(this, key
);
825 return m_pCurrentGroup
->DeleteSubgroupByName(path
.Name());
828 bool wxFileConfig::DeleteAll()
832 if ( remove(m_strLocalFile
.fn_str()) == -1 )
833 wxLogSysError(_("can't delete user configuration file '%s'"), m_strLocalFile
.c_str());
835 m_strLocalFile
= m_strGlobalFile
= _T("");
841 // ----------------------------------------------------------------------------
842 // linked list functions
843 // ----------------------------------------------------------------------------
845 // append a new line to the end of the list
846 LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
848 LineList
*pLine
= new LineList(str
);
850 if ( m_linesTail
== NULL
) {
856 m_linesTail
->SetNext(pLine
);
857 pLine
->SetPrev(m_linesTail
);
864 // insert a new line after the given one or in the very beginning if !pLine
865 LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
868 if ( pLine
== m_linesTail
)
869 return LineListAppend(str
);
871 LineList
*pNewLine
= new LineList(str
);
872 if ( pLine
== NULL
) {
873 // prepend to the list
874 pNewLine
->SetNext(m_linesHead
);
875 m_linesHead
->SetPrev(pNewLine
);
876 m_linesHead
= pNewLine
;
879 // insert before pLine
880 LineList
*pNext
= pLine
->Next();
881 pNewLine
->SetNext(pNext
);
882 pNewLine
->SetPrev(pLine
);
883 pNext
->SetPrev(pNewLine
);
884 pLine
->SetNext(pNewLine
);
890 void wxFileConfig::LineListRemove(LineList
*pLine
)
892 LineList
*pPrev
= pLine
->Prev(),
893 *pNext
= pLine
->Next();
899 pPrev
->SetNext(pNext
);
905 pNext
->SetPrev(pPrev
);
910 bool wxFileConfig::LineListIsEmpty()
912 return m_linesHead
== NULL
;
915 // ============================================================================
916 // wxFileConfig::ConfigGroup
917 // ============================================================================
919 // ----------------------------------------------------------------------------
921 // ----------------------------------------------------------------------------
924 ConfigGroup::ConfigGroup(ConfigGroup
*pParent
,
925 const wxString
& strName
,
926 wxFileConfig
*pConfig
)
927 : m_aEntries(CompareEntries
),
928 m_aSubgroups(CompareGroups
),
940 // dtor deletes all children
941 ConfigGroup::~ConfigGroup()
944 size_t n
, nCount
= m_aEntries
.Count();
945 for ( n
= 0; n
< nCount
; n
++ )
946 delete m_aEntries
[n
];
949 nCount
= m_aSubgroups
.Count();
950 for ( n
= 0; n
< nCount
; n
++ )
951 delete m_aSubgroups
[n
];
954 // ----------------------------------------------------------------------------
956 // ----------------------------------------------------------------------------
958 void ConfigGroup::SetLine(LineList
*pLine
)
960 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
966 This is a bit complicated, so let me explain it in details. All lines that
967 were read from the local file (the only one we will ever modify) are stored
968 in a (doubly) linked list. Our problem is to know at which position in this
969 list should we insert the new entries/subgroups. To solve it we keep three
970 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
972 m_pLine points to the line containing "[group_name]"
973 m_pLastEntry points to the last entry of this group in the local file.
974 m_pLastGroup subgroup
976 Initially, they're NULL all three. When the group (an entry/subgroup) is read
977 from the local file, the corresponding variable is set. However, if the group
978 was read from the global file and then modified or created by the application
979 these variables are still NULL and we need to create the corresponding lines.
980 See the following functions (and comments preceding them) for the details of
983 Also, when our last entry/group are deleted we need to find the new last
984 element - the code in DeleteEntry/Subgroup does this by backtracking the list
985 of lines until it either founds an entry/subgroup (and this is the new last
986 element) or the m_pLine of the group, in which case there are no more entries
987 (or subgroups) left and m_pLast<element> becomes NULL.
989 NB: This last problem could be avoided for entries if we added new entries
990 immediately after m_pLine, but in this case the entries would appear
991 backwards in the config file (OTOH, it's not that important) and as we
992 would still need to do it for the subgroups the code wouldn't have been
993 significantly less complicated.
996 // Return the line which contains "[our name]". If we're still not in the list,
997 // add our line to it immediately after the last line of our parent group if we
998 // have it or in the very beginning if we're the root group.
999 LineList
*ConfigGroup::GetGroupLine()
1001 if ( m_pLine
== NULL
) {
1002 ConfigGroup
*pParent
= Parent();
1004 // this group wasn't present in local config file, add it now
1005 if ( pParent
!= NULL
) {
1006 wxString strFullName
;
1007 strFullName
<< _T("[")
1009 << FilterOutEntryName(GetFullName().c_str() + 1)
1011 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
1012 pParent
->GetLastGroupLine());
1013 pParent
->SetLastGroup(this); // we're surely after all the others
1016 // we return NULL, so that LineListInsert() will insert us in the
1024 // Return the last line belonging to the subgroups of this group (after which
1025 // we can add a new subgroup), if we don't have any subgroups or entries our
1026 // last line is the group line (m_pLine) itself.
1027 LineList
*ConfigGroup::GetLastGroupLine()
1029 // if we have any subgroups, our last line is the last line of the last
1031 if ( m_pLastGroup
!= NULL
) {
1032 LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
1034 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
1038 // no subgroups, so the last line is the line of thelast entry (if any)
1039 return GetLastEntryLine();
1042 // return the last line belonging to the entries of this group (after which
1043 // we can add a new entry), if we don't have any entries we will add the new
1044 // one immediately after the group line itself.
1045 LineList
*ConfigGroup::GetLastEntryLine()
1047 if ( m_pLastEntry
!= NULL
) {
1048 LineList
*pLine
= m_pLastEntry
->GetLine();
1050 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
1054 // no entries: insert after the group header
1055 return GetGroupLine();
1058 // ----------------------------------------------------------------------------
1060 // ----------------------------------------------------------------------------
1062 void ConfigGroup::Rename(const wxString
& newName
)
1064 m_strName
= newName
;
1066 LineList
*line
= GetGroupLine();
1067 wxString strFullName
;
1068 strFullName
<< _T("[") << (GetFullName().c_str() + 1) << _T("]"); // +1: no '/'
1069 line
->SetText(strFullName
);
1074 wxString
ConfigGroup::GetFullName() const
1077 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
1082 // ----------------------------------------------------------------------------
1084 // ----------------------------------------------------------------------------
1086 // use binary search because the array is sorted
1088 ConfigGroup::FindEntry(const wxChar
*szName
) const
1092 hi
= m_aEntries
.Count();
1094 ConfigEntry
*pEntry
;
1098 pEntry
= m_aEntries
[i
];
1100 #if wxCONFIG_CASE_SENSITIVE
1101 res
= wxStrcmp(pEntry
->Name(), szName
);
1103 res
= wxStricmp(pEntry
->Name(), szName
);
1118 ConfigGroup::FindSubgroup(const wxChar
*szName
) const
1122 hi
= m_aSubgroups
.Count();
1124 ConfigGroup
*pGroup
;
1128 pGroup
= m_aSubgroups
[i
];
1130 #if wxCONFIG_CASE_SENSITIVE
1131 res
= wxStrcmp(pGroup
->Name(), szName
);
1133 res
= wxStricmp(pGroup
->Name(), szName
);
1147 // ----------------------------------------------------------------------------
1148 // create a new item
1149 // ----------------------------------------------------------------------------
1151 // create a new entry and add it to the current group
1153 ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
1155 wxASSERT( FindEntry(strName
) == NULL
);
1157 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
1158 m_aEntries
.Add(pEntry
);
1163 // create a new group and add it to the current group
1165 ConfigGroup::AddSubgroup(const wxString
& strName
)
1167 wxASSERT( FindSubgroup(strName
) == NULL
);
1169 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
1170 m_aSubgroups
.Add(pGroup
);
1175 // ----------------------------------------------------------------------------
1177 // ----------------------------------------------------------------------------
1180 The delete operations are _very_ slow if we delete the last item of this
1181 group (see comments before GetXXXLineXXX functions for more details),
1182 so it's much better to start with the first entry/group if we want to
1183 delete several of them.
1186 bool ConfigGroup::DeleteSubgroupByName(const wxChar
*szName
)
1188 return DeleteSubgroup(FindSubgroup(szName
));
1191 // doesn't delete the subgroup itself, but does remove references to it from
1192 // all other data structures (and normally the returned pointer should be
1193 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1194 bool ConfigGroup::DeleteSubgroup(ConfigGroup
*pGroup
)
1196 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1198 // delete all entries
1199 size_t nCount
= pGroup
->m_aEntries
.Count();
1200 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1201 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1202 if ( pLine
!= NULL
)
1203 m_pConfig
->LineListRemove(pLine
);
1206 // and subgroups of this sungroup
1207 nCount
= pGroup
->m_aSubgroups
.Count();
1208 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1209 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[0]);
1212 LineList
*pLine
= pGroup
->m_pLine
;
1213 if ( pLine
!= NULL
) {
1214 // notice that we may do this test inside the previous "if" because the
1215 // last entry's line is surely !NULL
1216 if ( pGroup
== m_pLastGroup
) {
1217 // our last entry is being deleted - find the last one which stays
1218 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1220 // go back until we find a subgroup or reach the group's line
1221 ConfigGroup
*pNewLast
= NULL
;
1222 size_t n
, nSubgroups
= m_aSubgroups
.Count();
1224 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1225 // is it our subgroup?
1226 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1227 // do _not_ call GetGroupLine! we don't want to add it to the local
1228 // file if it's not already there
1229 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1230 pNewLast
= m_aSubgroups
[n
];
1233 if ( pNewLast
!= NULL
) // found?
1237 if ( pl
== m_pLine
) {
1238 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1240 // we've reached the group line without finding any subgroups
1241 m_pLastGroup
= NULL
;
1244 m_pLastGroup
= pNewLast
;
1247 m_pConfig
->LineListRemove(pLine
);
1252 m_aSubgroups
.Remove(pGroup
);
1258 bool ConfigGroup::DeleteEntry(const wxChar
*szName
)
1260 ConfigEntry
*pEntry
= FindEntry(szName
);
1261 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1263 LineList
*pLine
= pEntry
->GetLine();
1264 if ( pLine
!= NULL
) {
1265 // notice that we may do this test inside the previous "if" because the
1266 // last entry's line is surely !NULL
1267 if ( pEntry
== m_pLastEntry
) {
1268 // our last entry is being deleted - find the last one which stays
1269 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1271 // go back until we find another entry or reach the group's line
1272 ConfigEntry
*pNewLast
= NULL
;
1273 size_t n
, nEntries
= m_aEntries
.Count();
1275 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1276 // is it our subgroup?
1277 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1278 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1279 pNewLast
= m_aEntries
[n
];
1282 if ( pNewLast
!= NULL
) // found?
1286 if ( pl
== m_pLine
) {
1287 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1289 // we've reached the group line without finding any subgroups
1290 m_pLastEntry
= NULL
;
1293 m_pLastEntry
= pNewLast
;
1296 m_pConfig
->LineListRemove(pLine
);
1299 // we must be written back for the changes to be saved
1302 m_aEntries
.Remove(pEntry
);
1308 // ----------------------------------------------------------------------------
1310 // ----------------------------------------------------------------------------
1311 void ConfigGroup::SetDirty()
1314 if ( Parent() != NULL
) // propagate upwards
1315 Parent()->SetDirty();
1318 // ============================================================================
1319 // wxFileConfig::ConfigEntry
1320 // ============================================================================
1322 // ----------------------------------------------------------------------------
1324 // ----------------------------------------------------------------------------
1325 ConfigEntry::ConfigEntry(ConfigGroup
*pParent
,
1326 const wxString
& strName
,
1328 : m_strName(strName
)
1330 wxASSERT( !strName
.IsEmpty() );
1332 m_pParent
= pParent
;
1338 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1340 m_strName
.erase(0, 1); // remove first character
1343 // ----------------------------------------------------------------------------
1345 // ----------------------------------------------------------------------------
1347 void ConfigEntry::SetLine(LineList
*pLine
)
1349 if ( m_pLine
!= NULL
) {
1350 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1351 Name().c_str(), m_pParent
->GetFullName().c_str());
1355 Group()->SetLastEntry(this);
1358 // second parameter is FALSE if we read the value from file and prevents the
1359 // entry from being marked as 'dirty'
1360 void ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1362 if ( bUser
&& IsImmutable() ) {
1363 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1368 // do nothing if it's the same value
1369 if ( strValue
== m_strValue
)
1372 m_strValue
= strValue
;
1375 wxString strVal
= FilterOutValue(strValue
);
1377 strLine
<< FilterOutEntryName(m_strName
) << _T('=') << strVal
;
1379 if ( m_pLine
!= NULL
) {
1380 // entry was read from the local config file, just modify the line
1381 m_pLine
->SetText(strLine
);
1384 // add a new line to the file
1385 wxASSERT( m_nLine
== wxNOT_FOUND
); // consistency check
1387 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1388 Group()->GetLastEntryLine());
1389 Group()->SetLastEntry(this);
1396 void ConfigEntry::SetDirty()
1399 Group()->SetDirty();
1402 // ============================================================================
1404 // ============================================================================
1406 // ----------------------------------------------------------------------------
1407 // compare functions for array sorting
1408 // ----------------------------------------------------------------------------
1410 int CompareEntries(ConfigEntry
*p1
,
1413 #if wxCONFIG_CASE_SENSITIVE
1414 return wxStrcmp(p1
->Name(), p2
->Name());
1416 return wxStricmp(p1
->Name(), p2
->Name());
1420 int CompareGroups(ConfigGroup
*p1
,
1423 #if wxCONFIG_CASE_SENSITIVE
1424 return wxStrcmp(p1
->Name(), p2
->Name());
1426 return wxStricmp(p1
->Name(), p2
->Name());
1430 // ----------------------------------------------------------------------------
1432 // ----------------------------------------------------------------------------
1434 // undo FilterOutValue
1435 static wxString
FilterInValue(const wxString
& str
)
1438 strResult
.Alloc(str
.Len());
1440 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1442 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1443 if ( str
[n
] == _T('\\') ) {
1444 switch ( str
[++n
] ) {
1446 strResult
+= _T('\n');
1450 strResult
+= _T('\r');
1454 strResult
+= _T('\t');
1458 strResult
+= _T('\\');
1462 strResult
+= _T('"');
1467 if ( str
[n
] != _T('"') || !bQuoted
)
1468 strResult
+= str
[n
];
1469 else if ( n
!= str
.Len() - 1 ) {
1470 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1473 //else: it's the last quote of a quoted string, ok
1480 // quote the string before writing it to file
1481 static wxString
FilterOutValue(const wxString
& str
)
1487 strResult
.Alloc(str
.Len());
1489 // quoting is necessary to preserve spaces in the beginning of the string
1490 bool bQuote
= wxIsspace(str
[0]) || str
[0] == _T('"');
1493 strResult
+= _T('"');
1496 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
1519 //else: fall through
1522 strResult
+= str
[n
];
1523 continue; // nothing special to do
1526 // we get here only for special characters
1527 strResult
<< _T('\\') << c
;
1531 strResult
+= _T('"');
1536 // undo FilterOutEntryName
1537 static wxString
FilterInEntryName(const wxString
& str
)
1540 strResult
.Alloc(str
.Len());
1542 for ( const wxChar
*pc
= str
.c_str(); *pc
!= '\0'; pc
++ ) {
1543 if ( *pc
== _T('\\') )
1552 // sanitize entry or group name: insert '\\' before any special characters
1553 static wxString
FilterOutEntryName(const wxString
& str
)
1556 strResult
.Alloc(str
.Len());
1558 for ( const wxChar
*pc
= str
.c_str(); *pc
!= _T('\0'); pc
++ ) {
1561 // we explicitly allow some of "safe" chars and 8bit ASCII characters
1562 // which will probably never have special meaning
1563 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
1564 // should *not* be quoted
1565 if ( !wxIsalnum(c
) && !wxStrchr(_T("@_/-!.*%"), c
) && ((c
& 0x80) == 0) )
1566 strResult
+= _T('\\');
1574 // we can't put ?: in the ctor initializer list because it confuses some
1575 // broken compilers (Borland C++)
1576 static wxString
GetAppName(const wxString
& appName
)
1578 if ( !appName
&& wxTheApp
)
1579 return wxTheApp
->GetAppName();
1584 #endif // wxUSE_CONFIG