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