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