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