don't try to delete our config file in DeleteAll() if we don't have any; don't assert...
[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 ( !m_strLocalFile.empty() )
1111 {
1112 if ( wxFile::Exists(m_strLocalFile) && wxRemove(m_strLocalFile) == -1 )
1113 {
1114 wxLogSysError(_("can't delete user configuration file '%s'"),
1115 m_strLocalFile.c_str());
1116 return false;
1117 }
1118
1119 m_strLocalFile =
1120 m_strGlobalFile = wxT("");
1121 }
1122
1123 Init();
1124
1125 return true;
1126 }
1127
1128 // ----------------------------------------------------------------------------
1129 // linked list functions
1130 // ----------------------------------------------------------------------------
1131
1132 // append a new line to the end of the list
1133
1134 wxFileConfigLineList *wxFileConfig::LineListAppend(const wxString& str)
1135 {
1136 wxLogTrace( _T("wxFileConfig"),
1137 _T(" ** Adding Line '%s'"),
1138 str.c_str() );
1139 wxLogTrace( _T("wxFileConfig"),
1140 _T(" head: %s"),
1141 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1142 wxLogTrace( _T("wxFileConfig"),
1143 _T(" tail: %s"),
1144 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1145
1146 wxFileConfigLineList *pLine = new wxFileConfigLineList(str);
1147
1148 if ( m_linesTail == NULL )
1149 {
1150 // list is empty
1151 m_linesHead = pLine;
1152 }
1153 else
1154 {
1155 // adjust pointers
1156 m_linesTail->SetNext(pLine);
1157 pLine->SetPrev(m_linesTail);
1158 }
1159
1160 m_linesTail = pLine;
1161
1162 wxLogTrace( _T("wxFileConfig"),
1163 _T(" head: %s"),
1164 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1165 wxLogTrace( _T("wxFileConfig"),
1166 _T(" tail: %s"),
1167 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1168
1169 return m_linesTail;
1170 }
1171
1172 // insert a new line after the given one or in the very beginning if !pLine
1173 wxFileConfigLineList *wxFileConfig::LineListInsert(const wxString& str,
1174 wxFileConfigLineList *pLine)
1175 {
1176 wxLogTrace( _T("wxFileConfig"),
1177 _T(" ** Inserting Line '%s' after '%s'"),
1178 str.c_str(),
1179 ((pLine) ? pLine->Text().c_str() : wxEmptyString) );
1180 wxLogTrace( _T("wxFileConfig"),
1181 _T(" head: %s"),
1182 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1183 wxLogTrace( _T("wxFileConfig"),
1184 _T(" tail: %s"),
1185 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1186
1187 if ( pLine == m_linesTail )
1188 return LineListAppend(str);
1189
1190 wxFileConfigLineList *pNewLine = new wxFileConfigLineList(str);
1191 if ( pLine == NULL )
1192 {
1193 // prepend to the list
1194 pNewLine->SetNext(m_linesHead);
1195 m_linesHead->SetPrev(pNewLine);
1196 m_linesHead = pNewLine;
1197 }
1198 else
1199 {
1200 // insert before pLine
1201 wxFileConfigLineList *pNext = pLine->Next();
1202 pNewLine->SetNext(pNext);
1203 pNewLine->SetPrev(pLine);
1204 pNext->SetPrev(pNewLine);
1205 pLine->SetNext(pNewLine);
1206 }
1207
1208 wxLogTrace( _T("wxFileConfig"),
1209 _T(" head: %s"),
1210 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1211 wxLogTrace( _T("wxFileConfig"),
1212 _T(" tail: %s"),
1213 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1214
1215 return pNewLine;
1216 }
1217
1218 void wxFileConfig::LineListRemove(wxFileConfigLineList *pLine)
1219 {
1220 wxLogTrace( _T("wxFileConfig"),
1221 _T(" ** Removing Line '%s'"),
1222 pLine->Text().c_str() );
1223 wxLogTrace( _T("wxFileConfig"),
1224 _T(" head: %s"),
1225 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1226 wxLogTrace( _T("wxFileConfig"),
1227 _T(" tail: %s"),
1228 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1229
1230 wxFileConfigLineList *pPrev = pLine->Prev(),
1231 *pNext = pLine->Next();
1232
1233 // first entry?
1234
1235 if ( pPrev == NULL )
1236 m_linesHead = pNext;
1237 else
1238 pPrev->SetNext(pNext);
1239
1240 // last entry?
1241
1242 if ( pNext == NULL )
1243 m_linesTail = pPrev;
1244 else
1245 pNext->SetPrev(pPrev);
1246
1247 wxLogTrace( _T("wxFileConfig"),
1248 _T(" head: %s"),
1249 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1250 wxLogTrace( _T("wxFileConfig"),
1251 _T(" tail: %s"),
1252 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1253
1254 delete pLine;
1255 }
1256
1257 bool wxFileConfig::LineListIsEmpty()
1258 {
1259 return m_linesHead == NULL;
1260 }
1261
1262 // ============================================================================
1263 // wxFileConfig::wxFileConfigGroup
1264 // ============================================================================
1265
1266 // ----------------------------------------------------------------------------
1267 // ctor/dtor
1268 // ----------------------------------------------------------------------------
1269
1270 // ctor
1271 wxFileConfigGroup::wxFileConfigGroup(wxFileConfigGroup *pParent,
1272 const wxString& strName,
1273 wxFileConfig *pConfig)
1274 : m_aEntries(CompareEntries),
1275 m_aSubgroups(CompareGroups),
1276 m_strName(strName)
1277 {
1278 m_pConfig = pConfig;
1279 m_pParent = pParent;
1280 m_pLine = NULL;
1281
1282 m_pLastEntry = NULL;
1283 m_pLastGroup = NULL;
1284 }
1285
1286 // dtor deletes all children
1287 wxFileConfigGroup::~wxFileConfigGroup()
1288 {
1289 // entries
1290 size_t n, nCount = m_aEntries.Count();
1291 for ( n = 0; n < nCount; n++ )
1292 delete m_aEntries[n];
1293
1294 // subgroups
1295 nCount = m_aSubgroups.Count();
1296 for ( n = 0; n < nCount; n++ )
1297 delete m_aSubgroups[n];
1298 }
1299
1300 // ----------------------------------------------------------------------------
1301 // line
1302 // ----------------------------------------------------------------------------
1303
1304 void wxFileConfigGroup::SetLine(wxFileConfigLineList *pLine)
1305 {
1306 wxASSERT( m_pLine == 0 ); // shouldn't be called twice
1307 m_pLine = pLine;
1308 }
1309
1310 /*
1311 This is a bit complicated, so let me explain it in details. All lines that
1312 were read from the local file (the only one we will ever modify) are stored
1313 in a (doubly) linked list. Our problem is to know at which position in this
1314 list should we insert the new entries/subgroups. To solve it we keep three
1315 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
1316
1317 m_pLine points to the line containing "[group_name]"
1318 m_pLastEntry points to the last entry of this group in the local file.
1319 m_pLastGroup subgroup
1320
1321 Initially, they're NULL all three. When the group (an entry/subgroup) is read
1322 from the local file, the corresponding variable is set. However, if the group
1323 was read from the global file and then modified or created by the application
1324 these variables are still NULL and we need to create the corresponding lines.
1325 See the following functions (and comments preceding them) for the details of
1326 how we do it.
1327
1328 Also, when our last entry/group are deleted we need to find the new last
1329 element - the code in DeleteEntry/Subgroup does this by backtracking the list
1330 of lines until it either founds an entry/subgroup (and this is the new last
1331 element) or the m_pLine of the group, in which case there are no more entries
1332 (or subgroups) left and m_pLast<element> becomes NULL.
1333
1334 NB: This last problem could be avoided for entries if we added new entries
1335 immediately after m_pLine, but in this case the entries would appear
1336 backwards in the config file (OTOH, it's not that important) and as we
1337 would still need to do it for the subgroups the code wouldn't have been
1338 significantly less complicated.
1339 */
1340
1341 // Return the line which contains "[our name]". If we're still not in the list,
1342 // add our line to it immediately after the last line of our parent group if we
1343 // have it or in the very beginning if we're the root group.
1344 wxFileConfigLineList *wxFileConfigGroup::GetGroupLine()
1345 {
1346 wxLogTrace( _T("wxFileConfig"),
1347 _T(" GetGroupLine() for Group '%s'"),
1348 Name().c_str() );
1349
1350 if ( !m_pLine )
1351 {
1352 wxLogTrace( _T("wxFileConfig"),
1353 _T(" Getting Line item pointer") );
1354
1355 wxFileConfigGroup *pParent = Parent();
1356
1357 // this group wasn't present in local config file, add it now
1358 if ( pParent )
1359 {
1360 wxLogTrace( _T("wxFileConfig"),
1361 _T(" checking parent '%s'"),
1362 pParent->Name().c_str() );
1363
1364 wxString strFullName;
1365
1366 // add 1 to the name because we don't want to start with '/'
1367 strFullName << wxT("[")
1368 << FilterOutEntryName(GetFullName().c_str() + 1)
1369 << wxT("]");
1370 m_pLine = m_pConfig->LineListInsert(strFullName,
1371 pParent->GetLastGroupLine());
1372 pParent->SetLastGroup(this); // we're surely after all the others
1373 }
1374 //else: this is the root group and so we return NULL because we don't
1375 // have any group line
1376 }
1377
1378 return m_pLine;
1379 }
1380
1381 // Return the last line belonging to the subgroups of this group (after which
1382 // we can add a new subgroup), if we don't have any subgroups or entries our
1383 // last line is the group line (m_pLine) itself.
1384 wxFileConfigLineList *wxFileConfigGroup::GetLastGroupLine()
1385 {
1386 // if we have any subgroups, our last line is the last line of the last
1387 // subgroup
1388 if ( m_pLastGroup )
1389 {
1390 wxFileConfigLineList *pLine = m_pLastGroup->GetLastGroupLine();
1391
1392 wxASSERT_MSG( pLine, _T("last group must have !NULL associated line") );
1393
1394 return pLine;
1395 }
1396
1397 // no subgroups, so the last line is the line of thelast entry (if any)
1398 return GetLastEntryLine();
1399 }
1400
1401 // return the last line belonging to the entries of this group (after which
1402 // we can add a new entry), if we don't have any entries we will add the new
1403 // one immediately after the group line itself.
1404 wxFileConfigLineList *wxFileConfigGroup::GetLastEntryLine()
1405 {
1406 wxLogTrace( _T("wxFileConfig"),
1407 _T(" GetLastEntryLine() for Group '%s'"),
1408 Name().c_str() );
1409
1410 if ( m_pLastEntry )
1411 {
1412 wxFileConfigLineList *pLine = m_pLastEntry->GetLine();
1413
1414 wxASSERT_MSG( pLine, _T("last entry must have !NULL associated line") );
1415
1416 return pLine;
1417 }
1418
1419 // no entries: insert after the group header, if any
1420 return GetGroupLine();
1421 }
1422
1423 void wxFileConfigGroup::SetLastEntry(wxFileConfigEntry *pEntry)
1424 {
1425 m_pLastEntry = pEntry;
1426
1427 if ( !m_pLine )
1428 {
1429 // the only situation in which a group without its own line can have
1430 // an entry is when the first entry is added to the initially empty
1431 // root pseudo-group
1432 wxASSERT_MSG( !m_pParent, _T("unexpected for non root group") );
1433
1434 // let the group know that it does have a line in the file now
1435 m_pLine = pEntry->GetLine();
1436 }
1437 }
1438
1439 // ----------------------------------------------------------------------------
1440 // group name
1441 // ----------------------------------------------------------------------------
1442
1443 void wxFileConfigGroup::Rename(const wxString& newName)
1444 {
1445 wxCHECK_RET( m_pParent, _T("the root group can't be renamed") );
1446
1447 m_strName = newName;
1448
1449 // +1: no leading '/'
1450 wxString strFullName;
1451 strFullName << wxT("[") << (GetFullName().c_str() + 1) << wxT("]");
1452
1453 wxFileConfigLineList *line = GetGroupLine();
1454 wxCHECK_RET( line, _T("a non root group must have a corresponding line!") );
1455
1456 line->SetText(strFullName);
1457 }
1458
1459 wxString wxFileConfigGroup::GetFullName() const
1460 {
1461 if ( Parent() )
1462 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR + Name();
1463 else
1464 return wxT("");
1465 }
1466
1467 // ----------------------------------------------------------------------------
1468 // find an item
1469 // ----------------------------------------------------------------------------
1470
1471 // use binary search because the array is sorted
1472 wxFileConfigEntry *
1473 wxFileConfigGroup::FindEntry(const wxChar *szName) const
1474 {
1475 size_t i,
1476 lo = 0,
1477 hi = m_aEntries.Count();
1478 int res;
1479 wxFileConfigEntry *pEntry;
1480
1481 while ( lo < hi ) {
1482 i = (lo + hi)/2;
1483 pEntry = m_aEntries[i];
1484
1485 #if wxCONFIG_CASE_SENSITIVE
1486 res = wxStrcmp(pEntry->Name(), szName);
1487 #else
1488 res = wxStricmp(pEntry->Name(), szName);
1489 #endif
1490
1491 if ( res > 0 )
1492 hi = i;
1493 else if ( res < 0 )
1494 lo = i + 1;
1495 else
1496 return pEntry;
1497 }
1498
1499 return NULL;
1500 }
1501
1502 wxFileConfigGroup *
1503 wxFileConfigGroup::FindSubgroup(const wxChar *szName) const
1504 {
1505 size_t i,
1506 lo = 0,
1507 hi = m_aSubgroups.Count();
1508 int res;
1509 wxFileConfigGroup *pGroup;
1510
1511 while ( lo < hi ) {
1512 i = (lo + hi)/2;
1513 pGroup = m_aSubgroups[i];
1514
1515 #if wxCONFIG_CASE_SENSITIVE
1516 res = wxStrcmp(pGroup->Name(), szName);
1517 #else
1518 res = wxStricmp(pGroup->Name(), szName);
1519 #endif
1520
1521 if ( res > 0 )
1522 hi = i;
1523 else if ( res < 0 )
1524 lo = i + 1;
1525 else
1526 return pGroup;
1527 }
1528
1529 return NULL;
1530 }
1531
1532 // ----------------------------------------------------------------------------
1533 // create a new item
1534 // ----------------------------------------------------------------------------
1535
1536 // create a new entry and add it to the current group
1537 wxFileConfigEntry *wxFileConfigGroup::AddEntry(const wxString& strName, int nLine)
1538 {
1539 wxASSERT( FindEntry(strName) == 0 );
1540
1541 wxFileConfigEntry *pEntry = new wxFileConfigEntry(this, strName, nLine);
1542
1543 m_aEntries.Add(pEntry);
1544 return pEntry;
1545 }
1546
1547 // create a new group and add it to the current group
1548 wxFileConfigGroup *wxFileConfigGroup::AddSubgroup(const wxString& strName)
1549 {
1550 wxASSERT( FindSubgroup(strName) == 0 );
1551
1552 wxFileConfigGroup *pGroup = new wxFileConfigGroup(this, strName, m_pConfig);
1553
1554 m_aSubgroups.Add(pGroup);
1555 return pGroup;
1556 }
1557
1558 // ----------------------------------------------------------------------------
1559 // delete an item
1560 // ----------------------------------------------------------------------------
1561
1562 /*
1563 The delete operations are _very_ slow if we delete the last item of this
1564 group (see comments before GetXXXLineXXX functions for more details),
1565 so it's much better to start with the first entry/group if we want to
1566 delete several of them.
1567 */
1568
1569 bool wxFileConfigGroup::DeleteSubgroupByName(const wxChar *szName)
1570 {
1571 wxFileConfigGroup * const pGroup = FindSubgroup(szName);
1572
1573 return pGroup ? DeleteSubgroup(pGroup) : false;
1574 }
1575
1576 // Delete the subgroup and remove all references to it from
1577 // other data structures.
1578 bool wxFileConfigGroup::DeleteSubgroup(wxFileConfigGroup *pGroup)
1579 {
1580 wxCHECK_MSG( pGroup, false, _T("deleting non existing group?") );
1581
1582 wxLogTrace( _T("wxFileConfig"),
1583 _T("Deleting group '%s' from '%s'"),
1584 pGroup->Name().c_str(),
1585 Name().c_str() );
1586
1587 wxLogTrace( _T("wxFileConfig"),
1588 _T(" (m_pLine) = prev: %p, this %p, next %p"),
1589 ((m_pLine) ? m_pLine->Prev() : 0),
1590 m_pLine,
1591 ((m_pLine) ? m_pLine->Next() : 0) );
1592 wxLogTrace( _T("wxFileConfig"),
1593 _T(" text: '%s'"),
1594 ((m_pLine) ? m_pLine->Text().c_str() : wxEmptyString) );
1595
1596 // delete all entries
1597 size_t nCount = pGroup->m_aEntries.Count();
1598
1599 wxLogTrace(_T("wxFileConfig"),
1600 _T("Removing %lu Entries"),
1601 (unsigned long)nCount );
1602
1603 for ( size_t nEntry = 0; nEntry < nCount; nEntry++ )
1604 {
1605 wxFileConfigLineList *pLine = pGroup->m_aEntries[nEntry]->GetLine();
1606
1607 if ( pLine != 0 )
1608 {
1609 wxLogTrace( _T("wxFileConfig"),
1610 _T(" '%s'"),
1611 pLine->Text().c_str() );
1612 m_pConfig->LineListRemove(pLine);
1613 }
1614 }
1615
1616 // and subgroups of this subgroup
1617
1618 nCount = pGroup->m_aSubgroups.Count();
1619
1620 wxLogTrace( _T("wxFileConfig"),
1621 _T("Removing %lu SubGroups"),
1622 (unsigned long)nCount );
1623
1624 for ( size_t nGroup = 0; nGroup < nCount; nGroup++ )
1625 {
1626 pGroup->DeleteSubgroup(pGroup->m_aSubgroups[0]);
1627 }
1628
1629 // finally the group itself
1630
1631 wxFileConfigLineList *pLine = pGroup->m_pLine;
1632
1633 if ( pLine != 0 )
1634 {
1635 wxLogTrace( _T("wxFileConfig"),
1636 _T(" Removing line entry for Group '%s' : '%s'"),
1637 pGroup->Name().c_str(),
1638 pLine->Text().c_str() );
1639 wxLogTrace( _T("wxFileConfig"),
1640 _T(" Removing from Group '%s' : '%s'"),
1641 Name().c_str(),
1642 ((m_pLine) ? m_pLine->Text().c_str() : wxEmptyString) );
1643
1644 // notice that we may do this test inside the previous "if"
1645 // because the last entry's line is surely !NULL
1646
1647 if ( pGroup == m_pLastGroup )
1648 {
1649 wxLogTrace( _T("wxFileConfig"),
1650 _T(" ------- Removing last group -------") );
1651
1652 // our last entry is being deleted, so find the last one which stays.
1653 // go back until we find a subgroup or reach the group's line, unless
1654 // we are the root group, which we'll notice shortly.
1655
1656 wxFileConfigGroup *pNewLast = 0;
1657 size_t nSubgroups = m_aSubgroups.Count();
1658 wxFileConfigLineList *pl;
1659
1660 for ( pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() )
1661 {
1662 // is it our subgroup?
1663
1664 for ( size_t n = 0; (pNewLast == 0) && (n < nSubgroups); n++ )
1665 {
1666 // do _not_ call GetGroupLine! we don't want to add it to the local
1667 // file if it's not already there
1668
1669 if ( m_aSubgroups[n]->m_pLine == m_pLine )
1670 pNewLast = m_aSubgroups[n];
1671 }
1672
1673 if ( pNewLast != 0 ) // found?
1674 break;
1675 }
1676
1677 if ( pl == m_pLine || m_pParent == 0 )
1678 {
1679 wxLogTrace( _T("wxFileConfig"),
1680 _T(" ------- No previous group found -------") );
1681
1682 wxASSERT_MSG( !pNewLast || m_pLine == 0,
1683 _T("how comes it has the same line as we?") );
1684
1685 // we've reached the group line without finding any subgroups,
1686 // or realised we removed the last group from the root.
1687
1688 m_pLastGroup = 0;
1689 }
1690 else
1691 {
1692 wxLogTrace( _T("wxFileConfig"),
1693 _T(" ------- Last Group set to '%s' -------"),
1694 pNewLast->Name().c_str() );
1695
1696 m_pLastGroup = pNewLast;
1697 }
1698 }
1699
1700 m_pConfig->LineListRemove(pLine);
1701 }
1702 else
1703 {
1704 wxLogTrace( _T("wxFileConfig"),
1705 _T(" No line entry for Group '%s'?"),
1706 pGroup->Name().c_str() );
1707 }
1708
1709 m_aSubgroups.Remove(pGroup);
1710 delete pGroup;
1711
1712 return true;
1713 }
1714
1715 bool wxFileConfigGroup::DeleteEntry(const wxChar *szName)
1716 {
1717 wxFileConfigEntry *pEntry = FindEntry(szName);
1718 if ( !pEntry )
1719 {
1720 // entry doesn't exist, nothing to do
1721 return false;
1722 }
1723
1724 wxFileConfigLineList *pLine = pEntry->GetLine();
1725 if ( pLine != NULL ) {
1726 // notice that we may do this test inside the previous "if" because the
1727 // last entry's line is surely !NULL
1728 if ( pEntry == m_pLastEntry ) {
1729 // our last entry is being deleted - find the last one which stays
1730 wxASSERT( m_pLine != NULL ); // if we have an entry with !NULL pLine...
1731
1732 // go back until we find another entry or reach the group's line
1733 wxFileConfigEntry *pNewLast = NULL;
1734 size_t n, nEntries = m_aEntries.Count();
1735 wxFileConfigLineList *pl;
1736 for ( pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() ) {
1737 // is it our subgroup?
1738 for ( n = 0; (pNewLast == NULL) && (n < nEntries); n++ ) {
1739 if ( m_aEntries[n]->GetLine() == m_pLine )
1740 pNewLast = m_aEntries[n];
1741 }
1742
1743 if ( pNewLast != NULL ) // found?
1744 break;
1745 }
1746
1747 if ( pl == m_pLine ) {
1748 wxASSERT( !pNewLast ); // how comes it has the same line as we?
1749
1750 // we've reached the group line without finding any subgroups
1751 m_pLastEntry = NULL;
1752 }
1753 else
1754 m_pLastEntry = pNewLast;
1755 }
1756
1757 m_pConfig->LineListRemove(pLine);
1758 }
1759
1760 m_aEntries.Remove(pEntry);
1761 delete pEntry;
1762
1763 return true;
1764 }
1765
1766 // ============================================================================
1767 // wxFileConfig::wxFileConfigEntry
1768 // ============================================================================
1769
1770 // ----------------------------------------------------------------------------
1771 // ctor
1772 // ----------------------------------------------------------------------------
1773 wxFileConfigEntry::wxFileConfigEntry(wxFileConfigGroup *pParent,
1774 const wxString& strName,
1775 int nLine)
1776 : m_strName(strName)
1777 {
1778 wxASSERT( !strName.IsEmpty() );
1779
1780 m_pParent = pParent;
1781 m_nLine = nLine;
1782 m_pLine = NULL;
1783
1784 m_bHasValue = false;
1785
1786 m_bImmutable = strName[0] == wxCONFIG_IMMUTABLE_PREFIX;
1787 if ( m_bImmutable )
1788 m_strName.erase(0, 1); // remove first character
1789 }
1790
1791 // ----------------------------------------------------------------------------
1792 // set value
1793 // ----------------------------------------------------------------------------
1794
1795 void wxFileConfigEntry::SetLine(wxFileConfigLineList *pLine)
1796 {
1797 if ( m_pLine != NULL ) {
1798 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1799 Name().c_str(), m_pParent->GetFullName().c_str());
1800 }
1801
1802 m_pLine = pLine;
1803 Group()->SetLastEntry(this);
1804 }
1805
1806 // second parameter is false if we read the value from file and prevents the
1807 // entry from being marked as 'dirty'
1808 void wxFileConfigEntry::SetValue(const wxString& strValue, bool bUser)
1809 {
1810 if ( bUser && IsImmutable() )
1811 {
1812 wxLogWarning( _("attempt to change immutable key '%s' ignored."),
1813 Name().c_str());
1814 return;
1815 }
1816
1817 // do nothing if it's the same value: but don't test for it if m_bHasValue
1818 // hadn't been set yet or we'd never write empty values to the file
1819 if ( m_bHasValue && strValue == m_strValue )
1820 return;
1821
1822 m_bHasValue = true;
1823 m_strValue = strValue;
1824
1825 if ( bUser )
1826 {
1827 wxString strValFiltered;
1828
1829 if ( Group()->Config()->GetStyle() & wxCONFIG_USE_NO_ESCAPE_CHARACTERS )
1830 {
1831 strValFiltered = strValue;
1832 }
1833 else {
1834 strValFiltered = FilterOutValue(strValue);
1835 }
1836
1837 wxString strLine;
1838 strLine << FilterOutEntryName(m_strName) << wxT('=') << strValFiltered;
1839
1840 if ( m_pLine )
1841 {
1842 // entry was read from the local config file, just modify the line
1843 m_pLine->SetText(strLine);
1844 }
1845 else // this entry didn't exist in the local file
1846 {
1847 // add a new line to the file
1848 wxFileConfigLineList *line = Group()->GetLastEntryLine();
1849 m_pLine = Group()->Config()->LineListInsert(strLine, line);
1850
1851 Group()->SetLastEntry(this);
1852 }
1853 }
1854 }
1855
1856 // ============================================================================
1857 // global functions
1858 // ============================================================================
1859
1860 // ----------------------------------------------------------------------------
1861 // compare functions for array sorting
1862 // ----------------------------------------------------------------------------
1863
1864 int CompareEntries(wxFileConfigEntry *p1, wxFileConfigEntry *p2)
1865 {
1866 #if wxCONFIG_CASE_SENSITIVE
1867 return wxStrcmp(p1->Name(), p2->Name());
1868 #else
1869 return wxStricmp(p1->Name(), p2->Name());
1870 #endif
1871 }
1872
1873 int CompareGroups(wxFileConfigGroup *p1, wxFileConfigGroup *p2)
1874 {
1875 #if wxCONFIG_CASE_SENSITIVE
1876 return wxStrcmp(p1->Name(), p2->Name());
1877 #else
1878 return wxStricmp(p1->Name(), p2->Name());
1879 #endif
1880 }
1881
1882 // ----------------------------------------------------------------------------
1883 // filter functions
1884 // ----------------------------------------------------------------------------
1885
1886 // undo FilterOutValue
1887 static wxString FilterInValue(const wxString& str)
1888 {
1889 wxString strResult;
1890 strResult.Alloc(str.Len());
1891
1892 bool bQuoted = !str.IsEmpty() && str[0] == '"';
1893
1894 for ( size_t n = bQuoted ? 1 : 0; n < str.Len(); n++ ) {
1895 if ( str[n] == wxT('\\') ) {
1896 switch ( str[++n] ) {
1897 case wxT('n'):
1898 strResult += wxT('\n');
1899 break;
1900
1901 case wxT('r'):
1902 strResult += wxT('\r');
1903 break;
1904
1905 case wxT('t'):
1906 strResult += wxT('\t');
1907 break;
1908
1909 case wxT('\\'):
1910 strResult += wxT('\\');
1911 break;
1912
1913 case wxT('"'):
1914 strResult += wxT('"');
1915 break;
1916 }
1917 }
1918 else {
1919 if ( str[n] != wxT('"') || !bQuoted )
1920 strResult += str[n];
1921 else if ( n != str.Len() - 1 ) {
1922 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1923 n, str.c_str());
1924 }
1925 //else: it's the last quote of a quoted string, ok
1926 }
1927 }
1928
1929 return strResult;
1930 }
1931
1932 // quote the string before writing it to file
1933 static wxString FilterOutValue(const wxString& str)
1934 {
1935 if ( !str )
1936 return str;
1937
1938 wxString strResult;
1939 strResult.Alloc(str.Len());
1940
1941 // quoting is necessary to preserve spaces in the beginning of the string
1942 bool bQuote = wxIsspace(str[0]) || str[0] == wxT('"');
1943
1944 if ( bQuote )
1945 strResult += wxT('"');
1946
1947 wxChar c;
1948 for ( size_t n = 0; n < str.Len(); n++ ) {
1949 switch ( str[n] ) {
1950 case wxT('\n'):
1951 c = wxT('n');
1952 break;
1953
1954 case wxT('\r'):
1955 c = wxT('r');
1956 break;
1957
1958 case wxT('\t'):
1959 c = wxT('t');
1960 break;
1961
1962 case wxT('\\'):
1963 c = wxT('\\');
1964 break;
1965
1966 case wxT('"'):
1967 if ( bQuote ) {
1968 c = wxT('"');
1969 break;
1970 }
1971 //else: fall through
1972
1973 default:
1974 strResult += str[n];
1975 continue; // nothing special to do
1976 }
1977
1978 // we get here only for special characters
1979 strResult << wxT('\\') << c;
1980 }
1981
1982 if ( bQuote )
1983 strResult += wxT('"');
1984
1985 return strResult;
1986 }
1987
1988 // undo FilterOutEntryName
1989 static wxString FilterInEntryName(const wxString& str)
1990 {
1991 wxString strResult;
1992 strResult.Alloc(str.Len());
1993
1994 for ( const wxChar *pc = str.c_str(); *pc != '\0'; pc++ ) {
1995 if ( *pc == wxT('\\') )
1996 pc++;
1997
1998 strResult += *pc;
1999 }
2000
2001 return strResult;
2002 }
2003
2004 // sanitize entry or group name: insert '\\' before any special characters
2005 static wxString FilterOutEntryName(const wxString& str)
2006 {
2007 wxString strResult;
2008 strResult.Alloc(str.Len());
2009
2010 for ( const wxChar *pc = str.c_str(); *pc != wxT('\0'); pc++ ) {
2011 const wxChar c = *pc;
2012
2013 // we explicitly allow some of "safe" chars and 8bit ASCII characters
2014 // which will probably never have special meaning and with which we can't
2015 // use isalnum() anyhow (in ASCII built, in Unicode it's just fine)
2016 //
2017 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
2018 // should *not* be quoted
2019 if (
2020 #if !wxUSE_UNICODE
2021 ((unsigned char)c < 127) &&
2022 #endif // ANSI
2023 !wxIsalnum(c) && !wxStrchr(wxT("@_/-!.*%"), c) )
2024 {
2025 strResult += wxT('\\');
2026 }
2027
2028 strResult += c;
2029 }
2030
2031 return strResult;
2032 }
2033
2034 // we can't put ?: in the ctor initializer list because it confuses some
2035 // broken compilers (Borland C++)
2036 static wxString GetAppName(const wxString& appName)
2037 {
2038 if ( !appName && wxTheApp )
2039 return wxTheApp->GetAppName();
2040 else
2041 return appName;
2042 }
2043
2044 #endif // wxUSE_CONFIG
2045
2046
2047 // vi:sts=4:sw=4:et