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