add wxUSE_BASE64 checks around DoRead/WriteBinary() functions
[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 wxString str;
441 {
442 static const size_t chunkLen = 1024;
443
444 wxMemoryBuffer buf(chunkLen);
445 do
446 {
447 inStream.Read(buf.GetAppendBuf(chunkLen), chunkLen);
448 buf.UngetAppendBuf(inStream.LastRead());
449
450 const wxStreamError err = inStream.GetLastError();
451
452 if ( err != wxSTREAM_NO_ERROR && err != wxSTREAM_EOF )
453 {
454 wxLogError(_("Error reading config options."));
455 break;
456 }
457 }
458 while ( !inStream.Eof() );
459
460 #if wxUSE_UNICODE
461 size_t len;
462 str = conv.cMB2WC((char *)buf.GetData(), buf.GetDataLen(), &len);
463 if ( !len && buf.GetDataLen() )
464 {
465 wxLogError(_("Failed to read config options."));
466 }
467 #else // !wxUSE_UNICODE
468 // no need for conversion
469 str.assign((char *)buf.GetData(), buf.GetDataLen());
470 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
471 }
472
473
474 // translate everything to the current (platform-dependent) line
475 // termination character
476 str = wxTextBuffer::Translate(str);
477
478 wxMemoryText memText;
479
480 // Now we can add the text to the memory text. To do this we extract line
481 // by line from the translated string, until we've reached the end.
482 //
483 // VZ: all this is horribly inefficient, we should do the translation on
484 // the fly in one pass saving both memory and time (TODO)
485
486 const wxChar *pEOL = wxTextBuffer::GetEOL(wxTextBuffer::typeDefault);
487 const size_t EOLLen = wxStrlen(pEOL);
488
489 int posLineStart = str.Find(pEOL);
490 while ( posLineStart != -1 )
491 {
492 wxString line(str.Left(posLineStart));
493
494 memText.AddLine(line);
495
496 str = str.Mid(posLineStart + EOLLen);
497
498 posLineStart = str.Find(pEOL);
499 }
500
501 // also add whatever we have left in the translated string.
502 if ( !str.empty() )
503 memText.AddLine(str);
504
505 // Finally we can parse it all.
506 Parse(memText, true /* local */);
507
508 SetRootPath();
509 ResetDirty();
510 }
511
512 #endif // wxUSE_STREAMS
513
514 void wxFileConfig::CleanUp()
515 {
516 delete m_pRootGroup;
517
518 wxFileConfigLineList *pCur = m_linesHead;
519 while ( pCur != NULL ) {
520 wxFileConfigLineList *pNext = pCur->Next();
521 delete pCur;
522 pCur = pNext;
523 }
524 }
525
526 wxFileConfig::~wxFileConfig()
527 {
528 Flush();
529
530 CleanUp();
531
532 delete m_conv;
533 }
534
535 // ----------------------------------------------------------------------------
536 // parse a config file
537 // ----------------------------------------------------------------------------
538
539 void wxFileConfig::Parse(const wxTextBuffer& buffer, bool bLocal)
540 {
541
542 size_t nLineCount = buffer.GetLineCount();
543
544 for ( size_t n = 0; n < nLineCount; n++ )
545 {
546 wxString strLine = buffer[n];
547 // FIXME-UTF8: rewrite using iterators, without this buffer
548 wxWxCharBuffer buf(strLine.c_str());
549 const wxChar *pStart;
550 const wxChar *pEnd;
551
552 // add the line to linked list
553 if ( bLocal )
554 {
555 LineListAppend(strLine);
556
557 // let the root group have its start line as well
558 if ( !n )
559 {
560 m_pCurrentGroup->SetLine(m_linesTail);
561 }
562 }
563
564
565 // skip leading spaces
566 for ( pStart = buf; wxIsspace(*pStart); pStart++ )
567 ;
568
569 // skip blank/comment lines
570 if ( *pStart == wxT('\0')|| *pStart == wxT(';') || *pStart == wxT('#') )
571 continue;
572
573 if ( *pStart == wxT('[') ) { // a new group
574 pEnd = pStart;
575
576 while ( *++pEnd != wxT(']') ) {
577 if ( *pEnd == wxT('\\') ) {
578 // the next char is escaped, so skip it even if it is ']'
579 pEnd++;
580 }
581
582 if ( *pEnd == wxT('\n') || *pEnd == wxT('\0') ) {
583 // we reached the end of line, break out of the loop
584 break;
585 }
586 }
587
588 if ( *pEnd != wxT(']') ) {
589 wxLogError(_("file '%s': unexpected character %c at line %d."),
590 buffer.GetName(), *pEnd, n + 1);
591 continue; // skip this line
592 }
593
594 // group name here is always considered as abs path
595 wxString strGroup;
596 pStart++;
597 strGroup << wxCONFIG_PATH_SEPARATOR
598 << FilterInEntryName(wxString(pStart, pEnd - pStart));
599
600 // will create it if doesn't yet exist
601 SetPath(strGroup);
602
603 if ( bLocal )
604 {
605 if ( m_pCurrentGroup->Parent() )
606 m_pCurrentGroup->Parent()->SetLastGroup(m_pCurrentGroup);
607 m_pCurrentGroup->SetLine(m_linesTail);
608 }
609
610 // check that there is nothing except comments left on this line
611 bool bCont = true;
612 while ( *++pEnd != wxT('\0') && bCont ) {
613 switch ( *pEnd ) {
614 case wxT('#'):
615 case wxT(';'):
616 bCont = false;
617 break;
618
619 case wxT(' '):
620 case wxT('\t'):
621 // ignore whitespace ('\n' impossible here)
622 break;
623
624 default:
625 wxLogWarning(_("file '%s', line %d: '%s' ignored after group header."),
626 buffer.GetName(), n + 1, pEnd);
627 bCont = false;
628 }
629 }
630 }
631 else { // a key
632 pEnd = pStart;
633 while ( *pEnd && *pEnd != wxT('=') /* && !wxIsspace(*pEnd)*/ ) {
634 if ( *pEnd == wxT('\\') ) {
635 // next character may be space or not - still take it because it's
636 // quoted (unless there is nothing)
637 pEnd++;
638 if ( !*pEnd ) {
639 // the error message will be given below anyhow
640 break;
641 }
642 }
643
644 pEnd++;
645 }
646
647 wxString strKey(FilterInEntryName(wxString(pStart, pEnd).Trim()));
648
649 // skip whitespace
650 while ( wxIsspace(*pEnd) )
651 pEnd++;
652
653 if ( *pEnd++ != wxT('=') ) {
654 wxLogError(_("file '%s', line %d: '=' expected."),
655 buffer.GetName(), n + 1);
656 }
657 else {
658 wxFileConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strKey);
659
660 if ( pEntry == NULL ) {
661 // new entry
662 pEntry = m_pCurrentGroup->AddEntry(strKey, n);
663 }
664 else {
665 if ( bLocal && pEntry->IsImmutable() ) {
666 // immutable keys can't be changed by user
667 wxLogWarning(_("file '%s', line %d: value for immutable key '%s' ignored."),
668 buffer.GetName(), n + 1, strKey.c_str());
669 continue;
670 }
671 // the condition below catches the cases (a) and (b) but not (c):
672 // (a) global key found second time in global file
673 // (b) key found second (or more) time in local file
674 // (c) key from global file now found in local one
675 // which is exactly what we want.
676 else if ( !bLocal || pEntry->IsLocal() ) {
677 wxLogWarning(_("file '%s', line %d: key '%s' was first found at line %d."),
678 buffer.GetName(), n + 1, strKey.c_str(), pEntry->Line());
679
680 }
681 }
682
683 if ( bLocal )
684 pEntry->SetLine(m_linesTail);
685
686 // skip whitespace
687 while ( wxIsspace(*pEnd) )
688 pEnd++;
689
690 wxString value = pEnd;
691 if ( !(GetStyle() & wxCONFIG_USE_NO_ESCAPE_CHARACTERS) )
692 value = FilterInValue(value);
693
694 pEntry->SetValue(value, false);
695 }
696 }
697 }
698 }
699
700 // ----------------------------------------------------------------------------
701 // set/retrieve path
702 // ----------------------------------------------------------------------------
703
704 void wxFileConfig::SetRootPath()
705 {
706 m_strPath.Empty();
707 m_pCurrentGroup = m_pRootGroup;
708 }
709
710 bool
711 wxFileConfig::DoSetPath(const wxString& strPath, bool createMissingComponents)
712 {
713 wxArrayString aParts;
714
715 if ( strPath.empty() ) {
716 SetRootPath();
717 return true;
718 }
719
720 if ( strPath[0] == wxCONFIG_PATH_SEPARATOR ) {
721 // absolute path
722 wxSplitPath(aParts, strPath);
723 }
724 else {
725 // relative path, combine with current one
726 wxString strFullPath = m_strPath;
727 strFullPath << wxCONFIG_PATH_SEPARATOR << strPath;
728 wxSplitPath(aParts, strFullPath);
729 }
730
731 // change current group
732 size_t n;
733 m_pCurrentGroup = m_pRootGroup;
734 for ( n = 0; n < aParts.GetCount(); n++ ) {
735 wxFileConfigGroup *pNextGroup = m_pCurrentGroup->FindSubgroup(aParts[n]);
736 if ( pNextGroup == NULL )
737 {
738 if ( !createMissingComponents )
739 return false;
740
741 pNextGroup = m_pCurrentGroup->AddSubgroup(aParts[n]);
742 }
743
744 m_pCurrentGroup = pNextGroup;
745 }
746
747 // recombine path parts in one variable
748 m_strPath.Empty();
749 for ( n = 0; n < aParts.GetCount(); n++ ) {
750 m_strPath << wxCONFIG_PATH_SEPARATOR << aParts[n];
751 }
752
753 return true;
754 }
755
756 void wxFileConfig::SetPath(const wxString& strPath)
757 {
758 DoSetPath(strPath, true /* create missing path components */);
759 }
760
761 // ----------------------------------------------------------------------------
762 // enumeration
763 // ----------------------------------------------------------------------------
764
765 bool wxFileConfig::GetFirstGroup(wxString& str, long& lIndex) const
766 {
767 lIndex = 0;
768 return GetNextGroup(str, lIndex);
769 }
770
771 bool wxFileConfig::GetNextGroup (wxString& str, long& lIndex) const
772 {
773 if ( size_t(lIndex) < m_pCurrentGroup->Groups().GetCount() ) {
774 str = m_pCurrentGroup->Groups()[(size_t)lIndex++]->Name();
775 return true;
776 }
777 else
778 return false;
779 }
780
781 bool wxFileConfig::GetFirstEntry(wxString& str, long& lIndex) const
782 {
783 lIndex = 0;
784 return GetNextEntry(str, lIndex);
785 }
786
787 bool wxFileConfig::GetNextEntry (wxString& str, long& lIndex) const
788 {
789 if ( size_t(lIndex) < m_pCurrentGroup->Entries().GetCount() ) {
790 str = m_pCurrentGroup->Entries()[(size_t)lIndex++]->Name();
791 return true;
792 }
793 else
794 return false;
795 }
796
797 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive) const
798 {
799 size_t n = m_pCurrentGroup->Entries().GetCount();
800 if ( bRecursive ) {
801 wxFileConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
802 size_t nSubgroups = m_pCurrentGroup->Groups().GetCount();
803 for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
804 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
805 n += GetNumberOfEntries(true);
806 CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
807 }
808 }
809
810 return n;
811 }
812
813 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive) const
814 {
815 size_t n = m_pCurrentGroup->Groups().GetCount();
816 if ( bRecursive ) {
817 wxFileConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
818 size_t nSubgroups = m_pCurrentGroup->Groups().GetCount();
819 for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
820 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
821 n += GetNumberOfGroups(true);
822 CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
823 }
824 }
825
826 return n;
827 }
828
829 // ----------------------------------------------------------------------------
830 // tests for existence
831 // ----------------------------------------------------------------------------
832
833 bool wxFileConfig::HasGroup(const wxString& strName) const
834 {
835 // special case: DoSetPath("") does work as it's equivalent to DoSetPath("/")
836 // but there is no group with empty name so treat this separately
837 if ( strName.empty() )
838 return false;
839
840 const wxString pathOld = GetPath();
841
842 wxFileConfig *self = wx_const_cast(wxFileConfig *, this);
843 const bool
844 rc = self->DoSetPath(strName, false /* don't create missing components */);
845
846 self->SetPath(pathOld);
847
848 return rc;
849 }
850
851 bool wxFileConfig::HasEntry(const wxString& entry) const
852 {
853 // path is the part before the last "/"
854 wxString path = entry.BeforeLast(wxCONFIG_PATH_SEPARATOR);
855
856 // except in the special case of "/keyname" when there is nothing before "/"
857 if ( path.empty() && *entry.c_str() == wxCONFIG_PATH_SEPARATOR )
858 {
859 path = wxCONFIG_PATH_SEPARATOR;
860 }
861
862 // change to the path of the entry if necessary and remember the old path
863 // to restore it later
864 wxString pathOld;
865 wxFileConfig * const self = wx_const_cast(wxFileConfig *, this);
866 if ( !path.empty() )
867 {
868 pathOld = GetPath();
869 if ( pathOld.empty() )
870 pathOld = wxCONFIG_PATH_SEPARATOR;
871
872 if ( !self->DoSetPath(path, false /* don't create if doesn't exist */) )
873 {
874 return false;
875 }
876 }
877
878 // check if the entry exists in this group
879 const bool exists = m_pCurrentGroup->FindEntry(
880 entry.AfterLast(wxCONFIG_PATH_SEPARATOR)) != NULL;
881
882 // restore the old path if we changed it above
883 if ( !pathOld.empty() )
884 {
885 self->SetPath(pathOld);
886 }
887
888 return exists;
889 }
890
891 // ----------------------------------------------------------------------------
892 // read/write values
893 // ----------------------------------------------------------------------------
894
895 bool wxFileConfig::DoReadString(const wxString& key, wxString* pStr) const
896 {
897 wxConfigPathChanger path(this, key);
898
899 wxFileConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
900 if (pEntry == NULL) {
901 return false;
902 }
903
904 *pStr = pEntry->Value();
905
906 return true;
907 }
908
909 bool wxFileConfig::DoReadLong(const wxString& key, long *pl) const
910 {
911 wxString str;
912 if ( !Read(key, &str) )
913 return false;
914
915 // extra spaces shouldn't prevent us from reading numeric values
916 str.Trim();
917
918 return str.ToLong(pl);
919 }
920
921 #if wxUSE_BASE64
922
923 bool wxFileConfig::DoReadBinary(const wxString& key, wxMemoryBuffer* buf) const
924 {
925 wxCHECK_MSG( buf, false, _T("NULL buffer") );
926
927 wxString str;
928 if ( !Read(key, &str) )
929 return false;
930
931 *buf = wxBase64Decode(str.ToAscii());
932 return true;
933 }
934
935 #endif // wxUSE_BASE64
936
937 bool wxFileConfig::DoWriteString(const wxString& key, const wxString& szValue)
938 {
939 wxConfigPathChanger path(this, key);
940 wxString strName = path.Name();
941
942 wxLogTrace( FILECONF_TRACE_MASK,
943 _T(" Writing String '%s' = '%s' to Group '%s'"),
944 strName.c_str(),
945 szValue.c_str(),
946 GetPath().c_str() );
947
948 if ( strName.empty() )
949 {
950 // setting the value of a group is an error
951
952 wxASSERT_MSG( szValue.empty(), wxT("can't set value of a group!") );
953
954 // ... except if it's empty in which case it's a way to force it's creation
955
956 wxLogTrace( FILECONF_TRACE_MASK,
957 _T(" Creating group %s"),
958 m_pCurrentGroup->Name().c_str() );
959
960 SetDirty();
961
962 // this will add a line for this group if it didn't have it before
963
964 (void)m_pCurrentGroup->GetGroupLine();
965 }
966 else
967 {
968 // writing an entry check that the name is reasonable
969 if ( strName[0u] == wxCONFIG_IMMUTABLE_PREFIX )
970 {
971 wxLogError( _("Config entry name cannot start with '%c'."),
972 wxCONFIG_IMMUTABLE_PREFIX);
973 return false;
974 }
975
976 wxFileConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strName);
977
978 if ( pEntry == 0 )
979 {
980 wxLogTrace( FILECONF_TRACE_MASK,
981 _T(" Adding Entry %s"),
982 strName.c_str() );
983 pEntry = m_pCurrentGroup->AddEntry(strName);
984 }
985
986 wxLogTrace( FILECONF_TRACE_MASK,
987 _T(" Setting value %s"),
988 szValue.c_str() );
989 pEntry->SetValue(szValue);
990
991 SetDirty();
992 }
993
994 return true;
995 }
996
997 bool wxFileConfig::DoWriteLong(const wxString& key, long lValue)
998 {
999 return Write(key, wxString::Format(_T("%ld"), lValue));
1000 }
1001
1002 #if wxUSE_BASE64
1003
1004 bool wxFileConfig::DoWriteBinary(const wxString& key, const wxMemoryBuffer& buf)
1005 {
1006 return Write(key, wxBase64Encode(buf));
1007 }
1008
1009 #endif // wxUSE_BASE64
1010
1011 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
1012 {
1013 if ( !IsDirty() || !m_fnLocalFile.GetFullPath() )
1014 return true;
1015
1016 // set the umask if needed
1017 wxCHANGE_UMASK(m_umask);
1018
1019 wxTempFile file(m_fnLocalFile.GetFullPath());
1020
1021 if ( !file.IsOpened() )
1022 {
1023 wxLogError(_("can't open user configuration file."));
1024 return false;
1025 }
1026
1027 // write all strings to file
1028 wxString filetext;
1029 filetext.reserve(4096);
1030 for ( wxFileConfigLineList *p = m_linesHead; p != NULL; p = p->Next() )
1031 {
1032 filetext << p->Text() << wxTextFile::GetEOL();
1033 }
1034
1035 if ( !file.Write(filetext, *m_conv) )
1036 {
1037 wxLogError(_("can't write user configuration file."));
1038 return false;
1039 }
1040
1041 if ( !file.Commit() )
1042 {
1043 wxLogError(_("Failed to update user configuration file."));
1044
1045 return false;
1046 }
1047
1048 ResetDirty();
1049
1050 #if defined(__WXMAC__)
1051 m_fnLocalFile.MacSetTypeAndCreator('TEXT', 'ttxt');
1052 #endif // __WXMAC__
1053
1054 return true;
1055 }
1056
1057 #if wxUSE_STREAMS
1058
1059 bool wxFileConfig::Save(wxOutputStream& os, const wxMBConv& conv)
1060 {
1061 // save unconditionally, even if not dirty
1062 for ( wxFileConfigLineList *p = m_linesHead; p != NULL; p = p->Next() )
1063 {
1064 wxString line = p->Text();
1065 line += wxTextFile::GetEOL();
1066
1067 wxCharBuffer buf(line.mb_str(conv));
1068 if ( !os.Write(buf, strlen(buf)) )
1069 {
1070 wxLogError(_("Error saving user configuration data."));
1071
1072 return false;
1073 }
1074 }
1075
1076 ResetDirty();
1077
1078 return true;
1079 }
1080
1081 #endif // wxUSE_STREAMS
1082
1083 // ----------------------------------------------------------------------------
1084 // renaming groups/entries
1085 // ----------------------------------------------------------------------------
1086
1087 bool wxFileConfig::RenameEntry(const wxString& oldName,
1088 const wxString& newName)
1089 {
1090 wxASSERT_MSG( oldName.find(wxCONFIG_PATH_SEPARATOR) == wxString::npos,
1091 _T("RenameEntry(): paths are not supported") );
1092
1093 // check that the entry exists
1094 wxFileConfigEntry *oldEntry = m_pCurrentGroup->FindEntry(oldName);
1095 if ( !oldEntry )
1096 return false;
1097
1098 // check that the new entry doesn't already exist
1099 if ( m_pCurrentGroup->FindEntry(newName) )
1100 return false;
1101
1102 // delete the old entry, create the new one
1103 wxString value = oldEntry->Value();
1104 if ( !m_pCurrentGroup->DeleteEntry(oldName) )
1105 return false;
1106
1107 SetDirty();
1108
1109 wxFileConfigEntry *newEntry = m_pCurrentGroup->AddEntry(newName);
1110 newEntry->SetValue(value);
1111
1112 return true;
1113 }
1114
1115 bool wxFileConfig::RenameGroup(const wxString& oldName,
1116 const wxString& newName)
1117 {
1118 // check that the group exists
1119 wxFileConfigGroup *group = m_pCurrentGroup->FindSubgroup(oldName);
1120 if ( !group )
1121 return false;
1122
1123 // check that the new group doesn't already exist
1124 if ( m_pCurrentGroup->FindSubgroup(newName) )
1125 return false;
1126
1127 group->Rename(newName);
1128
1129 SetDirty();
1130
1131 return true;
1132 }
1133
1134 // ----------------------------------------------------------------------------
1135 // delete groups/entries
1136 // ----------------------------------------------------------------------------
1137
1138 bool wxFileConfig::DeleteEntry(const wxString& key, bool bGroupIfEmptyAlso)
1139 {
1140 wxConfigPathChanger path(this, key);
1141
1142 if ( !m_pCurrentGroup->DeleteEntry(path.Name()) )
1143 return false;
1144
1145 SetDirty();
1146
1147 if ( bGroupIfEmptyAlso && m_pCurrentGroup->IsEmpty() ) {
1148 if ( m_pCurrentGroup != m_pRootGroup ) {
1149 wxFileConfigGroup *pGroup = m_pCurrentGroup;
1150 SetPath(wxT("..")); // changes m_pCurrentGroup!
1151 m_pCurrentGroup->DeleteSubgroupByName(pGroup->Name());
1152 }
1153 //else: never delete the root group
1154 }
1155
1156 return true;
1157 }
1158
1159 bool wxFileConfig::DeleteGroup(const wxString& key)
1160 {
1161 wxConfigPathChanger path(this, RemoveTrailingSeparator(key));
1162
1163 if ( !m_pCurrentGroup->DeleteSubgroupByName(path.Name()) )
1164 return false;
1165
1166 path.UpdateIfDeleted();
1167
1168 SetDirty();
1169
1170 return true;
1171 }
1172
1173 bool wxFileConfig::DeleteAll()
1174 {
1175 CleanUp();
1176
1177 if ( m_fnLocalFile.IsOk() )
1178 {
1179 if ( m_fnLocalFile.FileExists() &&
1180 !wxRemoveFile(m_fnLocalFile.GetFullPath()) )
1181 {
1182 wxLogSysError(_("can't delete user configuration file '%s'"),
1183 m_fnLocalFile.GetFullPath().c_str());
1184 return false;
1185 }
1186 }
1187
1188 Init();
1189
1190 return true;
1191 }
1192
1193 // ----------------------------------------------------------------------------
1194 // linked list functions
1195 // ----------------------------------------------------------------------------
1196
1197 // append a new line to the end of the list
1198
1199 wxFileConfigLineList *wxFileConfig::LineListAppend(const wxString& str)
1200 {
1201 wxLogTrace( FILECONF_TRACE_MASK,
1202 _T(" ** Adding Line '%s'"),
1203 str.c_str() );
1204 wxLogTrace( FILECONF_TRACE_MASK,
1205 _T(" head: %s"),
1206 ((m_linesHead) ? (const wxChar*)m_linesHead->Text().c_str()
1207 : wxEmptyString) );
1208 wxLogTrace( FILECONF_TRACE_MASK,
1209 _T(" tail: %s"),
1210 ((m_linesTail) ? (const wxChar*)m_linesTail->Text().c_str()
1211 : wxEmptyString) );
1212
1213 wxFileConfigLineList *pLine = new wxFileConfigLineList(str);
1214
1215 if ( m_linesTail == NULL )
1216 {
1217 // list is empty
1218 m_linesHead = pLine;
1219 }
1220 else
1221 {
1222 // adjust pointers
1223 m_linesTail->SetNext(pLine);
1224 pLine->SetPrev(m_linesTail);
1225 }
1226
1227 m_linesTail = pLine;
1228
1229 wxLogTrace( FILECONF_TRACE_MASK,
1230 _T(" head: %s"),
1231 ((m_linesHead) ? (const wxChar*)m_linesHead->Text().c_str()
1232 : wxEmptyString) );
1233 wxLogTrace( FILECONF_TRACE_MASK,
1234 _T(" tail: %s"),
1235 ((m_linesTail) ? (const wxChar*)m_linesTail->Text().c_str()
1236 : wxEmptyString) );
1237
1238 return m_linesTail;
1239 }
1240
1241 // insert a new line after the given one or in the very beginning if !pLine
1242 wxFileConfigLineList *wxFileConfig::LineListInsert(const wxString& str,
1243 wxFileConfigLineList *pLine)
1244 {
1245 wxLogTrace( FILECONF_TRACE_MASK,
1246 _T(" ** Inserting Line '%s' after '%s'"),
1247 str.c_str(),
1248 ((pLine) ? (const wxChar*)pLine->Text().c_str()
1249 : wxEmptyString) );
1250 wxLogTrace( FILECONF_TRACE_MASK,
1251 _T(" head: %s"),
1252 ((m_linesHead) ? (const wxChar*)m_linesHead->Text().c_str()
1253 : wxEmptyString) );
1254 wxLogTrace( FILECONF_TRACE_MASK,
1255 _T(" tail: %s"),
1256 ((m_linesTail) ? (const wxChar*)m_linesTail->Text().c_str()
1257 : wxEmptyString) );
1258
1259 if ( pLine == m_linesTail )
1260 return LineListAppend(str);
1261
1262 wxFileConfigLineList *pNewLine = new wxFileConfigLineList(str);
1263 if ( pLine == NULL )
1264 {
1265 // prepend to the list
1266 pNewLine->SetNext(m_linesHead);
1267 m_linesHead->SetPrev(pNewLine);
1268 m_linesHead = pNewLine;
1269 }
1270 else
1271 {
1272 // insert before pLine
1273 wxFileConfigLineList *pNext = pLine->Next();
1274 pNewLine->SetNext(pNext);
1275 pNewLine->SetPrev(pLine);
1276 pNext->SetPrev(pNewLine);
1277 pLine->SetNext(pNewLine);
1278 }
1279
1280 wxLogTrace( FILECONF_TRACE_MASK,
1281 _T(" head: %s"),
1282 ((m_linesHead) ? (const wxChar*)m_linesHead->Text().c_str()
1283 : wxEmptyString) );
1284 wxLogTrace( FILECONF_TRACE_MASK,
1285 _T(" tail: %s"),
1286 ((m_linesTail) ? (const wxChar*)m_linesTail->Text().c_str()
1287 : wxEmptyString) );
1288
1289 return pNewLine;
1290 }
1291
1292 void wxFileConfig::LineListRemove(wxFileConfigLineList *pLine)
1293 {
1294 wxLogTrace( FILECONF_TRACE_MASK,
1295 _T(" ** Removing Line '%s'"),
1296 pLine->Text().c_str() );
1297 wxLogTrace( FILECONF_TRACE_MASK,
1298 _T(" head: %s"),
1299 ((m_linesHead) ? (const wxChar*)m_linesHead->Text().c_str()
1300 : wxEmptyString) );
1301 wxLogTrace( FILECONF_TRACE_MASK,
1302 _T(" tail: %s"),
1303 ((m_linesTail) ? (const wxChar*)m_linesTail->Text().c_str()
1304 : wxEmptyString) );
1305
1306 wxFileConfigLineList *pPrev = pLine->Prev(),
1307 *pNext = pLine->Next();
1308
1309 // first entry?
1310
1311 if ( pPrev == NULL )
1312 m_linesHead = pNext;
1313 else
1314 pPrev->SetNext(pNext);
1315
1316 // last entry?
1317
1318 if ( pNext == NULL )
1319 m_linesTail = pPrev;
1320 else
1321 pNext->SetPrev(pPrev);
1322
1323 if ( m_pRootGroup->GetGroupLine() == pLine )
1324 m_pRootGroup->SetLine(m_linesHead);
1325
1326 wxLogTrace( FILECONF_TRACE_MASK,
1327 _T(" head: %s"),
1328 ((m_linesHead) ? (const wxChar*)m_linesHead->Text().c_str()
1329 : wxEmptyString) );
1330 wxLogTrace( FILECONF_TRACE_MASK,
1331 _T(" tail: %s"),
1332 ((m_linesTail) ? (const wxChar*)m_linesTail->Text().c_str()
1333 : wxEmptyString) );
1334
1335 delete pLine;
1336 }
1337
1338 bool wxFileConfig::LineListIsEmpty()
1339 {
1340 return m_linesHead == NULL;
1341 }
1342
1343 // ============================================================================
1344 // wxFileConfig::wxFileConfigGroup
1345 // ============================================================================
1346
1347 // ----------------------------------------------------------------------------
1348 // ctor/dtor
1349 // ----------------------------------------------------------------------------
1350
1351 // ctor
1352 wxFileConfigGroup::wxFileConfigGroup(wxFileConfigGroup *pParent,
1353 const wxString& strName,
1354 wxFileConfig *pConfig)
1355 : m_aEntries(CompareEntries),
1356 m_aSubgroups(CompareGroups),
1357 m_strName(strName)
1358 {
1359 m_pConfig = pConfig;
1360 m_pParent = pParent;
1361 m_pLine = NULL;
1362
1363 m_pLastEntry = NULL;
1364 m_pLastGroup = NULL;
1365 }
1366
1367 // dtor deletes all children
1368 wxFileConfigGroup::~wxFileConfigGroup()
1369 {
1370 // entries
1371 size_t n, nCount = m_aEntries.GetCount();
1372 for ( n = 0; n < nCount; n++ )
1373 delete m_aEntries[n];
1374
1375 // subgroups
1376 nCount = m_aSubgroups.GetCount();
1377 for ( n = 0; n < nCount; n++ )
1378 delete m_aSubgroups[n];
1379 }
1380
1381 // ----------------------------------------------------------------------------
1382 // line
1383 // ----------------------------------------------------------------------------
1384
1385 void wxFileConfigGroup::SetLine(wxFileConfigLineList *pLine)
1386 {
1387 // for a normal (i.e. not root) group this method shouldn't be called twice
1388 // unless we are resetting the line
1389 wxASSERT_MSG( !m_pParent || !m_pLine || !pLine,
1390 _T("changing line for a non-root group?") );
1391
1392 m_pLine = pLine;
1393 }
1394
1395 /*
1396 This is a bit complicated, so let me explain it in details. All lines that
1397 were read from the local file (the only one we will ever modify) are stored
1398 in a (doubly) linked list. Our problem is to know at which position in this
1399 list should we insert the new entries/subgroups. To solve it we keep three
1400 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
1401
1402 m_pLine points to the line containing "[group_name]"
1403 m_pLastEntry points to the last entry of this group in the local file.
1404 m_pLastGroup subgroup
1405
1406 Initially, they're NULL all three. When the group (an entry/subgroup) is read
1407 from the local file, the corresponding variable is set. However, if the group
1408 was read from the global file and then modified or created by the application
1409 these variables are still NULL and we need to create the corresponding lines.
1410 See the following functions (and comments preceding them) for the details of
1411 how we do it.
1412
1413 Also, when our last entry/group are deleted we need to find the new last
1414 element - the code in DeleteEntry/Subgroup does this by backtracking the list
1415 of lines until it either founds an entry/subgroup (and this is the new last
1416 element) or the m_pLine of the group, in which case there are no more entries
1417 (or subgroups) left and m_pLast<element> becomes NULL.
1418
1419 NB: This last problem could be avoided for entries if we added new entries
1420 immediately after m_pLine, but in this case the entries would appear
1421 backwards in the config file (OTOH, it's not that important) and as we
1422 would still need to do it for the subgroups the code wouldn't have been
1423 significantly less complicated.
1424 */
1425
1426 // Return the line which contains "[our name]". If we're still not in the list,
1427 // add our line to it immediately after the last line of our parent group if we
1428 // have it or in the very beginning if we're the root group.
1429 wxFileConfigLineList *wxFileConfigGroup::GetGroupLine()
1430 {
1431 wxLogTrace( FILECONF_TRACE_MASK,
1432 _T(" GetGroupLine() for Group '%s'"),
1433 Name().c_str() );
1434
1435 if ( !m_pLine )
1436 {
1437 wxLogTrace( FILECONF_TRACE_MASK,
1438 _T(" Getting Line item pointer") );
1439
1440 wxFileConfigGroup *pParent = Parent();
1441
1442 // this group wasn't present in local config file, add it now
1443 if ( pParent )
1444 {
1445 wxLogTrace( FILECONF_TRACE_MASK,
1446 _T(" checking parent '%s'"),
1447 pParent->Name().c_str() );
1448
1449 wxString strFullName;
1450
1451 // add 1 to the name because we don't want to start with '/'
1452 strFullName << wxT("[")
1453 << FilterOutEntryName(GetFullName().c_str() + 1)
1454 << wxT("]");
1455 m_pLine = m_pConfig->LineListInsert(strFullName,
1456 pParent->GetLastGroupLine());
1457 pParent->SetLastGroup(this); // we're surely after all the others
1458 }
1459 //else: this is the root group and so we return NULL because we don't
1460 // have any group line
1461 }
1462
1463 return m_pLine;
1464 }
1465
1466 // Return the last line belonging to the subgroups of this group (after which
1467 // we can add a new subgroup), if we don't have any subgroups or entries our
1468 // last line is the group line (m_pLine) itself.
1469 wxFileConfigLineList *wxFileConfigGroup::GetLastGroupLine()
1470 {
1471 // if we have any subgroups, our last line is the last line of the last
1472 // subgroup
1473 if ( m_pLastGroup )
1474 {
1475 wxFileConfigLineList *pLine = m_pLastGroup->GetLastGroupLine();
1476
1477 wxASSERT_MSG( pLine, _T("last group must have !NULL associated line") );
1478
1479 return pLine;
1480 }
1481
1482 // no subgroups, so the last line is the line of thelast entry (if any)
1483 return GetLastEntryLine();
1484 }
1485
1486 // return the last line belonging to the entries of this group (after which
1487 // we can add a new entry), if we don't have any entries we will add the new
1488 // one immediately after the group line itself.
1489 wxFileConfigLineList *wxFileConfigGroup::GetLastEntryLine()
1490 {
1491 wxLogTrace( FILECONF_TRACE_MASK,
1492 _T(" GetLastEntryLine() for Group '%s'"),
1493 Name().c_str() );
1494
1495 if ( m_pLastEntry )
1496 {
1497 wxFileConfigLineList *pLine = m_pLastEntry->GetLine();
1498
1499 wxASSERT_MSG( pLine, _T("last entry must have !NULL associated line") );
1500
1501 return pLine;
1502 }
1503
1504 // no entries: insert after the group header, if any
1505 return GetGroupLine();
1506 }
1507
1508 void wxFileConfigGroup::SetLastEntry(wxFileConfigEntry *pEntry)
1509 {
1510 m_pLastEntry = pEntry;
1511
1512 if ( !m_pLine )
1513 {
1514 // the only situation in which a group without its own line can have
1515 // an entry is when the first entry is added to the initially empty
1516 // root pseudo-group
1517 wxASSERT_MSG( !m_pParent, _T("unexpected for non root group") );
1518
1519 // let the group know that it does have a line in the file now
1520 m_pLine = pEntry->GetLine();
1521 }
1522 }
1523
1524 // ----------------------------------------------------------------------------
1525 // group name
1526 // ----------------------------------------------------------------------------
1527
1528 void wxFileConfigGroup::UpdateGroupAndSubgroupsLines()
1529 {
1530 // update the line of this group
1531 wxFileConfigLineList *line = GetGroupLine();
1532 wxCHECK_RET( line, _T("a non root group must have a corresponding line!") );
1533
1534 // +1: skip the leading '/'
1535 line->SetText(wxString::Format(_T("[%s]"), GetFullName().c_str() + 1));
1536
1537
1538 // also update all subgroups as they have this groups name in their lines
1539 const size_t nCount = m_aSubgroups.GetCount();
1540 for ( size_t n = 0; n < nCount; n++ )
1541 {
1542 m_aSubgroups[n]->UpdateGroupAndSubgroupsLines();
1543 }
1544 }
1545
1546 void wxFileConfigGroup::Rename(const wxString& newName)
1547 {
1548 wxCHECK_RET( m_pParent, _T("the root group can't be renamed") );
1549
1550 if ( newName == m_strName )
1551 return;
1552
1553 // we need to remove the group from the parent and it back under the new
1554 // name to keep the parents array of subgroups alphabetically sorted
1555 m_pParent->m_aSubgroups.Remove(this);
1556
1557 m_strName = newName;
1558
1559 m_pParent->m_aSubgroups.Add(this);
1560
1561 // update the group lines recursively
1562 UpdateGroupAndSubgroupsLines();
1563 }
1564
1565 wxString wxFileConfigGroup::GetFullName() const
1566 {
1567 wxString fullname;
1568 if ( Parent() )
1569 fullname = Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR + Name();
1570
1571 return fullname;
1572 }
1573
1574 // ----------------------------------------------------------------------------
1575 // find an item
1576 // ----------------------------------------------------------------------------
1577
1578 // use binary search because the array is sorted
1579 wxFileConfigEntry *
1580 wxFileConfigGroup::FindEntry(const wxString& name) const
1581 {
1582 size_t i,
1583 lo = 0,
1584 hi = m_aEntries.GetCount();
1585 int res;
1586 wxFileConfigEntry *pEntry;
1587
1588 while ( lo < hi ) {
1589 i = (lo + hi)/2;
1590 pEntry = m_aEntries[i];
1591
1592 #if wxCONFIG_CASE_SENSITIVE
1593 res = pEntry->Name().compare(name);
1594 #else
1595 res = pEntry->Name().CmpNoCase(name);
1596 #endif
1597
1598 if ( res > 0 )
1599 hi = i;
1600 else if ( res < 0 )
1601 lo = i + 1;
1602 else
1603 return pEntry;
1604 }
1605
1606 return NULL;
1607 }
1608
1609 wxFileConfigGroup *
1610 wxFileConfigGroup::FindSubgroup(const wxString& name) const
1611 {
1612 size_t i,
1613 lo = 0,
1614 hi = m_aSubgroups.GetCount();
1615 int res;
1616 wxFileConfigGroup *pGroup;
1617
1618 while ( lo < hi ) {
1619 i = (lo + hi)/2;
1620 pGroup = m_aSubgroups[i];
1621
1622 #if wxCONFIG_CASE_SENSITIVE
1623 res = pGroup->Name().compare(name);
1624 #else
1625 res = pGroup->Name().CmpNoCase(name);
1626 #endif
1627
1628 if ( res > 0 )
1629 hi = i;
1630 else if ( res < 0 )
1631 lo = i + 1;
1632 else
1633 return pGroup;
1634 }
1635
1636 return NULL;
1637 }
1638
1639 // ----------------------------------------------------------------------------
1640 // create a new item
1641 // ----------------------------------------------------------------------------
1642
1643 // create a new entry and add it to the current group
1644 wxFileConfigEntry *wxFileConfigGroup::AddEntry(const wxString& strName, int nLine)
1645 {
1646 wxASSERT( FindEntry(strName) == 0 );
1647
1648 wxFileConfigEntry *pEntry = new wxFileConfigEntry(this, strName, nLine);
1649
1650 m_aEntries.Add(pEntry);
1651 return pEntry;
1652 }
1653
1654 // create a new group and add it to the current group
1655 wxFileConfigGroup *wxFileConfigGroup::AddSubgroup(const wxString& strName)
1656 {
1657 wxASSERT( FindSubgroup(strName) == 0 );
1658
1659 wxFileConfigGroup *pGroup = new wxFileConfigGroup(this, strName, m_pConfig);
1660
1661 m_aSubgroups.Add(pGroup);
1662 return pGroup;
1663 }
1664
1665 // ----------------------------------------------------------------------------
1666 // delete an item
1667 // ----------------------------------------------------------------------------
1668
1669 /*
1670 The delete operations are _very_ slow if we delete the last item of this
1671 group (see comments before GetXXXLineXXX functions for more details),
1672 so it's much better to start with the first entry/group if we want to
1673 delete several of them.
1674 */
1675
1676 bool wxFileConfigGroup::DeleteSubgroupByName(const wxString& name)
1677 {
1678 wxFileConfigGroup * const pGroup = FindSubgroup(name);
1679
1680 return pGroup ? DeleteSubgroup(pGroup) : false;
1681 }
1682
1683 // Delete the subgroup and remove all references to it from
1684 // other data structures.
1685 bool wxFileConfigGroup::DeleteSubgroup(wxFileConfigGroup *pGroup)
1686 {
1687 wxCHECK_MSG( pGroup, false, _T("deleting non existing group?") );
1688
1689 wxLogTrace( FILECONF_TRACE_MASK,
1690 _T("Deleting group '%s' from '%s'"),
1691 pGroup->Name().c_str(),
1692 Name().c_str() );
1693
1694 wxLogTrace( FILECONF_TRACE_MASK,
1695 _T(" (m_pLine) = prev: %p, this %p, next %p"),
1696 m_pLine ? wx_static_cast(void*, m_pLine->Prev()) : 0,
1697 wx_static_cast(void*, m_pLine),
1698 m_pLine ? wx_static_cast(void*, m_pLine->Next()) : 0 );
1699 wxLogTrace( FILECONF_TRACE_MASK,
1700 _T(" text: '%s'"),
1701 m_pLine ? (const wxChar*)m_pLine->Text().c_str()
1702 : wxEmptyString );
1703
1704 // delete all entries...
1705 size_t nCount = pGroup->m_aEntries.GetCount();
1706
1707 wxLogTrace(FILECONF_TRACE_MASK,
1708 _T("Removing %lu entries"), (unsigned long)nCount );
1709
1710 for ( size_t nEntry = 0; nEntry < nCount; nEntry++ )
1711 {
1712 wxFileConfigLineList *pLine = pGroup->m_aEntries[nEntry]->GetLine();
1713
1714 if ( pLine )
1715 {
1716 wxLogTrace( FILECONF_TRACE_MASK,
1717 _T(" '%s'"),
1718 pLine->Text().c_str() );
1719 m_pConfig->LineListRemove(pLine);
1720 }
1721 }
1722
1723 // ...and subgroups of this subgroup
1724 nCount = pGroup->m_aSubgroups.GetCount();
1725
1726 wxLogTrace( FILECONF_TRACE_MASK,
1727 _T("Removing %lu subgroups"), (unsigned long)nCount );
1728
1729 for ( size_t nGroup = 0; nGroup < nCount; nGroup++ )
1730 {
1731 pGroup->DeleteSubgroup(pGroup->m_aSubgroups[0]);
1732 }
1733
1734 // and then finally the group itself
1735 wxFileConfigLineList *pLine = pGroup->m_pLine;
1736 if ( pLine )
1737 {
1738 wxLogTrace( FILECONF_TRACE_MASK,
1739 _T(" Removing line for group '%s' : '%s'"),
1740 pGroup->Name().c_str(),
1741 pLine->Text().c_str() );
1742 wxLogTrace( FILECONF_TRACE_MASK,
1743 _T(" Removing from group '%s' : '%s'"),
1744 Name().c_str(),
1745 ((m_pLine) ? (const wxChar*)m_pLine->Text().c_str()
1746 : wxEmptyString) );
1747
1748 // notice that we may do this test inside the previous "if"
1749 // because the last entry's line is surely !NULL
1750 if ( pGroup == m_pLastGroup )
1751 {
1752 wxLogTrace( FILECONF_TRACE_MASK,
1753 _T(" Removing last group") );
1754
1755 // our last entry is being deleted, so find the last one which
1756 // stays by going back until we find a subgroup or reach the
1757 // group line
1758 const size_t nSubgroups = m_aSubgroups.GetCount();
1759
1760 m_pLastGroup = NULL;
1761 for ( wxFileConfigLineList *pl = pLine->Prev();
1762 pl && !m_pLastGroup;
1763 pl = pl->Prev() )
1764 {
1765 // does this line belong to our subgroup?
1766 for ( size_t n = 0; n < nSubgroups; n++ )
1767 {
1768 // do _not_ call GetGroupLine! we don't want to add it to
1769 // the local file if it's not already there
1770 if ( m_aSubgroups[n]->m_pLine == pl )
1771 {
1772 m_pLastGroup = m_aSubgroups[n];
1773 break;
1774 }
1775 }
1776
1777 if ( pl == m_pLine )
1778 break;
1779 }
1780 }
1781
1782 m_pConfig->LineListRemove(pLine);
1783 }
1784 else
1785 {
1786 wxLogTrace( FILECONF_TRACE_MASK,
1787 _T(" No line entry for Group '%s'?"),
1788 pGroup->Name().c_str() );
1789 }
1790
1791 m_aSubgroups.Remove(pGroup);
1792 delete pGroup;
1793
1794 return true;
1795 }
1796
1797 bool wxFileConfigGroup::DeleteEntry(const wxString& name)
1798 {
1799 wxFileConfigEntry *pEntry = FindEntry(name);
1800 if ( !pEntry )
1801 {
1802 // entry doesn't exist, nothing to do
1803 return false;
1804 }
1805
1806 wxFileConfigLineList *pLine = pEntry->GetLine();
1807 if ( pLine != NULL ) {
1808 // notice that we may do this test inside the previous "if" because the
1809 // last entry's line is surely !NULL
1810 if ( pEntry == m_pLastEntry ) {
1811 // our last entry is being deleted - find the last one which stays
1812 wxASSERT( m_pLine != NULL ); // if we have an entry with !NULL pLine...
1813
1814 // go back until we find another entry or reach the group's line
1815 wxFileConfigEntry *pNewLast = NULL;
1816 size_t n, nEntries = m_aEntries.GetCount();
1817 wxFileConfigLineList *pl;
1818 for ( pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() ) {
1819 // is it our subgroup?
1820 for ( n = 0; (pNewLast == NULL) && (n < nEntries); n++ ) {
1821 if ( m_aEntries[n]->GetLine() == m_pLine )
1822 pNewLast = m_aEntries[n];
1823 }
1824
1825 if ( pNewLast != NULL ) // found?
1826 break;
1827 }
1828
1829 if ( pl == m_pLine ) {
1830 wxASSERT( !pNewLast ); // how comes it has the same line as we?
1831
1832 // we've reached the group line without finding any subgroups
1833 m_pLastEntry = NULL;
1834 }
1835 else
1836 m_pLastEntry = pNewLast;
1837 }
1838
1839 m_pConfig->LineListRemove(pLine);
1840 }
1841
1842 m_aEntries.Remove(pEntry);
1843 delete pEntry;
1844
1845 return true;
1846 }
1847
1848 // ============================================================================
1849 // wxFileConfig::wxFileConfigEntry
1850 // ============================================================================
1851
1852 // ----------------------------------------------------------------------------
1853 // ctor
1854 // ----------------------------------------------------------------------------
1855 wxFileConfigEntry::wxFileConfigEntry(wxFileConfigGroup *pParent,
1856 const wxString& strName,
1857 int nLine)
1858 : m_strName(strName)
1859 {
1860 wxASSERT( !strName.empty() );
1861
1862 m_pParent = pParent;
1863 m_nLine = nLine;
1864 m_pLine = NULL;
1865
1866 m_bHasValue = false;
1867
1868 m_bImmutable = strName[0] == wxCONFIG_IMMUTABLE_PREFIX;
1869 if ( m_bImmutable )
1870 m_strName.erase(0, 1); // remove first character
1871 }
1872
1873 // ----------------------------------------------------------------------------
1874 // set value
1875 // ----------------------------------------------------------------------------
1876
1877 void wxFileConfigEntry::SetLine(wxFileConfigLineList *pLine)
1878 {
1879 if ( m_pLine != NULL ) {
1880 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1881 Name().c_str(), m_pParent->GetFullName().c_str());
1882 }
1883
1884 m_pLine = pLine;
1885 Group()->SetLastEntry(this);
1886 }
1887
1888 // second parameter is false if we read the value from file and prevents the
1889 // entry from being marked as 'dirty'
1890 void wxFileConfigEntry::SetValue(const wxString& strValue, bool bUser)
1891 {
1892 if ( bUser && IsImmutable() )
1893 {
1894 wxLogWarning( _("attempt to change immutable key '%s' ignored."),
1895 Name().c_str());
1896 return;
1897 }
1898
1899 // do nothing if it's the same value: but don't test for it if m_bHasValue
1900 // hadn't been set yet or we'd never write empty values to the file
1901 if ( m_bHasValue && strValue == m_strValue )
1902 return;
1903
1904 m_bHasValue = true;
1905 m_strValue = strValue;
1906
1907 if ( bUser )
1908 {
1909 wxString strValFiltered;
1910
1911 if ( Group()->Config()->GetStyle() & wxCONFIG_USE_NO_ESCAPE_CHARACTERS )
1912 {
1913 strValFiltered = strValue;
1914 }
1915 else {
1916 strValFiltered = FilterOutValue(strValue);
1917 }
1918
1919 wxString strLine;
1920 strLine << FilterOutEntryName(m_strName) << wxT('=') << strValFiltered;
1921
1922 if ( m_pLine )
1923 {
1924 // entry was read from the local config file, just modify the line
1925 m_pLine->SetText(strLine);
1926 }
1927 else // this entry didn't exist in the local file
1928 {
1929 // add a new line to the file: note that line returned by
1930 // GetLastEntryLine() may be NULL if we're in the root group and it
1931 // doesn't have any entries yet, but this is ok as passing NULL
1932 // line to LineListInsert() means to prepend new line to the list
1933 wxFileConfigLineList *line = Group()->GetLastEntryLine();
1934 m_pLine = Group()->Config()->LineListInsert(strLine, line);
1935
1936 Group()->SetLastEntry(this);
1937 }
1938 }
1939 }
1940
1941 // ============================================================================
1942 // global functions
1943 // ============================================================================
1944
1945 // ----------------------------------------------------------------------------
1946 // compare functions for array sorting
1947 // ----------------------------------------------------------------------------
1948
1949 int CompareEntries(wxFileConfigEntry *p1, wxFileConfigEntry *p2)
1950 {
1951 #if wxCONFIG_CASE_SENSITIVE
1952 return p1->Name().compare(p2->Name());
1953 #else
1954 return p1->Name().CmpNoCase(p2->Name());
1955 #endif
1956 }
1957
1958 int CompareGroups(wxFileConfigGroup *p1, wxFileConfigGroup *p2)
1959 {
1960 #if wxCONFIG_CASE_SENSITIVE
1961 return p1->Name().compare(p2->Name());
1962 #else
1963 return p1->Name().CmpNoCase(p2->Name());
1964 #endif
1965 }
1966
1967 // ----------------------------------------------------------------------------
1968 // filter functions
1969 // ----------------------------------------------------------------------------
1970
1971 // undo FilterOutValue
1972 static wxString FilterInValue(const wxString& str)
1973 {
1974 wxString strResult;
1975 strResult.Alloc(str.Len());
1976
1977 bool bQuoted = !str.empty() && str[0] == '"';
1978
1979 for ( size_t n = bQuoted ? 1 : 0; n < str.Len(); n++ ) {
1980 if ( str[n] == wxT('\\') ) {
1981 switch ( str[++n].GetValue() ) {
1982 case wxT('n'):
1983 strResult += wxT('\n');
1984 break;
1985
1986 case wxT('r'):
1987 strResult += wxT('\r');
1988 break;
1989
1990 case wxT('t'):
1991 strResult += wxT('\t');
1992 break;
1993
1994 case wxT('\\'):
1995 strResult += wxT('\\');
1996 break;
1997
1998 case wxT('"'):
1999 strResult += wxT('"');
2000 break;
2001 }
2002 }
2003 else {
2004 if ( str[n] != wxT('"') || !bQuoted )
2005 strResult += str[n];
2006 else if ( n != str.Len() - 1 ) {
2007 wxLogWarning(_("unexpected \" at position %d in '%s'."),
2008 n, str.c_str());
2009 }
2010 //else: it's the last quote of a quoted string, ok
2011 }
2012 }
2013
2014 return strResult;
2015 }
2016
2017 // quote the string before writing it to file
2018 static wxString FilterOutValue(const wxString& str)
2019 {
2020 if ( !str )
2021 return str;
2022
2023 wxString strResult;
2024 strResult.Alloc(str.Len());
2025
2026 // quoting is necessary to preserve spaces in the beginning of the string
2027 bool bQuote = wxIsspace(str[0]) || str[0] == wxT('"');
2028
2029 if ( bQuote )
2030 strResult += wxT('"');
2031
2032 wxChar c;
2033 for ( size_t n = 0; n < str.Len(); n++ ) {
2034 switch ( str[n].GetValue() ) {
2035 case wxT('\n'):
2036 c = wxT('n');
2037 break;
2038
2039 case wxT('\r'):
2040 c = wxT('r');
2041 break;
2042
2043 case wxT('\t'):
2044 c = wxT('t');
2045 break;
2046
2047 case wxT('\\'):
2048 c = wxT('\\');
2049 break;
2050
2051 case wxT('"'):
2052 if ( bQuote ) {
2053 c = wxT('"');
2054 break;
2055 }
2056 //else: fall through
2057
2058 default:
2059 strResult += str[n];
2060 continue; // nothing special to do
2061 }
2062
2063 // we get here only for special characters
2064 strResult << wxT('\\') << c;
2065 }
2066
2067 if ( bQuote )
2068 strResult += wxT('"');
2069
2070 return strResult;
2071 }
2072
2073 // undo FilterOutEntryName
2074 static wxString FilterInEntryName(const wxString& str)
2075 {
2076 wxString strResult;
2077 strResult.Alloc(str.Len());
2078
2079 for ( const wxChar *pc = str.c_str(); *pc != '\0'; pc++ ) {
2080 if ( *pc == wxT('\\') ) {
2081 // we need to test it here or we'd skip past the NUL in the loop line
2082 if ( *++pc == _T('\0') )
2083 break;
2084 }
2085
2086 strResult += *pc;
2087 }
2088
2089 return strResult;
2090 }
2091
2092 // sanitize entry or group name: insert '\\' before any special characters
2093 static wxString FilterOutEntryName(const wxString& str)
2094 {
2095 wxString strResult;
2096 strResult.Alloc(str.Len());
2097
2098 for ( const wxChar *pc = str.c_str(); *pc != wxT('\0'); pc++ ) {
2099 const wxChar c = *pc;
2100
2101 // we explicitly allow some of "safe" chars and 8bit ASCII characters
2102 // which will probably never have special meaning and with which we can't
2103 // use isalnum() anyhow (in ASCII built, in Unicode it's just fine)
2104 //
2105 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
2106 // should *not* be quoted
2107 if (
2108 #if !wxUSE_UNICODE
2109 ((unsigned char)c < 127) &&
2110 #endif // ANSI
2111 !wxIsalnum(c) && !wxStrchr(wxT("@_/-!.*%"), c) )
2112 {
2113 strResult += wxT('\\');
2114 }
2115
2116 strResult += c;
2117 }
2118
2119 return strResult;
2120 }
2121
2122 // we can't put ?: in the ctor initializer list because it confuses some
2123 // broken compilers (Borland C++)
2124 static wxString GetAppName(const wxString& appName)
2125 {
2126 if ( !appName && wxTheApp )
2127 return wxTheApp->GetAppName();
2128 else
2129 return appName;
2130 }
2131
2132 #endif // wxUSE_CONFIG