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