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