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