]> git.saurik.com Git - wxWidgets.git/blame - src/common/fileconf.cpp
Fix calculation of the margins for owner-drawn menu items.
[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
3d5231db 49#if defined(__WXMSW__)
670f9935 50 #include "wx/msw/private.h"
1f905dc5 51#endif //windows.h
c2ff79b1 52#if defined(__WXPM__)
670f9935
WS
53 #define INCL_DOS
54 #include <os2.h>
c2ff79b1 55#endif
c801d85f 56
1f905dc5
VZ
57#include <stdlib.h>
58#include <ctype.h>
c801d85f 59
1c4a764c
VZ
60// ----------------------------------------------------------------------------
61// constants
62// ----------------------------------------------------------------------------
0516e0e8 63
1c4a764c 64#ifndef MAX_PATH
670f9935 65 #define MAX_PATH 512
1c4a764c
VZ
66#endif
67
9a83f860 68#define FILECONF_TRACE_MASK wxT("fileconf")
701fb11e 69
c801d85f
KB
70// ----------------------------------------------------------------------------
71// global functions declarations
72// ----------------------------------------------------------------------------
73
f5ae0449 74// compare functions for sorting the arrays
0516e0e8
VZ
75static int LINKAGEMODE CompareEntries(wxFileConfigEntry *p1, wxFileConfigEntry *p2);
76static int LINKAGEMODE CompareGroups(wxFileConfigGroup *p1, wxFileConfigGroup *p2);
f5ae0449 77
c801d85f 78// filter strings
b8e9dd43
VZ
79static wxString FilterInValue(const wxString& str);
80static wxString FilterOutValue(const wxString& str);
81
82static wxString FilterInEntryName(const wxString& str);
83static wxString FilterOutEntryName(const wxString& str);
c801d85f 84
004c69ab
VZ
85// get the name to use in wxFileConfig ctor
86static wxString GetAppName(const wxString& appname);
87
0516e0e8
VZ
88// ============================================================================
89// private classes
90// ============================================================================
91
92// ----------------------------------------------------------------------------
93// "template" array types
94// ----------------------------------------------------------------------------
95
34dceb47
VZ
96#ifdef WXMAKINGDLL_BASE
97 WX_DEFINE_SORTED_USER_EXPORTED_ARRAY(wxFileConfigEntry *, ArrayEntries,
e33f6503 98 WXDLLIMPEXP_BASE);
34dceb47 99 WX_DEFINE_SORTED_USER_EXPORTED_ARRAY(wxFileConfigGroup *, ArrayGroups,
e33f6503 100 WXDLLIMPEXP_BASE);
34dceb47
VZ
101#else
102 WX_DEFINE_SORTED_ARRAY(wxFileConfigEntry *, ArrayEntries);
103 WX_DEFINE_SORTED_ARRAY(wxFileConfigGroup *, ArrayGroups);
104#endif
0516e0e8
VZ
105
106// ----------------------------------------------------------------------------
107// wxFileConfigLineList
108// ----------------------------------------------------------------------------
109
110// we store all lines of the local config file as a linked list in memory
111class wxFileConfigLineList
112{
113public:
114 void SetNext(wxFileConfigLineList *pNext) { m_pNext = pNext; }
115 void SetPrev(wxFileConfigLineList *pPrev) { m_pPrev = pPrev; }
116
117 // ctor
118 wxFileConfigLineList(const wxString& str,
119 wxFileConfigLineList *pNext = NULL) : m_strLine(str)
120 { SetNext(pNext); SetPrev(NULL); }
121
122 // next/prev nodes in the linked list
123 wxFileConfigLineList *Next() const { return m_pNext; }
124 wxFileConfigLineList *Prev() const { return m_pPrev; }
125
126 // get/change lines text
127 void SetText(const wxString& str) { m_strLine = str; }
128 const wxString& Text() const { return m_strLine; }
129
130private:
131 wxString m_strLine; // line contents
132 wxFileConfigLineList *m_pNext, // next node
133 *m_pPrev; // previous one
22f3361e 134
c0c133e1 135 wxDECLARE_NO_COPY_CLASS(wxFileConfigLineList);
0516e0e8
VZ
136};
137
138// ----------------------------------------------------------------------------
139// wxFileConfigEntry: a name/value pair
140// ----------------------------------------------------------------------------
141
142class wxFileConfigEntry
143{
144private:
145 wxFileConfigGroup *m_pParent; // group that contains us
146
147 wxString m_strName, // entry name
148 m_strValue; // value
e1cc6874 149 bool m_bImmutable:1, // can be overriden locally?
0516e0e8
VZ
150 m_bHasValue:1; // set after first call to SetValue()
151
152 int m_nLine; // used if m_pLine == NULL only
153
154 // pointer to our line in the linked list or NULL if it was found in global
155 // file (which we don't modify)
156 wxFileConfigLineList *m_pLine;
157
158public:
159 wxFileConfigEntry(wxFileConfigGroup *pParent,
160 const wxString& strName, int nLine);
161
162 // simple accessors
163 const wxString& Name() const { return m_strName; }
164 const wxString& Value() const { return m_strValue; }
165 wxFileConfigGroup *Group() const { return m_pParent; }
0516e0e8
VZ
166 bool IsImmutable() const { return m_bImmutable; }
167 bool IsLocal() const { return m_pLine != 0; }
168 int Line() const { return m_nLine; }
169 wxFileConfigLineList *
170 GetLine() const { return m_pLine; }
171
172 // modify entry attributes
a62848fd 173 void SetValue(const wxString& strValue, bool bUser = true);
0516e0e8 174 void SetLine(wxFileConfigLineList *pLine);
22f3361e 175
c0c133e1 176 wxDECLARE_NO_COPY_CLASS(wxFileConfigEntry);
0516e0e8
VZ
177};
178
179// ----------------------------------------------------------------------------
180// wxFileConfigGroup: container of entries and other groups
181// ----------------------------------------------------------------------------
182
183class wxFileConfigGroup
184{
185private:
186 wxFileConfig *m_pConfig; // config object we belong to
187 wxFileConfigGroup *m_pParent; // parent group (NULL for root group)
188 ArrayEntries m_aEntries; // entries in this group
189 ArrayGroups m_aSubgroups; // subgroups
190 wxString m_strName; // group's name
0516e0e8
VZ
191 wxFileConfigLineList *m_pLine; // pointer to our line in the linked list
192 wxFileConfigEntry *m_pLastEntry; // last entry/subgroup of this group in the
193 wxFileConfigGroup *m_pLastGroup; // local file (we insert new ones after it)
194
195 // DeleteSubgroupByName helper
196 bool DeleteSubgroup(wxFileConfigGroup *pGroup);
197
6dda7a75
VZ
198 // used by Rename()
199 void UpdateGroupAndSubgroupsLines();
200
0516e0e8
VZ
201public:
202 // ctor
203 wxFileConfigGroup(wxFileConfigGroup *pParent, const wxString& strName, wxFileConfig *);
204
205 // dtor deletes all entries and subgroups also
206 ~wxFileConfigGroup();
207
208 // simple accessors
209 const wxString& Name() const { return m_strName; }
210 wxFileConfigGroup *Parent() const { return m_pParent; }
211 wxFileConfig *Config() const { return m_pConfig; }
0516e0e8
VZ
212
213 const ArrayEntries& Entries() const { return m_aEntries; }
214 const ArrayGroups& Groups() const { return m_aSubgroups; }
215 bool IsEmpty() const { return Entries().IsEmpty() && Groups().IsEmpty(); }
216
217 // find entry/subgroup (NULL if not found)
86501081
VS
218 wxFileConfigGroup *FindSubgroup(const wxString& name) const;
219 wxFileConfigEntry *FindEntry (const wxString& name) const;
0516e0e8 220
a62848fd 221 // delete entry/subgroup, return false if doesn't exist
86501081
VS
222 bool DeleteSubgroupByName(const wxString& name);
223 bool DeleteEntry(const wxString& name);
0516e0e8
VZ
224
225 // create new entry/subgroup returning pointer to newly created element
226 wxFileConfigGroup *AddSubgroup(const wxString& strName);
227 wxFileConfigEntry *AddEntry (const wxString& strName, int nLine = wxNOT_FOUND);
228
0516e0e8
VZ
229 void SetLine(wxFileConfigLineList *pLine);
230
231 // rename: no checks are done to ensure that the name is unique!
232 void Rename(const wxString& newName);
233
234 //
235 wxString GetFullName() const;
236
237 // get the last line belonging to an entry/subgroup of this group
238 wxFileConfigLineList *GetGroupLine(); // line which contains [group]
9a7b7798 239 // may be NULL for "/" only
0516e0e8
VZ
240 wxFileConfigLineList *GetLastEntryLine(); // after which our subgroups start
241 wxFileConfigLineList *GetLastGroupLine(); // after which the next group starts
242
243 // called by entries/subgroups when they're created/deleted
128e0251
VZ
244 void SetLastEntry(wxFileConfigEntry *pEntry);
245 void SetLastGroup(wxFileConfigGroup *pGroup)
246 { m_pLastGroup = pGroup; }
22f3361e 247
c0c133e1 248 wxDECLARE_NO_COPY_CLASS(wxFileConfigGroup);
0516e0e8
VZ
249};
250
1f905dc5
VZ
251// ============================================================================
252// implementation
253// ============================================================================
254
c801d85f 255// ----------------------------------------------------------------------------
1f905dc5 256// static functions
c801d85f 257// ----------------------------------------------------------------------------
1f905dc5 258
466e87bd
VZ
259// this function modifies in place the given wxFileName object if it doesn't
260// already have an extension
261//
262// note that it's slightly misnamed under Mac as there it doesn't add an
263// extension but modifies the file name instead, so you shouldn't suppose that
264// fn.HasExt() is true after it returns
265static void AddConfFileExtIfNeeded(wxFileName& fn)
266{
267 if ( !fn.HasExt() )
c2ff79b1 268 {
466e87bd
VZ
269#if defined( __WXMAC__ )
270 fn.SetName(fn.GetName() + wxT(" Preferences"));
271#elif defined( __UNIX__ )
b0bcc787 272 fn.SetExt(wxT("conf"));
466e87bd 273#else // Windows
b0bcc787 274 fn.SetExt(wxT("ini"));
466e87bd 275#endif // UNIX/Win
c2ff79b1 276 }
1f905dc5
VZ
277}
278
466e87bd 279wxString wxFileConfig::GetGlobalDir()
c801d85f 280{
466e87bd 281 return wxStandardPaths::Get().GetConfigDir();
da468d38
VZ
282}
283
466e87bd 284wxString wxFileConfig::GetLocalDir(int style)
da468d38 285{
466e87bd 286 wxUnusedVar(style);
da468d38 287
466e87bd 288 wxStandardPathsBase& stdp = wxStandardPaths::Get();
2b5f62a0 289
466e87bd
VZ
290 // it so happens that user data directory is a subdirectory of user config
291 // directory on all supported platforms, which explains why we use it here
292 return style & wxCONFIG_USE_SUBDIR ? stdp.GetUserDataDir()
293 : stdp.GetUserConfigDir();
da468d38
VZ
294}
295
466e87bd 296wxFileName wxFileConfig::GetGlobalFile(const wxString& szFile)
da468d38 297{
466e87bd 298 wxFileName fn(GetGlobalDir(), szFile);
8dce54c9 299
466e87bd 300 AddConfFileExtIfNeeded(fn);
da468d38 301
466e87bd
VZ
302 return fn;
303}
da468d38 304
466e87bd
VZ
305wxFileName wxFileConfig::GetLocalFile(const wxString& szFile, int style)
306{
307 wxFileName fn(GetLocalDir(style), szFile);
8bbe427f 308
0cd1c6e3 309#if defined( __UNIX__ ) && !defined( __WXMAC__ )
466e87bd
VZ
310 if ( !(style & wxCONFIG_USE_SUBDIR) )
311 {
312 // dot-files under Unix start with, well, a dot (but OTOH they usually
313 // don't have any specific extension)
314 fn.SetName(wxT('.') + fn.GetName());
315 }
316 else // we do append ".conf" extension to config files in subdirectories
bba0174a 317#endif // defined( __UNIX__ ) && !defined( __WXMAC__ )
466e87bd
VZ
318 {
319 AddConfFileExtIfNeeded(fn);
320 }
2b5f62a0 321
466e87bd 322 return fn;
1f905dc5 323}
c801d85f
KB
324
325// ----------------------------------------------------------------------------
326// ctor
327// ----------------------------------------------------------------------------
c4ec0ce8 328IMPLEMENT_ABSTRACT_CLASS(wxFileConfig, wxConfigBase)
c801d85f
KB
329
330void wxFileConfig::Init()
331{
2b5f62a0 332 m_pCurrentGroup =
b494c48b 333 m_pRootGroup = new wxFileConfigGroup(NULL, wxEmptyString, this);
c801d85f 334
2b5f62a0
VZ
335 m_linesHead =
336 m_linesTail = NULL;
c801d85f 337
2b5f62a0 338 // It's not an error if (one of the) file(s) doesn't exist.
c801d85f 339
2b5f62a0 340 // parse the global file
d2ea08b9 341 if ( m_fnGlobalFile.IsOk() && m_fnGlobalFile.FileExists() )
2b5f62a0 342 {
466e87bd 343 wxTextFile fileGlobal(m_fnGlobalFile.GetFullPath());
c801d85f 344
e835ec01 345 if ( fileGlobal.Open(*m_conv/*ignored in ANSI build*/) )
2b5f62a0 346 {
a62848fd 347 Parse(fileGlobal, false /* global */);
2b5f62a0
VZ
348 SetRootPath();
349 }
350 else
351 {
466e87bd 352 wxLogWarning(_("can't open global configuration file '%s'."), m_fnGlobalFile.GetFullPath().c_str());
2b5f62a0 353 }
c801d85f 354 }
c801d85f 355
2b5f62a0 356 // parse the local file
d2ea08b9 357 if ( m_fnLocalFile.IsOk() && m_fnLocalFile.FileExists() )
2b5f62a0 358 {
466e87bd 359 wxTextFile fileLocal(m_fnLocalFile.GetFullPath());
e835ec01 360 if ( fileLocal.Open(*m_conv/*ignored in ANSI build*/) )
2b5f62a0 361 {
a62848fd 362 Parse(fileLocal, true /* local */);
2b5f62a0
VZ
363 SetRootPath();
364 }
365 else
366 {
bf6d45eb
VZ
367 const wxString path = m_fnLocalFile.GetFullPath();
368 wxLogWarning(_("can't open user configuration file '%s'."),
369 path.c_str());
370
371 if ( m_fnLocalFile.FileExists() )
372 {
373 wxLogWarning(_("Changes won't be saved to avoid overwriting the existing file \"%s\""),
374 path.c_str());
375 m_fnLocalFile.Clear();
376 }
2b5f62a0 377 }
c801d85f 378 }
e1cc6874
VZ
379
380 m_isDirty = false;
c801d85f
KB
381}
382
b3031762 383// constructor supports creation of wxFileConfig objects of any type
18244936 384wxFileConfig::wxFileConfig(const wxString& appName, const wxString& vendorName,
b3031762 385 const wxString& strLocal, const wxString& strGlobal,
830f8f11
VZ
386 long style,
387 const wxMBConv& conv)
004c69ab
VZ
388 : wxConfigBase(::GetAppName(appName), vendorName,
389 strLocal, strGlobal,
390 style),
466e87bd
VZ
391 m_fnLocalFile(strLocal),
392 m_fnGlobalFile(strGlobal),
e835ec01 393 m_conv(conv.Clone())
18244936 394{
2b5f62a0 395 // Make up names for files if empty
466e87bd
VZ
396 if ( !m_fnLocalFile.IsOk() && (style & wxCONFIG_USE_LOCAL_FILE) )
397 m_fnLocalFile = GetLocalFile(GetAppName(), style);
18244936 398
466e87bd
VZ
399 if ( !m_fnGlobalFile.IsOk() && (style & wxCONFIG_USE_GLOBAL_FILE) )
400 m_fnGlobalFile = GetGlobalFile(GetAppName());
18244936 401
2b5f62a0
VZ
402 // Check if styles are not supplied, but filenames are, in which case
403 // add the correct styles.
466e87bd 404 if ( m_fnLocalFile.IsOk() )
2b5f62a0 405 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE);
18244936 406
466e87bd 407 if ( m_fnGlobalFile.IsOk() )
2b5f62a0 408 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE);
7beba2fc 409
2b5f62a0 410 // if the path is not absolute, prepend the standard directory to it
466e87bd 411 // unless explicitly asked not to
2b5f62a0
VZ
412 if ( !(style & wxCONFIG_USE_RELATIVE_PATH) )
413 {
466e87bd
VZ
414 if ( m_fnLocalFile.IsOk() )
415 m_fnLocalFile.MakeAbsolute(GetLocalDir(style));
18244936 416
466e87bd
VZ
417 if ( m_fnGlobalFile.IsOk() )
418 m_fnGlobalFile.MakeAbsolute(GetGlobalDir());
2b5f62a0 419 }
f6bcfd97 420
2b5f62a0 421 SetUmask(-1);
bc33be3b 422
2b5f62a0 423 Init();
18244936 424}
da468d38 425
a3a584a7
VZ
426#if wxUSE_STREAMS
427
830f8f11 428wxFileConfig::wxFileConfig(wxInputStream &inStream, const wxMBConv& conv)
e835ec01 429 : m_conv(conv.Clone())
a3a584a7
VZ
430{
431 // always local_file when this constructor is called (?)
432 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE);
433
434 m_pCurrentGroup =
b494c48b 435 m_pRootGroup = new wxFileConfigGroup(NULL, wxEmptyString, this);
a3a584a7
VZ
436
437 m_linesHead =
438 m_linesTail = NULL;
439
8d7bfa66 440 // read the entire stream contents in memory
5ad2b5ce
VZ
441 wxWxCharBuffer cbuf;
442 static const size_t chunkLen = 1024;
a3a584a7 443
5ad2b5ce
VZ
444 wxMemoryBuffer buf(chunkLen);
445 do
446 {
447 inStream.Read(buf.GetAppendBuf(chunkLen), chunkLen);
448 buf.UngetAppendBuf(inStream.LastRead());
2825b3f5 449
5ad2b5ce 450 const wxStreamError err = inStream.GetLastError();
01b18927 451
5ad2b5ce
VZ
452 if ( err != wxSTREAM_NO_ERROR && err != wxSTREAM_EOF )
453 {
454 wxLogError(_("Error reading config options."));
455 break;
2825b3f5 456 }
5ad2b5ce
VZ
457 }
458 while ( !inStream.Eof() );
a3a584a7 459
8d7bfa66 460#if wxUSE_UNICODE
5ad2b5ce 461 size_t len;
8ca1a013 462 cbuf = conv.cMB2WC((char *)buf.GetData(), buf.GetDataLen() + 1, &len);
5ad2b5ce
VZ
463 if ( !len && buf.GetDataLen() )
464 {
465 wxLogError(_("Failed to read config options."));
466 }
8d7bfa66 467#else // !wxUSE_UNICODE
5ad2b5ce 468 // no need for conversion
6df09f32 469 cbuf = wxCharBuffer::CreateNonOwned((char *)buf.GetData(), buf.GetDataLen());
8d7bfa66 470#endif // wxUSE_UNICODE/!wxUSE_UNICODE
a3a584a7 471
8d7bfa66 472
5ad2b5ce 473 // now break it into lines
a3a584a7 474 wxMemoryText memText;
5ad2b5ce 475 for ( const wxChar *s = cbuf; ; ++s )
a3a584a7 476 {
5ad2b5ce
VZ
477 const wxChar *e = s;
478 while ( *e != '\0' && *e != '\n' && *e != '\r' )
479 ++e;
a3a584a7 480
5ad2b5ce
VZ
481 // notice that we throw away the original EOL kind here, maybe we
482 // should preserve it?
89216929
VZ
483 if ( e != s )
484 memText.AddLine(wxString(s, e));
a3a584a7 485
5ad2b5ce
VZ
486 if ( *e == '\0' )
487 break;
a3a584a7 488
5ad2b5ce
VZ
489 // skip the second EOL byte if it's a DOS one
490 if ( *e == '\r' && e[1] == '\n' )
491 ++e;
a3a584a7 492
5ad2b5ce
VZ
493 s = e;
494 }
a3a584a7
VZ
495
496 // Finally we can parse it all.
a62848fd 497 Parse(memText, true /* local */);
a3a584a7
VZ
498
499 SetRootPath();
ca7dc59a 500 ResetDirty();
a3a584a7
VZ
501}
502
503#endif // wxUSE_STREAMS
504
da468d38 505void wxFileConfig::CleanUp()
c801d85f 506{
127eab18 507 delete m_pRootGroup;
128aec1d 508
127eab18
WS
509 wxFileConfigLineList *pCur = m_linesHead;
510 while ( pCur != NULL ) {
511 wxFileConfigLineList *pNext = pCur->Next();
512 delete pCur;
513 pCur = pNext;
514 }
c801d85f
KB
515}
516
da468d38
VZ
517wxFileConfig::~wxFileConfig()
518{
127eab18 519 Flush();
da468d38 520
127eab18 521 CleanUp();
2754877b
VZ
522
523 delete m_conv;
da468d38
VZ
524}
525
c801d85f
KB
526// ----------------------------------------------------------------------------
527// parse a config file
528// ----------------------------------------------------------------------------
529
fbfb8bcc 530void wxFileConfig::Parse(const wxTextBuffer& buffer, bool bLocal)
c801d85f 531{
9fbd8b8d 532
a3a584a7 533 size_t nLineCount = buffer.GetLineCount();
bc33be3b 534
2b5f62a0
VZ
535 for ( size_t n = 0; n < nLineCount; n++ )
536 {
86501081
VS
537 wxString strLine = buffer[n];
538 // FIXME-UTF8: rewrite using iterators, without this buffer
539 wxWxCharBuffer buf(strLine.c_str());
540 const wxChar *pStart;
541 const wxChar *pEnd;
876419ce 542
c801d85f
KB
543 // add the line to linked list
544 if ( bLocal )
9fbd8b8d 545 LineListAppend(strLine);
c801d85f 546
3362e80e 547
c801d85f 548 // skip leading spaces
86501081 549 for ( pStart = buf; wxIsspace(*pStart); pStart++ )
c801d85f
KB
550 ;
551
552 // skip blank/comment lines
223d09f6 553 if ( *pStart == wxT('\0')|| *pStart == wxT(';') || *pStart == wxT('#') )
c801d85f
KB
554 continue;
555
223d09f6 556 if ( *pStart == wxT('[') ) { // a new group
c801d85f
KB
557 pEnd = pStart;
558
223d09f6 559 while ( *++pEnd != wxT(']') ) {
1c68fb91
VZ
560 if ( *pEnd == wxT('\\') ) {
561 // the next char is escaped, so skip it even if it is ']'
562 pEnd++;
563 }
564
565 if ( *pEnd == wxT('\n') || *pEnd == wxT('\0') ) {
566 // we reached the end of line, break out of the loop
b8e9dd43 567 break;
1c68fb91 568 }
c801d85f
KB
569 }
570
223d09f6 571 if ( *pEnd != wxT(']') ) {
7502ba29 572 wxLogError(_("file '%s': unexpected character %c at line %d."),
a3a584a7 573 buffer.GetName(), *pEnd, n + 1);
c801d85f
KB
574 continue; // skip this line
575 }
576
577 // group name here is always considered as abs path
578 wxString strGroup;
579 pStart++;
b8e9dd43
VZ
580 strGroup << wxCONFIG_PATH_SEPARATOR
581 << FilterInEntryName(wxString(pStart, pEnd - pStart));
c801d85f
KB
582
583 // will create it if doesn't yet exist
584 SetPath(strGroup);
585
586 if ( bLocal )
80fdb8d6
VZ
587 {
588 if ( m_pCurrentGroup->Parent() )
589 m_pCurrentGroup->Parent()->SetLastGroup(m_pCurrentGroup);
c801d85f 590 m_pCurrentGroup->SetLine(m_linesTail);
80fdb8d6 591 }
c801d85f
KB
592
593 // check that there is nothing except comments left on this line
a62848fd 594 bool bCont = true;
223d09f6 595 while ( *++pEnd != wxT('\0') && bCont ) {
c801d85f 596 switch ( *pEnd ) {
223d09f6
KB
597 case wxT('#'):
598 case wxT(';'):
a62848fd 599 bCont = false;
c801d85f 600 break;
876419ce 601
223d09f6
KB
602 case wxT(' '):
603 case wxT('\t'):
c801d85f
KB
604 // ignore whitespace ('\n' impossible here)
605 break;
876419ce 606
c801d85f 607 default:
f6bcfd97 608 wxLogWarning(_("file '%s', line %d: '%s' ignored after group header."),
a3a584a7 609 buffer.GetName(), n + 1, pEnd);
a62848fd 610 bCont = false;
c801d85f
KB
611 }
612 }
613 }
614 else { // a key
17a1ebd1 615 pEnd = pStart;
7dfdce6b 616 while ( *pEnd && *pEnd != wxT('=') /* && !wxIsspace(*pEnd)*/ ) {
223d09f6 617 if ( *pEnd == wxT('\\') ) {
b8e9dd43 618 // next character may be space or not - still take it because it's
409c4ffd 619 // quoted (unless there is nothing)
b8e9dd43 620 pEnd++;
409c4ffd
VZ
621 if ( !*pEnd ) {
622 // the error message will be given below anyhow
623 break;
624 }
b8e9dd43
VZ
625 }
626
c801d85f 627 pEnd++;
b8e9dd43 628 }
c801d85f 629
17256d1e 630 wxString strKey(FilterInEntryName(wxString(pStart, pEnd).Trim()));
c801d85f
KB
631
632 // skip whitespace
f6bcfd97 633 while ( wxIsspace(*pEnd) )
c801d85f
KB
634 pEnd++;
635
223d09f6 636 if ( *pEnd++ != wxT('=') ) {
7502ba29 637 wxLogError(_("file '%s', line %d: '=' expected."),
a3a584a7 638 buffer.GetName(), n + 1);
c801d85f
KB
639 }
640 else {
0516e0e8 641 wxFileConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strKey);
c801d85f
KB
642
643 if ( pEntry == NULL ) {
644 // new entry
645 pEntry = m_pCurrentGroup->AddEntry(strKey, n);
c801d85f
KB
646 }
647 else {
648 if ( bLocal && pEntry->IsImmutable() ) {
649 // immutable keys can't be changed by user
f6bcfd97 650 wxLogWarning(_("file '%s', line %d: value for immutable key '%s' ignored."),
a3a584a7 651 buffer.GetName(), n + 1, strKey.c_str());
c801d85f
KB
652 continue;
653 }
654 // the condition below catches the cases (a) and (b) but not (c):
655 // (a) global key found second time in global file
656 // (b) key found second (or more) time in local file
657 // (c) key from global file now found in local one
658 // which is exactly what we want.
659 else if ( !bLocal || pEntry->IsLocal() ) {
f6bcfd97 660 wxLogWarning(_("file '%s', line %d: key '%s' was first found at line %d."),
a3a584a7 661 buffer.GetName(), n + 1, strKey.c_str(), pEntry->Line());
c801d85f 662
c801d85f
KB
663 }
664 }
665
80fdb8d6
VZ
666 if ( bLocal )
667 pEntry->SetLine(m_linesTail);
668
c801d85f 669 // skip whitespace
50920146 670 while ( wxIsspace(*pEnd) )
c801d85f
KB
671 pEnd++;
672
8dce54c9
VZ
673 wxString value = pEnd;
674 if ( !(GetStyle() & wxCONFIG_USE_NO_ESCAPE_CHARACTERS) )
675 value = FilterInValue(value);
676
a62848fd 677 pEntry->SetValue(value, false);
c801d85f
KB
678 }
679 }
680 }
681}
682
683// ----------------------------------------------------------------------------
684// set/retrieve path
685// ----------------------------------------------------------------------------
686
687void wxFileConfig::SetRootPath()
688{
127eab18
WS
689 m_strPath.Empty();
690 m_pCurrentGroup = m_pRootGroup;
c801d85f
KB
691}
692
6c99cd3d
VZ
693bool
694wxFileConfig::DoSetPath(const wxString& strPath, bool createMissingComponents)
c801d85f 695{
127eab18 696 wxArrayString aParts;
c801d85f 697
127eab18
WS
698 if ( strPath.empty() ) {
699 SetRootPath();
700 return true;
701 }
c801d85f 702
127eab18
WS
703 if ( strPath[0] == wxCONFIG_PATH_SEPARATOR ) {
704 // absolute path
705 wxSplitPath(aParts, strPath);
706 }
707 else {
708 // relative path, combine with current one
709 wxString strFullPath = m_strPath;
710 strFullPath << wxCONFIG_PATH_SEPARATOR << strPath;
711 wxSplitPath(aParts, strFullPath);
712 }
c801d85f 713
127eab18
WS
714 // change current group
715 size_t n;
716 m_pCurrentGroup = m_pRootGroup;
b4a980f4 717 for ( n = 0; n < aParts.GetCount(); n++ ) {
127eab18
WS
718 wxFileConfigGroup *pNextGroup = m_pCurrentGroup->FindSubgroup(aParts[n]);
719 if ( pNextGroup == NULL )
720 {
721 if ( !createMissingComponents )
722 return false;
6c99cd3d 723
127eab18
WS
724 pNextGroup = m_pCurrentGroup->AddSubgroup(aParts[n]);
725 }
6c99cd3d 726
127eab18
WS
727 m_pCurrentGroup = pNextGroup;
728 }
c801d85f 729
127eab18
WS
730 // recombine path parts in one variable
731 m_strPath.Empty();
b4a980f4 732 for ( n = 0; n < aParts.GetCount(); n++ ) {
127eab18
WS
733 m_strPath << wxCONFIG_PATH_SEPARATOR << aParts[n];
734 }
6c99cd3d 735
127eab18 736 return true;
6c99cd3d
VZ
737}
738
739void wxFileConfig::SetPath(const wxString& strPath)
740{
127eab18 741 DoSetPath(strPath, true /* create missing path components */);
c801d85f
KB
742}
743
d8a78293
VZ
744const wxString& wxFileConfig::GetPath() const
745{
746 return m_strPath;
747}
748
c801d85f
KB
749// ----------------------------------------------------------------------------
750// enumeration
751// ----------------------------------------------------------------------------
752
5fe256de 753bool wxFileConfig::GetFirstGroup(wxString& str, long& lIndex) const
c801d85f 754{
127eab18
WS
755 lIndex = 0;
756 return GetNextGroup(str, lIndex);
c801d85f
KB
757}
758
5fe256de 759bool wxFileConfig::GetNextGroup (wxString& str, long& lIndex) const
c801d85f 760{
b4a980f4 761 if ( size_t(lIndex) < m_pCurrentGroup->Groups().GetCount() ) {
127eab18
WS
762 str = m_pCurrentGroup->Groups()[(size_t)lIndex++]->Name();
763 return true;
764 }
765 else
766 return false;
c801d85f
KB
767}
768
5fe256de 769bool wxFileConfig::GetFirstEntry(wxString& str, long& lIndex) const
c801d85f 770{
127eab18
WS
771 lIndex = 0;
772 return GetNextEntry(str, lIndex);
c801d85f
KB
773}
774
5fe256de 775bool wxFileConfig::GetNextEntry (wxString& str, long& lIndex) const
c801d85f 776{
b4a980f4 777 if ( size_t(lIndex) < m_pCurrentGroup->Entries().GetCount() ) {
127eab18
WS
778 str = m_pCurrentGroup->Entries()[(size_t)lIndex++]->Name();
779 return true;
780 }
781 else
782 return false;
c801d85f
KB
783}
784
c86f1403 785size_t wxFileConfig::GetNumberOfEntries(bool bRecursive) const
9fbd8b8d 786{
b4a980f4 787 size_t n = m_pCurrentGroup->Entries().GetCount();
127eab18 788 if ( bRecursive ) {
a57bfed9
VZ
789 wxFileConfig * const self = const_cast<wxFileConfig *>(this);
790
127eab18 791 wxFileConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
b4a980f4 792 size_t nSubgroups = m_pCurrentGroup->Groups().GetCount();
127eab18 793 for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
a57bfed9 794 self->m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
127eab18 795 n += GetNumberOfEntries(true);
a57bfed9 796 self->m_pCurrentGroup = pOldCurrentGroup;
127eab18 797 }
9fbd8b8d 798 }
9fbd8b8d 799
127eab18 800 return n;
9fbd8b8d
VZ
801}
802
c86f1403 803size_t wxFileConfig::GetNumberOfGroups(bool bRecursive) const
9fbd8b8d 804{
b4a980f4 805 size_t n = m_pCurrentGroup->Groups().GetCount();
127eab18 806 if ( bRecursive ) {
a57bfed9
VZ
807 wxFileConfig * const self = const_cast<wxFileConfig *>(this);
808
127eab18 809 wxFileConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
b4a980f4 810 size_t nSubgroups = m_pCurrentGroup->Groups().GetCount();
127eab18 811 for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
a57bfed9 812 self->m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
127eab18 813 n += GetNumberOfGroups(true);
a57bfed9 814 self->m_pCurrentGroup = pOldCurrentGroup;
127eab18 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
5c33522f 834 wxFileConfig *self = const_cast<wxFileConfig *>(this);
127eab18
WS
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;
5c33522f 857 wxFileConfig * const self = const_cast<wxFileConfig *>(this);
2cf3a6d7
VZ
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{
9a83f860 917 wxCHECK_MSG( buf, false, wxT("NULL buffer") );
5814e8ba
VZ
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,
9a83f860 935 wxT(" Writing String '%s' = '%s' to Group '%s'"),
2b5f62a0
VZ
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,
9a83f860 949 wxT(" Creating group %s"),
2b5f62a0
VZ
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,
9a83f860 973 wxT(" Adding Entry %s"),
2b5f62a0
VZ
974 strName.c_str() );
975 pEntry = m_pCurrentGroup->AddEntry(strName);
976 }
c801d85f 977
701fb11e 978 wxLogTrace( FILECONF_TRACE_MASK,
9a83f860 979 wxT(" Setting value %s"),
2b5f62a0
VZ
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{
9a83f860 991 return Write(key, wxString::Format(wxT("%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
26eef304 1042#if defined( __WXOSX_MAC__ ) && wxOSX_USE_CARBON
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,
9a83f860 1083 wxT("RenameEntry(): paths are not supported") );
6dda7a75 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,
9a83f860 1194 wxT(" ** Adding Line '%s'"),
2b5f62a0 1195 str.c_str() );
701fb11e 1196 wxLogTrace( FILECONF_TRACE_MASK,
9a83f860 1197 wxT(" head: %s"),
c9f78968
VS
1198 ((m_linesHead) ? (const wxChar*)m_linesHead->Text().c_str()
1199 : wxEmptyString) );
701fb11e 1200 wxLogTrace( FILECONF_TRACE_MASK,
9a83f860 1201 wxT(" 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,
9a83f860 1222 wxT(" head: %s"),
c9f78968
VS
1223 ((m_linesHead) ? (const wxChar*)m_linesHead->Text().c_str()
1224 : wxEmptyString) );
701fb11e 1225 wxLogTrace( FILECONF_TRACE_MASK,
9a83f860 1226 wxT(" 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,
9a83f860 1238 wxT(" ** Inserting Line '%s' after '%s'"),
2b5f62a0 1239 str.c_str(),
c9f78968
VS
1240 ((pLine) ? (const wxChar*)pLine->Text().c_str()
1241 : wxEmptyString) );
701fb11e 1242 wxLogTrace( FILECONF_TRACE_MASK,
9a83f860 1243 wxT(" head: %s"),
c9f78968
VS
1244 ((m_linesHead) ? (const wxChar*)m_linesHead->Text().c_str()
1245 : wxEmptyString) );
701fb11e 1246 wxLogTrace( FILECONF_TRACE_MASK,
9a83f860 1247 wxT(" 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,
9a83f860 1273 wxT(" head: %s"),
c9f78968
VS
1274 ((m_linesHead) ? (const wxChar*)m_linesHead->Text().c_str()
1275 : wxEmptyString) );
701fb11e 1276 wxLogTrace( FILECONF_TRACE_MASK,
9a83f860 1277 wxT(" 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,
9a83f860 1287 wxT(" ** Removing Line '%s'"),
2b5f62a0 1288 pLine->Text().c_str() );
701fb11e 1289 wxLogTrace( FILECONF_TRACE_MASK,
9a83f860 1290 wxT(" head: %s"),
c9f78968
VS
1291 ((m_linesHead) ? (const wxChar*)m_linesHead->Text().c_str()
1292 : wxEmptyString) );
701fb11e 1293 wxLogTrace( FILECONF_TRACE_MASK,
9a83f860 1294 wxT(" 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,
9a83f860 1316 wxT(" head: %s"),
c9f78968
VS
1317 ((m_linesHead) ? (const wxChar*)m_linesHead->Text().c_str()
1318 : wxEmptyString) );
701fb11e 1319 wxLogTrace( FILECONF_TRACE_MASK,
9a83f860 1320 wxT(" 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,
9a83f860 1379 wxT("changing line for a non-root group?") );
b3a9e150 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,
9a83f860 1421 wxT(" GetGroupLine() for Group '%s'"),
2b5f62a0
VZ
1422 Name().c_str() );
1423
128e0251 1424 if ( !m_pLine )
2b5f62a0 1425 {
701fb11e 1426 wxLogTrace( FILECONF_TRACE_MASK,
9a83f860 1427 wxT(" Getting Line item pointer") );
2b5f62a0
VZ
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,
9a83f860 1435 wxT(" checking parent '%s'"),
2b5f62a0
VZ
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
9a83f860 1466 wxASSERT_MSG( pLine, wxT("last group must have !NULL associated line") );
128e0251 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,
9a83f860 1481 wxT(" GetLastEntryLine() for Group '%s'"),
2b5f62a0 1482 Name().c_str() );
b841e0e3 1483
128e0251 1484 if ( m_pLastEntry )
2b5f62a0
VZ
1485 {
1486 wxFileConfigLineList *pLine = m_pLastEntry->GetLine();
1487
9a83f860 1488 wxASSERT_MSG( pLine, wxT("last entry must have !NULL associated line") );
128e0251 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
9a83f860 1506 wxASSERT_MSG( !m_pParent, wxT("unexpected for non root group") );
128e0251
VZ
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();
9a83f860 1521 wxCHECK_RET( line, wxT("a non root group must have a corresponding line!") );
6dda7a75
VZ
1522
1523 // +1: skip the leading '/'
9a83f860 1524 line->SetText(wxString::Format(wxT("[%s]"), GetFullName().c_str() + 1));
6dda7a75
VZ
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{
9a83f860 1537 wxCHECK_RET( m_pParent, wxT("the root group can't be renamed") );
128e0251 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{
9a83f860 1676 wxCHECK_MSG( pGroup, false, wxT("deleting non existing group?") );
bc33be3b 1677
701fb11e 1678 wxLogTrace( FILECONF_TRACE_MASK,
9a83f860 1679 wxT("Deleting group '%s' from '%s'"),
2b5f62a0
VZ
1680 pGroup->Name().c_str(),
1681 Name().c_str() );
c801d85f 1682
701fb11e 1683 wxLogTrace( FILECONF_TRACE_MASK,
9a83f860 1684 wxT(" (m_pLine) = prev: %p, this %p, next %p"),
5c33522f
VZ
1685 m_pLine ? static_cast<void*>(m_pLine->Prev()) : 0,
1686 static_cast<void*>(m_pLine),
1687 m_pLine ? static_cast<void*>(m_pLine->Next()) : 0 );
701fb11e 1688 wxLogTrace( FILECONF_TRACE_MASK,
9a83f860 1689 wxT(" 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 1696 wxLogTrace(FILECONF_TRACE_MASK,
9a83f860 1697 wxT("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,
9a83f860 1706 wxT(" '%s'"),
2b5f62a0
VZ
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 1715 wxLogTrace( FILECONF_TRACE_MASK,
9a83f860 1716 wxT("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 1727 wxLogTrace( FILECONF_TRACE_MASK,
9a83f860 1728 wxT(" Removing line for group '%s' : '%s'"),
2b5f62a0
VZ
1729 pGroup->Name().c_str(),
1730 pLine->Text().c_str() );
701fb11e 1731 wxLogTrace( FILECONF_TRACE_MASK,
9a83f860 1732 wxT(" 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 1741 wxLogTrace( FILECONF_TRACE_MASK,
9a83f860 1742 wxT(" Removing last group") );
701fb11e
VZ
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,
9a83f860 1776 wxT(" No line entry for Group '%s'?"),
2b5f62a0
VZ
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
9a83f860 2082 if ( *++pc == wxT('\0') )
d9f5d54a
VZ
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