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