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