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