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