wxFileConfig in Unicode fixes
[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 ( fileGlobal.Open(m_conv/*ignored in ANSI build*/) )
393 {
394 Parse(fileGlobal, FALSE /* global */);
395 SetRootPath();
396 }
397 else
398 {
399 wxLogWarning(_("can't open global configuration file '%s'."), m_strGlobalFile.c_str());
400 }
401 }
402
403 // parse the local file
404 if ( !m_strLocalFile.IsEmpty() && wxFile::Exists(m_strLocalFile) )
405 {
406 wxTextFile fileLocal(m_strLocalFile);
407 if ( fileLocal.Open(m_conv/*ignored in ANSI build*/) )
408 {
409 Parse(fileLocal, TRUE /* local */);
410 SetRootPath();
411 }
412 else
413 {
414 wxLogWarning(_("can't open user configuration file '%s'."), m_strLocalFile.c_str() );
415 }
416 }
417 }
418
419 // constructor supports creation of wxFileConfig objects of any type
420 wxFileConfig::wxFileConfig(const wxString& appName, const wxString& vendorName,
421 const wxString& strLocal, const wxString& strGlobal,
422 long style, wxMBConv& conv)
423 : wxConfigBase(::GetAppName(appName), vendorName,
424 strLocal, strGlobal,
425 style),
426 m_strLocalFile(strLocal), m_strGlobalFile(strGlobal),
427 m_conv(conv)
428 {
429 // Make up names for files if empty
430 if ( m_strLocalFile.IsEmpty() && (style & wxCONFIG_USE_LOCAL_FILE) )
431 m_strLocalFile = GetLocalFileName(GetAppName());
432
433 if ( m_strGlobalFile.IsEmpty() && (style & wxCONFIG_USE_GLOBAL_FILE) )
434 m_strGlobalFile = GetGlobalFileName(GetAppName());
435
436 // Check if styles are not supplied, but filenames are, in which case
437 // add the correct styles.
438 if ( !m_strLocalFile.IsEmpty() )
439 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE);
440
441 if ( !m_strGlobalFile.IsEmpty() )
442 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE);
443
444 // if the path is not absolute, prepend the standard directory to it
445 // UNLESS wxCONFIG_USE_RELATIVE_PATH style is set
446 if ( !(style & wxCONFIG_USE_RELATIVE_PATH) )
447 {
448 if ( !m_strLocalFile.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile) )
449 {
450 wxString strLocal = m_strLocalFile;
451 m_strLocalFile = GetLocalDir();
452 m_strLocalFile << strLocal;
453 }
454
455 if ( !m_strGlobalFile.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile) )
456 {
457 wxString strGlobal = m_strGlobalFile;
458 m_strGlobalFile = GetGlobalDir();
459 m_strGlobalFile << strGlobal;
460 }
461 }
462
463 SetUmask(-1);
464
465 Init();
466 }
467
468 #if wxUSE_STREAMS
469
470 wxFileConfig::wxFileConfig(wxInputStream &inStream, wxMBConv& conv)
471 : m_conv(conv)
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 ( !file.Write(line, m_conv) )
959 {
960 wxLogError(_("can't write user configuration file."));
961 return FALSE;
962 }
963 }
964
965 bool ret = file.Commit();
966
967 #if defined(__WXMAC__)
968 if ( ret )
969 {
970 FSSpec spec ;
971
972 wxMacFilename2FSSpec( m_strLocalFile , &spec ) ;
973 FInfo finfo ;
974 if ( FSpGetFInfo( &spec , &finfo ) == noErr )
975 {
976 finfo.fdType = 'TEXT' ;
977 finfo.fdCreator = 'ttxt' ;
978 FSpSetFInfo( &spec , &finfo ) ;
979 }
980 }
981 #endif // __WXMAC__
982
983 #ifdef __UNIX__
984 // restore the old umask if we changed it
985 if ( m_umask != -1 )
986 {
987 (void)umask(umaskOld);
988 }
989 #endif // __UNIX__
990
991 return ret;
992 }
993
994 // ----------------------------------------------------------------------------
995 // renaming groups/entries
996 // ----------------------------------------------------------------------------
997
998 bool wxFileConfig::RenameEntry(const wxString& oldName,
999 const wxString& newName)
1000 {
1001 // check that the entry exists
1002 wxFileConfigEntry *oldEntry = m_pCurrentGroup->FindEntry(oldName);
1003 if ( !oldEntry )
1004 return FALSE;
1005
1006 // check that the new entry doesn't already exist
1007 if ( m_pCurrentGroup->FindEntry(newName) )
1008 return FALSE;
1009
1010 // delete the old entry, create the new one
1011 wxString value = oldEntry->Value();
1012 if ( !m_pCurrentGroup->DeleteEntry(oldName) )
1013 return FALSE;
1014
1015 wxFileConfigEntry *newEntry = m_pCurrentGroup->AddEntry(newName);
1016 newEntry->SetValue(value);
1017
1018 return TRUE;
1019 }
1020
1021 bool wxFileConfig::RenameGroup(const wxString& oldName,
1022 const wxString& newName)
1023 {
1024 // check that the group exists
1025 wxFileConfigGroup *group = m_pCurrentGroup->FindSubgroup(oldName);
1026 if ( !group )
1027 return FALSE;
1028
1029 // check that the new group doesn't already exist
1030 if ( m_pCurrentGroup->FindSubgroup(newName) )
1031 return FALSE;
1032
1033 group->Rename(newName);
1034
1035 return TRUE;
1036 }
1037
1038 // ----------------------------------------------------------------------------
1039 // delete groups/entries
1040 // ----------------------------------------------------------------------------
1041
1042 bool wxFileConfig::DeleteEntry(const wxString& key, bool bGroupIfEmptyAlso)
1043 {
1044 wxConfigPathChanger path(this, key);
1045
1046 if ( !m_pCurrentGroup->DeleteEntry(path.Name()) )
1047 return FALSE;
1048
1049 if ( bGroupIfEmptyAlso && m_pCurrentGroup->IsEmpty() ) {
1050 if ( m_pCurrentGroup != m_pRootGroup ) {
1051 wxFileConfigGroup *pGroup = m_pCurrentGroup;
1052 SetPath(wxT("..")); // changes m_pCurrentGroup!
1053 m_pCurrentGroup->DeleteSubgroupByName(pGroup->Name());
1054 }
1055 //else: never delete the root group
1056 }
1057
1058 return TRUE;
1059 }
1060
1061 bool wxFileConfig::DeleteGroup(const wxString& key)
1062 {
1063 wxConfigPathChanger path(this, key);
1064
1065 return m_pCurrentGroup->DeleteSubgroupByName(path.Name());
1066 }
1067
1068 bool wxFileConfig::DeleteAll()
1069 {
1070 CleanUp();
1071
1072 if ( wxRemove(m_strLocalFile) == -1 )
1073 wxLogSysError(_("can't delete user configuration file '%s'"), m_strLocalFile.c_str());
1074
1075 m_strLocalFile = m_strGlobalFile = wxT("");
1076 Init();
1077
1078 return TRUE;
1079 }
1080
1081 // ----------------------------------------------------------------------------
1082 // linked list functions
1083 // ----------------------------------------------------------------------------
1084
1085 // append a new line to the end of the list
1086
1087 wxFileConfigLineList *wxFileConfig::LineListAppend(const wxString& str)
1088 {
1089 wxLogTrace( _T("wxFileConfig"),
1090 _T(" ** Adding Line '%s'"),
1091 str.c_str() );
1092 wxLogTrace( _T("wxFileConfig"),
1093 _T(" head: %s"),
1094 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1095 wxLogTrace( _T("wxFileConfig"),
1096 _T(" tail: %s"),
1097 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1098
1099 wxFileConfigLineList *pLine = new wxFileConfigLineList(str);
1100
1101 if ( m_linesTail == NULL )
1102 {
1103 // list is empty
1104 m_linesHead = pLine;
1105 }
1106 else
1107 {
1108 // adjust pointers
1109 m_linesTail->SetNext(pLine);
1110 pLine->SetPrev(m_linesTail);
1111 }
1112
1113 m_linesTail = pLine;
1114
1115 wxLogTrace( _T("wxFileConfig"),
1116 _T(" head: %s"),
1117 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1118 wxLogTrace( _T("wxFileConfig"),
1119 _T(" tail: %s"),
1120 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1121
1122 return m_linesTail;
1123 }
1124
1125 // insert a new line after the given one or in the very beginning if !pLine
1126
1127 wxFileConfigLineList *wxFileConfig::LineListInsert(const wxString& str,
1128 wxFileConfigLineList *pLine)
1129 {
1130 wxLogTrace( _T("wxFileConfig"),
1131 _T(" ** Inserting Line '%s' after '%s'"),
1132 str.c_str(),
1133 ((pLine) ? pLine->Text().c_str() : wxEmptyString) );
1134 wxLogTrace( _T("wxFileConfig"),
1135 _T(" head: %s"),
1136 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1137 wxLogTrace( _T("wxFileConfig"),
1138 _T(" tail: %s"),
1139 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1140
1141 if ( pLine == m_linesTail )
1142 return LineListAppend(str);
1143
1144 wxFileConfigLineList *pNewLine = new wxFileConfigLineList(str);
1145 if ( pLine == NULL )
1146 {
1147 // prepend to the list
1148 pNewLine->SetNext(m_linesHead);
1149 m_linesHead->SetPrev(pNewLine);
1150 m_linesHead = pNewLine;
1151 }
1152 else
1153 {
1154 // insert before pLine
1155 wxFileConfigLineList *pNext = pLine->Next();
1156 pNewLine->SetNext(pNext);
1157 pNewLine->SetPrev(pLine);
1158 pNext->SetPrev(pNewLine);
1159 pLine->SetNext(pNewLine);
1160 }
1161
1162 wxLogTrace( _T("wxFileConfig"),
1163 _T(" head: %s"),
1164 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1165 wxLogTrace( _T("wxFileConfig"),
1166 _T(" tail: %s"),
1167 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1168
1169 return pNewLine;
1170 }
1171
1172 void wxFileConfig::LineListRemove(wxFileConfigLineList *pLine)
1173 {
1174 wxLogTrace( _T("wxFileConfig"),
1175 _T(" ** Removing Line '%s'"),
1176 pLine->Text().c_str() );
1177 wxLogTrace( _T("wxFileConfig"),
1178 _T(" head: %s"),
1179 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1180 wxLogTrace( _T("wxFileConfig"),
1181 _T(" tail: %s"),
1182 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1183
1184 wxFileConfigLineList *pPrev = pLine->Prev(),
1185 *pNext = pLine->Next();
1186
1187 // first entry?
1188
1189 if ( pPrev == NULL )
1190 m_linesHead = pNext;
1191 else
1192 pPrev->SetNext(pNext);
1193
1194 // last entry?
1195
1196 if ( pNext == NULL )
1197 m_linesTail = pPrev;
1198 else
1199 pNext->SetPrev(pPrev);
1200
1201 wxLogTrace( _T("wxFileConfig"),
1202 _T(" head: %s"),
1203 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1204 wxLogTrace( _T("wxFileConfig"),
1205 _T(" tail: %s"),
1206 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1207
1208 delete pLine;
1209 }
1210
1211 bool wxFileConfig::LineListIsEmpty()
1212 {
1213 return m_linesHead == NULL;
1214 }
1215
1216 // ============================================================================
1217 // wxFileConfig::wxFileConfigGroup
1218 // ============================================================================
1219
1220 // ----------------------------------------------------------------------------
1221 // ctor/dtor
1222 // ----------------------------------------------------------------------------
1223
1224 // ctor
1225 wxFileConfigGroup::wxFileConfigGroup(wxFileConfigGroup *pParent,
1226 const wxString& strName,
1227 wxFileConfig *pConfig)
1228 : m_aEntries(CompareEntries),
1229 m_aSubgroups(CompareGroups),
1230 m_strName(strName)
1231 {
1232 m_pConfig = pConfig;
1233 m_pParent = pParent;
1234 m_bDirty = FALSE;
1235 m_pLine = NULL;
1236
1237 m_pLastEntry = NULL;
1238 m_pLastGroup = NULL;
1239 }
1240
1241 // dtor deletes all children
1242 wxFileConfigGroup::~wxFileConfigGroup()
1243 {
1244 // entries
1245 size_t n, nCount = m_aEntries.Count();
1246 for ( n = 0; n < nCount; n++ )
1247 delete m_aEntries[n];
1248
1249 // subgroups
1250 nCount = m_aSubgroups.Count();
1251 for ( n = 0; n < nCount; n++ )
1252 delete m_aSubgroups[n];
1253 }
1254
1255 // ----------------------------------------------------------------------------
1256 // line
1257 // ----------------------------------------------------------------------------
1258
1259 void wxFileConfigGroup::SetLine(wxFileConfigLineList *pLine)
1260 {
1261 wxASSERT( m_pLine == 0 ); // shouldn't be called twice
1262 m_pLine = pLine;
1263 }
1264
1265 /*
1266 This is a bit complicated, so let me explain it in details. All lines that
1267 were read from the local file (the only one we will ever modify) are stored
1268 in a (doubly) linked list. Our problem is to know at which position in this
1269 list should we insert the new entries/subgroups. To solve it we keep three
1270 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
1271
1272 m_pLine points to the line containing "[group_name]"
1273 m_pLastEntry points to the last entry of this group in the local file.
1274 m_pLastGroup subgroup
1275
1276 Initially, they're NULL all three. When the group (an entry/subgroup) is read
1277 from the local file, the corresponding variable is set. However, if the group
1278 was read from the global file and then modified or created by the application
1279 these variables are still NULL and we need to create the corresponding lines.
1280 See the following functions (and comments preceding them) for the details of
1281 how we do it.
1282
1283 Also, when our last entry/group are deleted we need to find the new last
1284 element - the code in DeleteEntry/Subgroup does this by backtracking the list
1285 of lines until it either founds an entry/subgroup (and this is the new last
1286 element) or the m_pLine of the group, in which case there are no more entries
1287 (or subgroups) left and m_pLast<element> becomes NULL.
1288
1289 NB: This last problem could be avoided for entries if we added new entries
1290 immediately after m_pLine, but in this case the entries would appear
1291 backwards in the config file (OTOH, it's not that important) and as we
1292 would still need to do it for the subgroups the code wouldn't have been
1293 significantly less complicated.
1294 */
1295
1296 // Return the line which contains "[our name]". If we're still not in the list,
1297 // add our line to it immediately after the last line of our parent group if we
1298 // have it or in the very beginning if we're the root group.
1299 wxFileConfigLineList *wxFileConfigGroup::GetGroupLine()
1300 {
1301 wxLogTrace( _T("wxFileConfig"),
1302 _T(" GetGroupLine() for Group '%s'"),
1303 Name().c_str() );
1304
1305 if ( m_pLine == 0 )
1306 {
1307 wxLogTrace( _T("wxFileConfig"),
1308 _T(" Getting Line item pointer") );
1309
1310 wxFileConfigGroup *pParent = Parent();
1311
1312 // this group wasn't present in local config file, add it now
1313
1314 if ( pParent != 0 )
1315 {
1316 wxLogTrace( _T("wxFileConfig"),
1317 _T(" checking parent '%s'"),
1318 pParent->Name().c_str() );
1319
1320 wxString strFullName;
1321
1322 strFullName << wxT("[") // +1: no '/'
1323 << FilterOutEntryName(GetFullName().c_str() + 1)
1324 << wxT("]");
1325 m_pLine = m_pConfig->LineListInsert(strFullName,
1326 pParent->GetLastGroupLine());
1327 pParent->SetLastGroup(this); // we're surely after all the others
1328 }
1329 else
1330 {
1331 // we return NULL, so that LineListInsert() will insert us in the
1332 // very beginning
1333 }
1334 }
1335
1336 return m_pLine;
1337 }
1338
1339 // Return the last line belonging to the subgroups of this group (after which
1340 // we can add a new subgroup), if we don't have any subgroups or entries our
1341 // last line is the group line (m_pLine) itself.
1342 wxFileConfigLineList *wxFileConfigGroup::GetLastGroupLine()
1343 {
1344 // if we have any subgroups, our last line is
1345 // the last line of the last subgroup
1346
1347 if ( m_pLastGroup != 0 )
1348 {
1349 wxFileConfigLineList *pLine = m_pLastGroup->GetLastGroupLine();
1350
1351 wxASSERT( pLine != 0 ); // last group must have !NULL associated line
1352 return pLine;
1353 }
1354
1355 // no subgroups, so the last line is the line of thelast entry (if any)
1356
1357 return GetLastEntryLine();
1358 }
1359
1360 // return the last line belonging to the entries of this group (after which
1361 // we can add a new entry), if we don't have any entries we will add the new
1362 // one immediately after the group line itself.
1363 wxFileConfigLineList *wxFileConfigGroup::GetLastEntryLine()
1364 {
1365 wxLogTrace( _T("wxFileConfig"),
1366 _T(" GetLastEntryLine() for Group '%s'"),
1367 Name().c_str() );
1368
1369 if ( m_pLastEntry != 0 )
1370 {
1371 wxFileConfigLineList *pLine = m_pLastEntry->GetLine();
1372
1373 wxASSERT( pLine != 0 ); // last entry must have !NULL associated line
1374 return pLine;
1375 }
1376
1377 // no entries: insert after the group header
1378
1379 return GetGroupLine();
1380 }
1381
1382 // ----------------------------------------------------------------------------
1383 // group name
1384 // ----------------------------------------------------------------------------
1385
1386 void wxFileConfigGroup::Rename(const wxString& newName)
1387 {
1388 m_strName = newName;
1389
1390 wxFileConfigLineList *line = GetGroupLine();
1391 wxString strFullName;
1392 strFullName << wxT("[") << (GetFullName().c_str() + 1) << wxT("]"); // +1: no '/'
1393 line->SetText(strFullName);
1394
1395 SetDirty();
1396 }
1397
1398 wxString wxFileConfigGroup::GetFullName() const
1399 {
1400 if ( Parent() )
1401 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR + Name();
1402 else
1403 return wxT("");
1404 }
1405
1406 // ----------------------------------------------------------------------------
1407 // find an item
1408 // ----------------------------------------------------------------------------
1409
1410 // use binary search because the array is sorted
1411 wxFileConfigEntry *
1412 wxFileConfigGroup::FindEntry(const wxChar *szName) const
1413 {
1414 size_t i,
1415 lo = 0,
1416 hi = m_aEntries.Count();
1417 int res;
1418 wxFileConfigEntry *pEntry;
1419
1420 while ( lo < hi ) {
1421 i = (lo + hi)/2;
1422 pEntry = m_aEntries[i];
1423
1424 #if wxCONFIG_CASE_SENSITIVE
1425 res = wxStrcmp(pEntry->Name(), szName);
1426 #else
1427 res = wxStricmp(pEntry->Name(), szName);
1428 #endif
1429
1430 if ( res > 0 )
1431 hi = i;
1432 else if ( res < 0 )
1433 lo = i + 1;
1434 else
1435 return pEntry;
1436 }
1437
1438 return NULL;
1439 }
1440
1441 wxFileConfigGroup *
1442 wxFileConfigGroup::FindSubgroup(const wxChar *szName) const
1443 {
1444 size_t i,
1445 lo = 0,
1446 hi = m_aSubgroups.Count();
1447 int res;
1448 wxFileConfigGroup *pGroup;
1449
1450 while ( lo < hi ) {
1451 i = (lo + hi)/2;
1452 pGroup = m_aSubgroups[i];
1453
1454 #if wxCONFIG_CASE_SENSITIVE
1455 res = wxStrcmp(pGroup->Name(), szName);
1456 #else
1457 res = wxStricmp(pGroup->Name(), szName);
1458 #endif
1459
1460 if ( res > 0 )
1461 hi = i;
1462 else if ( res < 0 )
1463 lo = i + 1;
1464 else
1465 return pGroup;
1466 }
1467
1468 return NULL;
1469 }
1470
1471 // ----------------------------------------------------------------------------
1472 // create a new item
1473 // ----------------------------------------------------------------------------
1474
1475 // create a new entry and add it to the current group
1476 wxFileConfigEntry *wxFileConfigGroup::AddEntry(const wxString& strName, int nLine)
1477 {
1478 wxASSERT( FindEntry(strName) == 0 );
1479
1480 wxFileConfigEntry *pEntry = new wxFileConfigEntry(this, strName, nLine);
1481
1482 m_aEntries.Add(pEntry);
1483 return pEntry;
1484 }
1485
1486 // create a new group and add it to the current group
1487 wxFileConfigGroup *wxFileConfigGroup::AddSubgroup(const wxString& strName)
1488 {
1489 wxASSERT( FindSubgroup(strName) == 0 );
1490
1491 wxFileConfigGroup *pGroup = new wxFileConfigGroup(this, strName, m_pConfig);
1492
1493 m_aSubgroups.Add(pGroup);
1494 return pGroup;
1495 }
1496
1497 // ----------------------------------------------------------------------------
1498 // delete an item
1499 // ----------------------------------------------------------------------------
1500
1501 /*
1502 The delete operations are _very_ slow if we delete the last item of this
1503 group (see comments before GetXXXLineXXX functions for more details),
1504 so it's much better to start with the first entry/group if we want to
1505 delete several of them.
1506 */
1507
1508 bool wxFileConfigGroup::DeleteSubgroupByName(const wxChar *szName)
1509 {
1510 wxFileConfigGroup * const pGroup = FindSubgroup(szName);
1511
1512 return pGroup ? DeleteSubgroup(pGroup) : FALSE;
1513 }
1514
1515 // Delete the subgroup and remove all references to it from
1516 // other data structures.
1517 bool wxFileConfigGroup::DeleteSubgroup(wxFileConfigGroup *pGroup)
1518 {
1519 wxCHECK_MSG( pGroup, FALSE, _T("deleting non existing group?") );
1520
1521 wxLogTrace( _T("wxFileConfig"),
1522 _T("Deleting group '%s' from '%s'"),
1523 pGroup->Name().c_str(),
1524 Name().c_str() );
1525
1526 wxLogTrace( _T("wxFileConfig"),
1527 _T(" (m_pLine) = prev: %p, this %p, next %p"),
1528 ((m_pLine) ? m_pLine->Prev() : 0),
1529 m_pLine,
1530 ((m_pLine) ? m_pLine->Next() : 0) );
1531 wxLogTrace( _T("wxFileConfig"),
1532 _T(" text: '%s'"),
1533 ((m_pLine) ? m_pLine->Text().c_str() : wxEmptyString) );
1534
1535 // delete all entries
1536 size_t nCount = pGroup->m_aEntries.Count();
1537
1538 wxLogTrace(_T("wxFileConfig"),
1539 _T("Removing %lu Entries"),
1540 (unsigned long)nCount );
1541
1542 for ( size_t nEntry = 0; nEntry < nCount; nEntry++ )
1543 {
1544 wxFileConfigLineList *pLine = pGroup->m_aEntries[nEntry]->GetLine();
1545
1546 if ( pLine != 0 )
1547 {
1548 wxLogTrace( _T("wxFileConfig"),
1549 _T(" '%s'"),
1550 pLine->Text().c_str() );
1551 m_pConfig->LineListRemove(pLine);
1552 }
1553 }
1554
1555 // and subgroups of this subgroup
1556
1557 nCount = pGroup->m_aSubgroups.Count();
1558
1559 wxLogTrace( _T("wxFileConfig"),
1560 _T("Removing %lu SubGroups"),
1561 (unsigned long)nCount );
1562
1563 for ( size_t nGroup = 0; nGroup < nCount; nGroup++ )
1564 {
1565 pGroup->DeleteSubgroup(pGroup->m_aSubgroups[0]);
1566 }
1567
1568 // finally the group itself
1569
1570 wxFileConfigLineList *pLine = pGroup->m_pLine;
1571
1572 if ( pLine != 0 )
1573 {
1574 wxLogTrace( _T("wxFileConfig"),
1575 _T(" Removing line entry for Group '%s' : '%s'"),
1576 pGroup->Name().c_str(),
1577 pLine->Text().c_str() );
1578 wxLogTrace( _T("wxFileConfig"),
1579 _T(" Removing from Group '%s' : '%s'"),
1580 Name().c_str(),
1581 ((m_pLine) ? m_pLine->Text().c_str() : wxEmptyString) );
1582
1583 // notice that we may do this test inside the previous "if"
1584 // because the last entry's line is surely !NULL
1585
1586 if ( pGroup == m_pLastGroup )
1587 {
1588 wxLogTrace( _T("wxFileConfig"),
1589 _T(" ------- Removing last group -------") );
1590
1591 // our last entry is being deleted, so find the last one which stays.
1592 // go back until we find a subgroup or reach the group's line, unless
1593 // we are the root group, which we'll notice shortly.
1594
1595 wxFileConfigGroup *pNewLast = 0;
1596 size_t nSubgroups = m_aSubgroups.Count();
1597 wxFileConfigLineList *pl;
1598
1599 for ( pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() )
1600 {
1601 // is it our subgroup?
1602
1603 for ( size_t n = 0; (pNewLast == 0) && (n < nSubgroups); n++ )
1604 {
1605 // do _not_ call GetGroupLine! we don't want to add it to the local
1606 // file if it's not already there
1607
1608 if ( m_aSubgroups[n]->m_pLine == m_pLine )
1609 pNewLast = m_aSubgroups[n];
1610 }
1611
1612 if ( pNewLast != 0 ) // found?
1613 break;
1614 }
1615
1616 if ( pl == m_pLine || m_pParent == 0 )
1617 {
1618 wxLogTrace( _T("wxFileConfig"),
1619 _T(" ------- No previous group found -------") );
1620
1621 wxASSERT_MSG( !pNewLast || m_pLine == 0,
1622 _T("how comes it has the same line as we?") );
1623
1624 // we've reached the group line without finding any subgroups,
1625 // or realised we removed the last group from the root.
1626
1627 m_pLastGroup = 0;
1628 }
1629 else
1630 {
1631 wxLogTrace( _T("wxFileConfig"),
1632 _T(" ------- Last Group set to '%s' -------"),
1633 pNewLast->Name().c_str() );
1634
1635 m_pLastGroup = pNewLast;
1636 }
1637 }
1638
1639 m_pConfig->LineListRemove(pLine);
1640 }
1641 else
1642 {
1643 wxLogTrace( _T("wxFileConfig"),
1644 _T(" No line entry for Group '%s'?"),
1645 pGroup->Name().c_str() );
1646 }
1647
1648 SetDirty();
1649
1650 m_aSubgroups.Remove(pGroup);
1651 delete pGroup;
1652
1653 return TRUE;
1654 }
1655
1656 bool wxFileConfigGroup::DeleteEntry(const wxChar *szName)
1657 {
1658 wxFileConfigEntry *pEntry = FindEntry(szName);
1659 wxCHECK( pEntry != NULL, FALSE ); // deleting non existing item?
1660
1661 wxFileConfigLineList *pLine = pEntry->GetLine();
1662 if ( pLine != NULL ) {
1663 // notice that we may do this test inside the previous "if" because the
1664 // last entry's line is surely !NULL
1665 if ( pEntry == m_pLastEntry ) {
1666 // our last entry is being deleted - find the last one which stays
1667 wxASSERT( m_pLine != NULL ); // if we have an entry with !NULL pLine...
1668
1669 // go back until we find another entry or reach the group's line
1670 wxFileConfigEntry *pNewLast = NULL;
1671 size_t n, nEntries = m_aEntries.Count();
1672 wxFileConfigLineList *pl;
1673 for ( pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() ) {
1674 // is it our subgroup?
1675 for ( n = 0; (pNewLast == NULL) && (n < nEntries); n++ ) {
1676 if ( m_aEntries[n]->GetLine() == m_pLine )
1677 pNewLast = m_aEntries[n];
1678 }
1679
1680 if ( pNewLast != NULL ) // found?
1681 break;
1682 }
1683
1684 if ( pl == m_pLine ) {
1685 wxASSERT( !pNewLast ); // how comes it has the same line as we?
1686
1687 // we've reached the group line without finding any subgroups
1688 m_pLastEntry = NULL;
1689 }
1690 else
1691 m_pLastEntry = pNewLast;
1692 }
1693
1694 m_pConfig->LineListRemove(pLine);
1695 }
1696
1697 // we must be written back for the changes to be saved
1698 SetDirty();
1699
1700 m_aEntries.Remove(pEntry);
1701 delete pEntry;
1702
1703 return TRUE;
1704 }
1705
1706 // ----------------------------------------------------------------------------
1707 //
1708 // ----------------------------------------------------------------------------
1709 void wxFileConfigGroup::SetDirty()
1710 {
1711 m_bDirty = TRUE;
1712 if ( Parent() != NULL ) // propagate upwards
1713 Parent()->SetDirty();
1714 }
1715
1716 // ============================================================================
1717 // wxFileConfig::wxFileConfigEntry
1718 // ============================================================================
1719
1720 // ----------------------------------------------------------------------------
1721 // ctor
1722 // ----------------------------------------------------------------------------
1723 wxFileConfigEntry::wxFileConfigEntry(wxFileConfigGroup *pParent,
1724 const wxString& strName,
1725 int nLine)
1726 : m_strName(strName)
1727 {
1728 wxASSERT( !strName.IsEmpty() );
1729
1730 m_pParent = pParent;
1731 m_nLine = nLine;
1732 m_pLine = NULL;
1733
1734 m_bDirty =
1735 m_bHasValue = FALSE;
1736
1737 m_bImmutable = strName[0] == wxCONFIG_IMMUTABLE_PREFIX;
1738 if ( m_bImmutable )
1739 m_strName.erase(0, 1); // remove first character
1740 }
1741
1742 // ----------------------------------------------------------------------------
1743 // set value
1744 // ----------------------------------------------------------------------------
1745
1746 void wxFileConfigEntry::SetLine(wxFileConfigLineList *pLine)
1747 {
1748 if ( m_pLine != NULL ) {
1749 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1750 Name().c_str(), m_pParent->GetFullName().c_str());
1751 }
1752
1753 m_pLine = pLine;
1754 Group()->SetLastEntry(this);
1755 }
1756
1757 // second parameter is FALSE if we read the value from file and prevents the
1758 // entry from being marked as 'dirty'
1759 void wxFileConfigEntry::SetValue(const wxString& strValue, bool bUser)
1760 {
1761 if ( bUser && IsImmutable() )
1762 {
1763 wxLogWarning( _("attempt to change immutable key '%s' ignored."),
1764 Name().c_str());
1765 return;
1766 }
1767
1768 // do nothing if it's the same value: but don't test for it
1769 // if m_bHasValue hadn't been set yet or we'd never write
1770 // empty values to the file
1771
1772 if ( m_bHasValue && strValue == m_strValue )
1773 return;
1774
1775 m_bHasValue = TRUE;
1776 m_strValue = strValue;
1777
1778 if ( bUser )
1779 {
1780 wxString strValFiltered;
1781
1782 if ( Group()->Config()->GetStyle() & wxCONFIG_USE_NO_ESCAPE_CHARACTERS )
1783 {
1784 strValFiltered = strValue;
1785 }
1786 else {
1787 strValFiltered = FilterOutValue(strValue);
1788 }
1789
1790 wxString strLine;
1791 strLine << FilterOutEntryName(m_strName) << wxT('=') << strValFiltered;
1792
1793 if ( m_pLine != 0 )
1794 {
1795 // entry was read from the local config file, just modify the line
1796 m_pLine->SetText(strLine);
1797 }
1798 else {
1799 // add a new line to the file
1800 wxASSERT( m_nLine == wxNOT_FOUND ); // consistency check
1801
1802 m_pLine = Group()->Config()->LineListInsert(strLine,
1803 Group()->GetLastEntryLine());
1804 Group()->SetLastEntry(this);
1805 }
1806
1807 SetDirty();
1808 }
1809 }
1810
1811 void wxFileConfigEntry::SetDirty()
1812 {
1813 m_bDirty = TRUE;
1814 Group()->SetDirty();
1815 }
1816
1817 // ============================================================================
1818 // global functions
1819 // ============================================================================
1820
1821 // ----------------------------------------------------------------------------
1822 // compare functions for array sorting
1823 // ----------------------------------------------------------------------------
1824
1825 int CompareEntries(wxFileConfigEntry *p1, wxFileConfigEntry *p2)
1826 {
1827 #if wxCONFIG_CASE_SENSITIVE
1828 return wxStrcmp(p1->Name(), p2->Name());
1829 #else
1830 return wxStricmp(p1->Name(), p2->Name());
1831 #endif
1832 }
1833
1834 int CompareGroups(wxFileConfigGroup *p1, wxFileConfigGroup *p2)
1835 {
1836 #if wxCONFIG_CASE_SENSITIVE
1837 return wxStrcmp(p1->Name(), p2->Name());
1838 #else
1839 return wxStricmp(p1->Name(), p2->Name());
1840 #endif
1841 }
1842
1843 // ----------------------------------------------------------------------------
1844 // filter functions
1845 // ----------------------------------------------------------------------------
1846
1847 // undo FilterOutValue
1848 static wxString FilterInValue(const wxString& str)
1849 {
1850 wxString strResult;
1851 strResult.Alloc(str.Len());
1852
1853 bool bQuoted = !str.IsEmpty() && str[0] == '"';
1854
1855 for ( size_t n = bQuoted ? 1 : 0; n < str.Len(); n++ ) {
1856 if ( str[n] == wxT('\\') ) {
1857 switch ( str[++n] ) {
1858 case wxT('n'):
1859 strResult += wxT('\n');
1860 break;
1861
1862 case wxT('r'):
1863 strResult += wxT('\r');
1864 break;
1865
1866 case wxT('t'):
1867 strResult += wxT('\t');
1868 break;
1869
1870 case wxT('\\'):
1871 strResult += wxT('\\');
1872 break;
1873
1874 case wxT('"'):
1875 strResult += wxT('"');
1876 break;
1877 }
1878 }
1879 else {
1880 if ( str[n] != wxT('"') || !bQuoted )
1881 strResult += str[n];
1882 else if ( n != str.Len() - 1 ) {
1883 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1884 n, str.c_str());
1885 }
1886 //else: it's the last quote of a quoted string, ok
1887 }
1888 }
1889
1890 return strResult;
1891 }
1892
1893 // quote the string before writing it to file
1894 static wxString FilterOutValue(const wxString& str)
1895 {
1896 if ( !str )
1897 return str;
1898
1899 wxString strResult;
1900 strResult.Alloc(str.Len());
1901
1902 // quoting is necessary to preserve spaces in the beginning of the string
1903 bool bQuote = wxIsspace(str[0]) || str[0] == wxT('"');
1904
1905 if ( bQuote )
1906 strResult += wxT('"');
1907
1908 wxChar c;
1909 for ( size_t n = 0; n < str.Len(); n++ ) {
1910 switch ( str[n] ) {
1911 case wxT('\n'):
1912 c = wxT('n');
1913 break;
1914
1915 case wxT('\r'):
1916 c = wxT('r');
1917 break;
1918
1919 case wxT('\t'):
1920 c = wxT('t');
1921 break;
1922
1923 case wxT('\\'):
1924 c = wxT('\\');
1925 break;
1926
1927 case wxT('"'):
1928 if ( bQuote ) {
1929 c = wxT('"');
1930 break;
1931 }
1932 //else: fall through
1933
1934 default:
1935 strResult += str[n];
1936 continue; // nothing special to do
1937 }
1938
1939 // we get here only for special characters
1940 strResult << wxT('\\') << c;
1941 }
1942
1943 if ( bQuote )
1944 strResult += wxT('"');
1945
1946 return strResult;
1947 }
1948
1949 // undo FilterOutEntryName
1950 static wxString FilterInEntryName(const wxString& str)
1951 {
1952 wxString strResult;
1953 strResult.Alloc(str.Len());
1954
1955 for ( const wxChar *pc = str.c_str(); *pc != '\0'; pc++ ) {
1956 if ( *pc == wxT('\\') )
1957 pc++;
1958
1959 strResult += *pc;
1960 }
1961
1962 return strResult;
1963 }
1964
1965 // sanitize entry or group name: insert '\\' before any special characters
1966 static wxString FilterOutEntryName(const wxString& str)
1967 {
1968 wxString strResult;
1969 strResult.Alloc(str.Len());
1970
1971 for ( const wxChar *pc = str.c_str(); *pc != wxT('\0'); pc++ ) {
1972 wxChar c = *pc;
1973
1974 // we explicitly allow some of "safe" chars and 8bit ASCII characters
1975 // which will probably never have special meaning
1976 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
1977 // should *not* be quoted
1978 if ( !wxIsalnum(c) && !wxStrchr(wxT("@_/-!.*%"), c) && ((c & 0x80) == 0) )
1979 strResult += wxT('\\');
1980
1981 strResult += c;
1982 }
1983
1984 return strResult;
1985 }
1986
1987 // we can't put ?: in the ctor initializer list because it confuses some
1988 // broken compilers (Borland C++)
1989 static wxString GetAppName(const wxString& appName)
1990 {
1991 if ( !appName && wxTheApp )
1992 return wxTheApp->GetAppName();
1993 else
1994 return appName;
1995 }
1996
1997 #endif // wxUSE_CONFIG
1998
1999
2000 // vi:sts=4:sw=4:et