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