]> git.saurik.com Git - wxWidgets.git/blob - src/common/fileconf.cpp
bug in handling escaped chars in entries/group names corrected
[wxWidgets.git] / src / common / fileconf.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: fileconf.cpp
3 // Purpose: implementation of wxFileConfig derivation of wxConfig
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 07.04.98 (adapted from appconf.cpp)
7 // RCS-ID: $Id$
8 // Copyright: (c) 1997 Karsten Ballüder & Vadim Zeitlin
9 // Ballueder@usa.net <zeitlin@dptmaths.ens-cachan.fr>
10 // Licence: wxWindows license
11 ///////////////////////////////////////////////////////////////////////////////
12
13 #ifdef __GNUG__
14 #pragma implementation "fileconf.h"
15 #endif
16
17 // ============================================================================
18 // declarations
19 // ============================================================================
20
21 // ----------------------------------------------------------------------------
22 // headers
23 // ----------------------------------------------------------------------------
24 #include "wx/wxprec.h"
25
26 #ifdef __BORLANDC__
27 #pragma hdrstop
28 #endif //__BORLANDC__
29
30 #ifndef WX_PRECOMP
31 #include "wx/string.h"
32 #include "wx/intl.h"
33 #endif //WX_PRECOMP
34
35 #include "wx/app.h"
36 #include "wx/dynarray.h"
37 #include "wx/file.h"
38 #include "wx/log.h"
39 #include "wx/textfile.h"
40 #include "wx/config.h"
41 #include "wx/fileconf.h"
42
43 #include "wx/utils.h" // for wxGetHomeDir
44
45 // _WINDOWS_ is defined when windows.h is included,
46 // __WXMSW__ is defined for MS Windows compilation
47 #if defined(__WXMSW__) && !defined(_WINDOWS_)
48 #include <windows.h>
49 #endif //windows.h
50
51 #include <stdlib.h>
52 #include <ctype.h>
53
54 // ----------------------------------------------------------------------------
55 // macros
56 // ----------------------------------------------------------------------------
57 #define CONST_CAST ((wxFileConfig *)this)->
58
59 // ----------------------------------------------------------------------------
60 // constants
61 // ----------------------------------------------------------------------------
62 #ifndef MAX_PATH
63 #define MAX_PATH 512
64 #endif
65
66 // ----------------------------------------------------------------------------
67 // global functions declarations
68 // ----------------------------------------------------------------------------
69
70 // compare functions for sorting the arrays
71 static int CompareEntries(ConfigEntry *p1, ConfigEntry *p2);
72 static int CompareGroups(ConfigGroup *p1, ConfigGroup *p2);
73
74 // filter strings
75 static wxString FilterInValue(const wxString& str);
76 static wxString FilterOutValue(const wxString& str);
77
78 static wxString FilterInEntryName(const wxString& str);
79 static wxString FilterOutEntryName(const wxString& str);
80
81 // get the name to use in wxFileConfig ctor
82 static wxString GetAppName(const wxString& appname);
83
84 // ============================================================================
85 // implementation
86 // ============================================================================
87
88 // ----------------------------------------------------------------------------
89 // static functions
90 // ----------------------------------------------------------------------------
91 wxString wxFileConfig::GetGlobalDir()
92 {
93 wxString strDir;
94
95 #ifdef __UNIX__
96 strDir = _T("/etc/");
97 #elif defined(__WXSTUBS__)
98 wxASSERT_MSG( FALSE, _T("TODO") ) ;
99 #elif defined(__WXMAC__)
100 wxASSERT_MSG( FALSE, _T("TODO") ) ;
101 #else // Windows
102 wxChar szWinDir[MAX_PATH];
103 ::GetWindowsDirectory(szWinDir, MAX_PATH);
104
105 strDir = szWinDir;
106 strDir << _T('\\');
107 #endif // Unix/Windows
108
109 return strDir;
110 }
111
112 wxString wxFileConfig::GetLocalDir()
113 {
114 wxString strDir;
115
116 wxGetHomeDir(&strDir);
117
118 #ifdef __UNIX__
119 if (strDir.Last() != _T('/')) strDir << _T('/');
120 #else
121 if (strDir.Last() != _T('\\')) strDir << _T('\\');
122 #endif
123
124 return strDir;
125 }
126
127 wxString wxFileConfig::GetGlobalFileName(const wxChar *szFile)
128 {
129 wxString str = GetGlobalDir();
130 str << szFile;
131
132 if ( wxStrchr(szFile, _T('.')) == NULL )
133 #ifdef __UNIX__
134 str << _T(".conf");
135 #else // Windows
136 str << _T(".ini");
137 #endif // UNIX/Win
138
139 return str;
140 }
141
142 wxString wxFileConfig::GetLocalFileName(const wxChar *szFile)
143 {
144 wxString str = GetLocalDir();
145
146 #ifdef __UNIX__
147 str << _T('.');
148 #endif
149
150 str << szFile;
151
152 #ifdef __WXMSW__
153 if ( wxStrchr(szFile, _T('.')) == NULL )
154 str << _T(".ini");
155 #endif
156
157 return str;
158 }
159
160 // ----------------------------------------------------------------------------
161 // ctor
162 // ----------------------------------------------------------------------------
163
164 void wxFileConfig::Init()
165 {
166 m_pCurrentGroup =
167 m_pRootGroup = new ConfigGroup(NULL, "", this);
168
169 m_linesHead =
170 m_linesTail = NULL;
171
172 // it's not an error if (one of the) file(s) doesn't exist
173
174 // parse the global file
175 if ( !m_strGlobalFile.IsEmpty() && wxFile::Exists(m_strGlobalFile) ) {
176 wxTextFile fileGlobal(m_strGlobalFile);
177
178 if ( fileGlobal.Open() ) {
179 Parse(fileGlobal, FALSE /* global */);
180 SetRootPath();
181 }
182 else
183 wxLogWarning(_("can't open global configuration file '%s'."),
184 m_strGlobalFile.c_str());
185 }
186
187 // parse the local file
188 if ( !m_strLocalFile.IsEmpty() && wxFile::Exists(m_strLocalFile) ) {
189 wxTextFile fileLocal(m_strLocalFile);
190 if ( fileLocal.Open() ) {
191 Parse(fileLocal, TRUE /* local */);
192 SetRootPath();
193 }
194 else
195 wxLogWarning(_("can't open user configuration file '%s'."),
196 m_strLocalFile.c_str());
197 }
198 }
199
200 // constructor supports creation of wxFileConfig objects of any type
201 wxFileConfig::wxFileConfig(const wxString& appName, const wxString& vendorName,
202 const wxString& strLocal, const wxString& strGlobal,
203 long style)
204 : wxConfigBase(::GetAppName(appName), vendorName,
205 strLocal, strGlobal,
206 style),
207 m_strLocalFile(strLocal), m_strGlobalFile(strGlobal)
208 {
209 // Make up names for files if empty
210 if ( m_strLocalFile.IsEmpty() && (style & wxCONFIG_USE_LOCAL_FILE) )
211 {
212 m_strLocalFile = GetLocalFileName(GetAppName());
213 }
214
215 if ( m_strGlobalFile.IsEmpty() && (style & wxCONFIG_USE_GLOBAL_FILE) )
216 {
217 m_strGlobalFile = GetGlobalFileName(GetAppName());
218 }
219
220 // Check if styles are not supplied, but filenames are, in which case
221 // add the correct styles.
222 if ( !m_strLocalFile.IsEmpty() )
223 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE);
224
225 if ( !m_strGlobalFile.IsEmpty() )
226 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE);
227
228 // if the path is not absolute, prepend the standard directory to it
229 if ( !m_strLocalFile.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile) )
230 {
231 wxString strLocal = m_strLocalFile;
232 m_strLocalFile = GetLocalDir();
233 m_strLocalFile << strLocal;
234 }
235
236 if ( !m_strGlobalFile.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile) )
237 {
238 wxString strGlobal = m_strGlobalFile;
239 m_strGlobalFile = GetGlobalDir();
240 m_strGlobalFile << strGlobal;
241 }
242
243 Init();
244 }
245
246 void wxFileConfig::CleanUp()
247 {
248 delete m_pRootGroup;
249
250 LineList *pCur = m_linesHead;
251 while ( pCur != NULL ) {
252 LineList *pNext = pCur->Next();
253 delete pCur;
254 pCur = pNext;
255 }
256 }
257
258 wxFileConfig::~wxFileConfig()
259 {
260 Flush();
261
262 CleanUp();
263 }
264
265 // ----------------------------------------------------------------------------
266 // parse a config file
267 // ----------------------------------------------------------------------------
268
269 void wxFileConfig::Parse(wxTextFile& file, bool bLocal)
270 {
271 const wxChar *pStart;
272 const wxChar *pEnd;
273 wxString strLine;
274
275 size_t nLineCount = file.GetLineCount();
276 for ( size_t n = 0; n < nLineCount; n++ ) {
277 strLine = file[n];
278
279 // add the line to linked list
280 if ( bLocal )
281 LineListAppend(strLine);
282
283 // skip leading spaces
284 for ( pStart = strLine; wxIsspace(*pStart); pStart++ )
285 ;
286
287 // skip blank/comment lines
288 if ( *pStart == _T('\0')|| *pStart == _T(';') || *pStart == _T('#') )
289 continue;
290
291 if ( *pStart == _T('[') ) { // a new group
292 pEnd = pStart;
293
294 while ( *++pEnd != _T(']') ) {
295 if ( *pEnd == _T('\n') || *pEnd == _T('\0') )
296 break;
297 }
298
299 if ( *pEnd != _T(']') ) {
300 wxLogError(_("file '%s': unexpected character %c at line %d."),
301 file.GetName(), *pEnd, n + 1);
302 continue; // skip this line
303 }
304
305 // group name here is always considered as abs path
306 wxString strGroup;
307 pStart++;
308 strGroup << wxCONFIG_PATH_SEPARATOR
309 << FilterInEntryName(wxString(pStart, pEnd - pStart));
310
311 // will create it if doesn't yet exist
312 SetPath(strGroup);
313
314 if ( bLocal )
315 m_pCurrentGroup->SetLine(m_linesTail);
316
317 // check that there is nothing except comments left on this line
318 bool bCont = TRUE;
319 while ( *++pEnd != _T('\0') && bCont ) {
320 switch ( *pEnd ) {
321 case _T('#'):
322 case _T(';'):
323 bCont = FALSE;
324 break;
325
326 case _T(' '):
327 case _T('\t'):
328 // ignore whitespace ('\n' impossible here)
329 break;
330
331 default:
332 wxLogWarning(_("file '%s', line %d: '%s' "
333 "ignored after group header."),
334 file.GetName(), n + 1, pEnd);
335 bCont = FALSE;
336 }
337 }
338 }
339 else { // a key
340 const wxChar *pEnd = pStart;
341 while ( !wxIsspace(*pEnd) ) {
342 if ( *pEnd == _T('\\') ) {
343 // next character may be space or not - still take it because it's
344 // quoted
345 pEnd++;
346 }
347
348 pEnd++;
349 }
350
351 wxString strKey(FilterInEntryName(wxString(pStart, pEnd)));
352
353 // skip whitespace
354 while ( isspace(*pEnd) )
355 pEnd++;
356
357 if ( *pEnd++ != _T('=') ) {
358 wxLogError(_("file '%s', line %d: '=' expected."),
359 file.GetName(), n + 1);
360 }
361 else {
362 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strKey);
363
364 if ( pEntry == NULL ) {
365 // new entry
366 pEntry = m_pCurrentGroup->AddEntry(strKey, n);
367
368 if ( bLocal )
369 pEntry->SetLine(m_linesTail);
370 }
371 else {
372 if ( bLocal && pEntry->IsImmutable() ) {
373 // immutable keys can't be changed by user
374 wxLogWarning(_("file '%s', line %d: value for "
375 "immutable key '%s' ignored."),
376 file.GetName(), n + 1, strKey.c_str());
377 continue;
378 }
379 // the condition below catches the cases (a) and (b) but not (c):
380 // (a) global key found second time in global file
381 // (b) key found second (or more) time in local file
382 // (c) key from global file now found in local one
383 // which is exactly what we want.
384 else if ( !bLocal || pEntry->IsLocal() ) {
385 wxLogWarning(_("file '%s', line %d: key '%s' was first "
386 "found at line %d."),
387 file.GetName(), n + 1, strKey.c_str(), pEntry->Line());
388
389 if ( bLocal )
390 pEntry->SetLine(m_linesTail);
391 }
392 }
393
394 // skip whitespace
395 while ( wxIsspace(*pEnd) )
396 pEnd++;
397
398 pEntry->SetValue(FilterInValue(pEnd), FALSE /* read from file */);
399 }
400 }
401 }
402 }
403
404 // ----------------------------------------------------------------------------
405 // set/retrieve path
406 // ----------------------------------------------------------------------------
407
408 void wxFileConfig::SetRootPath()
409 {
410 m_strPath.Empty();
411 m_pCurrentGroup = m_pRootGroup;
412 }
413
414 void wxFileConfig::SetPath(const wxString& strPath)
415 {
416 wxArrayString aParts;
417
418 if ( strPath.IsEmpty() ) {
419 SetRootPath();
420 return;
421 }
422
423 if ( strPath[0] == wxCONFIG_PATH_SEPARATOR ) {
424 // absolute path
425 wxSplitPath(aParts, strPath);
426 }
427 else {
428 // relative path, combine with current one
429 wxString strFullPath = m_strPath;
430 strFullPath << wxCONFIG_PATH_SEPARATOR << strPath;
431 wxSplitPath(aParts, strFullPath);
432 }
433
434 // change current group
435 size_t n;
436 m_pCurrentGroup = m_pRootGroup;
437 for ( n = 0; n < aParts.Count(); n++ ) {
438 ConfigGroup *pNextGroup = m_pCurrentGroup->FindSubgroup(aParts[n]);
439 if ( pNextGroup == NULL )
440 pNextGroup = m_pCurrentGroup->AddSubgroup(aParts[n]);
441 m_pCurrentGroup = pNextGroup;
442 }
443
444 // recombine path parts in one variable
445 m_strPath.Empty();
446 for ( n = 0; n < aParts.Count(); n++ ) {
447 m_strPath << wxCONFIG_PATH_SEPARATOR << aParts[n];
448 }
449 }
450
451 // ----------------------------------------------------------------------------
452 // enumeration
453 // ----------------------------------------------------------------------------
454
455 bool wxFileConfig::GetFirstGroup(wxString& str, long& lIndex) const
456 {
457 lIndex = 0;
458 return GetNextGroup(str, lIndex);
459 }
460
461 bool wxFileConfig::GetNextGroup (wxString& str, long& lIndex) const
462 {
463 if ( size_t(lIndex) < m_pCurrentGroup->Groups().Count() ) {
464 str = m_pCurrentGroup->Groups()[lIndex++]->Name();
465 return TRUE;
466 }
467 else
468 return FALSE;
469 }
470
471 bool wxFileConfig::GetFirstEntry(wxString& str, long& lIndex) const
472 {
473 lIndex = 0;
474 return GetNextEntry(str, lIndex);
475 }
476
477 bool wxFileConfig::GetNextEntry (wxString& str, long& lIndex) const
478 {
479 if ( size_t(lIndex) < m_pCurrentGroup->Entries().Count() ) {
480 str = m_pCurrentGroup->Entries()[lIndex++]->Name();
481 return TRUE;
482 }
483 else
484 return FALSE;
485 }
486
487 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive) const
488 {
489 size_t n = m_pCurrentGroup->Entries().Count();
490 if ( bRecursive ) {
491 ConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
492 size_t nSubgroups = m_pCurrentGroup->Groups().Count();
493 for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
494 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
495 n += GetNumberOfEntries(TRUE);
496 CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
497 }
498 }
499
500 return n;
501 }
502
503 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive) const
504 {
505 size_t n = m_pCurrentGroup->Groups().Count();
506 if ( bRecursive ) {
507 ConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
508 size_t nSubgroups = m_pCurrentGroup->Groups().Count();
509 for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
510 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
511 n += GetNumberOfGroups(TRUE);
512 CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
513 }
514 }
515
516 return n;
517 }
518
519 // ----------------------------------------------------------------------------
520 // tests for existence
521 // ----------------------------------------------------------------------------
522
523 bool wxFileConfig::HasGroup(const wxString& strName) const
524 {
525 wxConfigPathChanger path(this, strName);
526
527 ConfigGroup *pGroup = m_pCurrentGroup->FindSubgroup(path.Name());
528 return pGroup != NULL;
529 }
530
531 bool wxFileConfig::HasEntry(const wxString& strName) const
532 {
533 wxConfigPathChanger path(this, strName);
534
535 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
536 return pEntry != NULL;
537 }
538
539 // ----------------------------------------------------------------------------
540 // read/write values
541 // ----------------------------------------------------------------------------
542
543 bool wxFileConfig::Read(const wxString& key,
544 wxString* pStr) const
545 {
546 wxConfigPathChanger path(this, key);
547
548 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
549 if (pEntry == NULL) {
550 return FALSE;
551 }
552 else {
553 *pStr = ExpandEnvVars(pEntry->Value());
554 return TRUE;
555 }
556 }
557
558 bool wxFileConfig::Read(const wxString& key,
559 wxString* pStr, const wxString& defVal) const
560 {
561 wxConfigPathChanger path(this, key);
562
563 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
564 if (pEntry == NULL) {
565 if( IsRecordingDefaults() )
566 ((wxFileConfig *)this)->Write(key,defVal);
567 *pStr = ExpandEnvVars(defVal);
568 return FALSE;
569 }
570 else {
571 *pStr = ExpandEnvVars(pEntry->Value());
572 return TRUE;
573 }
574 }
575
576 bool wxFileConfig::Read(const wxString& key, long *pl) const
577 {
578 wxString str;
579 if ( Read(key, & str) ) {
580 *pl = wxAtol(str);
581 return TRUE;
582 }
583 else {
584 return FALSE;
585 }
586 }
587
588 bool wxFileConfig::Write(const wxString& key, const wxString& szValue)
589 {
590 wxConfigPathChanger path(this, key);
591
592 wxString strName = path.Name();
593 if ( strName.IsEmpty() ) {
594 // setting the value of a group is an error
595 wxASSERT_MSG( wxIsEmpty(szValue), _T("can't set value of a group!") );
596
597 // ... except if it's empty in which case it's a way to force it's creation
598 m_pCurrentGroup->SetDirty();
599
600 // this will add a line for this group if it didn't have it before
601 (void)m_pCurrentGroup->GetGroupLine();
602 }
603 else {
604 // writing an entry
605
606 // check that the name is reasonable
607 if ( strName[0u] == wxCONFIG_IMMUTABLE_PREFIX ) {
608 wxLogError(_("Config entry name cannot start with '%c'."),
609 wxCONFIG_IMMUTABLE_PREFIX);
610 return FALSE;
611 }
612
613 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strName);
614 if ( pEntry == NULL )
615 pEntry = m_pCurrentGroup->AddEntry(strName);
616
617 pEntry->SetValue(szValue);
618 }
619
620 return TRUE;
621 }
622
623 bool wxFileConfig::Write(const wxString& key, long lValue)
624 {
625 // ltoa() is not ANSI :-(
626 wxString buf;
627 buf.Printf(_T("%ld"), lValue);
628 return Write(key, buf);
629 }
630
631 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
632 {
633 if ( LineListIsEmpty() || !m_pRootGroup->IsDirty() )
634 return TRUE;
635
636 wxTempFile file(m_strLocalFile);
637
638 if ( !file.IsOpened() ) {
639 wxLogError(_("can't open user configuration file."));
640 return FALSE;
641 }
642
643 // write all strings to file
644 for ( LineList *p = m_linesHead; p != NULL; p = p->Next() ) {
645 if ( !file.Write(p->Text() + wxTextFile::GetEOL()) ) {
646 wxLogError(_("can't write user configuration file."));
647 return FALSE;
648 }
649 }
650
651 return file.Commit();
652 }
653
654 // ----------------------------------------------------------------------------
655 // renaming groups/entries
656 // ----------------------------------------------------------------------------
657
658 bool wxFileConfig::RenameEntry(const wxString& oldName,
659 const wxString& newName)
660 {
661 // check that the entry exists
662 ConfigEntry *oldEntry = m_pCurrentGroup->FindEntry(oldName);
663 if ( !oldEntry )
664 return FALSE;
665
666 // check that the new entry doesn't already exist
667 if ( m_pCurrentGroup->FindEntry(newName) )
668 return FALSE;
669
670 // delete the old entry, create the new one
671 wxString value = oldEntry->Value();
672 if ( !m_pCurrentGroup->DeleteEntry(oldName) )
673 return FALSE;
674
675 ConfigEntry *newEntry = m_pCurrentGroup->AddEntry(newName);
676 newEntry->SetValue(value);
677
678 return TRUE;
679 }
680
681 bool wxFileConfig::RenameGroup(const wxString& oldName,
682 const wxString& newName)
683 {
684 // check that the group exists
685 ConfigGroup *group = m_pCurrentGroup->FindSubgroup(oldName);
686 if ( !group )
687 return FALSE;
688
689 // check that the new group doesn't already exist
690 if ( m_pCurrentGroup->FindSubgroup(newName) )
691 return FALSE;
692
693 group->Rename(newName);
694
695 return TRUE;
696 }
697
698 // ----------------------------------------------------------------------------
699 // delete groups/entries
700 // ----------------------------------------------------------------------------
701
702 bool wxFileConfig::DeleteEntry(const wxString& key, bool bGroupIfEmptyAlso)
703 {
704 wxConfigPathChanger path(this, key);
705
706 if ( !m_pCurrentGroup->DeleteEntry(path.Name()) )
707 return FALSE;
708
709 if ( bGroupIfEmptyAlso && m_pCurrentGroup->IsEmpty() ) {
710 if ( m_pCurrentGroup != m_pRootGroup ) {
711 ConfigGroup *pGroup = m_pCurrentGroup;
712 SetPath(_T("..")); // changes m_pCurrentGroup!
713 m_pCurrentGroup->DeleteSubgroupByName(pGroup->Name());
714 }
715 //else: never delete the root group
716 }
717
718 return TRUE;
719 }
720
721 bool wxFileConfig::DeleteGroup(const wxString& key)
722 {
723 wxConfigPathChanger path(this, key);
724
725 return m_pCurrentGroup->DeleteSubgroupByName(path.Name());
726 }
727
728 bool wxFileConfig::DeleteAll()
729 {
730 CleanUp();
731
732 if ( remove(m_strLocalFile.fn_str()) == -1 )
733 wxLogSysError(_("can't delete user configuration file '%s'"), m_strLocalFile.c_str());
734
735 m_strLocalFile = m_strGlobalFile = _T("");
736 Init();
737
738 return TRUE;
739 }
740
741 // ----------------------------------------------------------------------------
742 // linked list functions
743 // ----------------------------------------------------------------------------
744
745 // append a new line to the end of the list
746 LineList *wxFileConfig::LineListAppend(const wxString& str)
747 {
748 LineList *pLine = new LineList(str);
749
750 if ( m_linesTail == NULL ) {
751 // list is empty
752 m_linesHead = pLine;
753 }
754 else {
755 // adjust pointers
756 m_linesTail->SetNext(pLine);
757 pLine->SetPrev(m_linesTail);
758 }
759
760 m_linesTail = pLine;
761 return m_linesTail;
762 }
763
764 // insert a new line after the given one or in the very beginning if !pLine
765 LineList *wxFileConfig::LineListInsert(const wxString& str,
766 LineList *pLine)
767 {
768 if ( pLine == m_linesTail )
769 return LineListAppend(str);
770
771 LineList *pNewLine = new LineList(str);
772 if ( pLine == NULL ) {
773 // prepend to the list
774 pNewLine->SetNext(m_linesHead);
775 m_linesHead->SetPrev(pNewLine);
776 m_linesHead = pNewLine;
777 }
778 else {
779 // insert before pLine
780 LineList *pNext = pLine->Next();
781 pNewLine->SetNext(pNext);
782 pNewLine->SetPrev(pLine);
783 pNext->SetPrev(pNewLine);
784 pLine->SetNext(pNewLine);
785 }
786
787 return pNewLine;
788 }
789
790 void wxFileConfig::LineListRemove(LineList *pLine)
791 {
792 LineList *pPrev = pLine->Prev(),
793 *pNext = pLine->Next();
794
795 // first entry?
796 if ( pPrev == NULL )
797 m_linesHead = pNext;
798 else
799 pPrev->SetNext(pNext);
800
801 // last entry?
802 if ( pNext == NULL )
803 m_linesTail = pPrev;
804 else
805 pNext->SetPrev(pPrev);
806
807 delete pLine;
808 }
809
810 bool wxFileConfig::LineListIsEmpty()
811 {
812 return m_linesHead == NULL;
813 }
814
815 // ============================================================================
816 // wxFileConfig::ConfigGroup
817 // ============================================================================
818
819 // ----------------------------------------------------------------------------
820 // ctor/dtor
821 // ----------------------------------------------------------------------------
822
823 // ctor
824 ConfigGroup::ConfigGroup(ConfigGroup *pParent,
825 const wxString& strName,
826 wxFileConfig *pConfig)
827 : m_aEntries(CompareEntries),
828 m_aSubgroups(CompareGroups),
829 m_strName(strName)
830 {
831 m_pConfig = pConfig;
832 m_pParent = pParent;
833 m_bDirty = FALSE;
834 m_pLine = NULL;
835
836 m_pLastEntry = NULL;
837 m_pLastGroup = NULL;
838 }
839
840 // dtor deletes all children
841 ConfigGroup::~ConfigGroup()
842 {
843 // entries
844 size_t n, nCount = m_aEntries.Count();
845 for ( n = 0; n < nCount; n++ )
846 delete m_aEntries[n];
847
848 // subgroups
849 nCount = m_aSubgroups.Count();
850 for ( n = 0; n < nCount; n++ )
851 delete m_aSubgroups[n];
852 }
853
854 // ----------------------------------------------------------------------------
855 // line
856 // ----------------------------------------------------------------------------
857
858 void ConfigGroup::SetLine(LineList *pLine)
859 {
860 wxASSERT( m_pLine == NULL ); // shouldn't be called twice
861
862 m_pLine = pLine;
863 }
864
865 /*
866 This is a bit complicated, so let me explain it in details. All lines that
867 were read from the local file (the only one we will ever modify) are stored
868 in a (doubly) linked list. Our problem is to know at which position in this
869 list should we insert the new entries/subgroups. To solve it we keep three
870 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
871
872 m_pLine points to the line containing "[group_name]"
873 m_pLastEntry points to the last entry of this group in the local file.
874 m_pLastGroup subgroup
875
876 Initially, they're NULL all three. When the group (an entry/subgroup) is read
877 from the local file, the corresponding variable is set. However, if the group
878 was read from the global file and then modified or created by the application
879 these variables are still NULL and we need to create the corresponding lines.
880 See the following functions (and comments preceding them) for the details of
881 how we do it.
882
883 Also, when our last entry/group are deleted we need to find the new last
884 element - the code in DeleteEntry/Subgroup does this by backtracking the list
885 of lines until it either founds an entry/subgroup (and this is the new last
886 element) or the m_pLine of the group, in which case there are no more entries
887 (or subgroups) left and m_pLast<element> becomes NULL.
888
889 NB: This last problem could be avoided for entries if we added new entries
890 immediately after m_pLine, but in this case the entries would appear
891 backwards in the config file (OTOH, it's not that important) and as we
892 would still need to do it for the subgroups the code wouldn't have been
893 significantly less complicated.
894 */
895
896 // Return the line which contains "[our name]". If we're still not in the list,
897 // add our line to it immediately after the last line of our parent group if we
898 // have it or in the very beginning if we're the root group.
899 LineList *ConfigGroup::GetGroupLine()
900 {
901 if ( m_pLine == NULL ) {
902 ConfigGroup *pParent = Parent();
903
904 // this group wasn't present in local config file, add it now
905 if ( pParent != NULL ) {
906 wxString strFullName;
907 strFullName << _T("[")
908 // +1: no '/'
909 << FilterOutEntryName(GetFullName().c_str() + 1)
910 << _T("]");
911 m_pLine = m_pConfig->LineListInsert(strFullName,
912 pParent->GetLastGroupLine());
913 pParent->SetLastGroup(this); // we're surely after all the others
914 }
915 else {
916 // we return NULL, so that LineListInsert() will insert us in the
917 // very beginning
918 }
919 }
920
921 return m_pLine;
922 }
923
924 // Return the last line belonging to the subgroups of this group (after which
925 // we can add a new subgroup), if we don't have any subgroups or entries our
926 // last line is the group line (m_pLine) itself.
927 LineList *ConfigGroup::GetLastGroupLine()
928 {
929 // if we have any subgroups, our last line is the last line of the last
930 // subgroup
931 if ( m_pLastGroup != NULL ) {
932 LineList *pLine = m_pLastGroup->GetLastGroupLine();
933
934 wxASSERT( pLine != NULL ); // last group must have !NULL associated line
935 return pLine;
936 }
937
938 // no subgroups, so the last line is the line of thelast entry (if any)
939 return GetLastEntryLine();
940 }
941
942 // return the last line belonging to the entries of this group (after which
943 // we can add a new entry), if we don't have any entries we will add the new
944 // one immediately after the group line itself.
945 LineList *ConfigGroup::GetLastEntryLine()
946 {
947 if ( m_pLastEntry != NULL ) {
948 LineList *pLine = m_pLastEntry->GetLine();
949
950 wxASSERT( pLine != NULL ); // last entry must have !NULL associated line
951 return pLine;
952 }
953
954 // no entries: insert after the group header
955 return GetGroupLine();
956 }
957
958 // ----------------------------------------------------------------------------
959 // group name
960 // ----------------------------------------------------------------------------
961
962 void ConfigGroup::Rename(const wxString& newName)
963 {
964 m_strName = newName;
965
966 LineList *line = GetGroupLine();
967 wxString strFullName;
968 strFullName << _T("[") << (GetFullName().c_str() + 1) << _T("]"); // +1: no '/'
969 line->SetText(strFullName);
970
971 SetDirty();
972 }
973
974 wxString ConfigGroup::GetFullName() const
975 {
976 if ( Parent() )
977 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR + Name();
978 else
979 return _T("");
980 }
981
982 // ----------------------------------------------------------------------------
983 // find an item
984 // ----------------------------------------------------------------------------
985
986 // use binary search because the array is sorted
987 ConfigEntry *
988 ConfigGroup::FindEntry(const wxChar *szName) const
989 {
990 size_t i,
991 lo = 0,
992 hi = m_aEntries.Count();
993 int res;
994 ConfigEntry *pEntry;
995
996 while ( lo < hi ) {
997 i = (lo + hi)/2;
998 pEntry = m_aEntries[i];
999
1000 #if wxCONFIG_CASE_SENSITIVE
1001 res = wxStrcmp(pEntry->Name(), szName);
1002 #else
1003 res = wxStricmp(pEntry->Name(), szName);
1004 #endif
1005
1006 if ( res > 0 )
1007 hi = i;
1008 else if ( res < 0 )
1009 lo = i + 1;
1010 else
1011 return pEntry;
1012 }
1013
1014 return NULL;
1015 }
1016
1017 ConfigGroup *
1018 ConfigGroup::FindSubgroup(const wxChar *szName) const
1019 {
1020 size_t i,
1021 lo = 0,
1022 hi = m_aSubgroups.Count();
1023 int res;
1024 ConfigGroup *pGroup;
1025
1026 while ( lo < hi ) {
1027 i = (lo + hi)/2;
1028 pGroup = m_aSubgroups[i];
1029
1030 #if wxCONFIG_CASE_SENSITIVE
1031 res = wxStrcmp(pGroup->Name(), szName);
1032 #else
1033 res = wxStricmp(pGroup->Name(), szName);
1034 #endif
1035
1036 if ( res > 0 )
1037 hi = i;
1038 else if ( res < 0 )
1039 lo = i + 1;
1040 else
1041 return pGroup;
1042 }
1043
1044 return NULL;
1045 }
1046
1047 // ----------------------------------------------------------------------------
1048 // create a new item
1049 // ----------------------------------------------------------------------------
1050
1051 // create a new entry and add it to the current group
1052 ConfigEntry *
1053 ConfigGroup::AddEntry(const wxString& strName, int nLine)
1054 {
1055 wxASSERT( FindEntry(strName) == NULL );
1056
1057 ConfigEntry *pEntry = new ConfigEntry(this, strName, nLine);
1058 m_aEntries.Add(pEntry);
1059
1060 return pEntry;
1061 }
1062
1063 // create a new group and add it to the current group
1064 ConfigGroup *
1065 ConfigGroup::AddSubgroup(const wxString& strName)
1066 {
1067 wxASSERT( FindSubgroup(strName) == NULL );
1068
1069 ConfigGroup *pGroup = new ConfigGroup(this, strName, m_pConfig);
1070 m_aSubgroups.Add(pGroup);
1071
1072 return pGroup;
1073 }
1074
1075 // ----------------------------------------------------------------------------
1076 // delete an item
1077 // ----------------------------------------------------------------------------
1078
1079 /*
1080 The delete operations are _very_ slow if we delete the last item of this
1081 group (see comments before GetXXXLineXXX functions for more details),
1082 so it's much better to start with the first entry/group if we want to
1083 delete several of them.
1084 */
1085
1086 bool ConfigGroup::DeleteSubgroupByName(const wxChar *szName)
1087 {
1088 return DeleteSubgroup(FindSubgroup(szName));
1089 }
1090
1091 // doesn't delete the subgroup itself, but does remove references to it from
1092 // all other data structures (and normally the returned pointer should be
1093 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1094 bool ConfigGroup::DeleteSubgroup(ConfigGroup *pGroup)
1095 {
1096 wxCHECK( pGroup != NULL, FALSE ); // deleting non existing group?
1097
1098 // delete all entries
1099 size_t nCount = pGroup->m_aEntries.Count();
1100 for ( size_t nEntry = 0; nEntry < nCount; nEntry++ ) {
1101 LineList *pLine = pGroup->m_aEntries[nEntry]->GetLine();
1102 if ( pLine != NULL )
1103 m_pConfig->LineListRemove(pLine);
1104 }
1105
1106 // and subgroups of this sungroup
1107 nCount = pGroup->m_aSubgroups.Count();
1108 for ( size_t nGroup = 0; nGroup < nCount; nGroup++ ) {
1109 pGroup->DeleteSubgroup(pGroup->m_aSubgroups[0]);
1110 }
1111
1112 LineList *pLine = pGroup->m_pLine;
1113 if ( pLine != NULL ) {
1114 // notice that we may do this test inside the previous "if" because the
1115 // last entry's line is surely !NULL
1116 if ( pGroup == m_pLastGroup ) {
1117 // our last entry is being deleted - find the last one which stays
1118 wxASSERT( m_pLine != NULL ); // we have a subgroup with !NULL pLine...
1119
1120 // go back until we find a subgroup or reach the group's line
1121 ConfigGroup *pNewLast = NULL;
1122 size_t n, nSubgroups = m_aSubgroups.Count();
1123 LineList *pl;
1124 for ( pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() ) {
1125 // is it our subgroup?
1126 for ( n = 0; (pNewLast == NULL) && (n < nSubgroups); n++ ) {
1127 // do _not_ call GetGroupLine! we don't want to add it to the local
1128 // file if it's not already there
1129 if ( m_aSubgroups[n]->m_pLine == m_pLine )
1130 pNewLast = m_aSubgroups[n];
1131 }
1132
1133 if ( pNewLast != NULL ) // found?
1134 break;
1135 }
1136
1137 if ( pl == m_pLine ) {
1138 wxASSERT( !pNewLast ); // how comes it has the same line as we?
1139
1140 // we've reached the group line without finding any subgroups
1141 m_pLastGroup = NULL;
1142 }
1143 else
1144 m_pLastGroup = pNewLast;
1145 }
1146
1147 m_pConfig->LineListRemove(pLine);
1148 }
1149
1150 SetDirty();
1151
1152 m_aSubgroups.Remove(pGroup);
1153 delete pGroup;
1154
1155 return TRUE;
1156 }
1157
1158 bool ConfigGroup::DeleteEntry(const wxChar *szName)
1159 {
1160 ConfigEntry *pEntry = FindEntry(szName);
1161 wxCHECK( pEntry != NULL, FALSE ); // deleting non existing item?
1162
1163 LineList *pLine = pEntry->GetLine();
1164 if ( pLine != NULL ) {
1165 // notice that we may do this test inside the previous "if" because the
1166 // last entry's line is surely !NULL
1167 if ( pEntry == m_pLastEntry ) {
1168 // our last entry is being deleted - find the last one which stays
1169 wxASSERT( m_pLine != NULL ); // if we have an entry with !NULL pLine...
1170
1171 // go back until we find another entry or reach the group's line
1172 ConfigEntry *pNewLast = NULL;
1173 size_t n, nEntries = m_aEntries.Count();
1174 LineList *pl;
1175 for ( pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() ) {
1176 // is it our subgroup?
1177 for ( n = 0; (pNewLast == NULL) && (n < nEntries); n++ ) {
1178 if ( m_aEntries[n]->GetLine() == m_pLine )
1179 pNewLast = m_aEntries[n];
1180 }
1181
1182 if ( pNewLast != NULL ) // found?
1183 break;
1184 }
1185
1186 if ( pl == m_pLine ) {
1187 wxASSERT( !pNewLast ); // how comes it has the same line as we?
1188
1189 // we've reached the group line without finding any subgroups
1190 m_pLastEntry = NULL;
1191 }
1192 else
1193 m_pLastEntry = pNewLast;
1194 }
1195
1196 m_pConfig->LineListRemove(pLine);
1197 }
1198
1199 // we must be written back for the changes to be saved
1200 SetDirty();
1201
1202 m_aEntries.Remove(pEntry);
1203 delete pEntry;
1204
1205 return TRUE;
1206 }
1207
1208 // ----------------------------------------------------------------------------
1209 //
1210 // ----------------------------------------------------------------------------
1211 void ConfigGroup::SetDirty()
1212 {
1213 m_bDirty = TRUE;
1214 if ( Parent() != NULL ) // propagate upwards
1215 Parent()->SetDirty();
1216 }
1217
1218 // ============================================================================
1219 // wxFileConfig::ConfigEntry
1220 // ============================================================================
1221
1222 // ----------------------------------------------------------------------------
1223 // ctor
1224 // ----------------------------------------------------------------------------
1225 ConfigEntry::ConfigEntry(ConfigGroup *pParent,
1226 const wxString& strName,
1227 int nLine)
1228 : m_strName(strName)
1229 {
1230 wxASSERT( !strName.IsEmpty() );
1231
1232 m_pParent = pParent;
1233 m_nLine = nLine;
1234 m_pLine = NULL;
1235
1236 m_bDirty = FALSE;
1237
1238 m_bImmutable = strName[0] == wxCONFIG_IMMUTABLE_PREFIX;
1239 if ( m_bImmutable )
1240 m_strName.erase(0, 1); // remove first character
1241 }
1242
1243 // ----------------------------------------------------------------------------
1244 // set value
1245 // ----------------------------------------------------------------------------
1246
1247 void ConfigEntry::SetLine(LineList *pLine)
1248 {
1249 if ( m_pLine != NULL ) {
1250 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1251 Name().c_str(), m_pParent->GetFullName().c_str());
1252 }
1253
1254 m_pLine = pLine;
1255 Group()->SetLastEntry(this);
1256 }
1257
1258 // second parameter is FALSE if we read the value from file and prevents the
1259 // entry from being marked as 'dirty'
1260 void ConfigEntry::SetValue(const wxString& strValue, bool bUser)
1261 {
1262 if ( bUser && IsImmutable() ) {
1263 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1264 Name().c_str());
1265 return;
1266 }
1267
1268 // do nothing if it's the same value
1269 if ( strValue == m_strValue )
1270 return;
1271
1272 m_strValue = strValue;
1273
1274 if ( bUser ) {
1275 wxString strVal = FilterOutValue(strValue);
1276 wxString strLine;
1277 strLine << FilterOutEntryName(m_strName) << _T(" = ") << strVal;
1278
1279 if ( m_pLine != NULL ) {
1280 // entry was read from the local config file, just modify the line
1281 m_pLine->SetText(strLine);
1282 }
1283 else {
1284 // add a new line to the file
1285 wxASSERT( m_nLine == wxNOT_FOUND ); // consistency check
1286
1287 m_pLine = Group()->Config()->LineListInsert(strLine,
1288 Group()->GetLastEntryLine());
1289 Group()->SetLastEntry(this);
1290 }
1291
1292 SetDirty();
1293 }
1294 }
1295
1296 void ConfigEntry::SetDirty()
1297 {
1298 m_bDirty = TRUE;
1299 Group()->SetDirty();
1300 }
1301
1302 // ============================================================================
1303 // global functions
1304 // ============================================================================
1305
1306 // ----------------------------------------------------------------------------
1307 // compare functions for array sorting
1308 // ----------------------------------------------------------------------------
1309
1310 int CompareEntries(ConfigEntry *p1,
1311 ConfigEntry *p2)
1312 {
1313 #if wxCONFIG_CASE_SENSITIVE
1314 return wxStrcmp(p1->Name(), p2->Name());
1315 #else
1316 return wxStricmp(p1->Name(), p2->Name());
1317 #endif
1318 }
1319
1320 int CompareGroups(ConfigGroup *p1,
1321 ConfigGroup *p2)
1322 {
1323 #if wxCONFIG_CASE_SENSITIVE
1324 return wxStrcmp(p1->Name(), p2->Name());
1325 #else
1326 return wxStricmp(p1->Name(), p2->Name());
1327 #endif
1328 }
1329
1330 // ----------------------------------------------------------------------------
1331 // filter functions
1332 // ----------------------------------------------------------------------------
1333
1334 // undo FilterOutValue
1335 static wxString FilterInValue(const wxString& str)
1336 {
1337 wxString strResult;
1338 strResult.Alloc(str.Len());
1339
1340 bool bQuoted = !str.IsEmpty() && str[0] == '"';
1341
1342 for ( size_t n = bQuoted ? 1 : 0; n < str.Len(); n++ ) {
1343 if ( str[n] == _T('\\') ) {
1344 switch ( str[++n] ) {
1345 case _T('n'):
1346 strResult += _T('\n');
1347 break;
1348
1349 case _T('r'):
1350 strResult += _T('\r');
1351 break;
1352
1353 case _T('t'):
1354 strResult += _T('\t');
1355 break;
1356
1357 case _T('\\'):
1358 strResult += _T('\\');
1359 break;
1360
1361 case _T('"'):
1362 strResult += _T('"');
1363 break;
1364 }
1365 }
1366 else {
1367 if ( str[n] != _T('"') || !bQuoted )
1368 strResult += str[n];
1369 else if ( n != str.Len() - 1 ) {
1370 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1371 n, str.c_str());
1372 }
1373 //else: it's the last quote of a quoted string, ok
1374 }
1375 }
1376
1377 return strResult;
1378 }
1379
1380 // quote the string before writing it to file
1381 static wxString FilterOutValue(const wxString& str)
1382 {
1383 if ( !str )
1384 return str;
1385
1386 wxString strResult;
1387 strResult.Alloc(str.Len());
1388
1389 // quoting is necessary to preserve spaces in the beginning of the string
1390 bool bQuote = wxIsspace(str[0]) || str[0] == _T('"');
1391
1392 if ( bQuote )
1393 strResult += _T('"');
1394
1395 wxChar c;
1396 for ( size_t n = 0; n < str.Len(); n++ ) {
1397 switch ( str[n] ) {
1398 case _T('\n'):
1399 c = _T('n');
1400 break;
1401
1402 case _T('\r'):
1403 c = _T('r');
1404 break;
1405
1406 case _T('\t'):
1407 c = _T('t');
1408 break;
1409
1410 case _T('\\'):
1411 c = _T('\\');
1412 break;
1413
1414 case _T('"'):
1415 if ( bQuote ) {
1416 c = _T('"');
1417 break;
1418 }
1419 //else: fall through
1420
1421 default:
1422 strResult += str[n];
1423 continue; // nothing special to do
1424 }
1425
1426 // we get here only for special characters
1427 strResult << _T('\\') << c;
1428 }
1429
1430 if ( bQuote )
1431 strResult += _T('"');
1432
1433 return strResult;
1434 }
1435
1436 // undo FilterOutEntryName
1437 static wxString FilterInEntryName(const wxString& str)
1438 {
1439 wxString strResult;
1440 strResult.Alloc(str.Len());
1441
1442 for ( const wxChar *pc = str.c_str(); *pc != '\0'; pc++ ) {
1443 if ( *pc == _T('\\') )
1444 pc++;
1445
1446 strResult += *pc;
1447 }
1448
1449 return strResult;
1450 }
1451
1452 // sanitize entry or group name: insert '\\' before any special characters
1453 static wxString FilterOutEntryName(const wxString& str)
1454 {
1455 wxString strResult;
1456 strResult.Alloc(str.Len());
1457
1458 for ( const wxChar *pc = str.c_str(); *pc != _T('\0'); pc++ ) {
1459 wxChar c = *pc;
1460
1461 // we explicitly allow some of "safe" chars and 8bit ASCII characters
1462 // which will probably never have special meaning
1463 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
1464 // should *not* be quoted
1465 if ( !wxIsalnum(c) && !wxStrchr(_T("@_/-!.*%"), c) && ((c & 0x80) == 0) )
1466 strResult += _T('\\');
1467
1468 strResult += c;
1469 }
1470
1471 return strResult;
1472 }
1473
1474 // we can't put ?: in the ctor initializer list because it confuses some
1475 // broken compilers (Borland C++)
1476 static wxString GetAppName(const wxString& appName)
1477 {
1478 if ( !appName && wxTheApp )
1479 return wxTheApp->GetAppName();
1480 else
1481 return appName;
1482 }