A fix to prevent mungeing of the config file after reading in
[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 // <JACS>
676 // Take the opportunity to set some pointers now
677 // that we know there are items in this group.
678 // Otherwise, items added to a newly read file
679 // can be put in the wrong place.
680 m_pCurrentGroup->SetLastEntry(pEntry);
681 if (m_pCurrentGroup->Parent())
682 m_pCurrentGroup->Parent()->SetLastGroup(m_pCurrentGroup);
683 // </JACS>
684
685 if ( bLocal )
686 pEntry->SetLine(m_linesTail);
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 if ( bLocal )
705 pEntry->SetLine(m_linesTail);
706 }
707 }
708
709 // skip whitespace
710 while ( wxIsspace(*pEnd) )
711 pEnd++;
712
713 wxString value = pEnd;
714 if ( !(GetStyle() & wxCONFIG_USE_NO_ESCAPE_CHARACTERS) )
715 value = FilterInValue(value);
716
717 pEntry->SetValue(value, FALSE);
718 }
719 }
720 }
721 }
722
723 // ----------------------------------------------------------------------------
724 // set/retrieve path
725 // ----------------------------------------------------------------------------
726
727 void wxFileConfig::SetRootPath()
728 {
729 m_strPath.Empty();
730 m_pCurrentGroup = m_pRootGroup;
731 }
732
733 void wxFileConfig::SetPath(const wxString& strPath)
734 {
735 wxArrayString aParts;
736
737 if ( strPath.IsEmpty() ) {
738 SetRootPath();
739 return;
740 }
741
742 if ( strPath[0] == wxCONFIG_PATH_SEPARATOR ) {
743 // absolute path
744 wxSplitPath(aParts, strPath);
745 }
746 else {
747 // relative path, combine with current one
748 wxString strFullPath = m_strPath;
749 strFullPath << wxCONFIG_PATH_SEPARATOR << strPath;
750 wxSplitPath(aParts, strFullPath);
751 }
752
753 // change current group
754 size_t n;
755 m_pCurrentGroup = m_pRootGroup;
756 for ( n = 0; n < aParts.Count(); n++ ) {
757 wxFileConfigGroup *pNextGroup = m_pCurrentGroup->FindSubgroup(aParts[n]);
758 if ( pNextGroup == NULL )
759 pNextGroup = m_pCurrentGroup->AddSubgroup(aParts[n]);
760 m_pCurrentGroup = pNextGroup;
761 }
762
763 // recombine path parts in one variable
764 m_strPath.Empty();
765 for ( n = 0; n < aParts.Count(); n++ ) {
766 m_strPath << wxCONFIG_PATH_SEPARATOR << aParts[n];
767 }
768 }
769
770 // ----------------------------------------------------------------------------
771 // enumeration
772 // ----------------------------------------------------------------------------
773
774 bool wxFileConfig::GetFirstGroup(wxString& str, long& lIndex) const
775 {
776 lIndex = 0;
777 return GetNextGroup(str, lIndex);
778 }
779
780 bool wxFileConfig::GetNextGroup (wxString& str, long& lIndex) const
781 {
782 if ( size_t(lIndex) < m_pCurrentGroup->Groups().Count() ) {
783 str = m_pCurrentGroup->Groups()[(size_t)lIndex++]->Name();
784 return TRUE;
785 }
786 else
787 return FALSE;
788 }
789
790 bool wxFileConfig::GetFirstEntry(wxString& str, long& lIndex) const
791 {
792 lIndex = 0;
793 return GetNextEntry(str, lIndex);
794 }
795
796 bool wxFileConfig::GetNextEntry (wxString& str, long& lIndex) const
797 {
798 if ( size_t(lIndex) < m_pCurrentGroup->Entries().Count() ) {
799 str = m_pCurrentGroup->Entries()[(size_t)lIndex++]->Name();
800 return TRUE;
801 }
802 else
803 return FALSE;
804 }
805
806 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive) const
807 {
808 size_t n = m_pCurrentGroup->Entries().Count();
809 if ( bRecursive ) {
810 wxFileConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
811 size_t nSubgroups = m_pCurrentGroup->Groups().Count();
812 for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
813 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
814 n += GetNumberOfEntries(TRUE);
815 CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
816 }
817 }
818
819 return n;
820 }
821
822 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive) const
823 {
824 size_t n = m_pCurrentGroup->Groups().Count();
825 if ( bRecursive ) {
826 wxFileConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
827 size_t nSubgroups = m_pCurrentGroup->Groups().Count();
828 for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
829 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
830 n += GetNumberOfGroups(TRUE);
831 CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
832 }
833 }
834
835 return n;
836 }
837
838 // ----------------------------------------------------------------------------
839 // tests for existence
840 // ----------------------------------------------------------------------------
841
842 bool wxFileConfig::HasGroup(const wxString& strName) const
843 {
844 wxConfigPathChanger path(this, strName);
845
846 wxFileConfigGroup *pGroup = m_pCurrentGroup->FindSubgroup(path.Name());
847 return pGroup != NULL;
848 }
849
850 bool wxFileConfig::HasEntry(const wxString& strName) const
851 {
852 wxConfigPathChanger path(this, strName);
853
854 wxFileConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
855 return pEntry != NULL;
856 }
857
858 // ----------------------------------------------------------------------------
859 // read/write values
860 // ----------------------------------------------------------------------------
861
862 bool wxFileConfig::DoReadString(const wxString& key, wxString* pStr) const
863 {
864 wxConfigPathChanger path(this, key);
865
866 wxFileConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
867 if (pEntry == NULL) {
868 return FALSE;
869 }
870
871 *pStr = pEntry->Value();
872
873 return TRUE;
874 }
875
876 bool wxFileConfig::DoReadLong(const wxString& key, long *pl) const
877 {
878 wxString str;
879 if ( !Read(key, &str) )
880 return FALSE;
881
882 // extra spaces shouldn't prevent us from reading numeric values
883 str.Trim();
884
885 return str.ToLong(pl);
886 }
887
888 bool wxFileConfig::DoWriteString(const wxString& key, const wxString& szValue)
889 {
890 wxConfigPathChanger path(this, key);
891 wxString strName = path.Name();
892
893 wxLogTrace( _T("wxFileConfig"),
894 _T(" Writing String '%s' = '%s' to Group '%s'"),
895 strName.c_str(),
896 szValue.c_str(),
897 GetPath().c_str() );
898
899 if ( strName.IsEmpty() )
900 {
901 // setting the value of a group is an error
902
903 wxASSERT_MSG( wxIsEmpty(szValue), wxT("can't set value of a group!") );
904
905 // ... except if it's empty in which case it's a way to force it's creation
906
907 wxLogTrace( _T("wxFileConfig"),
908 _T(" Creating group %s"),
909 m_pCurrentGroup->Name().c_str() );
910
911 m_pCurrentGroup->SetDirty();
912
913 // this will add a line for this group if it didn't have it before
914
915 (void)m_pCurrentGroup->GetGroupLine();
916 }
917 else
918 {
919 // writing an entry
920 // check that the name is reasonable
921
922 if ( strName[0u] == wxCONFIG_IMMUTABLE_PREFIX )
923 {
924 wxLogError( _("Config entry name cannot start with '%c'."),
925 wxCONFIG_IMMUTABLE_PREFIX);
926 return FALSE;
927 }
928
929 wxFileConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strName);
930
931 if ( pEntry == 0 )
932 {
933 wxLogTrace( _T("wxFileConfig"),
934 _T(" Adding Entry %s"),
935 strName.c_str() );
936 pEntry = m_pCurrentGroup->AddEntry(strName);
937 }
938
939 wxLogTrace( _T("wxFileConfig"),
940 _T(" Setting value %s"),
941 szValue.c_str() );
942 pEntry->SetValue(szValue);
943 }
944
945 return TRUE;
946 }
947
948 bool wxFileConfig::DoWriteLong(const wxString& key, long lValue)
949 {
950 return Write(key, wxString::Format(_T("%ld"), lValue));
951 }
952
953 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
954 {
955 if ( LineListIsEmpty() || !m_pRootGroup->IsDirty() || !m_strLocalFile )
956 return TRUE;
957
958 #ifdef __UNIX__
959 // set the umask if needed
960 mode_t umaskOld = 0;
961 if ( m_umask != -1 )
962 {
963 umaskOld = umask((mode_t)m_umask);
964 }
965 #endif // __UNIX__
966
967 wxTempFile file(m_strLocalFile);
968
969 if ( !file.IsOpened() )
970 {
971 wxLogError(_("can't open user configuration file."));
972 return FALSE;
973 }
974
975 // write all strings to file
976 for ( wxFileConfigLineList *p = m_linesHead; p != NULL; p = p->Next() )
977 {
978 wxString line = p->Text();
979 line += wxTextFile::GetEOL();
980 if ( !file.Write(line, m_conv) )
981 {
982 wxLogError(_("can't write user configuration file."));
983 return FALSE;
984 }
985 }
986
987 bool ret = file.Commit();
988
989 #if defined(__WXMAC__)
990 if ( ret )
991 {
992 FSSpec spec ;
993
994 wxMacFilename2FSSpec( m_strLocalFile , &spec ) ;
995 FInfo finfo ;
996 if ( FSpGetFInfo( &spec , &finfo ) == noErr )
997 {
998 finfo.fdType = 'TEXT' ;
999 finfo.fdCreator = 'ttxt' ;
1000 FSpSetFInfo( &spec , &finfo ) ;
1001 }
1002 }
1003 #endif // __WXMAC__
1004
1005 #ifdef __UNIX__
1006 // restore the old umask if we changed it
1007 if ( m_umask != -1 )
1008 {
1009 (void)umask(umaskOld);
1010 }
1011 #endif // __UNIX__
1012
1013 return ret;
1014 }
1015
1016 // ----------------------------------------------------------------------------
1017 // renaming groups/entries
1018 // ----------------------------------------------------------------------------
1019
1020 bool wxFileConfig::RenameEntry(const wxString& oldName,
1021 const wxString& newName)
1022 {
1023 // check that the entry exists
1024 wxFileConfigEntry *oldEntry = m_pCurrentGroup->FindEntry(oldName);
1025 if ( !oldEntry )
1026 return FALSE;
1027
1028 // check that the new entry doesn't already exist
1029 if ( m_pCurrentGroup->FindEntry(newName) )
1030 return FALSE;
1031
1032 // delete the old entry, create the new one
1033 wxString value = oldEntry->Value();
1034 if ( !m_pCurrentGroup->DeleteEntry(oldName) )
1035 return FALSE;
1036
1037 wxFileConfigEntry *newEntry = m_pCurrentGroup->AddEntry(newName);
1038 newEntry->SetValue(value);
1039
1040 return TRUE;
1041 }
1042
1043 bool wxFileConfig::RenameGroup(const wxString& oldName,
1044 const wxString& newName)
1045 {
1046 // check that the group exists
1047 wxFileConfigGroup *group = m_pCurrentGroup->FindSubgroup(oldName);
1048 if ( !group )
1049 return FALSE;
1050
1051 // check that the new group doesn't already exist
1052 if ( m_pCurrentGroup->FindSubgroup(newName) )
1053 return FALSE;
1054
1055 group->Rename(newName);
1056
1057 return TRUE;
1058 }
1059
1060 // ----------------------------------------------------------------------------
1061 // delete groups/entries
1062 // ----------------------------------------------------------------------------
1063
1064 bool wxFileConfig::DeleteEntry(const wxString& key, bool bGroupIfEmptyAlso)
1065 {
1066 wxConfigPathChanger path(this, key);
1067
1068 if ( !m_pCurrentGroup->DeleteEntry(path.Name()) )
1069 return FALSE;
1070
1071 if ( bGroupIfEmptyAlso && m_pCurrentGroup->IsEmpty() ) {
1072 if ( m_pCurrentGroup != m_pRootGroup ) {
1073 wxFileConfigGroup *pGroup = m_pCurrentGroup;
1074 SetPath(wxT("..")); // changes m_pCurrentGroup!
1075 m_pCurrentGroup->DeleteSubgroupByName(pGroup->Name());
1076 }
1077 //else: never delete the root group
1078 }
1079
1080 return TRUE;
1081 }
1082
1083 bool wxFileConfig::DeleteGroup(const wxString& key)
1084 {
1085 wxConfigPathChanger path(this, key);
1086
1087 return m_pCurrentGroup->DeleteSubgroupByName(path.Name());
1088 }
1089
1090 bool wxFileConfig::DeleteAll()
1091 {
1092 CleanUp();
1093
1094 if ( wxRemove(m_strLocalFile) == -1 )
1095 wxLogSysError(_("can't delete user configuration file '%s'"), m_strLocalFile.c_str());
1096
1097 m_strLocalFile = m_strGlobalFile = wxT("");
1098 Init();
1099
1100 return TRUE;
1101 }
1102
1103 // ----------------------------------------------------------------------------
1104 // linked list functions
1105 // ----------------------------------------------------------------------------
1106
1107 // append a new line to the end of the list
1108
1109 wxFileConfigLineList *wxFileConfig::LineListAppend(const wxString& str)
1110 {
1111 wxLogTrace( _T("wxFileConfig"),
1112 _T(" ** Adding Line '%s'"),
1113 str.c_str() );
1114 wxLogTrace( _T("wxFileConfig"),
1115 _T(" head: %s"),
1116 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1117 wxLogTrace( _T("wxFileConfig"),
1118 _T(" tail: %s"),
1119 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1120
1121 wxFileConfigLineList *pLine = new wxFileConfigLineList(str);
1122
1123 if ( m_linesTail == NULL )
1124 {
1125 // list is empty
1126 m_linesHead = pLine;
1127 }
1128 else
1129 {
1130 // adjust pointers
1131 m_linesTail->SetNext(pLine);
1132 pLine->SetPrev(m_linesTail);
1133 }
1134
1135 m_linesTail = pLine;
1136
1137 wxLogTrace( _T("wxFileConfig"),
1138 _T(" head: %s"),
1139 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1140 wxLogTrace( _T("wxFileConfig"),
1141 _T(" tail: %s"),
1142 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1143
1144 return m_linesTail;
1145 }
1146
1147 // insert a new line after the given one or in the very beginning if !pLine
1148 wxFileConfigLineList *wxFileConfig::LineListInsert(const wxString& str,
1149 wxFileConfigLineList *pLine)
1150 {
1151 wxLogTrace( _T("wxFileConfig"),
1152 _T(" ** Inserting Line '%s' after '%s'"),
1153 str.c_str(),
1154 ((pLine) ? pLine->Text().c_str() : wxEmptyString) );
1155 wxLogTrace( _T("wxFileConfig"),
1156 _T(" head: %s"),
1157 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1158 wxLogTrace( _T("wxFileConfig"),
1159 _T(" tail: %s"),
1160 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1161
1162 if ( pLine == m_linesTail )
1163 return LineListAppend(str);
1164
1165 wxFileConfigLineList *pNewLine = new wxFileConfigLineList(str);
1166 if ( pLine == NULL )
1167 {
1168 // prepend to the list
1169 pNewLine->SetNext(m_linesHead);
1170 m_linesHead->SetPrev(pNewLine);
1171 m_linesHead = pNewLine;
1172 }
1173 else
1174 {
1175 // insert before pLine
1176 wxFileConfigLineList *pNext = pLine->Next();
1177 pNewLine->SetNext(pNext);
1178 pNewLine->SetPrev(pLine);
1179 pNext->SetPrev(pNewLine);
1180 pLine->SetNext(pNewLine);
1181 }
1182
1183 wxLogTrace( _T("wxFileConfig"),
1184 _T(" head: %s"),
1185 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1186 wxLogTrace( _T("wxFileConfig"),
1187 _T(" tail: %s"),
1188 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1189
1190 return pNewLine;
1191 }
1192
1193 void wxFileConfig::LineListRemove(wxFileConfigLineList *pLine)
1194 {
1195 wxLogTrace( _T("wxFileConfig"),
1196 _T(" ** Removing Line '%s'"),
1197 pLine->Text().c_str() );
1198 wxLogTrace( _T("wxFileConfig"),
1199 _T(" head: %s"),
1200 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1201 wxLogTrace( _T("wxFileConfig"),
1202 _T(" tail: %s"),
1203 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1204
1205 wxFileConfigLineList *pPrev = pLine->Prev(),
1206 *pNext = pLine->Next();
1207
1208 // first entry?
1209
1210 if ( pPrev == NULL )
1211 m_linesHead = pNext;
1212 else
1213 pPrev->SetNext(pNext);
1214
1215 // last entry?
1216
1217 if ( pNext == NULL )
1218 m_linesTail = pPrev;
1219 else
1220 pNext->SetPrev(pPrev);
1221
1222 wxLogTrace( _T("wxFileConfig"),
1223 _T(" head: %s"),
1224 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1225 wxLogTrace( _T("wxFileConfig"),
1226 _T(" tail: %s"),
1227 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1228
1229 delete pLine;
1230 }
1231
1232 bool wxFileConfig::LineListIsEmpty()
1233 {
1234 return m_linesHead == NULL;
1235 }
1236
1237 // ============================================================================
1238 // wxFileConfig::wxFileConfigGroup
1239 // ============================================================================
1240
1241 // ----------------------------------------------------------------------------
1242 // ctor/dtor
1243 // ----------------------------------------------------------------------------
1244
1245 // ctor
1246 wxFileConfigGroup::wxFileConfigGroup(wxFileConfigGroup *pParent,
1247 const wxString& strName,
1248 wxFileConfig *pConfig)
1249 : m_aEntries(CompareEntries),
1250 m_aSubgroups(CompareGroups),
1251 m_strName(strName)
1252 {
1253 m_pConfig = pConfig;
1254 m_pParent = pParent;
1255 m_bDirty = FALSE;
1256 m_pLine = NULL;
1257
1258 m_pLastEntry = NULL;
1259 m_pLastGroup = NULL;
1260 }
1261
1262 // dtor deletes all children
1263 wxFileConfigGroup::~wxFileConfigGroup()
1264 {
1265 // entries
1266 size_t n, nCount = m_aEntries.Count();
1267 for ( n = 0; n < nCount; n++ )
1268 delete m_aEntries[n];
1269
1270 // subgroups
1271 nCount = m_aSubgroups.Count();
1272 for ( n = 0; n < nCount; n++ )
1273 delete m_aSubgroups[n];
1274 }
1275
1276 // ----------------------------------------------------------------------------
1277 // line
1278 // ----------------------------------------------------------------------------
1279
1280 void wxFileConfigGroup::SetLine(wxFileConfigLineList *pLine)
1281 {
1282 wxASSERT( m_pLine == 0 ); // shouldn't be called twice
1283 m_pLine = pLine;
1284 }
1285
1286 /*
1287 This is a bit complicated, so let me explain it in details. All lines that
1288 were read from the local file (the only one we will ever modify) are stored
1289 in a (doubly) linked list. Our problem is to know at which position in this
1290 list should we insert the new entries/subgroups. To solve it we keep three
1291 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
1292
1293 m_pLine points to the line containing "[group_name]"
1294 m_pLastEntry points to the last entry of this group in the local file.
1295 m_pLastGroup subgroup
1296
1297 Initially, they're NULL all three. When the group (an entry/subgroup) is read
1298 from the local file, the corresponding variable is set. However, if the group
1299 was read from the global file and then modified or created by the application
1300 these variables are still NULL and we need to create the corresponding lines.
1301 See the following functions (and comments preceding them) for the details of
1302 how we do it.
1303
1304 Also, when our last entry/group are deleted we need to find the new last
1305 element - the code in DeleteEntry/Subgroup does this by backtracking the list
1306 of lines until it either founds an entry/subgroup (and this is the new last
1307 element) or the m_pLine of the group, in which case there are no more entries
1308 (or subgroups) left and m_pLast<element> becomes NULL.
1309
1310 NB: This last problem could be avoided for entries if we added new entries
1311 immediately after m_pLine, but in this case the entries would appear
1312 backwards in the config file (OTOH, it's not that important) and as we
1313 would still need to do it for the subgroups the code wouldn't have been
1314 significantly less complicated.
1315 */
1316
1317 // Return the line which contains "[our name]". If we're still not in the list,
1318 // add our line to it immediately after the last line of our parent group if we
1319 // have it or in the very beginning if we're the root group.
1320 wxFileConfigLineList *wxFileConfigGroup::GetGroupLine()
1321 {
1322 wxLogTrace( _T("wxFileConfig"),
1323 _T(" GetGroupLine() for Group '%s'"),
1324 Name().c_str() );
1325
1326 if ( !m_pLine )
1327 {
1328 wxLogTrace( _T("wxFileConfig"),
1329 _T(" Getting Line item pointer") );
1330
1331 wxFileConfigGroup *pParent = Parent();
1332
1333 // this group wasn't present in local config file, add it now
1334 if ( pParent )
1335 {
1336 wxLogTrace( _T("wxFileConfig"),
1337 _T(" checking parent '%s'"),
1338 pParent->Name().c_str() );
1339
1340 wxString strFullName;
1341
1342 // add 1 to the name because we don't want to start with '/'
1343 strFullName << wxT("[")
1344 << FilterOutEntryName(GetFullName().c_str() + 1)
1345 << wxT("]");
1346 m_pLine = m_pConfig->LineListInsert(strFullName,
1347 pParent->GetLastGroupLine());
1348 pParent->SetLastGroup(this); // we're surely after all the others
1349 }
1350 //else: this is the root group and so we return NULL because we don't
1351 // have any group line
1352 }
1353
1354 return m_pLine;
1355 }
1356
1357 // Return the last line belonging to the subgroups of this group (after which
1358 // we can add a new subgroup), if we don't have any subgroups or entries our
1359 // last line is the group line (m_pLine) itself.
1360 wxFileConfigLineList *wxFileConfigGroup::GetLastGroupLine()
1361 {
1362 // if we have any subgroups, our last line is the last line of the last
1363 // subgroup
1364 if ( m_pLastGroup )
1365 {
1366 wxFileConfigLineList *pLine = m_pLastGroup->GetLastGroupLine();
1367
1368 wxASSERT_MSG( pLine, _T("last group must have !NULL associated line") );
1369
1370 return pLine;
1371 }
1372
1373 // no subgroups, so the last line is the line of thelast entry (if any)
1374 return GetLastEntryLine();
1375 }
1376
1377 // return the last line belonging to the entries of this group (after which
1378 // we can add a new entry), if we don't have any entries we will add the new
1379 // one immediately after the group line itself.
1380 wxFileConfigLineList *wxFileConfigGroup::GetLastEntryLine()
1381 {
1382 wxLogTrace( _T("wxFileConfig"),
1383 _T(" GetLastEntryLine() for Group '%s'"),
1384 Name().c_str() );
1385
1386 if ( m_pLastEntry )
1387 {
1388 wxFileConfigLineList *pLine = m_pLastEntry->GetLine();
1389
1390 wxASSERT_MSG( pLine, _T("last entry must have !NULL associated line") );
1391
1392 return pLine;
1393 }
1394
1395 // no entries: insert after the group header, if any
1396 return GetGroupLine();
1397 }
1398
1399 void wxFileConfigGroup::SetLastEntry(wxFileConfigEntry *pEntry)
1400 {
1401 m_pLastEntry = pEntry;
1402
1403 if ( !m_pLine )
1404 {
1405 // the only situation in which a group without its own line can have
1406 // an entry is when the first entry is added to the initially empty
1407 // root pseudo-group
1408 wxASSERT_MSG( !m_pParent, _T("unexpected for non root group") );
1409
1410 // let the group know that it does have a line in the file now
1411 m_pLine = pEntry->GetLine();
1412 }
1413 }
1414
1415 // ----------------------------------------------------------------------------
1416 // group name
1417 // ----------------------------------------------------------------------------
1418
1419 void wxFileConfigGroup::Rename(const wxString& newName)
1420 {
1421 wxCHECK_RET( m_pParent, _T("the root group can't be renamed") );
1422
1423 m_strName = newName;
1424
1425 // +1: no leading '/'
1426 wxString strFullName;
1427 strFullName << wxT("[") << (GetFullName().c_str() + 1) << wxT("]");
1428
1429 wxFileConfigLineList *line = GetGroupLine();
1430 wxCHECK_RET( line, _T("a non root group must have a corresponding line!") );
1431
1432 line->SetText(strFullName);
1433
1434 SetDirty();
1435 }
1436
1437 wxString wxFileConfigGroup::GetFullName() const
1438 {
1439 if ( Parent() )
1440 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR + Name();
1441 else
1442 return wxT("");
1443 }
1444
1445 // ----------------------------------------------------------------------------
1446 // find an item
1447 // ----------------------------------------------------------------------------
1448
1449 // use binary search because the array is sorted
1450 wxFileConfigEntry *
1451 wxFileConfigGroup::FindEntry(const wxChar *szName) const
1452 {
1453 size_t i,
1454 lo = 0,
1455 hi = m_aEntries.Count();
1456 int res;
1457 wxFileConfigEntry *pEntry;
1458
1459 while ( lo < hi ) {
1460 i = (lo + hi)/2;
1461 pEntry = m_aEntries[i];
1462
1463 #if wxCONFIG_CASE_SENSITIVE
1464 res = wxStrcmp(pEntry->Name(), szName);
1465 #else
1466 res = wxStricmp(pEntry->Name(), szName);
1467 #endif
1468
1469 if ( res > 0 )
1470 hi = i;
1471 else if ( res < 0 )
1472 lo = i + 1;
1473 else
1474 return pEntry;
1475 }
1476
1477 return NULL;
1478 }
1479
1480 wxFileConfigGroup *
1481 wxFileConfigGroup::FindSubgroup(const wxChar *szName) const
1482 {
1483 size_t i,
1484 lo = 0,
1485 hi = m_aSubgroups.Count();
1486 int res;
1487 wxFileConfigGroup *pGroup;
1488
1489 while ( lo < hi ) {
1490 i = (lo + hi)/2;
1491 pGroup = m_aSubgroups[i];
1492
1493 #if wxCONFIG_CASE_SENSITIVE
1494 res = wxStrcmp(pGroup->Name(), szName);
1495 #else
1496 res = wxStricmp(pGroup->Name(), szName);
1497 #endif
1498
1499 if ( res > 0 )
1500 hi = i;
1501 else if ( res < 0 )
1502 lo = i + 1;
1503 else
1504 return pGroup;
1505 }
1506
1507 return NULL;
1508 }
1509
1510 // ----------------------------------------------------------------------------
1511 // create a new item
1512 // ----------------------------------------------------------------------------
1513
1514 // create a new entry and add it to the current group
1515 wxFileConfigEntry *wxFileConfigGroup::AddEntry(const wxString& strName, int nLine)
1516 {
1517 wxASSERT( FindEntry(strName) == 0 );
1518
1519 wxFileConfigEntry *pEntry = new wxFileConfigEntry(this, strName, nLine);
1520
1521 m_aEntries.Add(pEntry);
1522 return pEntry;
1523 }
1524
1525 // create a new group and add it to the current group
1526 wxFileConfigGroup *wxFileConfigGroup::AddSubgroup(const wxString& strName)
1527 {
1528 wxASSERT( FindSubgroup(strName) == 0 );
1529
1530 wxFileConfigGroup *pGroup = new wxFileConfigGroup(this, strName, m_pConfig);
1531
1532 m_aSubgroups.Add(pGroup);
1533 return pGroup;
1534 }
1535
1536 // ----------------------------------------------------------------------------
1537 // delete an item
1538 // ----------------------------------------------------------------------------
1539
1540 /*
1541 The delete operations are _very_ slow if we delete the last item of this
1542 group (see comments before GetXXXLineXXX functions for more details),
1543 so it's much better to start with the first entry/group if we want to
1544 delete several of them.
1545 */
1546
1547 bool wxFileConfigGroup::DeleteSubgroupByName(const wxChar *szName)
1548 {
1549 wxFileConfigGroup * const pGroup = FindSubgroup(szName);
1550
1551 return pGroup ? DeleteSubgroup(pGroup) : FALSE;
1552 }
1553
1554 // Delete the subgroup and remove all references to it from
1555 // other data structures.
1556 bool wxFileConfigGroup::DeleteSubgroup(wxFileConfigGroup *pGroup)
1557 {
1558 wxCHECK_MSG( pGroup, FALSE, _T("deleting non existing group?") );
1559
1560 wxLogTrace( _T("wxFileConfig"),
1561 _T("Deleting group '%s' from '%s'"),
1562 pGroup->Name().c_str(),
1563 Name().c_str() );
1564
1565 wxLogTrace( _T("wxFileConfig"),
1566 _T(" (m_pLine) = prev: %p, this %p, next %p"),
1567 ((m_pLine) ? m_pLine->Prev() : 0),
1568 m_pLine,
1569 ((m_pLine) ? m_pLine->Next() : 0) );
1570 wxLogTrace( _T("wxFileConfig"),
1571 _T(" text: '%s'"),
1572 ((m_pLine) ? m_pLine->Text().c_str() : wxEmptyString) );
1573
1574 // delete all entries
1575 size_t nCount = pGroup->m_aEntries.Count();
1576
1577 wxLogTrace(_T("wxFileConfig"),
1578 _T("Removing %lu Entries"),
1579 (unsigned long)nCount );
1580
1581 for ( size_t nEntry = 0; nEntry < nCount; nEntry++ )
1582 {
1583 wxFileConfigLineList *pLine = pGroup->m_aEntries[nEntry]->GetLine();
1584
1585 if ( pLine != 0 )
1586 {
1587 wxLogTrace( _T("wxFileConfig"),
1588 _T(" '%s'"),
1589 pLine->Text().c_str() );
1590 m_pConfig->LineListRemove(pLine);
1591 }
1592 }
1593
1594 // and subgroups of this subgroup
1595
1596 nCount = pGroup->m_aSubgroups.Count();
1597
1598 wxLogTrace( _T("wxFileConfig"),
1599 _T("Removing %lu SubGroups"),
1600 (unsigned long)nCount );
1601
1602 for ( size_t nGroup = 0; nGroup < nCount; nGroup++ )
1603 {
1604 pGroup->DeleteSubgroup(pGroup->m_aSubgroups[0]);
1605 }
1606
1607 // finally the group itself
1608
1609 wxFileConfigLineList *pLine = pGroup->m_pLine;
1610
1611 if ( pLine != 0 )
1612 {
1613 wxLogTrace( _T("wxFileConfig"),
1614 _T(" Removing line entry for Group '%s' : '%s'"),
1615 pGroup->Name().c_str(),
1616 pLine->Text().c_str() );
1617 wxLogTrace( _T("wxFileConfig"),
1618 _T(" Removing from Group '%s' : '%s'"),
1619 Name().c_str(),
1620 ((m_pLine) ? m_pLine->Text().c_str() : wxEmptyString) );
1621
1622 // notice that we may do this test inside the previous "if"
1623 // because the last entry's line is surely !NULL
1624
1625 if ( pGroup == m_pLastGroup )
1626 {
1627 wxLogTrace( _T("wxFileConfig"),
1628 _T(" ------- Removing last group -------") );
1629
1630 // our last entry is being deleted, so find the last one which stays.
1631 // go back until we find a subgroup or reach the group's line, unless
1632 // we are the root group, which we'll notice shortly.
1633
1634 wxFileConfigGroup *pNewLast = 0;
1635 size_t nSubgroups = m_aSubgroups.Count();
1636 wxFileConfigLineList *pl;
1637
1638 for ( pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() )
1639 {
1640 // is it our subgroup?
1641
1642 for ( size_t n = 0; (pNewLast == 0) && (n < nSubgroups); n++ )
1643 {
1644 // do _not_ call GetGroupLine! we don't want to add it to the local
1645 // file if it's not already there
1646
1647 if ( m_aSubgroups[n]->m_pLine == m_pLine )
1648 pNewLast = m_aSubgroups[n];
1649 }
1650
1651 if ( pNewLast != 0 ) // found?
1652 break;
1653 }
1654
1655 if ( pl == m_pLine || m_pParent == 0 )
1656 {
1657 wxLogTrace( _T("wxFileConfig"),
1658 _T(" ------- No previous group found -------") );
1659
1660 wxASSERT_MSG( !pNewLast || m_pLine == 0,
1661 _T("how comes it has the same line as we?") );
1662
1663 // we've reached the group line without finding any subgroups,
1664 // or realised we removed the last group from the root.
1665
1666 m_pLastGroup = 0;
1667 }
1668 else
1669 {
1670 wxLogTrace( _T("wxFileConfig"),
1671 _T(" ------- Last Group set to '%s' -------"),
1672 pNewLast->Name().c_str() );
1673
1674 m_pLastGroup = pNewLast;
1675 }
1676 }
1677
1678 m_pConfig->LineListRemove(pLine);
1679 }
1680 else
1681 {
1682 wxLogTrace( _T("wxFileConfig"),
1683 _T(" No line entry for Group '%s'?"),
1684 pGroup->Name().c_str() );
1685 }
1686
1687 SetDirty();
1688
1689 m_aSubgroups.Remove(pGroup);
1690 delete pGroup;
1691
1692 return TRUE;
1693 }
1694
1695 bool wxFileConfigGroup::DeleteEntry(const wxChar *szName)
1696 {
1697 wxFileConfigEntry *pEntry = FindEntry(szName);
1698 wxCHECK( pEntry != NULL, FALSE ); // deleting non existing item?
1699
1700 wxFileConfigLineList *pLine = pEntry->GetLine();
1701 if ( pLine != NULL ) {
1702 // notice that we may do this test inside the previous "if" because the
1703 // last entry's line is surely !NULL
1704 if ( pEntry == m_pLastEntry ) {
1705 // our last entry is being deleted - find the last one which stays
1706 wxASSERT( m_pLine != NULL ); // if we have an entry with !NULL pLine...
1707
1708 // go back until we find another entry or reach the group's line
1709 wxFileConfigEntry *pNewLast = NULL;
1710 size_t n, nEntries = m_aEntries.Count();
1711 wxFileConfigLineList *pl;
1712 for ( pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() ) {
1713 // is it our subgroup?
1714 for ( n = 0; (pNewLast == NULL) && (n < nEntries); n++ ) {
1715 if ( m_aEntries[n]->GetLine() == m_pLine )
1716 pNewLast = m_aEntries[n];
1717 }
1718
1719 if ( pNewLast != NULL ) // found?
1720 break;
1721 }
1722
1723 if ( pl == m_pLine ) {
1724 wxASSERT( !pNewLast ); // how comes it has the same line as we?
1725
1726 // we've reached the group line without finding any subgroups
1727 m_pLastEntry = NULL;
1728 }
1729 else
1730 m_pLastEntry = pNewLast;
1731 }
1732
1733 m_pConfig->LineListRemove(pLine);
1734 }
1735
1736 // we must be written back for the changes to be saved
1737 SetDirty();
1738
1739 m_aEntries.Remove(pEntry);
1740 delete pEntry;
1741
1742 return TRUE;
1743 }
1744
1745 // ----------------------------------------------------------------------------
1746 //
1747 // ----------------------------------------------------------------------------
1748 void wxFileConfigGroup::SetDirty()
1749 {
1750 m_bDirty = TRUE;
1751 if ( Parent() != NULL ) // propagate upwards
1752 Parent()->SetDirty();
1753 }
1754
1755 // ============================================================================
1756 // wxFileConfig::wxFileConfigEntry
1757 // ============================================================================
1758
1759 // ----------------------------------------------------------------------------
1760 // ctor
1761 // ----------------------------------------------------------------------------
1762 wxFileConfigEntry::wxFileConfigEntry(wxFileConfigGroup *pParent,
1763 const wxString& strName,
1764 int nLine)
1765 : m_strName(strName)
1766 {
1767 wxASSERT( !strName.IsEmpty() );
1768
1769 m_pParent = pParent;
1770 m_nLine = nLine;
1771 m_pLine = NULL;
1772
1773 m_bDirty =
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
1808 // if m_bHasValue hadn't been set yet or we'd never write
1809 // empty values to the file
1810
1811 if ( m_bHasValue && strValue == m_strValue )
1812 return;
1813
1814 m_bHasValue = TRUE;
1815 m_strValue = strValue;
1816
1817 if ( bUser )
1818 {
1819 wxString strValFiltered;
1820
1821 if ( Group()->Config()->GetStyle() & wxCONFIG_USE_NO_ESCAPE_CHARACTERS )
1822 {
1823 strValFiltered = strValue;
1824 }
1825 else {
1826 strValFiltered = FilterOutValue(strValue);
1827 }
1828
1829 wxString strLine;
1830 strLine << FilterOutEntryName(m_strName) << wxT('=') << strValFiltered;
1831
1832 if ( m_pLine )
1833 {
1834 // entry was read from the local config file, just modify the line
1835 m_pLine->SetText(strLine);
1836 }
1837 else // this entry didn't exist in the local file
1838 {
1839 // add a new line to the file
1840 wxASSERT( m_nLine == wxNOT_FOUND ); // consistency check
1841
1842 wxFileConfigLineList *line = Group()->GetLastEntryLine();
1843 m_pLine = Group()->Config()->LineListInsert(strLine, line);
1844
1845 Group()->SetLastEntry(this);
1846 }
1847
1848 SetDirty();
1849 }
1850 }
1851
1852 void wxFileConfigEntry::SetDirty()
1853 {
1854 m_bDirty = TRUE;
1855 Group()->SetDirty();
1856 }
1857
1858 // ============================================================================
1859 // global functions
1860 // ============================================================================
1861
1862 // ----------------------------------------------------------------------------
1863 // compare functions for array sorting
1864 // ----------------------------------------------------------------------------
1865
1866 int CompareEntries(wxFileConfigEntry *p1, wxFileConfigEntry *p2)
1867 {
1868 #if wxCONFIG_CASE_SENSITIVE
1869 return wxStrcmp(p1->Name(), p2->Name());
1870 #else
1871 return wxStricmp(p1->Name(), p2->Name());
1872 #endif
1873 }
1874
1875 int CompareGroups(wxFileConfigGroup *p1, wxFileConfigGroup *p2)
1876 {
1877 #if wxCONFIG_CASE_SENSITIVE
1878 return wxStrcmp(p1->Name(), p2->Name());
1879 #else
1880 return wxStricmp(p1->Name(), p2->Name());
1881 #endif
1882 }
1883
1884 // ----------------------------------------------------------------------------
1885 // filter functions
1886 // ----------------------------------------------------------------------------
1887
1888 // undo FilterOutValue
1889 static wxString FilterInValue(const wxString& str)
1890 {
1891 wxString strResult;
1892 strResult.Alloc(str.Len());
1893
1894 bool bQuoted = !str.IsEmpty() && str[0] == '"';
1895
1896 for ( size_t n = bQuoted ? 1 : 0; n < str.Len(); n++ ) {
1897 if ( str[n] == wxT('\\') ) {
1898 switch ( str[++n] ) {
1899 case wxT('n'):
1900 strResult += wxT('\n');
1901 break;
1902
1903 case wxT('r'):
1904 strResult += wxT('\r');
1905 break;
1906
1907 case wxT('t'):
1908 strResult += wxT('\t');
1909 break;
1910
1911 case wxT('\\'):
1912 strResult += wxT('\\');
1913 break;
1914
1915 case wxT('"'):
1916 strResult += wxT('"');
1917 break;
1918 }
1919 }
1920 else {
1921 if ( str[n] != wxT('"') || !bQuoted )
1922 strResult += str[n];
1923 else if ( n != str.Len() - 1 ) {
1924 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1925 n, str.c_str());
1926 }
1927 //else: it's the last quote of a quoted string, ok
1928 }
1929 }
1930
1931 return strResult;
1932 }
1933
1934 // quote the string before writing it to file
1935 static wxString FilterOutValue(const wxString& str)
1936 {
1937 if ( !str )
1938 return str;
1939
1940 wxString strResult;
1941 strResult.Alloc(str.Len());
1942
1943 // quoting is necessary to preserve spaces in the beginning of the string
1944 bool bQuote = wxIsspace(str[0]) || str[0] == wxT('"');
1945
1946 if ( bQuote )
1947 strResult += wxT('"');
1948
1949 wxChar c;
1950 for ( size_t n = 0; n < str.Len(); n++ ) {
1951 switch ( str[n] ) {
1952 case wxT('\n'):
1953 c = wxT('n');
1954 break;
1955
1956 case wxT('\r'):
1957 c = wxT('r');
1958 break;
1959
1960 case wxT('\t'):
1961 c = wxT('t');
1962 break;
1963
1964 case wxT('\\'):
1965 c = wxT('\\');
1966 break;
1967
1968 case wxT('"'):
1969 if ( bQuote ) {
1970 c = wxT('"');
1971 break;
1972 }
1973 //else: fall through
1974
1975 default:
1976 strResult += str[n];
1977 continue; // nothing special to do
1978 }
1979
1980 // we get here only for special characters
1981 strResult << wxT('\\') << c;
1982 }
1983
1984 if ( bQuote )
1985 strResult += wxT('"');
1986
1987 return strResult;
1988 }
1989
1990 // undo FilterOutEntryName
1991 static wxString FilterInEntryName(const wxString& str)
1992 {
1993 wxString strResult;
1994 strResult.Alloc(str.Len());
1995
1996 for ( const wxChar *pc = str.c_str(); *pc != '\0'; pc++ ) {
1997 if ( *pc == wxT('\\') )
1998 pc++;
1999
2000 strResult += *pc;
2001 }
2002
2003 return strResult;
2004 }
2005
2006 // sanitize entry or group name: insert '\\' before any special characters
2007 static wxString FilterOutEntryName(const wxString& str)
2008 {
2009 wxString strResult;
2010 strResult.Alloc(str.Len());
2011
2012 for ( const wxChar *pc = str.c_str(); *pc != wxT('\0'); pc++ ) {
2013 wxChar c = *pc;
2014
2015 // we explicitly allow some of "safe" chars and 8bit ASCII characters
2016 // which will probably never have special meaning
2017 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
2018 // should *not* be quoted
2019 if ( !wxIsalnum(c) && !wxStrchr(wxT("@_/-!.*%"), c) && ((c & 0x80) == 0) )
2020 strResult += wxT('\\');
2021
2022 strResult += c;
2023 }
2024
2025 return strResult;
2026 }
2027
2028 // we can't put ?: in the ctor initializer list because it confuses some
2029 // broken compilers (Borland C++)
2030 static wxString GetAppName(const wxString& appName)
2031 {
2032 if ( !appName && wxTheApp )
2033 return wxTheApp->GetAppName();
2034 else
2035 return appName;
2036 }
2037
2038 #endif // wxUSE_CONFIG
2039
2040
2041 // vi:sts=4:sw=4:et