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__)
197 wxASSERT_MSG( FALSE
, wxT("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() != wxT('/')) strDir
<< wxT('/');
218 if (strDir
.Last() != wxT('\\')) strDir
<< wxT('\\');
224 wxString
wxFileConfig::GetGlobalFileName(const wxChar
*szFile
)
226 wxString str
= GetGlobalDir();
229 if ( wxStrchr(szFile
, wxT('.')) == NULL
)
239 wxString
wxFileConfig::GetLocalFileName(const wxChar
*szFile
)
241 wxString str
= GetLocalDir();
250 if ( wxStrchr(szFile
, wxT('.')) == 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
) )
329 if ( !m_strLocalFile
.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile
) )
331 wxString strLocal
= m_strLocalFile
;
332 m_strLocalFile
= GetLocalDir();
333 m_strLocalFile
<< strLocal
;
336 if ( !m_strGlobalFile
.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile
) )
338 wxString strGlobal
= m_strGlobalFile
;
339 m_strGlobalFile
= GetGlobalDir();
340 m_strGlobalFile
<< strGlobal
;
347 void wxFileConfig::CleanUp()
351 LineList
*pCur
= m_linesHead
;
352 while ( pCur
!= NULL
) {
353 LineList
*pNext
= pCur
->Next();
359 wxFileConfig::~wxFileConfig()
366 // ----------------------------------------------------------------------------
367 // parse a config file
368 // ----------------------------------------------------------------------------
370 void wxFileConfig::Parse(wxTextFile
& file
, bool bLocal
)
372 const wxChar
*pStart
;
376 size_t nLineCount
= file
.GetLineCount();
377 for ( size_t n
= 0; n
< nLineCount
; n
++ ) {
380 // add the line to linked list
382 LineListAppend(strLine
);
384 // skip leading spaces
385 for ( pStart
= strLine
; wxIsspace(*pStart
); pStart
++ )
388 // skip blank/comment lines
389 if ( *pStart
== wxT('\0')|| *pStart
== wxT(';') || *pStart
== wxT('#') )
392 if ( *pStart
== wxT('[') ) { // a new group
395 while ( *++pEnd
!= wxT(']') ) {
396 if ( *pEnd
== wxT('\n') || *pEnd
== wxT('\0') )
400 if ( *pEnd
!= wxT(']') ) {
401 wxLogError(_("file '%s': unexpected character %c at line %d."),
402 file
.GetName(), *pEnd
, n
+ 1);
403 continue; // skip this line
406 // group name here is always considered as abs path
409 strGroup
<< wxCONFIG_PATH_SEPARATOR
410 << FilterInEntryName(wxString(pStart
, pEnd
- pStart
));
412 // will create it if doesn't yet exist
416 m_pCurrentGroup
->SetLine(m_linesTail
);
418 // check that there is nothing except comments left on this line
420 while ( *++pEnd
!= wxT('\0') && bCont
) {
429 // ignore whitespace ('\n' impossible here)
433 wxLogWarning(_("file '%s', line %d: '%s' "
434 "ignored after group header."),
435 file
.GetName(), n
+ 1, pEnd
);
441 const wxChar
*pEnd
= pStart
;
442 while ( *pEnd
!= wxT('=') && !wxIsspace(*pEnd
) ) {
443 if ( *pEnd
== wxT('\\') ) {
444 // next character may be space or not - still take it because it's
452 wxString
strKey(FilterInEntryName(wxString(pStart
, pEnd
)));
455 while ( isspace(*pEnd
) )
458 if ( *pEnd
++ != wxT('=') ) {
459 wxLogError(_("file '%s', line %d: '=' expected."),
460 file
.GetName(), n
+ 1);
463 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strKey
);
465 if ( pEntry
== NULL
) {
467 pEntry
= m_pCurrentGroup
->AddEntry(strKey
, n
);
470 pEntry
->SetLine(m_linesTail
);
473 if ( bLocal
&& pEntry
->IsImmutable() ) {
474 // immutable keys can't be changed by user
475 wxLogWarning(_("file '%s', line %d: value for "
476 "immutable key '%s' ignored."),
477 file
.GetName(), n
+ 1, strKey
.c_str());
480 // the condition below catches the cases (a) and (b) but not (c):
481 // (a) global key found second time in global file
482 // (b) key found second (or more) time in local file
483 // (c) key from global file now found in local one
484 // which is exactly what we want.
485 else if ( !bLocal
|| pEntry
->IsLocal() ) {
486 wxLogWarning(_("file '%s', line %d: key '%s' was first "
487 "found at line %d."),
488 file
.GetName(), n
+ 1, strKey
.c_str(), pEntry
->Line());
491 pEntry
->SetLine(m_linesTail
);
496 while ( wxIsspace(*pEnd
) )
499 pEntry
->SetValue(FilterInValue(pEnd
), FALSE
/* read from file */);
505 // ----------------------------------------------------------------------------
507 // ----------------------------------------------------------------------------
509 void wxFileConfig::SetRootPath()
512 m_pCurrentGroup
= m_pRootGroup
;
515 void wxFileConfig::SetPath(const wxString
& strPath
)
517 wxArrayString aParts
;
519 if ( strPath
.IsEmpty() ) {
524 if ( strPath
[0] == wxCONFIG_PATH_SEPARATOR
) {
526 wxSplitPath(aParts
, strPath
);
529 // relative path, combine with current one
530 wxString strFullPath
= m_strPath
;
531 strFullPath
<< wxCONFIG_PATH_SEPARATOR
<< strPath
;
532 wxSplitPath(aParts
, strFullPath
);
535 // change current group
537 m_pCurrentGroup
= m_pRootGroup
;
538 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
539 ConfigGroup
*pNextGroup
= m_pCurrentGroup
->FindSubgroup(aParts
[n
]);
540 if ( pNextGroup
== NULL
)
541 pNextGroup
= m_pCurrentGroup
->AddSubgroup(aParts
[n
]);
542 m_pCurrentGroup
= pNextGroup
;
545 // recombine path parts in one variable
547 for ( n
= 0; n
< aParts
.Count(); n
++ ) {
548 m_strPath
<< wxCONFIG_PATH_SEPARATOR
<< aParts
[n
];
552 // ----------------------------------------------------------------------------
554 // ----------------------------------------------------------------------------
556 bool wxFileConfig::GetFirstGroup(wxString
& str
, long& lIndex
) const
559 return GetNextGroup(str
, lIndex
);
562 bool wxFileConfig::GetNextGroup (wxString
& str
, long& lIndex
) const
564 if ( size_t(lIndex
) < m_pCurrentGroup
->Groups().Count() ) {
565 str
= m_pCurrentGroup
->Groups()[lIndex
++]->Name();
572 bool wxFileConfig::GetFirstEntry(wxString
& str
, long& lIndex
) const
575 return GetNextEntry(str
, lIndex
);
578 bool wxFileConfig::GetNextEntry (wxString
& str
, long& lIndex
) const
580 if ( size_t(lIndex
) < m_pCurrentGroup
->Entries().Count() ) {
581 str
= m_pCurrentGroup
->Entries()[lIndex
++]->Name();
588 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive
) const
590 size_t n
= m_pCurrentGroup
->Entries().Count();
592 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
593 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
594 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
595 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
596 n
+= GetNumberOfEntries(TRUE
);
597 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
604 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive
) const
606 size_t n
= m_pCurrentGroup
->Groups().Count();
608 ConfigGroup
*pOldCurrentGroup
= m_pCurrentGroup
;
609 size_t nSubgroups
= m_pCurrentGroup
->Groups().Count();
610 for ( size_t nGroup
= 0; nGroup
< nSubgroups
; nGroup
++ ) {
611 CONST_CAST m_pCurrentGroup
= m_pCurrentGroup
->Groups()[nGroup
];
612 n
+= GetNumberOfGroups(TRUE
);
613 CONST_CAST m_pCurrentGroup
= pOldCurrentGroup
;
620 // ----------------------------------------------------------------------------
621 // tests for existence
622 // ----------------------------------------------------------------------------
624 bool wxFileConfig::HasGroup(const wxString
& strName
) const
626 wxConfigPathChanger
path(this, strName
);
628 ConfigGroup
*pGroup
= m_pCurrentGroup
->FindSubgroup(path
.Name());
629 return pGroup
!= NULL
;
632 bool wxFileConfig::HasEntry(const wxString
& strName
) const
634 wxConfigPathChanger
path(this, strName
);
636 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
637 return pEntry
!= NULL
;
640 // ----------------------------------------------------------------------------
642 // ----------------------------------------------------------------------------
644 bool wxFileConfig::Read(const wxString
& key
,
645 wxString
* pStr
) const
647 wxConfigPathChanger
path(this, key
);
649 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
650 if (pEntry
== NULL
) {
654 *pStr
= ExpandEnvVars(pEntry
->Value());
659 bool wxFileConfig::Read(const wxString
& key
,
660 wxString
* pStr
, const wxString
& defVal
) const
662 wxConfigPathChanger
path(this, key
);
664 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(path
.Name());
665 if (pEntry
== NULL
) {
666 if( IsRecordingDefaults() )
667 ((wxFileConfig
*)this)->Write(key
,defVal
);
668 *pStr
= ExpandEnvVars(defVal
);
672 *pStr
= ExpandEnvVars(pEntry
->Value());
677 bool wxFileConfig::Read(const wxString
& key
, long *pl
) const
680 if ( Read(key
, & str
) ) {
689 bool wxFileConfig::Write(const wxString
& key
, const wxString
& szValue
)
691 wxConfigPathChanger
path(this, key
);
693 wxString strName
= path
.Name();
694 if ( strName
.IsEmpty() ) {
695 // setting the value of a group is an error
696 wxASSERT_MSG( wxIsEmpty(szValue
), wxT("can't set value of a group!") );
698 // ... except if it's empty in which case it's a way to force it's creation
699 m_pCurrentGroup
->SetDirty();
701 // this will add a line for this group if it didn't have it before
702 (void)m_pCurrentGroup
->GetGroupLine();
707 // check that the name is reasonable
708 if ( strName
[0u] == wxCONFIG_IMMUTABLE_PREFIX
) {
709 wxLogError(_("Config entry name cannot start with '%c'."),
710 wxCONFIG_IMMUTABLE_PREFIX
);
714 ConfigEntry
*pEntry
= m_pCurrentGroup
->FindEntry(strName
);
715 if ( pEntry
== NULL
)
716 pEntry
= m_pCurrentGroup
->AddEntry(strName
);
718 pEntry
->SetValue(szValue
);
724 bool wxFileConfig::Write(const wxString
& key
, long lValue
)
726 // ltoa() is not ANSI :-(
728 buf
.Printf(wxT("%ld"), lValue
);
729 return Write(key
, buf
);
732 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
734 if ( LineListIsEmpty() || !m_pRootGroup
->IsDirty() )
737 wxTempFile
file(m_strLocalFile
);
739 if ( !file
.IsOpened() ) {
740 wxLogError(_("can't open user configuration file."));
744 // write all strings to file
745 for ( LineList
*p
= m_linesHead
; p
!= NULL
; p
= p
->Next() ) {
746 if ( !file
.Write(p
->Text() + wxTextFile::GetEOL()) ) {
747 wxLogError(_("can't write user configuration file."));
752 return file
.Commit();
755 // ----------------------------------------------------------------------------
756 // renaming groups/entries
757 // ----------------------------------------------------------------------------
759 bool wxFileConfig::RenameEntry(const wxString
& oldName
,
760 const wxString
& newName
)
762 // check that the entry exists
763 ConfigEntry
*oldEntry
= m_pCurrentGroup
->FindEntry(oldName
);
767 // check that the new entry doesn't already exist
768 if ( m_pCurrentGroup
->FindEntry(newName
) )
771 // delete the old entry, create the new one
772 wxString value
= oldEntry
->Value();
773 if ( !m_pCurrentGroup
->DeleteEntry(oldName
) )
776 ConfigEntry
*newEntry
= m_pCurrentGroup
->AddEntry(newName
);
777 newEntry
->SetValue(value
);
782 bool wxFileConfig::RenameGroup(const wxString
& oldName
,
783 const wxString
& newName
)
785 // check that the group exists
786 ConfigGroup
*group
= m_pCurrentGroup
->FindSubgroup(oldName
);
790 // check that the new group doesn't already exist
791 if ( m_pCurrentGroup
->FindSubgroup(newName
) )
794 group
->Rename(newName
);
799 // ----------------------------------------------------------------------------
800 // delete groups/entries
801 // ----------------------------------------------------------------------------
803 bool wxFileConfig::DeleteEntry(const wxString
& key
, bool bGroupIfEmptyAlso
)
805 wxConfigPathChanger
path(this, key
);
807 if ( !m_pCurrentGroup
->DeleteEntry(path
.Name()) )
810 if ( bGroupIfEmptyAlso
&& m_pCurrentGroup
->IsEmpty() ) {
811 if ( m_pCurrentGroup
!= m_pRootGroup
) {
812 ConfigGroup
*pGroup
= m_pCurrentGroup
;
813 SetPath(wxT("..")); // changes m_pCurrentGroup!
814 m_pCurrentGroup
->DeleteSubgroupByName(pGroup
->Name());
816 //else: never delete the root group
822 bool wxFileConfig::DeleteGroup(const wxString
& key
)
824 wxConfigPathChanger
path(this, key
);
826 return m_pCurrentGroup
->DeleteSubgroupByName(path
.Name());
829 bool wxFileConfig::DeleteAll()
833 if ( remove(m_strLocalFile
.fn_str()) == -1 )
834 wxLogSysError(_("can't delete user configuration file '%s'"), m_strLocalFile
.c_str());
836 m_strLocalFile
= m_strGlobalFile
= wxT("");
842 // ----------------------------------------------------------------------------
843 // linked list functions
844 // ----------------------------------------------------------------------------
846 // append a new line to the end of the list
847 LineList
*wxFileConfig::LineListAppend(const wxString
& str
)
849 LineList
*pLine
= new LineList(str
);
851 if ( m_linesTail
== NULL
) {
857 m_linesTail
->SetNext(pLine
);
858 pLine
->SetPrev(m_linesTail
);
865 // insert a new line after the given one or in the very beginning if !pLine
866 LineList
*wxFileConfig::LineListInsert(const wxString
& str
,
869 if ( pLine
== m_linesTail
)
870 return LineListAppend(str
);
872 LineList
*pNewLine
= new LineList(str
);
873 if ( pLine
== NULL
) {
874 // prepend to the list
875 pNewLine
->SetNext(m_linesHead
);
876 m_linesHead
->SetPrev(pNewLine
);
877 m_linesHead
= pNewLine
;
880 // insert before pLine
881 LineList
*pNext
= pLine
->Next();
882 pNewLine
->SetNext(pNext
);
883 pNewLine
->SetPrev(pLine
);
884 pNext
->SetPrev(pNewLine
);
885 pLine
->SetNext(pNewLine
);
891 void wxFileConfig::LineListRemove(LineList
*pLine
)
893 LineList
*pPrev
= pLine
->Prev(),
894 *pNext
= pLine
->Next();
900 pPrev
->SetNext(pNext
);
906 pNext
->SetPrev(pPrev
);
911 bool wxFileConfig::LineListIsEmpty()
913 return m_linesHead
== NULL
;
916 // ============================================================================
917 // wxFileConfig::ConfigGroup
918 // ============================================================================
920 // ----------------------------------------------------------------------------
922 // ----------------------------------------------------------------------------
925 ConfigGroup::ConfigGroup(ConfigGroup
*pParent
,
926 const wxString
& strName
,
927 wxFileConfig
*pConfig
)
928 : m_aEntries(CompareEntries
),
929 m_aSubgroups(CompareGroups
),
941 // dtor deletes all children
942 ConfigGroup::~ConfigGroup()
945 size_t n
, nCount
= m_aEntries
.Count();
946 for ( n
= 0; n
< nCount
; n
++ )
947 delete m_aEntries
[n
];
950 nCount
= m_aSubgroups
.Count();
951 for ( n
= 0; n
< nCount
; n
++ )
952 delete m_aSubgroups
[n
];
955 // ----------------------------------------------------------------------------
957 // ----------------------------------------------------------------------------
959 void ConfigGroup::SetLine(LineList
*pLine
)
961 wxASSERT( m_pLine
== NULL
); // shouldn't be called twice
967 This is a bit complicated, so let me explain it in details. All lines that
968 were read from the local file (the only one we will ever modify) are stored
969 in a (doubly) linked list. Our problem is to know at which position in this
970 list should we insert the new entries/subgroups. To solve it we keep three
971 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
973 m_pLine points to the line containing "[group_name]"
974 m_pLastEntry points to the last entry of this group in the local file.
975 m_pLastGroup subgroup
977 Initially, they're NULL all three. When the group (an entry/subgroup) is read
978 from the local file, the corresponding variable is set. However, if the group
979 was read from the global file and then modified or created by the application
980 these variables are still NULL and we need to create the corresponding lines.
981 See the following functions (and comments preceding them) for the details of
984 Also, when our last entry/group are deleted we need to find the new last
985 element - the code in DeleteEntry/Subgroup does this by backtracking the list
986 of lines until it either founds an entry/subgroup (and this is the new last
987 element) or the m_pLine of the group, in which case there are no more entries
988 (or subgroups) left and m_pLast<element> becomes NULL.
990 NB: This last problem could be avoided for entries if we added new entries
991 immediately after m_pLine, but in this case the entries would appear
992 backwards in the config file (OTOH, it's not that important) and as we
993 would still need to do it for the subgroups the code wouldn't have been
994 significantly less complicated.
997 // Return the line which contains "[our name]". If we're still not in the list,
998 // add our line to it immediately after the last line of our parent group if we
999 // have it or in the very beginning if we're the root group.
1000 LineList
*ConfigGroup::GetGroupLine()
1002 if ( m_pLine
== NULL
) {
1003 ConfigGroup
*pParent
= Parent();
1005 // this group wasn't present in local config file, add it now
1006 if ( pParent
!= NULL
) {
1007 wxString strFullName
;
1008 strFullName
<< wxT("[")
1010 << FilterOutEntryName(GetFullName().c_str() + 1)
1012 m_pLine
= m_pConfig
->LineListInsert(strFullName
,
1013 pParent
->GetLastGroupLine());
1014 pParent
->SetLastGroup(this); // we're surely after all the others
1017 // we return NULL, so that LineListInsert() will insert us in the
1025 // Return the last line belonging to the subgroups of this group (after which
1026 // we can add a new subgroup), if we don't have any subgroups or entries our
1027 // last line is the group line (m_pLine) itself.
1028 LineList
*ConfigGroup::GetLastGroupLine()
1030 // if we have any subgroups, our last line is the last line of the last
1032 if ( m_pLastGroup
!= NULL
) {
1033 LineList
*pLine
= m_pLastGroup
->GetLastGroupLine();
1035 wxASSERT( pLine
!= NULL
); // last group must have !NULL associated line
1039 // no subgroups, so the last line is the line of thelast entry (if any)
1040 return GetLastEntryLine();
1043 // return the last line belonging to the entries of this group (after which
1044 // we can add a new entry), if we don't have any entries we will add the new
1045 // one immediately after the group line itself.
1046 LineList
*ConfigGroup::GetLastEntryLine()
1048 if ( m_pLastEntry
!= NULL
) {
1049 LineList
*pLine
= m_pLastEntry
->GetLine();
1051 wxASSERT( pLine
!= NULL
); // last entry must have !NULL associated line
1055 // no entries: insert after the group header
1056 return GetGroupLine();
1059 // ----------------------------------------------------------------------------
1061 // ----------------------------------------------------------------------------
1063 void ConfigGroup::Rename(const wxString
& newName
)
1065 m_strName
= newName
;
1067 LineList
*line
= GetGroupLine();
1068 wxString strFullName
;
1069 strFullName
<< wxT("[") << (GetFullName().c_str() + 1) << wxT("]"); // +1: no '/'
1070 line
->SetText(strFullName
);
1075 wxString
ConfigGroup::GetFullName() const
1078 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR
+ Name();
1083 // ----------------------------------------------------------------------------
1085 // ----------------------------------------------------------------------------
1087 // use binary search because the array is sorted
1089 ConfigGroup::FindEntry(const wxChar
*szName
) const
1093 hi
= m_aEntries
.Count();
1095 ConfigEntry
*pEntry
;
1099 pEntry
= m_aEntries
[i
];
1101 #if wxCONFIG_CASE_SENSITIVE
1102 res
= wxStrcmp(pEntry
->Name(), szName
);
1104 res
= wxStricmp(pEntry
->Name(), szName
);
1119 ConfigGroup::FindSubgroup(const wxChar
*szName
) const
1123 hi
= m_aSubgroups
.Count();
1125 ConfigGroup
*pGroup
;
1129 pGroup
= m_aSubgroups
[i
];
1131 #if wxCONFIG_CASE_SENSITIVE
1132 res
= wxStrcmp(pGroup
->Name(), szName
);
1134 res
= wxStricmp(pGroup
->Name(), szName
);
1148 // ----------------------------------------------------------------------------
1149 // create a new item
1150 // ----------------------------------------------------------------------------
1152 // create a new entry and add it to the current group
1154 ConfigGroup::AddEntry(const wxString
& strName
, int nLine
)
1156 wxASSERT( FindEntry(strName
) == NULL
);
1158 ConfigEntry
*pEntry
= new ConfigEntry(this, strName
, nLine
);
1159 m_aEntries
.Add(pEntry
);
1164 // create a new group and add it to the current group
1166 ConfigGroup::AddSubgroup(const wxString
& strName
)
1168 wxASSERT( FindSubgroup(strName
) == NULL
);
1170 ConfigGroup
*pGroup
= new ConfigGroup(this, strName
, m_pConfig
);
1171 m_aSubgroups
.Add(pGroup
);
1176 // ----------------------------------------------------------------------------
1178 // ----------------------------------------------------------------------------
1181 The delete operations are _very_ slow if we delete the last item of this
1182 group (see comments before GetXXXLineXXX functions for more details),
1183 so it's much better to start with the first entry/group if we want to
1184 delete several of them.
1187 bool ConfigGroup::DeleteSubgroupByName(const wxChar
*szName
)
1189 return DeleteSubgroup(FindSubgroup(szName
));
1192 // doesn't delete the subgroup itself, but does remove references to it from
1193 // all other data structures (and normally the returned pointer should be
1194 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1195 bool ConfigGroup::DeleteSubgroup(ConfigGroup
*pGroup
)
1197 wxCHECK( pGroup
!= NULL
, FALSE
); // deleting non existing group?
1199 // delete all entries
1200 size_t nCount
= pGroup
->m_aEntries
.Count();
1201 for ( size_t nEntry
= 0; nEntry
< nCount
; nEntry
++ ) {
1202 LineList
*pLine
= pGroup
->m_aEntries
[nEntry
]->GetLine();
1203 if ( pLine
!= NULL
)
1204 m_pConfig
->LineListRemove(pLine
);
1207 // and subgroups of this sungroup
1208 nCount
= pGroup
->m_aSubgroups
.Count();
1209 for ( size_t nGroup
= 0; nGroup
< nCount
; nGroup
++ ) {
1210 pGroup
->DeleteSubgroup(pGroup
->m_aSubgroups
[0]);
1213 LineList
*pLine
= pGroup
->m_pLine
;
1214 if ( pLine
!= NULL
) {
1215 // notice that we may do this test inside the previous "if" because the
1216 // last entry's line is surely !NULL
1217 if ( pGroup
== m_pLastGroup
) {
1218 // our last entry is being deleted - find the last one which stays
1219 wxASSERT( m_pLine
!= NULL
); // we have a subgroup with !NULL pLine...
1221 // go back until we find a subgroup or reach the group's line
1222 ConfigGroup
*pNewLast
= NULL
;
1223 size_t n
, nSubgroups
= m_aSubgroups
.Count();
1225 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1226 // is it our subgroup?
1227 for ( n
= 0; (pNewLast
== NULL
) && (n
< nSubgroups
); n
++ ) {
1228 // do _not_ call GetGroupLine! we don't want to add it to the local
1229 // file if it's not already there
1230 if ( m_aSubgroups
[n
]->m_pLine
== m_pLine
)
1231 pNewLast
= m_aSubgroups
[n
];
1234 if ( pNewLast
!= NULL
) // found?
1238 if ( pl
== m_pLine
) {
1239 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1241 // we've reached the group line without finding any subgroups
1242 m_pLastGroup
= NULL
;
1245 m_pLastGroup
= pNewLast
;
1248 m_pConfig
->LineListRemove(pLine
);
1253 m_aSubgroups
.Remove(pGroup
);
1259 bool ConfigGroup::DeleteEntry(const wxChar
*szName
)
1261 ConfigEntry
*pEntry
= FindEntry(szName
);
1262 wxCHECK( pEntry
!= NULL
, FALSE
); // deleting non existing item?
1264 LineList
*pLine
= pEntry
->GetLine();
1265 if ( pLine
!= NULL
) {
1266 // notice that we may do this test inside the previous "if" because the
1267 // last entry's line is surely !NULL
1268 if ( pEntry
== m_pLastEntry
) {
1269 // our last entry is being deleted - find the last one which stays
1270 wxASSERT( m_pLine
!= NULL
); // if we have an entry with !NULL pLine...
1272 // go back until we find another entry or reach the group's line
1273 ConfigEntry
*pNewLast
= NULL
;
1274 size_t n
, nEntries
= m_aEntries
.Count();
1276 for ( pl
= pLine
->Prev(); pl
!= m_pLine
; pl
= pl
->Prev() ) {
1277 // is it our subgroup?
1278 for ( n
= 0; (pNewLast
== NULL
) && (n
< nEntries
); n
++ ) {
1279 if ( m_aEntries
[n
]->GetLine() == m_pLine
)
1280 pNewLast
= m_aEntries
[n
];
1283 if ( pNewLast
!= NULL
) // found?
1287 if ( pl
== m_pLine
) {
1288 wxASSERT( !pNewLast
); // how comes it has the same line as we?
1290 // we've reached the group line without finding any subgroups
1291 m_pLastEntry
= NULL
;
1294 m_pLastEntry
= pNewLast
;
1297 m_pConfig
->LineListRemove(pLine
);
1300 // we must be written back for the changes to be saved
1303 m_aEntries
.Remove(pEntry
);
1309 // ----------------------------------------------------------------------------
1311 // ----------------------------------------------------------------------------
1312 void ConfigGroup::SetDirty()
1315 if ( Parent() != NULL
) // propagate upwards
1316 Parent()->SetDirty();
1319 // ============================================================================
1320 // wxFileConfig::ConfigEntry
1321 // ============================================================================
1323 // ----------------------------------------------------------------------------
1325 // ----------------------------------------------------------------------------
1326 ConfigEntry::ConfigEntry(ConfigGroup
*pParent
,
1327 const wxString
& strName
,
1329 : m_strName(strName
)
1331 wxASSERT( !strName
.IsEmpty() );
1333 m_pParent
= pParent
;
1339 m_bImmutable
= strName
[0] == wxCONFIG_IMMUTABLE_PREFIX
;
1341 m_strName
.erase(0, 1); // remove first character
1344 // ----------------------------------------------------------------------------
1346 // ----------------------------------------------------------------------------
1348 void ConfigEntry::SetLine(LineList
*pLine
)
1350 if ( m_pLine
!= NULL
) {
1351 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1352 Name().c_str(), m_pParent
->GetFullName().c_str());
1356 Group()->SetLastEntry(this);
1359 // second parameter is FALSE if we read the value from file and prevents the
1360 // entry from being marked as 'dirty'
1361 void ConfigEntry::SetValue(const wxString
& strValue
, bool bUser
)
1363 if ( bUser
&& IsImmutable() ) {
1364 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1369 // do nothing if it's the same value
1370 if ( strValue
== m_strValue
)
1373 m_strValue
= strValue
;
1376 wxString strVal
= FilterOutValue(strValue
);
1378 strLine
<< FilterOutEntryName(m_strName
) << wxT('=') << strVal
;
1380 if ( m_pLine
!= NULL
) {
1381 // entry was read from the local config file, just modify the line
1382 m_pLine
->SetText(strLine
);
1385 // add a new line to the file
1386 wxASSERT( m_nLine
== wxNOT_FOUND
); // consistency check
1388 m_pLine
= Group()->Config()->LineListInsert(strLine
,
1389 Group()->GetLastEntryLine());
1390 Group()->SetLastEntry(this);
1397 void ConfigEntry::SetDirty()
1400 Group()->SetDirty();
1403 // ============================================================================
1405 // ============================================================================
1407 // ----------------------------------------------------------------------------
1408 // compare functions for array sorting
1409 // ----------------------------------------------------------------------------
1411 int CompareEntries(ConfigEntry
*p1
,
1414 #if wxCONFIG_CASE_SENSITIVE
1415 return wxStrcmp(p1
->Name(), p2
->Name());
1417 return wxStricmp(p1
->Name(), p2
->Name());
1421 int CompareGroups(ConfigGroup
*p1
,
1424 #if wxCONFIG_CASE_SENSITIVE
1425 return wxStrcmp(p1
->Name(), p2
->Name());
1427 return wxStricmp(p1
->Name(), p2
->Name());
1431 // ----------------------------------------------------------------------------
1433 // ----------------------------------------------------------------------------
1435 // undo FilterOutValue
1436 static wxString
FilterInValue(const wxString
& str
)
1439 strResult
.Alloc(str
.Len());
1441 bool bQuoted
= !str
.IsEmpty() && str
[0] == '"';
1443 for ( size_t n
= bQuoted
? 1 : 0; n
< str
.Len(); n
++ ) {
1444 if ( str
[n
] == wxT('\\') ) {
1445 switch ( str
[++n
] ) {
1447 strResult
+= wxT('\n');
1451 strResult
+= wxT('\r');
1455 strResult
+= wxT('\t');
1459 strResult
+= wxT('\\');
1463 strResult
+= wxT('"');
1468 if ( str
[n
] != wxT('"') || !bQuoted
)
1469 strResult
+= str
[n
];
1470 else if ( n
!= str
.Len() - 1 ) {
1471 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1474 //else: it's the last quote of a quoted string, ok
1481 // quote the string before writing it to file
1482 static wxString
FilterOutValue(const wxString
& str
)
1488 strResult
.Alloc(str
.Len());
1490 // quoting is necessary to preserve spaces in the beginning of the string
1491 bool bQuote
= wxIsspace(str
[0]) || str
[0] == wxT('"');
1494 strResult
+= wxT('"');
1497 for ( size_t n
= 0; n
< str
.Len(); n
++ ) {
1520 //else: fall through
1523 strResult
+= str
[n
];
1524 continue; // nothing special to do
1527 // we get here only for special characters
1528 strResult
<< wxT('\\') << c
;
1532 strResult
+= wxT('"');
1537 // undo FilterOutEntryName
1538 static wxString
FilterInEntryName(const wxString
& str
)
1541 strResult
.Alloc(str
.Len());
1543 for ( const wxChar
*pc
= str
.c_str(); *pc
!= '\0'; pc
++ ) {
1544 if ( *pc
== wxT('\\') )
1553 // sanitize entry or group name: insert '\\' before any special characters
1554 static wxString
FilterOutEntryName(const wxString
& str
)
1557 strResult
.Alloc(str
.Len());
1559 for ( const wxChar
*pc
= str
.c_str(); *pc
!= wxT('\0'); pc
++ ) {
1562 // we explicitly allow some of "safe" chars and 8bit ASCII characters
1563 // which will probably never have special meaning
1564 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
1565 // should *not* be quoted
1566 if ( !wxIsalnum(c
) && !wxStrchr(wxT("@_/-!.*%"), c
) && ((c
& 0x80) == 0) )
1567 strResult
+= wxT('\\');
1575 // we can't put ?: in the ctor initializer list because it confuses some
1576 // broken compilers (Borland C++)
1577 static wxString
GetAppName(const wxString
& appName
)
1579 if ( !appName
&& wxTheApp
)
1580 return wxTheApp
->GetAppName();
1585 #endif // wxUSE_CONFIG