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