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