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