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