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