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