assert that name, not full path, is used in RenameEntry(); fixed RenameGroup() which...
[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 #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
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 #include "wx/filefn.h"
43
44 #if wxUSE_STREAMS
45 #include "wx/stream.h"
46 #endif // wxUSE_STREAMS
47
48 #include "wx/utils.h" // for wxGetHomeDir
49
50 #if defined(__WXMAC__)
51 #include "wx/mac/private.h" // includes mac headers
52 #endif
53
54 #if defined(__WXMSW__)
55 #include "wx/msw/private.h"
56 #endif //windows.h
57 #if defined(__WXPM__)
58 #define INCL_DOS
59 #include <os2.h>
60 #endif
61
62 #include <stdlib.h>
63 #include <ctype.h>
64
65 // ----------------------------------------------------------------------------
66 // macros
67 // ----------------------------------------------------------------------------
68 #define CONST_CAST ((wxFileConfig *)this)->
69
70 // ----------------------------------------------------------------------------
71 // constants
72 // ----------------------------------------------------------------------------
73
74 #ifndef MAX_PATH
75 #define MAX_PATH 512
76 #endif
77
78 #define FILECONF_TRACE_MASK _T("fileconf")
79
80 // ----------------------------------------------------------------------------
81 // global functions declarations
82 // ----------------------------------------------------------------------------
83
84 // compare functions for sorting the arrays
85 static int LINKAGEMODE CompareEntries(wxFileConfigEntry *p1, wxFileConfigEntry *p2);
86 static int LINKAGEMODE CompareGroups(wxFileConfigGroup *p1, wxFileConfigGroup *p2);
87
88 // filter strings
89 static wxString FilterInValue(const wxString& str);
90 static wxString FilterOutValue(const wxString& str);
91
92 static wxString FilterInEntryName(const wxString& str);
93 static wxString FilterOutEntryName(const wxString& str);
94
95 // get the name to use in wxFileConfig ctor
96 static wxString GetAppName(const wxString& appname);
97
98 // ============================================================================
99 // private classes
100 // ============================================================================
101
102 // ----------------------------------------------------------------------------
103 // "template" array types
104 // ----------------------------------------------------------------------------
105
106 #ifdef WXMAKINGDLL_BASE
107 WX_DEFINE_SORTED_USER_EXPORTED_ARRAY(wxFileConfigEntry *, ArrayEntries,
108 WXDLLIMPEXP_BASE);
109 WX_DEFINE_SORTED_USER_EXPORTED_ARRAY(wxFileConfigGroup *, ArrayGroups,
110 WXDLLIMPEXP_BASE);
111 #else
112 WX_DEFINE_SORTED_ARRAY(wxFileConfigEntry *, ArrayEntries);
113 WX_DEFINE_SORTED_ARRAY(wxFileConfigGroup *, ArrayGroups);
114 #endif
115
116 // ----------------------------------------------------------------------------
117 // wxFileConfigLineList
118 // ----------------------------------------------------------------------------
119
120 // we store all lines of the local config file as a linked list in memory
121 class wxFileConfigLineList
122 {
123 public:
124 void SetNext(wxFileConfigLineList *pNext) { m_pNext = pNext; }
125 void SetPrev(wxFileConfigLineList *pPrev) { m_pPrev = pPrev; }
126
127 // ctor
128 wxFileConfigLineList(const wxString& str,
129 wxFileConfigLineList *pNext = NULL) : m_strLine(str)
130 { SetNext(pNext); SetPrev(NULL); }
131
132 // next/prev nodes in the linked list
133 wxFileConfigLineList *Next() const { return m_pNext; }
134 wxFileConfigLineList *Prev() const { return m_pPrev; }
135
136 // get/change lines text
137 void SetText(const wxString& str) { m_strLine = str; }
138 const wxString& Text() const { return m_strLine; }
139
140 private:
141 wxString m_strLine; // line contents
142 wxFileConfigLineList *m_pNext, // next node
143 *m_pPrev; // previous one
144
145 DECLARE_NO_COPY_CLASS(wxFileConfigLineList)
146 };
147
148 // ----------------------------------------------------------------------------
149 // wxFileConfigEntry: a name/value pair
150 // ----------------------------------------------------------------------------
151
152 class wxFileConfigEntry
153 {
154 private:
155 wxFileConfigGroup *m_pParent; // group that contains us
156
157 wxString m_strName, // entry name
158 m_strValue; // value
159 bool m_bImmutable:1, // can be overriden locally?
160 m_bHasValue:1; // set after first call to SetValue()
161
162 int m_nLine; // used if m_pLine == NULL only
163
164 // pointer to our line in the linked list or NULL if it was found in global
165 // file (which we don't modify)
166 wxFileConfigLineList *m_pLine;
167
168 public:
169 wxFileConfigEntry(wxFileConfigGroup *pParent,
170 const wxString& strName, int nLine);
171
172 // simple accessors
173 const wxString& Name() const { return m_strName; }
174 const wxString& Value() const { return m_strValue; }
175 wxFileConfigGroup *Group() const { return m_pParent; }
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 SetLine(wxFileConfigLineList *pLine);
185
186 DECLARE_NO_COPY_CLASS(wxFileConfigEntry)
187 };
188
189 // ----------------------------------------------------------------------------
190 // wxFileConfigGroup: container of entries and other groups
191 // ----------------------------------------------------------------------------
192
193 class wxFileConfigGroup
194 {
195 private:
196 wxFileConfig *m_pConfig; // config object we belong to
197 wxFileConfigGroup *m_pParent; // parent group (NULL for root group)
198 ArrayEntries m_aEntries; // entries in this group
199 ArrayGroups m_aSubgroups; // subgroups
200 wxString m_strName; // group's name
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 // used by Rename()
209 void UpdateGroupAndSubgroupsLines();
210
211 public:
212 // ctor
213 wxFileConfigGroup(wxFileConfigGroup *pParent, const wxString& strName, wxFileConfig *);
214
215 // dtor deletes all entries and subgroups also
216 ~wxFileConfigGroup();
217
218 // simple accessors
219 const wxString& Name() const { return m_strName; }
220 wxFileConfigGroup *Parent() const { return m_pParent; }
221 wxFileConfig *Config() const { return m_pConfig; }
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 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 m_isDirty = false;
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 do
493 {
494 inStream.Read(buf, WXSIZEOF(buf));
495
496 const wxStreamError err = inStream.GetLastError();
497
498 if ( err != wxSTREAM_NO_ERROR && err != wxSTREAM_EOF )
499 {
500 wxLogError(_("Error reading config options."));
501 break;
502 }
503
504 strTmp.append(wxConvertMB2WX(buf), inStream.LastRead());
505 }
506 while ( !inStream.Eof() );
507
508 strTrans = wxTextBuffer::Translate(strTmp);
509 }
510
511 wxMemoryText memText;
512
513 // Now we can add the text to the memory text. To do this we extract line
514 // by line from the translated string, until we've reached the end.
515 //
516 // VZ: all this is horribly inefficient, we should do the translation on
517 // the fly in one pass saving both memory and time (TODO)
518
519 const wxChar *pEOL = wxTextBuffer::GetEOL(wxTextBuffer::typeDefault);
520 const size_t EOLLen = wxStrlen(pEOL);
521
522 int posLineStart = strTrans.Find(pEOL);
523 while ( posLineStart != -1 )
524 {
525 wxString line(strTrans.Left(posLineStart));
526
527 memText.AddLine(line);
528
529 strTrans = strTrans.Mid(posLineStart + EOLLen);
530
531 posLineStart = strTrans.Find(pEOL);
532 }
533
534 // also add whatever we have left in the translated string.
535 if ( !strTrans.empty() )
536 memText.AddLine(strTrans);
537
538 // Finally we can parse it all.
539 Parse(memText, true /* local */);
540
541 SetRootPath();
542 }
543
544 #endif // wxUSE_STREAMS
545
546 void wxFileConfig::CleanUp()
547 {
548 delete m_pRootGroup;
549
550 wxFileConfigLineList *pCur = m_linesHead;
551 while ( pCur != NULL ) {
552 wxFileConfigLineList *pNext = pCur->Next();
553 delete pCur;
554 pCur = pNext;
555 }
556 }
557
558 wxFileConfig::~wxFileConfig()
559 {
560 Flush();
561
562 CleanUp();
563 }
564
565 // ----------------------------------------------------------------------------
566 // parse a config file
567 // ----------------------------------------------------------------------------
568
569 void wxFileConfig::Parse(wxTextBuffer& buffer, bool bLocal)
570 {
571 const wxChar *pStart;
572 const wxChar *pEnd;
573 wxString strLine;
574
575 size_t nLineCount = buffer.GetLineCount();
576
577 for ( size_t n = 0; n < nLineCount; n++ )
578 {
579 strLine = buffer[n];
580
581 // add the line to linked list
582 if ( bLocal )
583 {
584 LineListAppend(strLine);
585
586 // let the root group have it start line as well
587 if ( !n )
588 {
589 m_pCurrentGroup->SetLine(m_linesTail);
590 }
591 }
592
593
594 // skip leading spaces
595 for ( pStart = strLine; wxIsspace(*pStart); pStart++ )
596 ;
597
598 // skip blank/comment lines
599 if ( *pStart == wxT('\0')|| *pStart == wxT(';') || *pStart == wxT('#') )
600 continue;
601
602 if ( *pStart == wxT('[') ) { // a new group
603 pEnd = pStart;
604
605 while ( *++pEnd != wxT(']') ) {
606 if ( *pEnd == wxT('\\') ) {
607 // the next char is escaped, so skip it even if it is ']'
608 pEnd++;
609 }
610
611 if ( *pEnd == wxT('\n') || *pEnd == wxT('\0') ) {
612 // we reached the end of line, break out of the loop
613 break;
614 }
615 }
616
617 if ( *pEnd != wxT(']') ) {
618 wxLogError(_("file '%s': unexpected character %c at line %d."),
619 buffer.GetName(), *pEnd, n + 1);
620 continue; // skip this line
621 }
622
623 // group name here is always considered as abs path
624 wxString strGroup;
625 pStart++;
626 strGroup << wxCONFIG_PATH_SEPARATOR
627 << FilterInEntryName(wxString(pStart, pEnd - pStart));
628
629 // will create it if doesn't yet exist
630 SetPath(strGroup);
631
632 if ( bLocal )
633 {
634 if ( m_pCurrentGroup->Parent() )
635 m_pCurrentGroup->Parent()->SetLastGroup(m_pCurrentGroup);
636 m_pCurrentGroup->SetLine(m_linesTail);
637 }
638
639 // check that there is nothing except comments left on this line
640 bool bCont = true;
641 while ( *++pEnd != wxT('\0') && bCont ) {
642 switch ( *pEnd ) {
643 case wxT('#'):
644 case wxT(';'):
645 bCont = false;
646 break;
647
648 case wxT(' '):
649 case wxT('\t'):
650 // ignore whitespace ('\n' impossible here)
651 break;
652
653 default:
654 wxLogWarning(_("file '%s', line %d: '%s' ignored after group header."),
655 buffer.GetName(), n + 1, pEnd);
656 bCont = false;
657 }
658 }
659 }
660 else { // a key
661 const wxChar *pEnd = pStart;
662 while ( *pEnd && *pEnd != wxT('=') /* && !wxIsspace(*pEnd)*/ ) {
663 if ( *pEnd == wxT('\\') ) {
664 // next character may be space or not - still take it because it's
665 // quoted (unless there is nothing)
666 pEnd++;
667 if ( !*pEnd ) {
668 // the error message will be given below anyhow
669 break;
670 }
671 }
672
673 pEnd++;
674 }
675
676 wxString strKey(FilterInEntryName(wxString(pStart, pEnd).Trim()));
677
678 // skip whitespace
679 while ( wxIsspace(*pEnd) )
680 pEnd++;
681
682 if ( *pEnd++ != wxT('=') ) {
683 wxLogError(_("file '%s', line %d: '=' expected."),
684 buffer.GetName(), n + 1);
685 }
686 else {
687 wxFileConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strKey);
688
689 if ( pEntry == NULL ) {
690 // new entry
691 pEntry = m_pCurrentGroup->AddEntry(strKey, n);
692 }
693 else {
694 if ( bLocal && pEntry->IsImmutable() ) {
695 // immutable keys can't be changed by user
696 wxLogWarning(_("file '%s', line %d: value for immutable key '%s' ignored."),
697 buffer.GetName(), n + 1, strKey.c_str());
698 continue;
699 }
700 // the condition below catches the cases (a) and (b) but not (c):
701 // (a) global key found second time in global file
702 // (b) key found second (or more) time in local file
703 // (c) key from global file now found in local one
704 // which is exactly what we want.
705 else if ( !bLocal || pEntry->IsLocal() ) {
706 wxLogWarning(_("file '%s', line %d: key '%s' was first found at line %d."),
707 buffer.GetName(), n + 1, strKey.c_str(), pEntry->Line());
708
709 }
710 }
711
712 if ( bLocal )
713 pEntry->SetLine(m_linesTail);
714
715 // skip whitespace
716 while ( wxIsspace(*pEnd) )
717 pEnd++;
718
719 wxString value = pEnd;
720 if ( !(GetStyle() & wxCONFIG_USE_NO_ESCAPE_CHARACTERS) )
721 value = FilterInValue(value);
722
723 pEntry->SetValue(value, false);
724 }
725 }
726 }
727 }
728
729 // ----------------------------------------------------------------------------
730 // set/retrieve path
731 // ----------------------------------------------------------------------------
732
733 void wxFileConfig::SetRootPath()
734 {
735 m_strPath.Empty();
736 m_pCurrentGroup = m_pRootGroup;
737 }
738
739 void wxFileConfig::SetPath(const wxString& strPath)
740 {
741 wxArrayString aParts;
742
743 if ( strPath.IsEmpty() ) {
744 SetRootPath();
745 return;
746 }
747
748 if ( strPath[0] == wxCONFIG_PATH_SEPARATOR ) {
749 // absolute path
750 wxSplitPath(aParts, strPath);
751 }
752 else {
753 // relative path, combine with current one
754 wxString strFullPath = m_strPath;
755 strFullPath << wxCONFIG_PATH_SEPARATOR << strPath;
756 wxSplitPath(aParts, strFullPath);
757 }
758
759 // change current group
760 size_t n;
761 m_pCurrentGroup = m_pRootGroup;
762 for ( n = 0; n < aParts.Count(); n++ ) {
763 wxFileConfigGroup *pNextGroup = m_pCurrentGroup->FindSubgroup(aParts[n]);
764 if ( pNextGroup == NULL )
765 pNextGroup = m_pCurrentGroup->AddSubgroup(aParts[n]);
766 m_pCurrentGroup = pNextGroup;
767 }
768
769 // recombine path parts in one variable
770 m_strPath.Empty();
771 for ( n = 0; n < aParts.Count(); n++ ) {
772 m_strPath << wxCONFIG_PATH_SEPARATOR << aParts[n];
773 }
774 }
775
776 // ----------------------------------------------------------------------------
777 // enumeration
778 // ----------------------------------------------------------------------------
779
780 bool wxFileConfig::GetFirstGroup(wxString& str, long& lIndex) const
781 {
782 lIndex = 0;
783 return GetNextGroup(str, lIndex);
784 }
785
786 bool wxFileConfig::GetNextGroup (wxString& str, long& lIndex) const
787 {
788 if ( size_t(lIndex) < m_pCurrentGroup->Groups().Count() ) {
789 str = m_pCurrentGroup->Groups()[(size_t)lIndex++]->Name();
790 return true;
791 }
792 else
793 return false;
794 }
795
796 bool wxFileConfig::GetFirstEntry(wxString& str, long& lIndex) const
797 {
798 lIndex = 0;
799 return GetNextEntry(str, lIndex);
800 }
801
802 bool wxFileConfig::GetNextEntry (wxString& str, long& lIndex) const
803 {
804 if ( size_t(lIndex) < m_pCurrentGroup->Entries().Count() ) {
805 str = m_pCurrentGroup->Entries()[(size_t)lIndex++]->Name();
806 return true;
807 }
808 else
809 return false;
810 }
811
812 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive) const
813 {
814 size_t n = m_pCurrentGroup->Entries().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 += GetNumberOfEntries(true);
821 CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
822 }
823 }
824
825 return n;
826 }
827
828 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive) const
829 {
830 size_t n = m_pCurrentGroup->Groups().Count();
831 if ( bRecursive ) {
832 wxFileConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
833 size_t nSubgroups = m_pCurrentGroup->Groups().Count();
834 for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
835 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
836 n += GetNumberOfGroups(true);
837 CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
838 }
839 }
840
841 return n;
842 }
843
844 // ----------------------------------------------------------------------------
845 // tests for existence
846 // ----------------------------------------------------------------------------
847
848 bool wxFileConfig::HasGroup(const wxString& strName) const
849 {
850 wxConfigPathChanger path(this, strName);
851
852 wxFileConfigGroup *pGroup = m_pCurrentGroup->FindSubgroup(path.Name());
853 return pGroup != NULL;
854 }
855
856 bool wxFileConfig::HasEntry(const wxString& strName) const
857 {
858 wxConfigPathChanger path(this, strName);
859
860 wxFileConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
861 return pEntry != NULL;
862 }
863
864 // ----------------------------------------------------------------------------
865 // read/write values
866 // ----------------------------------------------------------------------------
867
868 bool wxFileConfig::DoReadString(const wxString& key, wxString* pStr) const
869 {
870 wxConfigPathChanger path(this, key);
871
872 wxFileConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
873 if (pEntry == NULL) {
874 return false;
875 }
876
877 *pStr = pEntry->Value();
878
879 return true;
880 }
881
882 bool wxFileConfig::DoReadLong(const wxString& key, long *pl) const
883 {
884 wxString str;
885 if ( !Read(key, &str) )
886 return false;
887
888 // extra spaces shouldn't prevent us from reading numeric values
889 str.Trim();
890
891 return str.ToLong(pl);
892 }
893
894 bool wxFileConfig::DoWriteString(const wxString& key, const wxString& szValue)
895 {
896 wxConfigPathChanger path(this, key);
897 wxString strName = path.Name();
898
899 wxLogTrace( FILECONF_TRACE_MASK,
900 _T(" Writing String '%s' = '%s' to Group '%s'"),
901 strName.c_str(),
902 szValue.c_str(),
903 GetPath().c_str() );
904
905 if ( strName.IsEmpty() )
906 {
907 // setting the value of a group is an error
908
909 wxASSERT_MSG( wxIsEmpty(szValue), wxT("can't set value of a group!") );
910
911 // ... except if it's empty in which case it's a way to force it's creation
912
913 wxLogTrace( FILECONF_TRACE_MASK,
914 _T(" Creating group %s"),
915 m_pCurrentGroup->Name().c_str() );
916
917 SetDirty();
918
919 // this will add a line for this group if it didn't have it before
920
921 (void)m_pCurrentGroup->GetGroupLine();
922 }
923 else
924 {
925 // writing an entry check that the name is reasonable
926 if ( strName[0u] == wxCONFIG_IMMUTABLE_PREFIX )
927 {
928 wxLogError( _("Config entry name cannot start with '%c'."),
929 wxCONFIG_IMMUTABLE_PREFIX);
930 return false;
931 }
932
933 wxFileConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strName);
934
935 if ( pEntry == 0 )
936 {
937 wxLogTrace( FILECONF_TRACE_MASK,
938 _T(" Adding Entry %s"),
939 strName.c_str() );
940 pEntry = m_pCurrentGroup->AddEntry(strName);
941 }
942
943 wxLogTrace( FILECONF_TRACE_MASK,
944 _T(" Setting value %s"),
945 szValue.c_str() );
946 pEntry->SetValue(szValue);
947
948 SetDirty();
949 }
950
951 return true;
952 }
953
954 bool wxFileConfig::DoWriteLong(const wxString& key, long lValue)
955 {
956 return Write(key, wxString::Format(_T("%ld"), lValue));
957 }
958
959 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
960 {
961 if ( !IsDirty() || !m_strLocalFile )
962 return true;
963
964 // set the umask if needed
965 wxCHANGE_UMASK(m_umask);
966
967 wxTempFile file(m_strLocalFile);
968
969 if ( !file.IsOpened() )
970 {
971 wxLogError(_("can't open user configuration file."));
972 return false;
973 }
974
975 // write all strings to file
976 for ( wxFileConfigLineList *p = m_linesHead; p != NULL; p = p->Next() )
977 {
978 wxString line = p->Text();
979 line += wxTextFile::GetEOL();
980 if ( !file.Write(line, m_conv) )
981 {
982 wxLogError(_("can't write user configuration file."));
983 return false;
984 }
985 }
986
987 if ( !file.Commit() )
988 {
989 wxLogError(_("Failed to update user configuration file."));
990
991 return false;
992 }
993
994 ResetDirty();
995
996 #if defined(__WXMAC__)
997 wxFileName(m_strLocalFile).MacSetTypeAndCreator('TEXT', 'ttxt');
998 #endif // __WXMAC__
999
1000 return true;
1001 }
1002
1003 #if wxUSE_STREAMS
1004
1005 bool wxFileConfig::Save(wxOutputStream& os, wxMBConv& conv)
1006 {
1007 // save unconditionally, even if not dirty
1008 for ( wxFileConfigLineList *p = m_linesHead; p != NULL; p = p->Next() )
1009 {
1010 wxString line = p->Text();
1011 line += wxTextFile::GetEOL();
1012 if ( !os.Write(line.mb_str(conv), line.length()) )
1013 {
1014 wxLogError(_("Error saving user configuration data."));
1015
1016 return false;
1017 }
1018 }
1019
1020 ResetDirty();
1021
1022 return true;
1023 }
1024
1025 #endif // wxUSE_STREAMS
1026
1027 // ----------------------------------------------------------------------------
1028 // renaming groups/entries
1029 // ----------------------------------------------------------------------------
1030
1031 bool wxFileConfig::RenameEntry(const wxString& oldName,
1032 const wxString& newName)
1033 {
1034 wxASSERT_MSG( !wxStrchr(oldName, wxCONFIG_PATH_SEPARATOR),
1035 _T("RenameEntry(): paths are not supported") );
1036
1037 // check that the entry exists
1038 wxFileConfigEntry *oldEntry = m_pCurrentGroup->FindEntry(oldName);
1039 if ( !oldEntry )
1040 return false;
1041
1042 // check that the new entry doesn't already exist
1043 if ( m_pCurrentGroup->FindEntry(newName) )
1044 return false;
1045
1046 // delete the old entry, create the new one
1047 wxString value = oldEntry->Value();
1048 if ( !m_pCurrentGroup->DeleteEntry(oldName) )
1049 return false;
1050
1051 SetDirty();
1052
1053 wxFileConfigEntry *newEntry = m_pCurrentGroup->AddEntry(newName);
1054 newEntry->SetValue(value);
1055
1056 return true;
1057 }
1058
1059 bool wxFileConfig::RenameGroup(const wxString& oldName,
1060 const wxString& newName)
1061 {
1062 // check that the group exists
1063 wxFileConfigGroup *group = m_pCurrentGroup->FindSubgroup(oldName);
1064 if ( !group )
1065 return false;
1066
1067 // check that the new group doesn't already exist
1068 if ( m_pCurrentGroup->FindSubgroup(newName) )
1069 return false;
1070
1071 group->Rename(newName);
1072
1073 SetDirty();
1074
1075 return true;
1076 }
1077
1078 // ----------------------------------------------------------------------------
1079 // delete groups/entries
1080 // ----------------------------------------------------------------------------
1081
1082 bool wxFileConfig::DeleteEntry(const wxString& key, bool bGroupIfEmptyAlso)
1083 {
1084 wxConfigPathChanger path(this, key);
1085
1086 if ( !m_pCurrentGroup->DeleteEntry(path.Name()) )
1087 return false;
1088
1089 if ( bGroupIfEmptyAlso && m_pCurrentGroup->IsEmpty() ) {
1090 if ( m_pCurrentGroup != m_pRootGroup ) {
1091 wxFileConfigGroup *pGroup = m_pCurrentGroup;
1092 SetPath(wxT("..")); // changes m_pCurrentGroup!
1093 if ( m_pCurrentGroup->DeleteSubgroupByName(pGroup->Name()) )
1094 SetDirty();
1095 }
1096 //else: never delete the root group
1097 }
1098
1099 return true;
1100 }
1101
1102 bool wxFileConfig::DeleteGroup(const wxString& key)
1103 {
1104 wxConfigPathChanger path(this, key);
1105
1106 if ( !m_pCurrentGroup->DeleteSubgroupByName(path.Name()) )
1107 return false;
1108
1109 SetDirty();
1110
1111 return true;
1112 }
1113
1114 bool wxFileConfig::DeleteAll()
1115 {
1116 CleanUp();
1117
1118 if ( !m_strLocalFile.empty() )
1119 {
1120 if ( wxFile::Exists(m_strLocalFile) && wxRemove(m_strLocalFile) == -1 )
1121 {
1122 wxLogSysError(_("can't delete user configuration file '%s'"),
1123 m_strLocalFile.c_str());
1124 return false;
1125 }
1126
1127 m_strLocalFile =
1128 m_strGlobalFile = wxT("");
1129 }
1130
1131 Init();
1132
1133 return true;
1134 }
1135
1136 // ----------------------------------------------------------------------------
1137 // linked list functions
1138 // ----------------------------------------------------------------------------
1139
1140 // append a new line to the end of the list
1141
1142 wxFileConfigLineList *wxFileConfig::LineListAppend(const wxString& str)
1143 {
1144 wxLogTrace( FILECONF_TRACE_MASK,
1145 _T(" ** Adding Line '%s'"),
1146 str.c_str() );
1147 wxLogTrace( FILECONF_TRACE_MASK,
1148 _T(" head: %s"),
1149 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1150 wxLogTrace( FILECONF_TRACE_MASK,
1151 _T(" tail: %s"),
1152 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1153
1154 wxFileConfigLineList *pLine = new wxFileConfigLineList(str);
1155
1156 if ( m_linesTail == NULL )
1157 {
1158 // list is empty
1159 m_linesHead = pLine;
1160 }
1161 else
1162 {
1163 // adjust pointers
1164 m_linesTail->SetNext(pLine);
1165 pLine->SetPrev(m_linesTail);
1166 }
1167
1168 m_linesTail = pLine;
1169
1170 wxLogTrace( FILECONF_TRACE_MASK,
1171 _T(" head: %s"),
1172 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1173 wxLogTrace( FILECONF_TRACE_MASK,
1174 _T(" tail: %s"),
1175 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1176
1177 return m_linesTail;
1178 }
1179
1180 // insert a new line after the given one or in the very beginning if !pLine
1181 wxFileConfigLineList *wxFileConfig::LineListInsert(const wxString& str,
1182 wxFileConfigLineList *pLine)
1183 {
1184 wxLogTrace( FILECONF_TRACE_MASK,
1185 _T(" ** Inserting Line '%s' after '%s'"),
1186 str.c_str(),
1187 ((pLine) ? pLine->Text().c_str() : wxEmptyString) );
1188 wxLogTrace( FILECONF_TRACE_MASK,
1189 _T(" head: %s"),
1190 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1191 wxLogTrace( FILECONF_TRACE_MASK,
1192 _T(" tail: %s"),
1193 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1194
1195 if ( pLine == m_linesTail )
1196 return LineListAppend(str);
1197
1198 wxFileConfigLineList *pNewLine = new wxFileConfigLineList(str);
1199 if ( pLine == NULL )
1200 {
1201 // prepend to the list
1202 pNewLine->SetNext(m_linesHead);
1203 m_linesHead->SetPrev(pNewLine);
1204 m_linesHead = pNewLine;
1205 }
1206 else
1207 {
1208 // insert before pLine
1209 wxFileConfigLineList *pNext = pLine->Next();
1210 pNewLine->SetNext(pNext);
1211 pNewLine->SetPrev(pLine);
1212 pNext->SetPrev(pNewLine);
1213 pLine->SetNext(pNewLine);
1214 }
1215
1216 wxLogTrace( FILECONF_TRACE_MASK,
1217 _T(" head: %s"),
1218 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1219 wxLogTrace( FILECONF_TRACE_MASK,
1220 _T(" tail: %s"),
1221 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1222
1223 return pNewLine;
1224 }
1225
1226 void wxFileConfig::LineListRemove(wxFileConfigLineList *pLine)
1227 {
1228 wxLogTrace( FILECONF_TRACE_MASK,
1229 _T(" ** Removing Line '%s'"),
1230 pLine->Text().c_str() );
1231 wxLogTrace( FILECONF_TRACE_MASK,
1232 _T(" head: %s"),
1233 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1234 wxLogTrace( FILECONF_TRACE_MASK,
1235 _T(" tail: %s"),
1236 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1237
1238 wxFileConfigLineList *pPrev = pLine->Prev(),
1239 *pNext = pLine->Next();
1240
1241 // first entry?
1242
1243 if ( pPrev == NULL )
1244 m_linesHead = pNext;
1245 else
1246 pPrev->SetNext(pNext);
1247
1248 // last entry?
1249
1250 if ( pNext == NULL )
1251 m_linesTail = pPrev;
1252 else
1253 pNext->SetPrev(pPrev);
1254
1255 wxLogTrace( FILECONF_TRACE_MASK,
1256 _T(" head: %s"),
1257 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) );
1258 wxLogTrace( FILECONF_TRACE_MASK,
1259 _T(" tail: %s"),
1260 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) );
1261
1262 delete pLine;
1263 }
1264
1265 bool wxFileConfig::LineListIsEmpty()
1266 {
1267 return m_linesHead == NULL;
1268 }
1269
1270 // ============================================================================
1271 // wxFileConfig::wxFileConfigGroup
1272 // ============================================================================
1273
1274 // ----------------------------------------------------------------------------
1275 // ctor/dtor
1276 // ----------------------------------------------------------------------------
1277
1278 // ctor
1279 wxFileConfigGroup::wxFileConfigGroup(wxFileConfigGroup *pParent,
1280 const wxString& strName,
1281 wxFileConfig *pConfig)
1282 : m_aEntries(CompareEntries),
1283 m_aSubgroups(CompareGroups),
1284 m_strName(strName)
1285 {
1286 m_pConfig = pConfig;
1287 m_pParent = pParent;
1288 m_pLine = NULL;
1289
1290 m_pLastEntry = NULL;
1291 m_pLastGroup = NULL;
1292 }
1293
1294 // dtor deletes all children
1295 wxFileConfigGroup::~wxFileConfigGroup()
1296 {
1297 // entries
1298 size_t n, nCount = m_aEntries.Count();
1299 for ( n = 0; n < nCount; n++ )
1300 delete m_aEntries[n];
1301
1302 // subgroups
1303 nCount = m_aSubgroups.Count();
1304 for ( n = 0; n < nCount; n++ )
1305 delete m_aSubgroups[n];
1306 }
1307
1308 // ----------------------------------------------------------------------------
1309 // line
1310 // ----------------------------------------------------------------------------
1311
1312 void wxFileConfigGroup::SetLine(wxFileConfigLineList *pLine)
1313 {
1314 wxASSERT( m_pLine == 0 ); // shouldn't be called twice
1315 m_pLine = pLine;
1316 }
1317
1318 /*
1319 This is a bit complicated, so let me explain it in details. All lines that
1320 were read from the local file (the only one we will ever modify) are stored
1321 in a (doubly) linked list. Our problem is to know at which position in this
1322 list should we insert the new entries/subgroups. To solve it we keep three
1323 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
1324
1325 m_pLine points to the line containing "[group_name]"
1326 m_pLastEntry points to the last entry of this group in the local file.
1327 m_pLastGroup subgroup
1328
1329 Initially, they're NULL all three. When the group (an entry/subgroup) is read
1330 from the local file, the corresponding variable is set. However, if the group
1331 was read from the global file and then modified or created by the application
1332 these variables are still NULL and we need to create the corresponding lines.
1333 See the following functions (and comments preceding them) for the details of
1334 how we do it.
1335
1336 Also, when our last entry/group are deleted we need to find the new last
1337 element - the code in DeleteEntry/Subgroup does this by backtracking the list
1338 of lines until it either founds an entry/subgroup (and this is the new last
1339 element) or the m_pLine of the group, in which case there are no more entries
1340 (or subgroups) left and m_pLast<element> becomes NULL.
1341
1342 NB: This last problem could be avoided for entries if we added new entries
1343 immediately after m_pLine, but in this case the entries would appear
1344 backwards in the config file (OTOH, it's not that important) and as we
1345 would still need to do it for the subgroups the code wouldn't have been
1346 significantly less complicated.
1347 */
1348
1349 // Return the line which contains "[our name]". If we're still not in the list,
1350 // add our line to it immediately after the last line of our parent group if we
1351 // have it or in the very beginning if we're the root group.
1352 wxFileConfigLineList *wxFileConfigGroup::GetGroupLine()
1353 {
1354 wxLogTrace( FILECONF_TRACE_MASK,
1355 _T(" GetGroupLine() for Group '%s'"),
1356 Name().c_str() );
1357
1358 if ( !m_pLine )
1359 {
1360 wxLogTrace( FILECONF_TRACE_MASK,
1361 _T(" Getting Line item pointer") );
1362
1363 wxFileConfigGroup *pParent = Parent();
1364
1365 // this group wasn't present in local config file, add it now
1366 if ( pParent )
1367 {
1368 wxLogTrace( FILECONF_TRACE_MASK,
1369 _T(" checking parent '%s'"),
1370 pParent->Name().c_str() );
1371
1372 wxString strFullName;
1373
1374 // add 1 to the name because we don't want to start with '/'
1375 strFullName << wxT("[")
1376 << FilterOutEntryName(GetFullName().c_str() + 1)
1377 << wxT("]");
1378 m_pLine = m_pConfig->LineListInsert(strFullName,
1379 pParent->GetLastGroupLine());
1380 pParent->SetLastGroup(this); // we're surely after all the others
1381 }
1382 //else: this is the root group and so we return NULL because we don't
1383 // have any group line
1384 }
1385
1386 return m_pLine;
1387 }
1388
1389 // Return the last line belonging to the subgroups of this group (after which
1390 // we can add a new subgroup), if we don't have any subgroups or entries our
1391 // last line is the group line (m_pLine) itself.
1392 wxFileConfigLineList *wxFileConfigGroup::GetLastGroupLine()
1393 {
1394 // if we have any subgroups, our last line is the last line of the last
1395 // subgroup
1396 if ( m_pLastGroup )
1397 {
1398 wxFileConfigLineList *pLine = m_pLastGroup->GetLastGroupLine();
1399
1400 wxASSERT_MSG( pLine, _T("last group must have !NULL associated line") );
1401
1402 return pLine;
1403 }
1404
1405 // no subgroups, so the last line is the line of thelast entry (if any)
1406 return GetLastEntryLine();
1407 }
1408
1409 // return the last line belonging to the entries of this group (after which
1410 // we can add a new entry), if we don't have any entries we will add the new
1411 // one immediately after the group line itself.
1412 wxFileConfigLineList *wxFileConfigGroup::GetLastEntryLine()
1413 {
1414 wxLogTrace( FILECONF_TRACE_MASK,
1415 _T(" GetLastEntryLine() for Group '%s'"),
1416 Name().c_str() );
1417
1418 if ( m_pLastEntry )
1419 {
1420 wxFileConfigLineList *pLine = m_pLastEntry->GetLine();
1421
1422 wxASSERT_MSG( pLine, _T("last entry must have !NULL associated line") );
1423
1424 return pLine;
1425 }
1426
1427 // no entries: insert after the group header, if any
1428 return GetGroupLine();
1429 }
1430
1431 void wxFileConfigGroup::SetLastEntry(wxFileConfigEntry *pEntry)
1432 {
1433 m_pLastEntry = pEntry;
1434
1435 if ( !m_pLine )
1436 {
1437 // the only situation in which a group without its own line can have
1438 // an entry is when the first entry is added to the initially empty
1439 // root pseudo-group
1440 wxASSERT_MSG( !m_pParent, _T("unexpected for non root group") );
1441
1442 // let the group know that it does have a line in the file now
1443 m_pLine = pEntry->GetLine();
1444 }
1445 }
1446
1447 // ----------------------------------------------------------------------------
1448 // group name
1449 // ----------------------------------------------------------------------------
1450
1451 void wxFileConfigGroup::UpdateGroupAndSubgroupsLines()
1452 {
1453 // update the line of this group
1454 wxFileConfigLineList *line = GetGroupLine();
1455 wxCHECK_RET( line, _T("a non root group must have a corresponding line!") );
1456
1457 // +1: skip the leading '/'
1458 line->SetText(wxString::Format(_T("[%s]"), GetFullName().c_str() + 1));
1459
1460
1461 // also update all subgroups as they have this groups name in their lines
1462 const size_t nCount = m_aSubgroups.Count();
1463 for ( size_t n = 0; n < nCount; n++ )
1464 {
1465 m_aSubgroups[n]->UpdateGroupAndSubgroupsLines();
1466 }
1467 }
1468
1469 void wxFileConfigGroup::Rename(const wxString& newName)
1470 {
1471 wxCHECK_RET( m_pParent, _T("the root group can't be renamed") );
1472
1473 m_strName = newName;
1474
1475 // update the group lines recursively
1476 UpdateGroupAndSubgroupsLines();
1477 }
1478
1479 wxString wxFileConfigGroup::GetFullName() const
1480 {
1481 wxString fullname;
1482 if ( Parent() )
1483 fullname = Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR + Name();
1484
1485 return fullname;
1486 }
1487
1488 // ----------------------------------------------------------------------------
1489 // find an item
1490 // ----------------------------------------------------------------------------
1491
1492 // use binary search because the array is sorted
1493 wxFileConfigEntry *
1494 wxFileConfigGroup::FindEntry(const wxChar *szName) const
1495 {
1496 size_t i,
1497 lo = 0,
1498 hi = m_aEntries.Count();
1499 int res;
1500 wxFileConfigEntry *pEntry;
1501
1502 while ( lo < hi ) {
1503 i = (lo + hi)/2;
1504 pEntry = m_aEntries[i];
1505
1506 #if wxCONFIG_CASE_SENSITIVE
1507 res = wxStrcmp(pEntry->Name(), szName);
1508 #else
1509 res = wxStricmp(pEntry->Name(), szName);
1510 #endif
1511
1512 if ( res > 0 )
1513 hi = i;
1514 else if ( res < 0 )
1515 lo = i + 1;
1516 else
1517 return pEntry;
1518 }
1519
1520 return NULL;
1521 }
1522
1523 wxFileConfigGroup *
1524 wxFileConfigGroup::FindSubgroup(const wxChar *szName) const
1525 {
1526 size_t i,
1527 lo = 0,
1528 hi = m_aSubgroups.Count();
1529 int res;
1530 wxFileConfigGroup *pGroup;
1531
1532 while ( lo < hi ) {
1533 i = (lo + hi)/2;
1534 pGroup = m_aSubgroups[i];
1535
1536 #if wxCONFIG_CASE_SENSITIVE
1537 res = wxStrcmp(pGroup->Name(), szName);
1538 #else
1539 res = wxStricmp(pGroup->Name(), szName);
1540 #endif
1541
1542 if ( res > 0 )
1543 hi = i;
1544 else if ( res < 0 )
1545 lo = i + 1;
1546 else
1547 return pGroup;
1548 }
1549
1550 return NULL;
1551 }
1552
1553 // ----------------------------------------------------------------------------
1554 // create a new item
1555 // ----------------------------------------------------------------------------
1556
1557 // create a new entry and add it to the current group
1558 wxFileConfigEntry *wxFileConfigGroup::AddEntry(const wxString& strName, int nLine)
1559 {
1560 wxASSERT( FindEntry(strName) == 0 );
1561
1562 wxFileConfigEntry *pEntry = new wxFileConfigEntry(this, strName, nLine);
1563
1564 m_aEntries.Add(pEntry);
1565 return pEntry;
1566 }
1567
1568 // create a new group and add it to the current group
1569 wxFileConfigGroup *wxFileConfigGroup::AddSubgroup(const wxString& strName)
1570 {
1571 wxASSERT( FindSubgroup(strName) == 0 );
1572
1573 wxFileConfigGroup *pGroup = new wxFileConfigGroup(this, strName, m_pConfig);
1574
1575 m_aSubgroups.Add(pGroup);
1576 return pGroup;
1577 }
1578
1579 // ----------------------------------------------------------------------------
1580 // delete an item
1581 // ----------------------------------------------------------------------------
1582
1583 /*
1584 The delete operations are _very_ slow if we delete the last item of this
1585 group (see comments before GetXXXLineXXX functions for more details),
1586 so it's much better to start with the first entry/group if we want to
1587 delete several of them.
1588 */
1589
1590 bool wxFileConfigGroup::DeleteSubgroupByName(const wxChar *szName)
1591 {
1592 wxFileConfigGroup * const pGroup = FindSubgroup(szName);
1593
1594 return pGroup ? DeleteSubgroup(pGroup) : false;
1595 }
1596
1597 // Delete the subgroup and remove all references to it from
1598 // other data structures.
1599 bool wxFileConfigGroup::DeleteSubgroup(wxFileConfigGroup *pGroup)
1600 {
1601 wxCHECK_MSG( pGroup, false, _T("deleting non existing group?") );
1602
1603 wxLogTrace( FILECONF_TRACE_MASK,
1604 _T("Deleting group '%s' from '%s'"),
1605 pGroup->Name().c_str(),
1606 Name().c_str() );
1607
1608 wxLogTrace( FILECONF_TRACE_MASK,
1609 _T(" (m_pLine) = prev: %p, this %p, next %p"),
1610 ((m_pLine) ? m_pLine->Prev() : 0),
1611 m_pLine,
1612 ((m_pLine) ? m_pLine->Next() : 0) );
1613 wxLogTrace( FILECONF_TRACE_MASK,
1614 _T(" text: '%s'"),
1615 ((m_pLine) ? m_pLine->Text().c_str() : wxEmptyString) );
1616
1617 // delete all entries...
1618 size_t nCount = pGroup->m_aEntries.Count();
1619
1620 wxLogTrace(FILECONF_TRACE_MASK,
1621 _T("Removing %lu entries"), (unsigned long)nCount );
1622
1623 for ( size_t nEntry = 0; nEntry < nCount; nEntry++ )
1624 {
1625 wxFileConfigLineList *pLine = pGroup->m_aEntries[nEntry]->GetLine();
1626
1627 if ( pLine )
1628 {
1629 wxLogTrace( FILECONF_TRACE_MASK,
1630 _T(" '%s'"),
1631 pLine->Text().c_str() );
1632 m_pConfig->LineListRemove(pLine);
1633 }
1634 }
1635
1636 // ...and subgroups of this subgroup
1637 nCount = pGroup->m_aSubgroups.Count();
1638
1639 wxLogTrace( FILECONF_TRACE_MASK,
1640 _T("Removing %lu subgroups"), (unsigned long)nCount );
1641
1642 for ( size_t nGroup = 0; nGroup < nCount; nGroup++ )
1643 {
1644 pGroup->DeleteSubgroup(pGroup->m_aSubgroups[0]);
1645 }
1646
1647 // and then finally the group itself
1648 wxFileConfigLineList *pLine = pGroup->m_pLine;
1649 if ( pLine )
1650 {
1651 wxLogTrace( FILECONF_TRACE_MASK,
1652 _T(" Removing line for group '%s' : '%s'"),
1653 pGroup->Name().c_str(),
1654 pLine->Text().c_str() );
1655 wxLogTrace( FILECONF_TRACE_MASK,
1656 _T(" Removing from group '%s' : '%s'"),
1657 Name().c_str(),
1658 ((m_pLine) ? m_pLine->Text().c_str() : wxEmptyString) );
1659
1660 // notice that we may do this test inside the previous "if"
1661 // because the last entry's line is surely !NULL
1662 if ( pGroup == m_pLastGroup )
1663 {
1664 wxLogTrace( FILECONF_TRACE_MASK,
1665 _T(" Removing last group") );
1666
1667 // our last entry is being deleted, so find the last one which
1668 // stays by going back until we find a subgroup or reach the
1669 // group line
1670 const size_t nSubgroups = m_aSubgroups.Count();
1671
1672 m_pLastGroup = NULL;
1673 for ( wxFileConfigLineList *pl = pLine->Prev();
1674 pl && pl != m_pLine && !m_pLastGroup;
1675 pl = pl->Prev() )
1676 {
1677 // does this line belong to our subgroup?
1678 for ( size_t n = 0; n < nSubgroups; n++ )
1679 {
1680 // do _not_ call GetGroupLine! we don't want to add it to
1681 // the local file if it's not already there
1682 if ( m_aSubgroups[n]->m_pLine == pl )
1683 {
1684 m_pLastGroup = m_aSubgroups[n];
1685 break;
1686 }
1687 }
1688 }
1689 }
1690
1691 m_pConfig->LineListRemove(pLine);
1692 }
1693 else
1694 {
1695 wxLogTrace( FILECONF_TRACE_MASK,
1696 _T(" No line entry for Group '%s'?"),
1697 pGroup->Name().c_str() );
1698 }
1699
1700 m_aSubgroups.Remove(pGroup);
1701 delete pGroup;
1702
1703 return true;
1704 }
1705
1706 bool wxFileConfigGroup::DeleteEntry(const wxChar *szName)
1707 {
1708 wxFileConfigEntry *pEntry = FindEntry(szName);
1709 if ( !pEntry )
1710 {
1711 // entry doesn't exist, nothing to do
1712 return false;
1713 }
1714
1715 wxFileConfigLineList *pLine = pEntry->GetLine();
1716 if ( pLine != NULL ) {
1717 // notice that we may do this test inside the previous "if" because the
1718 // last entry's line is surely !NULL
1719 if ( pEntry == m_pLastEntry ) {
1720 // our last entry is being deleted - find the last one which stays
1721 wxASSERT( m_pLine != NULL ); // if we have an entry with !NULL pLine...
1722
1723 // go back until we find another entry or reach the group's line
1724 wxFileConfigEntry *pNewLast = NULL;
1725 size_t n, nEntries = m_aEntries.Count();
1726 wxFileConfigLineList *pl;
1727 for ( pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() ) {
1728 // is it our subgroup?
1729 for ( n = 0; (pNewLast == NULL) && (n < nEntries); n++ ) {
1730 if ( m_aEntries[n]->GetLine() == m_pLine )
1731 pNewLast = m_aEntries[n];
1732 }
1733
1734 if ( pNewLast != NULL ) // found?
1735 break;
1736 }
1737
1738 if ( pl == m_pLine ) {
1739 wxASSERT( !pNewLast ); // how comes it has the same line as we?
1740
1741 // we've reached the group line without finding any subgroups
1742 m_pLastEntry = NULL;
1743 }
1744 else
1745 m_pLastEntry = pNewLast;
1746 }
1747
1748 m_pConfig->LineListRemove(pLine);
1749 }
1750
1751 m_aEntries.Remove(pEntry);
1752 delete pEntry;
1753
1754 return true;
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_bHasValue = false;
1776
1777 m_bImmutable = strName[0] == wxCONFIG_IMMUTABLE_PREFIX;
1778 if ( m_bImmutable )
1779 m_strName.erase(0, 1); // remove first character
1780 }
1781
1782 // ----------------------------------------------------------------------------
1783 // set value
1784 // ----------------------------------------------------------------------------
1785
1786 void wxFileConfigEntry::SetLine(wxFileConfigLineList *pLine)
1787 {
1788 if ( m_pLine != NULL ) {
1789 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1790 Name().c_str(), m_pParent->GetFullName().c_str());
1791 }
1792
1793 m_pLine = pLine;
1794 Group()->SetLastEntry(this);
1795 }
1796
1797 // second parameter is false if we read the value from file and prevents the
1798 // entry from being marked as 'dirty'
1799 void wxFileConfigEntry::SetValue(const wxString& strValue, bool bUser)
1800 {
1801 if ( bUser && IsImmutable() )
1802 {
1803 wxLogWarning( _("attempt to change immutable key '%s' ignored."),
1804 Name().c_str());
1805 return;
1806 }
1807
1808 // do nothing if it's the same value: but don't test for it if m_bHasValue
1809 // hadn't been set yet or we'd never write empty values to the file
1810 if ( m_bHasValue && strValue == m_strValue )
1811 return;
1812
1813 m_bHasValue = true;
1814 m_strValue = strValue;
1815
1816 if ( bUser )
1817 {
1818 wxString strValFiltered;
1819
1820 if ( Group()->Config()->GetStyle() & wxCONFIG_USE_NO_ESCAPE_CHARACTERS )
1821 {
1822 strValFiltered = strValue;
1823 }
1824 else {
1825 strValFiltered = FilterOutValue(strValue);
1826 }
1827
1828 wxString strLine;
1829 strLine << FilterOutEntryName(m_strName) << wxT('=') << strValFiltered;
1830
1831 if ( m_pLine )
1832 {
1833 // entry was read from the local config file, just modify the line
1834 m_pLine->SetText(strLine);
1835 }
1836 else // this entry didn't exist in the local file
1837 {
1838 // add a new line to the file
1839 wxFileConfigLineList *line = Group()->GetLastEntryLine();
1840 m_pLine = Group()->Config()->LineListInsert(strLine, line);
1841
1842 Group()->SetLastEntry(this);
1843 }
1844 }
1845 }
1846
1847 // ============================================================================
1848 // global functions
1849 // ============================================================================
1850
1851 // ----------------------------------------------------------------------------
1852 // compare functions for array sorting
1853 // ----------------------------------------------------------------------------
1854
1855 int CompareEntries(wxFileConfigEntry *p1, wxFileConfigEntry *p2)
1856 {
1857 #if wxCONFIG_CASE_SENSITIVE
1858 return wxStrcmp(p1->Name(), p2->Name());
1859 #else
1860 return wxStricmp(p1->Name(), p2->Name());
1861 #endif
1862 }
1863
1864 int CompareGroups(wxFileConfigGroup *p1, wxFileConfigGroup *p2)
1865 {
1866 #if wxCONFIG_CASE_SENSITIVE
1867 return wxStrcmp(p1->Name(), p2->Name());
1868 #else
1869 return wxStricmp(p1->Name(), p2->Name());
1870 #endif
1871 }
1872
1873 // ----------------------------------------------------------------------------
1874 // filter functions
1875 // ----------------------------------------------------------------------------
1876
1877 // undo FilterOutValue
1878 static wxString FilterInValue(const wxString& str)
1879 {
1880 wxString strResult;
1881 strResult.Alloc(str.Len());
1882
1883 bool bQuoted = !str.IsEmpty() && str[0] == '"';
1884
1885 for ( size_t n = bQuoted ? 1 : 0; n < str.Len(); n++ ) {
1886 if ( str[n] == wxT('\\') ) {
1887 switch ( str[++n] ) {
1888 case wxT('n'):
1889 strResult += wxT('\n');
1890 break;
1891
1892 case wxT('r'):
1893 strResult += wxT('\r');
1894 break;
1895
1896 case wxT('t'):
1897 strResult += wxT('\t');
1898 break;
1899
1900 case wxT('\\'):
1901 strResult += wxT('\\');
1902 break;
1903
1904 case wxT('"'):
1905 strResult += wxT('"');
1906 break;
1907 }
1908 }
1909 else {
1910 if ( str[n] != wxT('"') || !bQuoted )
1911 strResult += str[n];
1912 else if ( n != str.Len() - 1 ) {
1913 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1914 n, str.c_str());
1915 }
1916 //else: it's the last quote of a quoted string, ok
1917 }
1918 }
1919
1920 return strResult;
1921 }
1922
1923 // quote the string before writing it to file
1924 static wxString FilterOutValue(const wxString& str)
1925 {
1926 if ( !str )
1927 return str;
1928
1929 wxString strResult;
1930 strResult.Alloc(str.Len());
1931
1932 // quoting is necessary to preserve spaces in the beginning of the string
1933 bool bQuote = wxIsspace(str[0]) || str[0] == wxT('"');
1934
1935 if ( bQuote )
1936 strResult += wxT('"');
1937
1938 wxChar c;
1939 for ( size_t n = 0; n < str.Len(); n++ ) {
1940 switch ( str[n] ) {
1941 case wxT('\n'):
1942 c = wxT('n');
1943 break;
1944
1945 case wxT('\r'):
1946 c = wxT('r');
1947 break;
1948
1949 case wxT('\t'):
1950 c = wxT('t');
1951 break;
1952
1953 case wxT('\\'):
1954 c = wxT('\\');
1955 break;
1956
1957 case wxT('"'):
1958 if ( bQuote ) {
1959 c = wxT('"');
1960 break;
1961 }
1962 //else: fall through
1963
1964 default:
1965 strResult += str[n];
1966 continue; // nothing special to do
1967 }
1968
1969 // we get here only for special characters
1970 strResult << wxT('\\') << c;
1971 }
1972
1973 if ( bQuote )
1974 strResult += wxT('"');
1975
1976 return strResult;
1977 }
1978
1979 // undo FilterOutEntryName
1980 static wxString FilterInEntryName(const wxString& str)
1981 {
1982 wxString strResult;
1983 strResult.Alloc(str.Len());
1984
1985 for ( const wxChar *pc = str.c_str(); *pc != '\0'; pc++ ) {
1986 if ( *pc == wxT('\\') )
1987 pc++;
1988
1989 strResult += *pc;
1990 }
1991
1992 return strResult;
1993 }
1994
1995 // sanitize entry or group name: insert '\\' before any special characters
1996 static wxString FilterOutEntryName(const wxString& str)
1997 {
1998 wxString strResult;
1999 strResult.Alloc(str.Len());
2000
2001 for ( const wxChar *pc = str.c_str(); *pc != wxT('\0'); pc++ ) {
2002 const wxChar c = *pc;
2003
2004 // we explicitly allow some of "safe" chars and 8bit ASCII characters
2005 // which will probably never have special meaning and with which we can't
2006 // use isalnum() anyhow (in ASCII built, in Unicode it's just fine)
2007 //
2008 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
2009 // should *not* be quoted
2010 if (
2011 #if !wxUSE_UNICODE
2012 ((unsigned char)c < 127) &&
2013 #endif // ANSI
2014 !wxIsalnum(c) && !wxStrchr(wxT("@_/-!.*%"), c) )
2015 {
2016 strResult += wxT('\\');
2017 }
2018
2019 strResult += c;
2020 }
2021
2022 return strResult;
2023 }
2024
2025 // we can't put ?: in the ctor initializer list because it confuses some
2026 // broken compilers (Borland C++)
2027 static wxString GetAppName(const wxString& appName)
2028 {
2029 if ( !appName && wxTheApp )
2030 return wxTheApp->GetAppName();
2031 else
2032 return appName;
2033 }
2034
2035 #endif // wxUSE_CONFIG
2036