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