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