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