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