]> git.saurik.com Git - wxWidgets.git/blob - src/common/fileconf.cpp
MicroWindows tweaks
[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::DoReadString(const wxString& key, wxString* pStr) const
823 {
824 wxConfigPathChanger path(this, key);
825
826 wxFileConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
827 if (pEntry == NULL) {
828 return FALSE;
829 }
830
831 *pStr = pEntry->Value();
832
833 return TRUE;
834 }
835
836 bool wxFileConfig::DoReadLong(const wxString& key, long *pl) const
837 {
838 wxString str;
839 if ( !Read(key, & str) )
840 {
841 return FALSE;
842 }
843
844 return str.ToLong(pl);
845 }
846
847 bool wxFileConfig::DoWriteString(const wxString& key, const wxString& szValue)
848 {
849 wxConfigPathChanger path(this, key);
850
851 wxString strName = path.Name();
852 if ( strName.IsEmpty() ) {
853 // setting the value of a group is an error
854 wxASSERT_MSG( wxIsEmpty(szValue), wxT("can't set value of a group!") );
855
856 // ... except if it's empty in which case it's a way to force it's creation
857 m_pCurrentGroup->SetDirty();
858
859 // this will add a line for this group if it didn't have it before
860 (void)m_pCurrentGroup->GetGroupLine();
861 }
862 else {
863 // writing an entry
864
865 // check that the name is reasonable
866 if ( strName[0u] == wxCONFIG_IMMUTABLE_PREFIX ) {
867 wxLogError(_("Config entry name cannot start with '%c'."),
868 wxCONFIG_IMMUTABLE_PREFIX);
869 return FALSE;
870 }
871
872 wxFileConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strName);
873 if ( pEntry == NULL )
874 pEntry = m_pCurrentGroup->AddEntry(strName);
875
876 pEntry->SetValue(szValue);
877 }
878
879 return TRUE;
880 }
881
882 bool wxFileConfig::DoWriteLong(const wxString& key, long lValue)
883 {
884 return Write(key, wxString::Format(_T("%ld"), lValue));
885 }
886
887 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
888 {
889 if ( LineListIsEmpty() || !m_pRootGroup->IsDirty() || !m_strLocalFile )
890 return TRUE;
891
892 #ifdef __UNIX__
893 // set the umask if needed
894 mode_t umaskOld = 0;
895 if ( m_umask != -1 )
896 {
897 umaskOld = umask((mode_t)m_umask);
898 }
899 #endif // __UNIX__
900
901 wxTempFile file(m_strLocalFile);
902
903 if ( !file.IsOpened() ) {
904 wxLogError(_("can't open user configuration file."));
905 return FALSE;
906 }
907
908 // write all strings to file
909 for ( wxFileConfigLineList *p = m_linesHead; p != NULL; p = p->Next() ) {
910 if ( !file.Write(p->Text() + wxTextFile::GetEOL()) ) {
911 wxLogError(_("can't write user configuration file."));
912 return FALSE;
913 }
914 }
915
916 bool ret = file.Commit();
917
918 #if defined(__WXMAC__)
919 if ( ret )
920 {
921 FSSpec spec ;
922
923 wxMacFilename2FSSpec( m_strLocalFile , &spec ) ;
924 FInfo finfo ;
925 if ( FSpGetFInfo( &spec , &finfo ) == noErr )
926 {
927 finfo.fdType = 'TEXT' ;
928 finfo.fdCreator = 'ttxt' ;
929 FSpSetFInfo( &spec , &finfo ) ;
930 }
931 }
932 #endif // __WXMAC__
933
934 #ifdef __UNIX__
935 // restore the old umask if we changed it
936 if ( m_umask != -1 )
937 {
938 (void)umask(umaskOld);
939 }
940 #endif // __UNIX__
941
942 return ret;
943 }
944
945 // ----------------------------------------------------------------------------
946 // renaming groups/entries
947 // ----------------------------------------------------------------------------
948
949 bool wxFileConfig::RenameEntry(const wxString& oldName,
950 const wxString& newName)
951 {
952 // check that the entry exists
953 wxFileConfigEntry *oldEntry = m_pCurrentGroup->FindEntry(oldName);
954 if ( !oldEntry )
955 return FALSE;
956
957 // check that the new entry doesn't already exist
958 if ( m_pCurrentGroup->FindEntry(newName) )
959 return FALSE;
960
961 // delete the old entry, create the new one
962 wxString value = oldEntry->Value();
963 if ( !m_pCurrentGroup->DeleteEntry(oldName) )
964 return FALSE;
965
966 wxFileConfigEntry *newEntry = m_pCurrentGroup->AddEntry(newName);
967 newEntry->SetValue(value);
968
969 return TRUE;
970 }
971
972 bool wxFileConfig::RenameGroup(const wxString& oldName,
973 const wxString& newName)
974 {
975 // check that the group exists
976 wxFileConfigGroup *group = m_pCurrentGroup->FindSubgroup(oldName);
977 if ( !group )
978 return FALSE;
979
980 // check that the new group doesn't already exist
981 if ( m_pCurrentGroup->FindSubgroup(newName) )
982 return FALSE;
983
984 group->Rename(newName);
985
986 return TRUE;
987 }
988
989 // ----------------------------------------------------------------------------
990 // delete groups/entries
991 // ----------------------------------------------------------------------------
992
993 bool wxFileConfig::DeleteEntry(const wxString& key, bool bGroupIfEmptyAlso)
994 {
995 wxConfigPathChanger path(this, key);
996
997 if ( !m_pCurrentGroup->DeleteEntry(path.Name()) )
998 return FALSE;
999
1000 if ( bGroupIfEmptyAlso && m_pCurrentGroup->IsEmpty() ) {
1001 if ( m_pCurrentGroup != m_pRootGroup ) {
1002 wxFileConfigGroup *pGroup = m_pCurrentGroup;
1003 SetPath(wxT("..")); // changes m_pCurrentGroup!
1004 m_pCurrentGroup->DeleteSubgroupByName(pGroup->Name());
1005 }
1006 //else: never delete the root group
1007 }
1008
1009 return TRUE;
1010 }
1011
1012 bool wxFileConfig::DeleteGroup(const wxString& key)
1013 {
1014 wxConfigPathChanger path(this, key);
1015
1016 return m_pCurrentGroup->DeleteSubgroupByName(path.Name());
1017 }
1018
1019 bool wxFileConfig::DeleteAll()
1020 {
1021 CleanUp();
1022
1023 if ( wxRemove(m_strLocalFile) == -1 )
1024 wxLogSysError(_("can't delete user configuration file '%s'"), m_strLocalFile.c_str());
1025
1026 m_strLocalFile = m_strGlobalFile = wxT("");
1027 Init();
1028
1029 return TRUE;
1030 }
1031
1032 // ----------------------------------------------------------------------------
1033 // linked list functions
1034 // ----------------------------------------------------------------------------
1035
1036 // append a new line to the end of the list
1037 wxFileConfigLineList *wxFileConfig::LineListAppend(const wxString& str)
1038 {
1039 wxFileConfigLineList *pLine = new wxFileConfigLineList(str);
1040
1041 if ( m_linesTail == NULL ) {
1042 // list is empty
1043 m_linesHead = pLine;
1044 }
1045 else {
1046 // adjust pointers
1047 m_linesTail->SetNext(pLine);
1048 pLine->SetPrev(m_linesTail);
1049 }
1050
1051 m_linesTail = pLine;
1052 return m_linesTail;
1053 }
1054
1055 // insert a new line after the given one or in the very beginning if !pLine
1056 wxFileConfigLineList *wxFileConfig::LineListInsert(const wxString& str,
1057 wxFileConfigLineList *pLine)
1058 {
1059 if ( pLine == m_linesTail )
1060 return LineListAppend(str);
1061
1062 wxFileConfigLineList *pNewLine = new wxFileConfigLineList(str);
1063 if ( pLine == NULL ) {
1064 // prepend to the list
1065 pNewLine->SetNext(m_linesHead);
1066 m_linesHead->SetPrev(pNewLine);
1067 m_linesHead = pNewLine;
1068 }
1069 else {
1070 // insert before pLine
1071 wxFileConfigLineList *pNext = pLine->Next();
1072 pNewLine->SetNext(pNext);
1073 pNewLine->SetPrev(pLine);
1074 pNext->SetPrev(pNewLine);
1075 pLine->SetNext(pNewLine);
1076 }
1077
1078 return pNewLine;
1079 }
1080
1081 void wxFileConfig::LineListRemove(wxFileConfigLineList *pLine)
1082 {
1083 wxFileConfigLineList *pPrev = pLine->Prev(),
1084 *pNext = pLine->Next();
1085
1086 // first entry?
1087 if ( pPrev == NULL )
1088 m_linesHead = pNext;
1089 else
1090 pPrev->SetNext(pNext);
1091
1092 // last entry?
1093 if ( pNext == NULL )
1094 m_linesTail = pPrev;
1095 else
1096 pNext->SetPrev(pPrev);
1097
1098 delete pLine;
1099 }
1100
1101 bool wxFileConfig::LineListIsEmpty()
1102 {
1103 return m_linesHead == NULL;
1104 }
1105
1106 // ============================================================================
1107 // wxFileConfig::wxFileConfigGroup
1108 // ============================================================================
1109
1110 // ----------------------------------------------------------------------------
1111 // ctor/dtor
1112 // ----------------------------------------------------------------------------
1113
1114 // ctor
1115 wxFileConfigGroup::wxFileConfigGroup(wxFileConfigGroup *pParent,
1116 const wxString& strName,
1117 wxFileConfig *pConfig)
1118 : m_aEntries(CompareEntries),
1119 m_aSubgroups(CompareGroups),
1120 m_strName(strName)
1121 {
1122 m_pConfig = pConfig;
1123 m_pParent = pParent;
1124 m_bDirty = FALSE;
1125 m_pLine = NULL;
1126
1127 m_pLastEntry = NULL;
1128 m_pLastGroup = NULL;
1129 }
1130
1131 // dtor deletes all children
1132 wxFileConfigGroup::~wxFileConfigGroup()
1133 {
1134 // entries
1135 size_t n, nCount = m_aEntries.Count();
1136 for ( n = 0; n < nCount; n++ )
1137 delete m_aEntries[n];
1138
1139 // subgroups
1140 nCount = m_aSubgroups.Count();
1141 for ( n = 0; n < nCount; n++ )
1142 delete m_aSubgroups[n];
1143 }
1144
1145 // ----------------------------------------------------------------------------
1146 // line
1147 // ----------------------------------------------------------------------------
1148
1149 void wxFileConfigGroup::SetLine(wxFileConfigLineList *pLine)
1150 {
1151 wxASSERT( m_pLine == NULL ); // shouldn't be called twice
1152
1153 m_pLine = pLine;
1154 }
1155
1156 /*
1157 This is a bit complicated, so let me explain it in details. All lines that
1158 were read from the local file (the only one we will ever modify) are stored
1159 in a (doubly) linked list. Our problem is to know at which position in this
1160 list should we insert the new entries/subgroups. To solve it we keep three
1161 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
1162
1163 m_pLine points to the line containing "[group_name]"
1164 m_pLastEntry points to the last entry of this group in the local file.
1165 m_pLastGroup subgroup
1166
1167 Initially, they're NULL all three. When the group (an entry/subgroup) is read
1168 from the local file, the corresponding variable is set. However, if the group
1169 was read from the global file and then modified or created by the application
1170 these variables are still NULL and we need to create the corresponding lines.
1171 See the following functions (and comments preceding them) for the details of
1172 how we do it.
1173
1174 Also, when our last entry/group are deleted we need to find the new last
1175 element - the code in DeleteEntry/Subgroup does this by backtracking the list
1176 of lines until it either founds an entry/subgroup (and this is the new last
1177 element) or the m_pLine of the group, in which case there are no more entries
1178 (or subgroups) left and m_pLast<element> becomes NULL.
1179
1180 NB: This last problem could be avoided for entries if we added new entries
1181 immediately after m_pLine, but in this case the entries would appear
1182 backwards in the config file (OTOH, it's not that important) and as we
1183 would still need to do it for the subgroups the code wouldn't have been
1184 significantly less complicated.
1185 */
1186
1187 // Return the line which contains "[our name]". If we're still not in the list,
1188 // add our line to it immediately after the last line of our parent group if we
1189 // have it or in the very beginning if we're the root group.
1190 wxFileConfigLineList *wxFileConfigGroup::GetGroupLine()
1191 {
1192 if ( m_pLine == NULL ) {
1193 wxFileConfigGroup *pParent = Parent();
1194
1195 // this group wasn't present in local config file, add it now
1196 if ( pParent != NULL ) {
1197 wxString strFullName;
1198 strFullName << wxT("[")
1199 // +1: no '/'
1200 << FilterOutEntryName(GetFullName().c_str() + 1)
1201 << wxT("]");
1202 m_pLine = m_pConfig->LineListInsert(strFullName,
1203 pParent->GetLastGroupLine());
1204 pParent->SetLastGroup(this); // we're surely after all the others
1205 }
1206 else {
1207 // we return NULL, so that LineListInsert() will insert us in the
1208 // very beginning
1209 }
1210 }
1211
1212 return m_pLine;
1213 }
1214
1215 // Return the last line belonging to the subgroups of this group (after which
1216 // we can add a new subgroup), if we don't have any subgroups or entries our
1217 // last line is the group line (m_pLine) itself.
1218 wxFileConfigLineList *wxFileConfigGroup::GetLastGroupLine()
1219 {
1220 // if we have any subgroups, our last line is the last line of the last
1221 // subgroup
1222 if ( m_pLastGroup != NULL ) {
1223 wxFileConfigLineList *pLine = m_pLastGroup->GetLastGroupLine();
1224
1225 wxASSERT( pLine != NULL ); // last group must have !NULL associated line
1226 return pLine;
1227 }
1228
1229 // no subgroups, so the last line is the line of thelast entry (if any)
1230 return GetLastEntryLine();
1231 }
1232
1233 // return the last line belonging to the entries of this group (after which
1234 // we can add a new entry), if we don't have any entries we will add the new
1235 // one immediately after the group line itself.
1236 wxFileConfigLineList *wxFileConfigGroup::GetLastEntryLine()
1237 {
1238 if ( m_pLastEntry != NULL ) {
1239 wxFileConfigLineList *pLine = m_pLastEntry->GetLine();
1240
1241 wxASSERT( pLine != NULL ); // last entry must have !NULL associated line
1242 return pLine;
1243 }
1244
1245 // no entries: insert after the group header
1246 return GetGroupLine();
1247 }
1248
1249 // ----------------------------------------------------------------------------
1250 // group name
1251 // ----------------------------------------------------------------------------
1252
1253 void wxFileConfigGroup::Rename(const wxString& newName)
1254 {
1255 m_strName = newName;
1256
1257 wxFileConfigLineList *line = GetGroupLine();
1258 wxString strFullName;
1259 strFullName << wxT("[") << (GetFullName().c_str() + 1) << wxT("]"); // +1: no '/'
1260 line->SetText(strFullName);
1261
1262 SetDirty();
1263 }
1264
1265 wxString wxFileConfigGroup::GetFullName() const
1266 {
1267 if ( Parent() )
1268 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR + Name();
1269 else
1270 return wxT("");
1271 }
1272
1273 // ----------------------------------------------------------------------------
1274 // find an item
1275 // ----------------------------------------------------------------------------
1276
1277 // use binary search because the array is sorted
1278 wxFileConfigEntry *
1279 wxFileConfigGroup::FindEntry(const wxChar *szName) const
1280 {
1281 size_t i,
1282 lo = 0,
1283 hi = m_aEntries.Count();
1284 int res;
1285 wxFileConfigEntry *pEntry;
1286
1287 while ( lo < hi ) {
1288 i = (lo + hi)/2;
1289 pEntry = m_aEntries[i];
1290
1291 #if wxCONFIG_CASE_SENSITIVE
1292 res = wxStrcmp(pEntry->Name(), szName);
1293 #else
1294 res = wxStricmp(pEntry->Name(), szName);
1295 #endif
1296
1297 if ( res > 0 )
1298 hi = i;
1299 else if ( res < 0 )
1300 lo = i + 1;
1301 else
1302 return pEntry;
1303 }
1304
1305 return NULL;
1306 }
1307
1308 wxFileConfigGroup *
1309 wxFileConfigGroup::FindSubgroup(const wxChar *szName) const
1310 {
1311 size_t i,
1312 lo = 0,
1313 hi = m_aSubgroups.Count();
1314 int res;
1315 wxFileConfigGroup *pGroup;
1316
1317 while ( lo < hi ) {
1318 i = (lo + hi)/2;
1319 pGroup = m_aSubgroups[i];
1320
1321 #if wxCONFIG_CASE_SENSITIVE
1322 res = wxStrcmp(pGroup->Name(), szName);
1323 #else
1324 res = wxStricmp(pGroup->Name(), szName);
1325 #endif
1326
1327 if ( res > 0 )
1328 hi = i;
1329 else if ( res < 0 )
1330 lo = i + 1;
1331 else
1332 return pGroup;
1333 }
1334
1335 return NULL;
1336 }
1337
1338 // ----------------------------------------------------------------------------
1339 // create a new item
1340 // ----------------------------------------------------------------------------
1341
1342 // create a new entry and add it to the current group
1343 wxFileConfigEntry *
1344 wxFileConfigGroup::AddEntry(const wxString& strName, int nLine)
1345 {
1346 wxASSERT( FindEntry(strName) == NULL );
1347
1348 wxFileConfigEntry *pEntry = new wxFileConfigEntry(this, strName, nLine);
1349 m_aEntries.Add(pEntry);
1350
1351 return pEntry;
1352 }
1353
1354 // create a new group and add it to the current group
1355 wxFileConfigGroup *
1356 wxFileConfigGroup::AddSubgroup(const wxString& strName)
1357 {
1358 wxASSERT( FindSubgroup(strName) == NULL );
1359
1360 wxFileConfigGroup *pGroup = new wxFileConfigGroup(this, strName, m_pConfig);
1361 m_aSubgroups.Add(pGroup);
1362
1363 return pGroup;
1364 }
1365
1366 // ----------------------------------------------------------------------------
1367 // delete an item
1368 // ----------------------------------------------------------------------------
1369
1370 /*
1371 The delete operations are _very_ slow if we delete the last item of this
1372 group (see comments before GetXXXLineXXX functions for more details),
1373 so it's much better to start with the first entry/group if we want to
1374 delete several of them.
1375 */
1376
1377 bool wxFileConfigGroup::DeleteSubgroupByName(const wxChar *szName)
1378 {
1379 return DeleteSubgroup(FindSubgroup(szName));
1380 }
1381
1382 // doesn't delete the subgroup itself, but does remove references to it from
1383 // all other data structures (and normally the returned pointer should be
1384 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1385 bool wxFileConfigGroup::DeleteSubgroup(wxFileConfigGroup *pGroup)
1386 {
1387 wxCHECK( pGroup != NULL, FALSE ); // deleting non existing group?
1388
1389 // delete all entries
1390 size_t nCount = pGroup->m_aEntries.Count();
1391 for ( size_t nEntry = 0; nEntry < nCount; nEntry++ ) {
1392 wxFileConfigLineList *pLine = pGroup->m_aEntries[nEntry]->GetLine();
1393 if ( pLine != NULL )
1394 m_pConfig->LineListRemove(pLine);
1395 }
1396
1397 // and subgroups of this sungroup
1398 nCount = pGroup->m_aSubgroups.Count();
1399 for ( size_t nGroup = 0; nGroup < nCount; nGroup++ ) {
1400 pGroup->DeleteSubgroup(pGroup->m_aSubgroups[0]);
1401 }
1402
1403 wxFileConfigLineList *pLine = pGroup->m_pLine;
1404 if ( pLine != NULL ) {
1405 // notice that we may do this test inside the previous "if" because the
1406 // last entry's line is surely !NULL
1407 if ( pGroup == m_pLastGroup ) {
1408 // our last entry is being deleted - find the last one which stays
1409 wxASSERT( m_pLine != NULL ); // we have a subgroup with !NULL pLine...
1410
1411 // go back until we find a subgroup or reach the group's line
1412 wxFileConfigGroup *pNewLast = NULL;
1413 size_t n, nSubgroups = m_aSubgroups.Count();
1414 wxFileConfigLineList *pl;
1415 for ( pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() ) {
1416 // is it our subgroup?
1417 for ( n = 0; (pNewLast == NULL) && (n < nSubgroups); n++ ) {
1418 // do _not_ call GetGroupLine! we don't want to add it to the local
1419 // file if it's not already there
1420 if ( m_aSubgroups[n]->m_pLine == m_pLine )
1421 pNewLast = m_aSubgroups[n];
1422 }
1423
1424 if ( pNewLast != NULL ) // found?
1425 break;
1426 }
1427
1428 if ( pl == m_pLine ) {
1429 wxASSERT( !pNewLast ); // how comes it has the same line as we?
1430
1431 // we've reached the group line without finding any subgroups
1432 m_pLastGroup = NULL;
1433 }
1434 else
1435 m_pLastGroup = pNewLast;
1436 }
1437
1438 m_pConfig->LineListRemove(pLine);
1439 }
1440
1441 SetDirty();
1442
1443 m_aSubgroups.Remove(pGroup);
1444 delete pGroup;
1445
1446 return TRUE;
1447 }
1448
1449 bool wxFileConfigGroup::DeleteEntry(const wxChar *szName)
1450 {
1451 wxFileConfigEntry *pEntry = FindEntry(szName);
1452 wxCHECK( pEntry != NULL, FALSE ); // deleting non existing item?
1453
1454 wxFileConfigLineList *pLine = pEntry->GetLine();
1455 if ( pLine != NULL ) {
1456 // notice that we may do this test inside the previous "if" because the
1457 // last entry's line is surely !NULL
1458 if ( pEntry == m_pLastEntry ) {
1459 // our last entry is being deleted - find the last one which stays
1460 wxASSERT( m_pLine != NULL ); // if we have an entry with !NULL pLine...
1461
1462 // go back until we find another entry or reach the group's line
1463 wxFileConfigEntry *pNewLast = NULL;
1464 size_t n, nEntries = m_aEntries.Count();
1465 wxFileConfigLineList *pl;
1466 for ( pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() ) {
1467 // is it our subgroup?
1468 for ( n = 0; (pNewLast == NULL) && (n < nEntries); n++ ) {
1469 if ( m_aEntries[n]->GetLine() == m_pLine )
1470 pNewLast = m_aEntries[n];
1471 }
1472
1473 if ( pNewLast != NULL ) // found?
1474 break;
1475 }
1476
1477 if ( pl == m_pLine ) {
1478 wxASSERT( !pNewLast ); // how comes it has the same line as we?
1479
1480 // we've reached the group line without finding any subgroups
1481 m_pLastEntry = NULL;
1482 }
1483 else
1484 m_pLastEntry = pNewLast;
1485 }
1486
1487 m_pConfig->LineListRemove(pLine);
1488 }
1489
1490 // we must be written back for the changes to be saved
1491 SetDirty();
1492
1493 m_aEntries.Remove(pEntry);
1494 delete pEntry;
1495
1496 return TRUE;
1497 }
1498
1499 // ----------------------------------------------------------------------------
1500 //
1501 // ----------------------------------------------------------------------------
1502 void wxFileConfigGroup::SetDirty()
1503 {
1504 m_bDirty = TRUE;
1505 if ( Parent() != NULL ) // propagate upwards
1506 Parent()->SetDirty();
1507 }
1508
1509 // ============================================================================
1510 // wxFileConfig::wxFileConfigEntry
1511 // ============================================================================
1512
1513 // ----------------------------------------------------------------------------
1514 // ctor
1515 // ----------------------------------------------------------------------------
1516 wxFileConfigEntry::wxFileConfigEntry(wxFileConfigGroup *pParent,
1517 const wxString& strName,
1518 int nLine)
1519 : m_strName(strName)
1520 {
1521 wxASSERT( !strName.IsEmpty() );
1522
1523 m_pParent = pParent;
1524 m_nLine = nLine;
1525 m_pLine = NULL;
1526
1527 m_bDirty =
1528 m_bHasValue = FALSE;
1529
1530 m_bImmutable = strName[0] == wxCONFIG_IMMUTABLE_PREFIX;
1531 if ( m_bImmutable )
1532 m_strName.erase(0, 1); // remove first character
1533 }
1534
1535 // ----------------------------------------------------------------------------
1536 // set value
1537 // ----------------------------------------------------------------------------
1538
1539 void wxFileConfigEntry::SetLine(wxFileConfigLineList *pLine)
1540 {
1541 if ( m_pLine != NULL ) {
1542 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1543 Name().c_str(), m_pParent->GetFullName().c_str());
1544 }
1545
1546 m_pLine = pLine;
1547 Group()->SetLastEntry(this);
1548 }
1549
1550 // second parameter is FALSE if we read the value from file and prevents the
1551 // entry from being marked as 'dirty'
1552 void wxFileConfigEntry::SetValue(const wxString& strValue, bool bUser)
1553 {
1554 if ( bUser && IsImmutable() ) {
1555 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1556 Name().c_str());
1557 return;
1558 }
1559
1560 // do nothing if it's the same value: but don't test for it if m_bHasValue
1561 // hadn't been set yet or we'd never write empty values to the file
1562 if ( m_bHasValue && strValue == m_strValue )
1563 return;
1564
1565 m_bHasValue = TRUE;
1566 m_strValue = strValue;
1567
1568 if ( bUser ) {
1569 wxString strVal = FilterOutValue(strValue);
1570 wxString strLine;
1571 strLine << FilterOutEntryName(m_strName) << wxT('=') << strVal;
1572
1573 if ( m_pLine != NULL ) {
1574 // entry was read from the local config file, just modify the line
1575 m_pLine->SetText(strLine);
1576 }
1577 else {
1578 // add a new line to the file
1579 wxASSERT( m_nLine == wxNOT_FOUND ); // consistency check
1580
1581 m_pLine = Group()->Config()->LineListInsert(strLine,
1582 Group()->GetLastEntryLine());
1583 Group()->SetLastEntry(this);
1584 }
1585
1586 SetDirty();
1587 }
1588 }
1589
1590 void wxFileConfigEntry::SetDirty()
1591 {
1592 m_bDirty = TRUE;
1593 Group()->SetDirty();
1594 }
1595
1596 // ============================================================================
1597 // global functions
1598 // ============================================================================
1599
1600 // ----------------------------------------------------------------------------
1601 // compare functions for array sorting
1602 // ----------------------------------------------------------------------------
1603
1604 int CompareEntries(wxFileConfigEntry *p1, wxFileConfigEntry *p2)
1605 {
1606 #if wxCONFIG_CASE_SENSITIVE
1607 return wxStrcmp(p1->Name(), p2->Name());
1608 #else
1609 return wxStricmp(p1->Name(), p2->Name());
1610 #endif
1611 }
1612
1613 int CompareGroups(wxFileConfigGroup *p1, wxFileConfigGroup *p2)
1614 {
1615 #if wxCONFIG_CASE_SENSITIVE
1616 return wxStrcmp(p1->Name(), p2->Name());
1617 #else
1618 return wxStricmp(p1->Name(), p2->Name());
1619 #endif
1620 }
1621
1622 // ----------------------------------------------------------------------------
1623 // filter functions
1624 // ----------------------------------------------------------------------------
1625
1626 // undo FilterOutValue
1627 static wxString FilterInValue(const wxString& str)
1628 {
1629 wxString strResult;
1630 strResult.Alloc(str.Len());
1631
1632 bool bQuoted = !str.IsEmpty() && str[0] == '"';
1633
1634 for ( size_t n = bQuoted ? 1 : 0; n < str.Len(); n++ ) {
1635 if ( str[n] == wxT('\\') ) {
1636 switch ( str[++n] ) {
1637 case wxT('n'):
1638 strResult += wxT('\n');
1639 break;
1640
1641 case wxT('r'):
1642 strResult += wxT('\r');
1643 break;
1644
1645 case wxT('t'):
1646 strResult += wxT('\t');
1647 break;
1648
1649 case wxT('\\'):
1650 strResult += wxT('\\');
1651 break;
1652
1653 case wxT('"'):
1654 strResult += wxT('"');
1655 break;
1656 }
1657 }
1658 else {
1659 if ( str[n] != wxT('"') || !bQuoted )
1660 strResult += str[n];
1661 else if ( n != str.Len() - 1 ) {
1662 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1663 n, str.c_str());
1664 }
1665 //else: it's the last quote of a quoted string, ok
1666 }
1667 }
1668
1669 return strResult;
1670 }
1671
1672 // quote the string before writing it to file
1673 static wxString FilterOutValue(const wxString& str)
1674 {
1675 if ( !str )
1676 return str;
1677
1678 wxString strResult;
1679 strResult.Alloc(str.Len());
1680
1681 // quoting is necessary to preserve spaces in the beginning of the string
1682 bool bQuote = wxIsspace(str[0]) || str[0] == wxT('"');
1683
1684 if ( bQuote )
1685 strResult += wxT('"');
1686
1687 wxChar c;
1688 for ( size_t n = 0; n < str.Len(); n++ ) {
1689 switch ( str[n] ) {
1690 case wxT('\n'):
1691 c = wxT('n');
1692 break;
1693
1694 case wxT('\r'):
1695 c = wxT('r');
1696 break;
1697
1698 case wxT('\t'):
1699 c = wxT('t');
1700 break;
1701
1702 case wxT('\\'):
1703 c = wxT('\\');
1704 break;
1705
1706 case wxT('"'):
1707 if ( bQuote ) {
1708 c = wxT('"');
1709 break;
1710 }
1711 //else: fall through
1712
1713 default:
1714 strResult += str[n];
1715 continue; // nothing special to do
1716 }
1717
1718 // we get here only for special characters
1719 strResult << wxT('\\') << c;
1720 }
1721
1722 if ( bQuote )
1723 strResult += wxT('"');
1724
1725 return strResult;
1726 }
1727
1728 // undo FilterOutEntryName
1729 static wxString FilterInEntryName(const wxString& str)
1730 {
1731 wxString strResult;
1732 strResult.Alloc(str.Len());
1733
1734 for ( const wxChar *pc = str.c_str(); *pc != '\0'; pc++ ) {
1735 if ( *pc == wxT('\\') )
1736 pc++;
1737
1738 strResult += *pc;
1739 }
1740
1741 return strResult;
1742 }
1743
1744 // sanitize entry or group name: insert '\\' before any special characters
1745 static wxString FilterOutEntryName(const wxString& str)
1746 {
1747 wxString strResult;
1748 strResult.Alloc(str.Len());
1749
1750 for ( const wxChar *pc = str.c_str(); *pc != wxT('\0'); pc++ ) {
1751 wxChar c = *pc;
1752
1753 // we explicitly allow some of "safe" chars and 8bit ASCII characters
1754 // which will probably never have special meaning
1755 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
1756 // should *not* be quoted
1757 if ( !wxIsalnum(c) && !wxStrchr(wxT("@_/-!.*%"), c) && ((c & 0x80) == 0) )
1758 strResult += wxT('\\');
1759
1760 strResult += c;
1761 }
1762
1763 return strResult;
1764 }
1765
1766 // we can't put ?: in the ctor initializer list because it confuses some
1767 // broken compilers (Borland C++)
1768 static wxString GetAppName(const wxString& appName)
1769 {
1770 if ( !appName && wxTheApp )
1771 return wxTheApp->GetAppName();
1772 else
1773 return appName;
1774 }
1775
1776 #endif // wxUSE_CONFIG
1777