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