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