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