home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / wxos2233.zip / wxOS2-2_3_3.zip / wxWindows-2.3.3 / src / common / fileconf.cpp < prev    next >
C/C++ Source or Header  |  2002-08-21  |  54KB  |  1,800 lines

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