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