]> git.saurik.com Git - wxWidgets.git/blame - src/common/fileconf.cpp
activating ustring again
[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
VZ
139
140 DECLARE_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
VZ
180
181 DECLARE_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
128e0251 253 DECLARE_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
VZ
473 // no need for conversion
474 cbuf = wxCharBuffer::CreateNonOwned((char *)buf.GetData());
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
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
WS
788 if ( bRecursive ) {
789 wxFileConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
b4a980f4 790 size_t nSubgroups = m_pCurrentGroup->Groups().GetCount();
127eab18
WS
791 for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
792 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
793 n += GetNumberOfEntries(true);
794 CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
795 }
9fbd8b8d 796 }
9fbd8b8d 797
127eab18 798 return n;
9fbd8b8d
VZ
799}
800
c86f1403 801size_t wxFileConfig::GetNumberOfGroups(bool bRecursive) const
9fbd8b8d 802{
b4a980f4 803 size_t n = m_pCurrentGroup->Groups().GetCount();
127eab18
WS
804 if ( bRecursive ) {
805 wxFileConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
b4a980f4 806 size_t nSubgroups = m_pCurrentGroup->Groups().GetCount();
127eab18
WS
807 for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
808 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
809 n += GetNumberOfGroups(true);
810 CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
811 }
9fbd8b8d 812 }
9fbd8b8d 813
127eab18 814 return n;
9fbd8b8d
VZ
815}
816
876419ce
VZ
817// ----------------------------------------------------------------------------
818// tests for existence
819// ----------------------------------------------------------------------------
820
821bool wxFileConfig::HasGroup(const wxString& strName) const
822{
127eab18
WS
823 // special case: DoSetPath("") does work as it's equivalent to DoSetPath("/")
824 // but there is no group with empty name so treat this separately
825 if ( strName.empty() )
826 return false;
6c99cd3d 827
127eab18 828 const wxString pathOld = GetPath();
6c99cd3d 829
127eab18
WS
830 wxFileConfig *self = wx_const_cast(wxFileConfig *, this);
831 const bool
832 rc = self->DoSetPath(strName, false /* don't create missing components */);
6c99cd3d 833
127eab18 834 self->SetPath(pathOld);
876419ce 835
127eab18 836 return rc;
876419ce
VZ
837}
838
2cf3a6d7 839bool wxFileConfig::HasEntry(const wxString& entry) const
876419ce 840{
2cf3a6d7
VZ
841 // path is the part before the last "/"
842 wxString path = entry.BeforeLast(wxCONFIG_PATH_SEPARATOR);
876419ce 843
2cf3a6d7
VZ
844 // except in the special case of "/keyname" when there is nothing before "/"
845 if ( path.empty() && *entry.c_str() == wxCONFIG_PATH_SEPARATOR )
846 {
847 path = wxCONFIG_PATH_SEPARATOR;
848 }
849
850 // change to the path of the entry if necessary and remember the old path
851 // to restore it later
852 wxString pathOld;
853 wxFileConfig * const self = wx_const_cast(wxFileConfig *, this);
854 if ( !path.empty() )
855 {
856 pathOld = GetPath();
857 if ( pathOld.empty() )
858 pathOld = wxCONFIG_PATH_SEPARATOR;
859
860 if ( !self->DoSetPath(path, false /* don't create if doesn't exist */) )
861 {
862 return false;
863 }
864 }
865
866 // check if the entry exists in this group
867 const bool exists = m_pCurrentGroup->FindEntry(
868 entry.AfterLast(wxCONFIG_PATH_SEPARATOR)) != NULL;
869
870 // restore the old path if we changed it above
871 if ( !pathOld.empty() )
872 {
873 self->SetPath(pathOld);
874 }
875
876 return exists;
876419ce
VZ
877}
878
c801d85f
KB
879// ----------------------------------------------------------------------------
880// read/write values
881// ----------------------------------------------------------------------------
882
2ba41305 883bool wxFileConfig::DoReadString(const wxString& key, wxString* pStr) const
c801d85f 884{
127eab18 885 wxConfigPathChanger path(this, key);
1f905dc5 886
127eab18
WS
887 wxFileConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
888 if (pEntry == NULL) {
889 return false;
890 }
f6bcfd97 891
127eab18 892 *pStr = pEntry->Value();
1f905dc5 893
127eab18 894 return true;
9fbd8b8d
VZ
895}
896
2ba41305 897bool wxFileConfig::DoReadLong(const wxString& key, long *pl) const
1f905dc5 898{
444d3e01
VZ
899 wxString str;
900 if ( !Read(key, &str) )
a62848fd 901 return false;
444d3e01
VZ
902
903 // extra spaces shouldn't prevent us from reading numeric values
904 str.Trim();
905
906 return str.ToLong(pl);
c801d85f
KB
907}
908
434e2903
VZ
909#if wxUSE_BASE64
910
5814e8ba
VZ
911bool wxFileConfig::DoReadBinary(const wxString& key, wxMemoryBuffer* buf) const
912{
913 wxCHECK_MSG( buf, false, _T("NULL buffer") );
914
915 wxString str;
916 if ( !Read(key, &str) )
917 return false;
918
d20d5ee8 919 *buf = wxBase64Decode(str);
5814e8ba
VZ
920 return true;
921}
922
434e2903
VZ
923#endif // wxUSE_BASE64
924
2ba41305 925bool wxFileConfig::DoWriteString(const wxString& key, const wxString& szValue)
c801d85f 926{
2b5f62a0
VZ
927 wxConfigPathChanger path(this, key);
928 wxString strName = path.Name();
bc33be3b 929
701fb11e 930 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0
VZ
931 _T(" Writing String '%s' = '%s' to Group '%s'"),
932 strName.c_str(),
933 szValue.c_str(),
934 GetPath().c_str() );
c801d85f 935
b494c48b 936 if ( strName.empty() )
2b5f62a0
VZ
937 {
938 // setting the value of a group is an error
c3b0ff9c 939
b494c48b 940 wxASSERT_MSG( szValue.empty(), wxT("can't set value of a group!") );
c3b0ff9c 941
2b5f62a0 942 // ... except if it's empty in which case it's a way to force it's creation
c3b0ff9c 943
701fb11e 944 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0
VZ
945 _T(" Creating group %s"),
946 m_pCurrentGroup->Name().c_str() );
947
e1cc6874 948 SetDirty();
2b5f62a0 949
9a7b7798
VZ
950 // this will add a line for this group if it didn't have it before (or
951 // do nothing for the root but it's ok as it always exists anyhow)
2b5f62a0 952 (void)m_pCurrentGroup->GetGroupLine();
c3b0ff9c 953 }
2b5f62a0
VZ
954 else
955 {
e1cc6874 956 // writing an entry check that the name is reasonable
2b5f62a0
VZ
957 if ( strName[0u] == wxCONFIG_IMMUTABLE_PREFIX )
958 {
959 wxLogError( _("Config entry name cannot start with '%c'."),
960 wxCONFIG_IMMUTABLE_PREFIX);
a62848fd 961 return false;
2b5f62a0 962 }
c3b0ff9c 963
2b5f62a0 964 wxFileConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strName);
c3b0ff9c 965
2b5f62a0
VZ
966 if ( pEntry == 0 )
967 {
701fb11e 968 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0
VZ
969 _T(" Adding Entry %s"),
970 strName.c_str() );
971 pEntry = m_pCurrentGroup->AddEntry(strName);
972 }
c801d85f 973
701fb11e 974 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0
VZ
975 _T(" Setting value %s"),
976 szValue.c_str() );
977 pEntry->SetValue(szValue);
e1cc6874
VZ
978
979 SetDirty();
2b5f62a0
VZ
980 }
981
a62848fd 982 return true;
c801d85f
KB
983}
984
2ba41305 985bool wxFileConfig::DoWriteLong(const wxString& key, long lValue)
c801d85f 986{
2ba41305 987 return Write(key, wxString::Format(_T("%ld"), lValue));
c801d85f
KB
988}
989
434e2903
VZ
990#if wxUSE_BASE64
991
5814e8ba
VZ
992bool wxFileConfig::DoWriteBinary(const wxString& key, const wxMemoryBuffer& buf)
993{
994 return Write(key, wxBase64Encode(buf));
995}
996
434e2903
VZ
997#endif // wxUSE_BASE64
998
c801d85f
KB
999bool wxFileConfig::Flush(bool /* bCurrentOnly */)
1000{
466e87bd 1001 if ( !IsDirty() || !m_fnLocalFile.GetFullPath() )
a62848fd 1002 return true;
c801d85f 1003
f6bcfd97 1004 // set the umask if needed
8482e4bd 1005 wxCHANGE_UMASK(m_umask);
f6bcfd97 1006
466e87bd 1007 wxTempFile file(m_fnLocalFile.GetFullPath());
c801d85f 1008
ca11abde
RR
1009 if ( !file.IsOpened() )
1010 {
7502ba29 1011 wxLogError(_("can't open user configuration file."));
a62848fd 1012 return false;
c801d85f
KB
1013 }
1014
1015 // write all strings to file
20160a32
VZ
1016 wxString filetext;
1017 filetext.reserve(4096);
ca11abde
RR
1018 for ( wxFileConfigLineList *p = m_linesHead; p != NULL; p = p->Next() )
1019 {
20160a32
VZ
1020 filetext << p->Text() << wxTextFile::GetEOL();
1021 }
1022
1023 if ( !file.Write(filetext, *m_conv) )
1024 {
1025 wxLogError(_("can't write user configuration file."));
1026 return false;
c801d85f
KB
1027 }
1028
60829985
VZ
1029 if ( !file.Commit() )
1030 {
1031 wxLogError(_("Failed to update user configuration file."));
f6bcfd97 1032
60829985
VZ
1033 return false;
1034 }
a62848fd 1035
e1cc6874
VZ
1036 ResetDirty();
1037
26eef304 1038#if defined( __WXOSX_MAC__ ) && wxOSX_USE_CARBON
466e87bd 1039 m_fnLocalFile.MacSetTypeAndCreator('TEXT', 'ttxt');
e7e1b01e 1040#endif // __WXMAC__
f6bcfd97 1041
60829985 1042 return true;
c801d85f
KB
1043}
1044
a121d720
VZ
1045#if wxUSE_STREAMS
1046
830f8f11 1047bool wxFileConfig::Save(wxOutputStream& os, const wxMBConv& conv)
a121d720
VZ
1048{
1049 // save unconditionally, even if not dirty
1050 for ( wxFileConfigLineList *p = m_linesHead; p != NULL; p = p->Next() )
1051 {
1052 wxString line = p->Text();
1053 line += wxTextFile::GetEOL();
894d74dc
VZ
1054
1055 wxCharBuffer buf(line.mb_str(conv));
1056 if ( !os.Write(buf, strlen(buf)) )
a121d720
VZ
1057 {
1058 wxLogError(_("Error saving user configuration data."));
1059
1060 return false;
1061 }
1062 }
1063
1064 ResetDirty();
1065
1066 return true;
1067}
1068
1069#endif // wxUSE_STREAMS
1070
5d1902d6
VZ
1071// ----------------------------------------------------------------------------
1072// renaming groups/entries
1073// ----------------------------------------------------------------------------
1074
1075bool wxFileConfig::RenameEntry(const wxString& oldName,
1076 const wxString& newName)
1077{
86501081 1078 wxASSERT_MSG( oldName.find(wxCONFIG_PATH_SEPARATOR) == wxString::npos,
6dda7a75
VZ
1079 _T("RenameEntry(): paths are not supported") );
1080
5d1902d6 1081 // check that the entry exists
0516e0e8 1082 wxFileConfigEntry *oldEntry = m_pCurrentGroup->FindEntry(oldName);
5d1902d6 1083 if ( !oldEntry )
a62848fd 1084 return false;
5d1902d6
VZ
1085
1086 // check that the new entry doesn't already exist
1087 if ( m_pCurrentGroup->FindEntry(newName) )
a62848fd 1088 return false;
5d1902d6
VZ
1089
1090 // delete the old entry, create the new one
1091 wxString value = oldEntry->Value();
1092 if ( !m_pCurrentGroup->DeleteEntry(oldName) )
a62848fd 1093 return false;
5d1902d6 1094
e1cc6874
VZ
1095 SetDirty();
1096
0516e0e8 1097 wxFileConfigEntry *newEntry = m_pCurrentGroup->AddEntry(newName);
5d1902d6
VZ
1098 newEntry->SetValue(value);
1099
a62848fd 1100 return true;
5d1902d6
VZ
1101}
1102
1103bool wxFileConfig::RenameGroup(const wxString& oldName,
1104 const wxString& newName)
1105{
1106 // check that the group exists
0516e0e8 1107 wxFileConfigGroup *group = m_pCurrentGroup->FindSubgroup(oldName);
5d1902d6 1108 if ( !group )
a62848fd 1109 return false;
5d1902d6
VZ
1110
1111 // check that the new group doesn't already exist
1112 if ( m_pCurrentGroup->FindSubgroup(newName) )
a62848fd 1113 return false;
5d1902d6
VZ
1114
1115 group->Rename(newName);
1116
e1cc6874
VZ
1117 SetDirty();
1118
a62848fd 1119 return true;
5d1902d6
VZ
1120}
1121
c801d85f
KB
1122// ----------------------------------------------------------------------------
1123// delete groups/entries
1124// ----------------------------------------------------------------------------
1125
18244936 1126bool wxFileConfig::DeleteEntry(const wxString& key, bool bGroupIfEmptyAlso)
c801d85f 1127{
18244936 1128 wxConfigPathChanger path(this, key);
c801d85f
KB
1129
1130 if ( !m_pCurrentGroup->DeleteEntry(path.Name()) )
a62848fd 1131 return false;
c801d85f 1132
a625d949
RD
1133 SetDirty();
1134
c801d85f
KB
1135 if ( bGroupIfEmptyAlso && m_pCurrentGroup->IsEmpty() ) {
1136 if ( m_pCurrentGroup != m_pRootGroup ) {
0516e0e8 1137 wxFileConfigGroup *pGroup = m_pCurrentGroup;
223d09f6 1138 SetPath(wxT("..")); // changes m_pCurrentGroup!
a625d949 1139 m_pCurrentGroup->DeleteSubgroupByName(pGroup->Name());
c801d85f
KB
1140 }
1141 //else: never delete the root group
1142 }
1143
a62848fd 1144 return true;
c801d85f
KB
1145}
1146
18244936 1147bool wxFileConfig::DeleteGroup(const wxString& key)
c801d85f 1148{
35c4b4da 1149 wxConfigPathChanger path(this, RemoveTrailingSeparator(key));
c801d85f 1150
e1cc6874
VZ
1151 if ( !m_pCurrentGroup->DeleteSubgroupByName(path.Name()) )
1152 return false;
1153
41f30152
VZ
1154 path.UpdateIfDeleted();
1155
e1cc6874
VZ
1156 SetDirty();
1157
1158 return true;
c801d85f
KB
1159}
1160
1161bool wxFileConfig::DeleteAll()
1162{
da468d38
VZ
1163 CleanUp();
1164
466e87bd 1165 if ( m_fnLocalFile.IsOk() )
cb820f80 1166 {
86501081
VS
1167 if ( m_fnLocalFile.FileExists() &&
1168 !wxRemoveFile(m_fnLocalFile.GetFullPath()) )
c8adf5ef
VZ
1169 {
1170 wxLogSysError(_("can't delete user configuration file '%s'"),
466e87bd 1171 m_fnLocalFile.GetFullPath().c_str());
c8adf5ef
VZ
1172 return false;
1173 }
cb820f80 1174 }
da468d38 1175
275bf4c1
VZ
1176 Init();
1177
a62848fd 1178 return true;
c801d85f
KB
1179}
1180
1181// ----------------------------------------------------------------------------
1182// linked list functions
1183// ----------------------------------------------------------------------------
1184
2b5f62a0
VZ
1185 // append a new line to the end of the list
1186
0516e0e8 1187wxFileConfigLineList *wxFileConfig::LineListAppend(const wxString& str)
c801d85f 1188{
701fb11e 1189 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0
VZ
1190 _T(" ** Adding Line '%s'"),
1191 str.c_str() );
701fb11e 1192 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0 1193 _T(" head: %s"),
c9f78968
VS
1194 ((m_linesHead) ? (const wxChar*)m_linesHead->Text().c_str()
1195 : wxEmptyString) );
701fb11e 1196 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0 1197 _T(" tail: %s"),
c9f78968
VS
1198 ((m_linesTail) ? (const wxChar*)m_linesTail->Text().c_str()
1199 : wxEmptyString) );
c801d85f 1200
2b5f62a0 1201 wxFileConfigLineList *pLine = new wxFileConfigLineList(str);
c801d85f 1202
2b5f62a0
VZ
1203 if ( m_linesTail == NULL )
1204 {
1205 // list is empty
1206 m_linesHead = pLine;
1207 }
1208 else
1209 {
1210 // adjust pointers
1211 m_linesTail->SetNext(pLine);
1212 pLine->SetPrev(m_linesTail);
1213 }
1214
1215 m_linesTail = pLine;
1216
701fb11e 1217 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0 1218 _T(" head: %s"),
c9f78968
VS
1219 ((m_linesHead) ? (const wxChar*)m_linesHead->Text().c_str()
1220 : wxEmptyString) );
701fb11e 1221 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0 1222 _T(" tail: %s"),
c9f78968
VS
1223 ((m_linesTail) ? (const wxChar*)m_linesTail->Text().c_str()
1224 : wxEmptyString) );
2b5f62a0
VZ
1225
1226 return m_linesTail;
c801d85f
KB
1227}
1228
128e0251 1229// insert a new line after the given one or in the very beginning if !pLine
0516e0e8 1230wxFileConfigLineList *wxFileConfig::LineListInsert(const wxString& str,
2b5f62a0
VZ
1231 wxFileConfigLineList *pLine)
1232{
701fb11e 1233 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0
VZ
1234 _T(" ** Inserting Line '%s' after '%s'"),
1235 str.c_str(),
c9f78968
VS
1236 ((pLine) ? (const wxChar*)pLine->Text().c_str()
1237 : wxEmptyString) );
701fb11e 1238 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0 1239 _T(" head: %s"),
c9f78968
VS
1240 ((m_linesHead) ? (const wxChar*)m_linesHead->Text().c_str()
1241 : wxEmptyString) );
701fb11e 1242 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0 1243 _T(" tail: %s"),
c9f78968
VS
1244 ((m_linesTail) ? (const wxChar*)m_linesTail->Text().c_str()
1245 : wxEmptyString) );
2b5f62a0
VZ
1246
1247 if ( pLine == m_linesTail )
1248 return LineListAppend(str);
1249
1250 wxFileConfigLineList *pNewLine = new wxFileConfigLineList(str);
1251 if ( pLine == NULL )
1252 {
1253 // prepend to the list
1254 pNewLine->SetNext(m_linesHead);
1255 m_linesHead->SetPrev(pNewLine);
1256 m_linesHead = pNewLine;
1257 }
1258 else
1259 {
1260 // insert before pLine
1261 wxFileConfigLineList *pNext = pLine->Next();
1262 pNewLine->SetNext(pNext);
1263 pNewLine->SetPrev(pLine);
1264 pNext->SetPrev(pNewLine);
1265 pLine->SetNext(pNewLine);
1266 }
c801d85f 1267
701fb11e 1268 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0 1269 _T(" head: %s"),
c9f78968
VS
1270 ((m_linesHead) ? (const wxChar*)m_linesHead->Text().c_str()
1271 : wxEmptyString) );
701fb11e 1272 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0 1273 _T(" tail: %s"),
c9f78968
VS
1274 ((m_linesTail) ? (const wxChar*)m_linesTail->Text().c_str()
1275 : wxEmptyString) );
c801d85f 1276
2b5f62a0 1277 return pNewLine;
c801d85f
KB
1278}
1279
0516e0e8 1280void wxFileConfig::LineListRemove(wxFileConfigLineList *pLine)
876419ce 1281{
701fb11e 1282 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0
VZ
1283 _T(" ** Removing Line '%s'"),
1284 pLine->Text().c_str() );
701fb11e 1285 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0 1286 _T(" head: %s"),
c9f78968
VS
1287 ((m_linesHead) ? (const wxChar*)m_linesHead->Text().c_str()
1288 : wxEmptyString) );
701fb11e 1289 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0 1290 _T(" tail: %s"),
c9f78968
VS
1291 ((m_linesTail) ? (const wxChar*)m_linesTail->Text().c_str()
1292 : wxEmptyString) );
c3b0ff9c 1293
2b5f62a0
VZ
1294 wxFileConfigLineList *pPrev = pLine->Prev(),
1295 *pNext = pLine->Next();
876419ce 1296
2b5f62a0
VZ
1297 // first entry?
1298
1299 if ( pPrev == NULL )
1300 m_linesHead = pNext;
1301 else
1302 pPrev->SetNext(pNext);
1303
1304 // last entry?
876419ce 1305
2b5f62a0
VZ
1306 if ( pNext == NULL )
1307 m_linesTail = pPrev;
1308 else
1309 pNext->SetPrev(pPrev);
1310
701fb11e 1311 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0 1312 _T(" head: %s"),
c9f78968
VS
1313 ((m_linesHead) ? (const wxChar*)m_linesHead->Text().c_str()
1314 : wxEmptyString) );
701fb11e 1315 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0 1316 _T(" tail: %s"),
c9f78968
VS
1317 ((m_linesTail) ? (const wxChar*)m_linesTail->Text().c_str()
1318 : wxEmptyString) );
2b5f62a0
VZ
1319
1320 delete pLine;
876419ce
VZ
1321}
1322
c801d85f
KB
1323bool wxFileConfig::LineListIsEmpty()
1324{
2b5f62a0 1325 return m_linesHead == NULL;
c801d85f
KB
1326}
1327
1328// ============================================================================
0516e0e8 1329// wxFileConfig::wxFileConfigGroup
c801d85f
KB
1330// ============================================================================
1331
1332// ----------------------------------------------------------------------------
1333// ctor/dtor
1334// ----------------------------------------------------------------------------
1335
1336// ctor
0516e0e8 1337wxFileConfigGroup::wxFileConfigGroup(wxFileConfigGroup *pParent,
c801d85f
KB
1338 const wxString& strName,
1339 wxFileConfig *pConfig)
f5ae0449
VZ
1340 : m_aEntries(CompareEntries),
1341 m_aSubgroups(CompareGroups),
1342 m_strName(strName)
c801d85f
KB
1343{
1344 m_pConfig = pConfig;
1345 m_pParent = pParent;
c3b0ff9c
VZ
1346 m_pLine = NULL;
1347
b841e0e3
VZ
1348 m_pLastEntry = NULL;
1349 m_pLastGroup = NULL;
c801d85f
KB
1350}
1351
1352// dtor deletes all children
0516e0e8 1353wxFileConfigGroup::~wxFileConfigGroup()
c801d85f
KB
1354{
1355 // entries
b4a980f4 1356 size_t n, nCount = m_aEntries.GetCount();
c801d85f
KB
1357 for ( n = 0; n < nCount; n++ )
1358 delete m_aEntries[n];
1359
1360 // subgroups
b4a980f4 1361 nCount = m_aSubgroups.GetCount();
c801d85f
KB
1362 for ( n = 0; n < nCount; n++ )
1363 delete m_aSubgroups[n];
1364}
1365
1366// ----------------------------------------------------------------------------
1367// line
1368// ----------------------------------------------------------------------------
1369
0516e0e8 1370void wxFileConfigGroup::SetLine(wxFileConfigLineList *pLine)
c801d85f 1371{
b3a9e150
VZ
1372 // for a normal (i.e. not root) group this method shouldn't be called twice
1373 // unless we are resetting the line
1374 wxASSERT_MSG( !m_pParent || !m_pLine || !pLine,
1375 _T("changing line for a non-root group?") );
1376
2b5f62a0 1377 m_pLine = pLine;
c801d85f
KB
1378}
1379
c3b0ff9c
VZ
1380/*
1381 This is a bit complicated, so let me explain it in details. All lines that
1382 were read from the local file (the only one we will ever modify) are stored
1383 in a (doubly) linked list. Our problem is to know at which position in this
1384 list should we insert the new entries/subgroups. To solve it we keep three
1385 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
1386
1387 m_pLine points to the line containing "[group_name]"
1388 m_pLastEntry points to the last entry of this group in the local file.
1389 m_pLastGroup subgroup
1390
1391 Initially, they're NULL all three. When the group (an entry/subgroup) is read
1392 from the local file, the corresponding variable is set. However, if the group
1393 was read from the global file and then modified or created by the application
1394 these variables are still NULL and we need to create the corresponding lines.
1395 See the following functions (and comments preceding them) for the details of
1396 how we do it.
1397
1398 Also, when our last entry/group are deleted we need to find the new last
1399 element - the code in DeleteEntry/Subgroup does this by backtracking the list
1400 of lines until it either founds an entry/subgroup (and this is the new last
1401 element) or the m_pLine of the group, in which case there are no more entries
1402 (or subgroups) left and m_pLast<element> becomes NULL.
1403
1404 NB: This last problem could be avoided for entries if we added new entries
1405 immediately after m_pLine, but in this case the entries would appear
1406 backwards in the config file (OTOH, it's not that important) and as we
1407 would still need to do it for the subgroups the code wouldn't have been
1408 significantly less complicated.
5d1902d6 1409*/
c3b0ff9c
VZ
1410
1411// Return the line which contains "[our name]". If we're still not in the list,
1412// add our line to it immediately after the last line of our parent group if we
1413// have it or in the very beginning if we're the root group.
0516e0e8 1414wxFileConfigLineList *wxFileConfigGroup::GetGroupLine()
c801d85f 1415{
701fb11e 1416 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0
VZ
1417 _T(" GetGroupLine() for Group '%s'"),
1418 Name().c_str() );
1419
128e0251 1420 if ( !m_pLine )
2b5f62a0 1421 {
701fb11e 1422 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0
VZ
1423 _T(" Getting Line item pointer") );
1424
1425 wxFileConfigGroup *pParent = Parent();
1426
128e0251
VZ
1427 // this group wasn't present in local config file, add it now
1428 if ( pParent )
2b5f62a0 1429 {
701fb11e 1430 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0
VZ
1431 _T(" checking parent '%s'"),
1432 pParent->Name().c_str() );
1433
1434 wxString strFullName;
1435
128e0251
VZ
1436 // add 1 to the name because we don't want to start with '/'
1437 strFullName << wxT("[")
2b5f62a0
VZ
1438 << FilterOutEntryName(GetFullName().c_str() + 1)
1439 << wxT("]");
1440 m_pLine = m_pConfig->LineListInsert(strFullName,
1441 pParent->GetLastGroupLine());
1442 pParent->SetLastGroup(this); // we're surely after all the others
1443 }
128e0251
VZ
1444 //else: this is the root group and so we return NULL because we don't
1445 // have any group line
c801d85f 1446 }
c801d85f 1447
2b5f62a0 1448 return m_pLine;
c801d85f
KB
1449}
1450
c3b0ff9c
VZ
1451// Return the last line belonging to the subgroups of this group (after which
1452// we can add a new subgroup), if we don't have any subgroups or entries our
1453// last line is the group line (m_pLine) itself.
0516e0e8 1454wxFileConfigLineList *wxFileConfigGroup::GetLastGroupLine()
c801d85f 1455{
128e0251
VZ
1456 // if we have any subgroups, our last line is the last line of the last
1457 // subgroup
1458 if ( m_pLastGroup )
2b5f62a0
VZ
1459 {
1460 wxFileConfigLineList *pLine = m_pLastGroup->GetLastGroupLine();
1461
128e0251
VZ
1462 wxASSERT_MSG( pLine, _T("last group must have !NULL associated line") );
1463
2b5f62a0
VZ
1464 return pLine;
1465 }
c801d85f 1466
128e0251 1467 // no subgroups, so the last line is the line of thelast entry (if any)
2b5f62a0 1468 return GetLastEntryLine();
c801d85f
KB
1469}
1470
c3b0ff9c
VZ
1471// return the last line belonging to the entries of this group (after which
1472// we can add a new entry), if we don't have any entries we will add the new
1473// one immediately after the group line itself.
0516e0e8 1474wxFileConfigLineList *wxFileConfigGroup::GetLastEntryLine()
c801d85f 1475{
701fb11e 1476 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0
VZ
1477 _T(" GetLastEntryLine() for Group '%s'"),
1478 Name().c_str() );
b841e0e3 1479
128e0251 1480 if ( m_pLastEntry )
2b5f62a0
VZ
1481 {
1482 wxFileConfigLineList *pLine = m_pLastEntry->GetLine();
1483
128e0251
VZ
1484 wxASSERT_MSG( pLine, _T("last entry must have !NULL associated line") );
1485
2b5f62a0
VZ
1486 return pLine;
1487 }
b841e0e3 1488
128e0251 1489 // no entries: insert after the group header, if any
2b5f62a0 1490 return GetGroupLine();
c801d85f
KB
1491}
1492
128e0251
VZ
1493void wxFileConfigGroup::SetLastEntry(wxFileConfigEntry *pEntry)
1494{
1495 m_pLastEntry = pEntry;
1496
1497 if ( !m_pLine )
1498 {
1499 // the only situation in which a group without its own line can have
1500 // an entry is when the first entry is added to the initially empty
1501 // root pseudo-group
1502 wxASSERT_MSG( !m_pParent, _T("unexpected for non root group") );
1503
1504 // let the group know that it does have a line in the file now
1505 m_pLine = pEntry->GetLine();
1506 }
1507}
1508
c801d85f
KB
1509// ----------------------------------------------------------------------------
1510// group name
1511// ----------------------------------------------------------------------------
1512
6dda7a75
VZ
1513void wxFileConfigGroup::UpdateGroupAndSubgroupsLines()
1514{
1515 // update the line of this group
1516 wxFileConfigLineList *line = GetGroupLine();
1517 wxCHECK_RET( line, _T("a non root group must have a corresponding line!") );
1518
1519 // +1: skip the leading '/'
1520 line->SetText(wxString::Format(_T("[%s]"), GetFullName().c_str() + 1));
1521
1522
1523 // also update all subgroups as they have this groups name in their lines
b4a980f4 1524 const size_t nCount = m_aSubgroups.GetCount();
6dda7a75
VZ
1525 for ( size_t n = 0; n < nCount; n++ )
1526 {
1527 m_aSubgroups[n]->UpdateGroupAndSubgroupsLines();
1528 }
1529}
1530
0516e0e8 1531void wxFileConfigGroup::Rename(const wxString& newName)
5d1902d6 1532{
128e0251
VZ
1533 wxCHECK_RET( m_pParent, _T("the root group can't be renamed") );
1534
e10b8ce8
VZ
1535 if ( newName == m_strName )
1536 return;
1537
1538 // we need to remove the group from the parent and it back under the new
1539 // name to keep the parents array of subgroups alphabetically sorted
1540 m_pParent->m_aSubgroups.Remove(this);
1541
5d1902d6
VZ
1542 m_strName = newName;
1543
e10b8ce8
VZ
1544 m_pParent->m_aSubgroups.Add(this);
1545
6dda7a75
VZ
1546 // update the group lines recursively
1547 UpdateGroupAndSubgroupsLines();
5d1902d6
VZ
1548}
1549
0516e0e8 1550wxString wxFileConfigGroup::GetFullName() const
c801d85f 1551{
6dda7a75
VZ
1552 wxString fullname;
1553 if ( Parent() )
1554 fullname = Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR + Name();
1555
1556 return fullname;
c801d85f
KB
1557}
1558
1559// ----------------------------------------------------------------------------
1560// find an item
1561// ----------------------------------------------------------------------------
1562
f5ae0449 1563// use binary search because the array is sorted
0516e0e8 1564wxFileConfigEntry *
86501081 1565wxFileConfigGroup::FindEntry(const wxString& name) const
c801d85f 1566{
c86f1403 1567 size_t i,
f5ae0449 1568 lo = 0,
b4a980f4 1569 hi = m_aEntries.GetCount();
f5ae0449 1570 int res;
0516e0e8 1571 wxFileConfigEntry *pEntry;
f5ae0449
VZ
1572
1573 while ( lo < hi ) {
1574 i = (lo + hi)/2;
1575 pEntry = m_aEntries[i];
1576
da468d38 1577 #if wxCONFIG_CASE_SENSITIVE
86501081 1578 res = pEntry->Name().compare(name);
f5ae0449 1579 #else
86501081 1580 res = pEntry->Name().CmpNoCase(name);
f5ae0449
VZ
1581 #endif
1582
9fbd8b8d 1583 if ( res > 0 )
f5ae0449 1584 hi = i;
9fbd8b8d 1585 else if ( res < 0 )
f5ae0449
VZ
1586 lo = i + 1;
1587 else
1588 return pEntry;
c801d85f
KB
1589 }
1590
1591 return NULL;
1592}
1593
0516e0e8 1594wxFileConfigGroup *
86501081 1595wxFileConfigGroup::FindSubgroup(const wxString& name) const
c801d85f 1596{
c86f1403 1597 size_t i,
f5ae0449 1598 lo = 0,
b4a980f4 1599 hi = m_aSubgroups.GetCount();
f5ae0449 1600 int res;
0516e0e8 1601 wxFileConfigGroup *pGroup;
f5ae0449
VZ
1602
1603 while ( lo < hi ) {
1604 i = (lo + hi)/2;
1605 pGroup = m_aSubgroups[i];
1606
da468d38 1607 #if wxCONFIG_CASE_SENSITIVE
86501081 1608 res = pGroup->Name().compare(name);
f5ae0449 1609 #else
86501081 1610 res = pGroup->Name().CmpNoCase(name);
f5ae0449
VZ
1611 #endif
1612
9fbd8b8d 1613 if ( res > 0 )
f5ae0449 1614 hi = i;
9fbd8b8d 1615 else if ( res < 0 )
f5ae0449
VZ
1616 lo = i + 1;
1617 else
1618 return pGroup;
c801d85f
KB
1619 }
1620
1621 return NULL;
1622}
1623
1624// ----------------------------------------------------------------------------
1625// create a new item
1626// ----------------------------------------------------------------------------
1627
1628// create a new entry and add it to the current group
2b5f62a0 1629wxFileConfigEntry *wxFileConfigGroup::AddEntry(const wxString& strName, int nLine)
c801d85f 1630{
2b5f62a0 1631 wxASSERT( FindEntry(strName) == 0 );
c801d85f 1632
2b5f62a0 1633 wxFileConfigEntry *pEntry = new wxFileConfigEntry(this, strName, nLine);
c801d85f 1634
2b5f62a0
VZ
1635 m_aEntries.Add(pEntry);
1636 return pEntry;
c801d85f
KB
1637}
1638
1639// create a new group and add it to the current group
2b5f62a0 1640wxFileConfigGroup *wxFileConfigGroup::AddSubgroup(const wxString& strName)
c801d85f 1641{
2b5f62a0 1642 wxASSERT( FindSubgroup(strName) == 0 );
c801d85f 1643
2b5f62a0 1644 wxFileConfigGroup *pGroup = new wxFileConfigGroup(this, strName, m_pConfig);
c801d85f 1645
2b5f62a0
VZ
1646 m_aSubgroups.Add(pGroup);
1647 return pGroup;
c801d85f
KB
1648}
1649
1650// ----------------------------------------------------------------------------
1651// delete an item
1652// ----------------------------------------------------------------------------
1653
c3b0ff9c
VZ
1654/*
1655 The delete operations are _very_ slow if we delete the last item of this
1656 group (see comments before GetXXXLineXXX functions for more details),
1657 so it's much better to start with the first entry/group if we want to
1658 delete several of them.
1659 */
1660
86501081 1661bool wxFileConfigGroup::DeleteSubgroupByName(const wxString& name)
5fe256de 1662{
86501081 1663 wxFileConfigGroup * const pGroup = FindSubgroup(name);
bc33be3b 1664
a62848fd 1665 return pGroup ? DeleteSubgroup(pGroup) : false;
5fe256de
VZ
1666}
1667
2b5f62a0
VZ
1668// Delete the subgroup and remove all references to it from
1669// other data structures.
0516e0e8 1670bool wxFileConfigGroup::DeleteSubgroup(wxFileConfigGroup *pGroup)
c801d85f 1671{
a62848fd 1672 wxCHECK_MSG( pGroup, false, _T("deleting non existing group?") );
bc33be3b 1673
701fb11e 1674 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0
VZ
1675 _T("Deleting group '%s' from '%s'"),
1676 pGroup->Name().c_str(),
1677 Name().c_str() );
c801d85f 1678
701fb11e 1679 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0 1680 _T(" (m_pLine) = prev: %p, this %p, next %p"),
008202b7
VZ
1681 m_pLine ? wx_static_cast(void*, m_pLine->Prev()) : 0,
1682 wx_static_cast(void*, m_pLine),
1683 m_pLine ? wx_static_cast(void*, m_pLine->Next()) : 0 );
701fb11e 1684 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0 1685 _T(" text: '%s'"),
c9f78968
VS
1686 m_pLine ? (const wxChar*)m_pLine->Text().c_str()
1687 : wxEmptyString );
876419ce 1688
701fb11e 1689 // delete all entries...
b4a980f4 1690 size_t nCount = pGroup->m_aEntries.GetCount();
2b5f62a0 1691
701fb11e
VZ
1692 wxLogTrace(FILECONF_TRACE_MASK,
1693 _T("Removing %lu entries"), (unsigned long)nCount );
2b5f62a0
VZ
1694
1695 for ( size_t nEntry = 0; nEntry < nCount; nEntry++ )
1696 {
701fb11e 1697 wxFileConfigLineList *pLine = pGroup->m_aEntries[nEntry]->GetLine();
2b5f62a0 1698
701fb11e 1699 if ( pLine )
2b5f62a0 1700 {
701fb11e 1701 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0
VZ
1702 _T(" '%s'"),
1703 pLine->Text().c_str() );
1704 m_pConfig->LineListRemove(pLine);
c3b0ff9c 1705 }
2b5f62a0 1706 }
c3b0ff9c 1707
701fb11e 1708 // ...and subgroups of this subgroup
b4a980f4 1709 nCount = pGroup->m_aSubgroups.GetCount();
c3b0ff9c 1710
701fb11e
VZ
1711 wxLogTrace( FILECONF_TRACE_MASK,
1712 _T("Removing %lu subgroups"), (unsigned long)nCount );
2b5f62a0
VZ
1713
1714 for ( size_t nGroup = 0; nGroup < nCount; nGroup++ )
1715 {
1716 pGroup->DeleteSubgroup(pGroup->m_aSubgroups[0]);
c3b0ff9c
VZ
1717 }
1718
701fb11e
VZ
1719 // and then finally the group itself
1720 wxFileConfigLineList *pLine = pGroup->m_pLine;
1721 if ( pLine )
2b5f62a0 1722 {
701fb11e
VZ
1723 wxLogTrace( FILECONF_TRACE_MASK,
1724 _T(" Removing line for group '%s' : '%s'"),
2b5f62a0
VZ
1725 pGroup->Name().c_str(),
1726 pLine->Text().c_str() );
701fb11e
VZ
1727 wxLogTrace( FILECONF_TRACE_MASK,
1728 _T(" Removing from group '%s' : '%s'"),
2b5f62a0 1729 Name().c_str(),
c9f78968
VS
1730 ((m_pLine) ? (const wxChar*)m_pLine->Text().c_str()
1731 : wxEmptyString) );
2b5f62a0 1732
701fb11e
VZ
1733 // notice that we may do this test inside the previous "if"
1734 // because the last entry's line is surely !NULL
2b5f62a0
VZ
1735 if ( pGroup == m_pLastGroup )
1736 {
701fb11e
VZ
1737 wxLogTrace( FILECONF_TRACE_MASK,
1738 _T(" Removing last group") );
1739
1740 // our last entry is being deleted, so find the last one which
1741 // stays by going back until we find a subgroup or reach the
1742 // group line
b4a980f4 1743 const size_t nSubgroups = m_aSubgroups.GetCount();
701fb11e
VZ
1744
1745 m_pLastGroup = NULL;
1746 for ( wxFileConfigLineList *pl = pLine->Prev();
6ad0a7d5 1747 pl && !m_pLastGroup;
701fb11e 1748 pl = pl->Prev() )
2b5f62a0 1749 {
701fb11e
VZ
1750 // does this line belong to our subgroup?
1751 for ( size_t n = 0; n < nSubgroups; n++ )
2b5f62a0 1752 {
701fb11e
VZ
1753 // do _not_ call GetGroupLine! we don't want to add it to
1754 // the local file if it's not already there
1755 if ( m_aSubgroups[n]->m_pLine == pl )
1756 {
1757 m_pLastGroup = m_aSubgroups[n];
1758 break;
1759 }
2b5f62a0 1760 }
6ad0a7d5
VZ
1761
1762 if ( pl == m_pLine )
1763 break;
2b5f62a0
VZ
1764 }
1765 }
9fbd8b8d 1766
2b5f62a0
VZ
1767 m_pConfig->LineListRemove(pLine);
1768 }
1769 else
1770 {
701fb11e 1771 wxLogTrace( FILECONF_TRACE_MASK,
2b5f62a0
VZ
1772 _T(" No line entry for Group '%s'?"),
1773 pGroup->Name().c_str() );
1774 }
1775
2b5f62a0
VZ
1776 m_aSubgroups.Remove(pGroup);
1777 delete pGroup;
1778
a62848fd 1779 return true;
c801d85f
KB
1780}
1781
86501081 1782bool wxFileConfigGroup::DeleteEntry(const wxString& name)
c801d85f 1783{
86501081 1784 wxFileConfigEntry *pEntry = FindEntry(name);
c8adf5ef
VZ
1785 if ( !pEntry )
1786 {
1787 // entry doesn't exist, nothing to do
1788 return false;
1789 }
c801d85f 1790
0516e0e8 1791 wxFileConfigLineList *pLine = pEntry->GetLine();
c3b0ff9c
VZ
1792 if ( pLine != NULL ) {
1793 // notice that we may do this test inside the previous "if" because the
1794 // last entry's line is surely !NULL
1795 if ( pEntry == m_pLastEntry ) {
1796 // our last entry is being deleted - find the last one which stays
1797 wxASSERT( m_pLine != NULL ); // if we have an entry with !NULL pLine...
1798
e86882e3 1799 // find the previous entry (if any)
0516e0e8 1800 wxFileConfigEntry *pNewLast = NULL;
e86882e3
VZ
1801 const wxFileConfigLineList * const
1802 pNewLastLine = m_pLastEntry->GetLine()->Prev();
1803 const size_t nEntries = m_aEntries.GetCount();
1804 for ( size_t n = 0; n < nEntries; n++ ) {
1805 if ( m_aEntries[n]->GetLine() == pNewLastLine ) {
1806 pNewLast = m_aEntries[n];
c3b0ff9c 1807 break;
e86882e3 1808 }
c3b0ff9c
VZ
1809 }
1810
e86882e3
VZ
1811 // pNewLast can be NULL here -- it's ok and can happen if we have no
1812 // entries left
1813 m_pLastEntry = pNewLast;
c3b0ff9c
VZ
1814 }
1815
876419ce 1816 m_pConfig->LineListRemove(pLine);
c3b0ff9c 1817 }
876419ce 1818
9fbd8b8d
VZ
1819 m_aEntries.Remove(pEntry);
1820 delete pEntry;
1821
a62848fd 1822 return true;
c801d85f
KB
1823}
1824
c801d85f 1825// ============================================================================
0516e0e8 1826// wxFileConfig::wxFileConfigEntry
c801d85f
KB
1827// ============================================================================
1828
1829// ----------------------------------------------------------------------------
1830// ctor
1831// ----------------------------------------------------------------------------
0516e0e8 1832wxFileConfigEntry::wxFileConfigEntry(wxFileConfigGroup *pParent,
c801d85f
KB
1833 const wxString& strName,
1834 int nLine)
1835 : m_strName(strName)
1836{
b494c48b 1837 wxASSERT( !strName.empty() );
c3b0ff9c 1838
c801d85f
KB
1839 m_pParent = pParent;
1840 m_nLine = nLine;
1841 m_pLine = NULL;
1842
a62848fd 1843 m_bHasValue = false;
c801d85f 1844
da468d38 1845 m_bImmutable = strName[0] == wxCONFIG_IMMUTABLE_PREFIX;
c801d85f
KB
1846 if ( m_bImmutable )
1847 m_strName.erase(0, 1); // remove first character
1848}
1849
1850// ----------------------------------------------------------------------------
1851// set value
1852// ----------------------------------------------------------------------------
1853
0516e0e8 1854void wxFileConfigEntry::SetLine(wxFileConfigLineList *pLine)
c801d85f 1855{
1f905dc5 1856 if ( m_pLine != NULL ) {
7502ba29 1857 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1f905dc5
VZ
1858 Name().c_str(), m_pParent->GetFullName().c_str());
1859 }
c801d85f
KB
1860
1861 m_pLine = pLine;
b841e0e3 1862 Group()->SetLastEntry(this);
c801d85f
KB
1863}
1864
a62848fd 1865// second parameter is false if we read the value from file and prevents the
c801d85f 1866// entry from being marked as 'dirty'
0516e0e8 1867void wxFileConfigEntry::SetValue(const wxString& strValue, bool bUser)
c801d85f 1868{
2b5f62a0
VZ
1869 if ( bUser && IsImmutable() )
1870 {
1871 wxLogWarning( _("attempt to change immutable key '%s' ignored."),
1872 Name().c_str());
1873 return;
1874 }
c801d85f 1875
e1cc6874
VZ
1876 // do nothing if it's the same value: but don't test for it if m_bHasValue
1877 // hadn't been set yet or we'd never write empty values to the file
2b5f62a0
VZ
1878 if ( m_bHasValue && strValue == m_strValue )
1879 return;
c801d85f 1880
a62848fd 1881 m_bHasValue = true;
2b5f62a0 1882 m_strValue = strValue;
8dce54c9 1883
2b5f62a0
VZ
1884 if ( bUser )
1885 {
e1cc6874 1886 wxString strValFiltered;
c801d85f 1887
2b5f62a0
VZ
1888 if ( Group()->Config()->GetStyle() & wxCONFIG_USE_NO_ESCAPE_CHARACTERS )
1889 {
1890 strValFiltered = strValue;
1891 }
1892 else {
1893 strValFiltered = FilterOutValue(strValue);
1894 }
c801d85f 1895
2b5f62a0
VZ
1896 wxString strLine;
1897 strLine << FilterOutEntryName(m_strName) << wxT('=') << strValFiltered;
c801d85f 1898
128e0251 1899 if ( m_pLine )
2b5f62a0
VZ
1900 {
1901 // entry was read from the local config file, just modify the line
1902 m_pLine->SetText(strLine);
1903 }
128e0251
VZ
1904 else // this entry didn't exist in the local file
1905 {
b1143304
VZ
1906 // add a new line to the file: note that line returned by
1907 // GetLastEntryLine() may be NULL if we're in the root group and it
1908 // doesn't have any entries yet, but this is ok as passing NULL
1909 // line to LineListInsert() means to prepend new line to the list
128e0251
VZ
1910 wxFileConfigLineList *line = Group()->GetLastEntryLine();
1911 m_pLine = Group()->Config()->LineListInsert(strLine, line);
1912
2b5f62a0
VZ
1913 Group()->SetLastEntry(this);
1914 }
2b5f62a0 1915 }
c801d85f
KB
1916}
1917
c801d85f
KB
1918// ============================================================================
1919// global functions
1920// ============================================================================
1921
f5ae0449
VZ
1922// ----------------------------------------------------------------------------
1923// compare functions for array sorting
1924// ----------------------------------------------------------------------------
1925
0516e0e8 1926int CompareEntries(wxFileConfigEntry *p1, wxFileConfigEntry *p2)
f5ae0449 1927{
670f9935 1928#if wxCONFIG_CASE_SENSITIVE
86501081 1929 return p1->Name().compare(p2->Name());
670f9935 1930#else
86501081 1931 return p1->Name().CmpNoCase(p2->Name());
670f9935 1932#endif
f5ae0449
VZ
1933}
1934
0516e0e8 1935int CompareGroups(wxFileConfigGroup *p1, wxFileConfigGroup *p2)
f5ae0449 1936{
670f9935 1937#if wxCONFIG_CASE_SENSITIVE
86501081 1938 return p1->Name().compare(p2->Name());
670f9935 1939#else
86501081 1940 return p1->Name().CmpNoCase(p2->Name());
670f9935 1941#endif
f5ae0449
VZ
1942}
1943
1944// ----------------------------------------------------------------------------
1945// filter functions
1946// ----------------------------------------------------------------------------
1947
b8e9dd43
VZ
1948// undo FilterOutValue
1949static wxString FilterInValue(const wxString& str)
c801d85f 1950{
cdbcd6c1
VZ
1951 wxString strResult;
1952 if ( str.empty() )
1953 return strResult;
c801d85f 1954
cdbcd6c1 1955 strResult.reserve(str.length());
c801d85f 1956
cdbcd6c1
VZ
1957 wxString::const_iterator i = str.begin();
1958 const bool bQuoted = *i == '"';
1959 if ( bQuoted )
1960 ++i;
c801d85f 1961
cdbcd6c1
VZ
1962 for ( const wxString::const_iterator end = str.end(); i != end; ++i )
1963 {
1964 if ( *i == wxT('\\') )
1965 {
1966 if ( ++i == end )
1967 {
1968 wxLogWarning(_("trailing backslash ignored in '%s'"), str.c_str());
1969 break;
1970 }
7502ba29 1971
cdbcd6c1
VZ
1972 switch ( (*i).GetValue() )
1973 {
1974 case wxT('n'):
1975 strResult += wxT('\n');
1976 break;
c801d85f 1977
cdbcd6c1
VZ
1978 case wxT('r'):
1979 strResult += wxT('\r');
1980 break;
c801d85f 1981
cdbcd6c1
VZ
1982 case wxT('t'):
1983 strResult += wxT('\t');
1984 break;
1985
1986 case wxT('\\'):
1987 strResult += wxT('\\');
1988 break;
1989
1990 case wxT('"'):
1991 strResult += wxT('"');
1992 break;
1993 }
1994 }
1995 else // not a backslash
1996 {
1997 if ( *i != wxT('"') || !bQuoted )
1998 {
1999 strResult += *i;
2000 }
2001 else if ( i != end - 1 )
2002 {
2003 wxLogWarning(_("unexpected \" at position %d in '%s'."),
2004 i - str.begin(), str.c_str());
2005 }
2006 //else: it's the last quote of a quoted string, ok
2007 }
c801d85f 2008 }
c801d85f 2009
cdbcd6c1 2010 return strResult;
c801d85f
KB
2011}
2012
2013// quote the string before writing it to file
b8e9dd43 2014static wxString FilterOutValue(const wxString& str)
c801d85f 2015{
b8e9dd43 2016 if ( !str )
76f53a0e 2017 return str;
8bbe427f 2018
c801d85f 2019 wxString strResult;
ba7f9a90 2020 strResult.Alloc(str.Len());
c801d85f
KB
2021
2022 // quoting is necessary to preserve spaces in the beginning of the string
223d09f6 2023 bool bQuote = wxIsspace(str[0]) || str[0] == wxT('"');
c801d85f
KB
2024
2025 if ( bQuote )
223d09f6 2026 strResult += wxT('"');
c801d85f 2027
50920146 2028 wxChar c;
c86f1403 2029 for ( size_t n = 0; n < str.Len(); n++ ) {
c9f78968 2030 switch ( str[n].GetValue() ) {
223d09f6
KB
2031 case wxT('\n'):
2032 c = wxT('n');
c801d85f
KB
2033 break;
2034
223d09f6
KB
2035 case wxT('\r'):
2036 c = wxT('r');
7502ba29
VZ
2037 break;
2038
223d09f6
KB
2039 case wxT('\t'):
2040 c = wxT('t');
c801d85f
KB
2041 break;
2042
223d09f6
KB
2043 case wxT('\\'):
2044 c = wxT('\\');
c801d85f
KB
2045 break;
2046
223d09f6 2047 case wxT('"'):
c5c16a30 2048 if ( bQuote ) {
223d09f6 2049 c = wxT('"');
c5c16a30
VZ
2050 break;
2051 }
c801d85f
KB
2052 //else: fall through
2053
2054 default:
2055 strResult += str[n];
2056 continue; // nothing special to do
2057 }
2058
2059 // we get here only for special characters
223d09f6 2060 strResult << wxT('\\') << c;
c801d85f
KB
2061 }
2062
2063 if ( bQuote )
223d09f6 2064 strResult += wxT('"');
c801d85f
KB
2065
2066 return strResult;
2067}
ac57418f 2068
b8e9dd43
VZ
2069// undo FilterOutEntryName
2070static wxString FilterInEntryName(const wxString& str)
2071{
2072 wxString strResult;
2073 strResult.Alloc(str.Len());
2074
50920146 2075 for ( const wxChar *pc = str.c_str(); *pc != '\0'; pc++ ) {
d9f5d54a
VZ
2076 if ( *pc == wxT('\\') ) {
2077 // we need to test it here or we'd skip past the NUL in the loop line
2078 if ( *++pc == _T('\0') )
2079 break;
2080 }
b8e9dd43
VZ
2081
2082 strResult += *pc;
2083 }
2084
2085 return strResult;
2086}
ac57418f 2087
b8e9dd43
VZ
2088// sanitize entry or group name: insert '\\' before any special characters
2089static wxString FilterOutEntryName(const wxString& str)
2090{
2091 wxString strResult;
2092 strResult.Alloc(str.Len());
a3ef5bf5 2093
223d09f6 2094 for ( const wxChar *pc = str.c_str(); *pc != wxT('\0'); pc++ ) {
3c9642b5 2095 const wxChar c = *pc;
a3ef5bf5 2096
b8e9dd43 2097 // we explicitly allow some of "safe" chars and 8bit ASCII characters
3c9642b5
VZ
2098 // which will probably never have special meaning and with which we can't
2099 // use isalnum() anyhow (in ASCII built, in Unicode it's just fine)
2100 //
b8e9dd43
VZ
2101 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
2102 // should *not* be quoted
a62848fd 2103 if (
3c9642b5 2104#if !wxUSE_UNICODE
cc3dd6fc 2105 ((unsigned char)c < 127) &&
3c9642b5
VZ
2106#endif // ANSI
2107 !wxIsalnum(c) && !wxStrchr(wxT("@_/-!.*%"), c) )
2108 {
223d09f6 2109 strResult += wxT('\\');
3c9642b5 2110 }
a3ef5bf5 2111
b8e9dd43
VZ
2112 strResult += c;
2113 }
a3ef5bf5 2114
b8e9dd43
VZ
2115 return strResult;
2116}
a3ef5bf5 2117
004c69ab
VZ
2118// we can't put ?: in the ctor initializer list because it confuses some
2119// broken compilers (Borland C++)
2120static wxString GetAppName(const wxString& appName)
2121{
2122 if ( !appName && wxTheApp )
2123 return wxTheApp->GetAppName();
2124 else
2125 return appName;
2126}
d427503c
VZ
2127
2128#endif // wxUSE_CONFIG