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