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