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