]> git.saurik.com Git - wxWidgets.git/blob - src/common/fileconf.cpp
bug in wxFileConfig::DeleteEntry/Group corrected
[wxWidgets.git] / src / common / fileconf.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: fileconf.cpp
3 // Purpose: implementation of wxFileConfig derivation of wxConfig
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 07.04.98 (adapted from appconf.cpp)
7 // RCS-ID: $Id$
8 // Copyright: (c) 1997 Karsten Ballüder & Vadim Zeitlin
9 // Ballueder@usa.net <zeitlin@dptmaths.ens-cachan.fr>
10 // Licence: wxWindows license
11 ///////////////////////////////////////////////////////////////////////////////
12
13 #ifdef __GNUG__
14 #pragma implementation "fileconf.h"
15 #endif
16
17 // ============================================================================
18 // declarations
19 // ============================================================================
20
21 // ----------------------------------------------------------------------------
22 // headers
23 // ----------------------------------------------------------------------------
24 #include "wx/wxprec.h"
25
26 #ifdef __BORLANDC__
27 #pragma hdrstop
28 #endif //__BORLANDC__
29
30 #ifndef WX_PRECOMP
31 #include <wx/string.h>
32 #include <wx/intl.h>
33 #endif //WX_PRECOMP
34
35 #include <wx/dynarray.h>
36 #include <wx/file.h>
37 #include <wx/log.h>
38 #include <wx/textfile.h>
39 #include <wx/config.h>
40 #include <wx/fileconf.h>
41
42 // _WINDOWS_ is defined when windows.h is included,
43 // __WXMSW__ is defined for MS Windows compilation
44 #if defined(__WXMSW__) && !defined(_WINDOWS_)
45 #include <windows.h>
46 #endif //windows.h
47
48 #include <stdlib.h>
49 #include <ctype.h>
50
51 // ----------------------------------------------------------------------------
52 // macros
53 // ----------------------------------------------------------------------------
54 #define CONST_CAST ((wxFileConfig *)this)->
55
56 // ----------------------------------------------------------------------------
57 // global functions declarations
58 // ----------------------------------------------------------------------------
59
60 // is 'c' a valid character in group name?
61 // NB: APPCONF_IMMUTABLE_PREFIX and APPCONF_PATH_SEPARATOR must be valid chars,
62 // but _not_ ']' (group name delimiter)
63 inline bool IsValid(char c) { return isalnum(c) || strchr("@_/-!.*%", c); }
64
65 // compare functions for sorting the arrays
66 static int CompareEntries(wxFileConfig::ConfigEntry *p1,
67 wxFileConfig::ConfigEntry *p2);
68 static int CompareGroups(wxFileConfig::ConfigGroup *p1,
69 wxFileConfig::ConfigGroup *p2);
70
71 // filter strings
72 static wxString FilterIn(const wxString& str);
73 static wxString FilterOut(const wxString& str);
74
75 // ============================================================================
76 // implementation
77 // ============================================================================
78
79 // ----------------------------------------------------------------------------
80 // static functions
81 // ----------------------------------------------------------------------------
82 wxString wxFileConfig::GetGlobalFileName(const char *szFile)
83 {
84 wxString str;
85
86 bool bNoExt = strchr(szFile, '.') == NULL;
87
88 #ifdef __UNIX__
89 str << "/etc/" << szFile;
90 if ( bNoExt )
91 str << ".conf";
92 #else // Windows
93 #ifndef _MAX_PATH
94 #define _MAX_PATH 512
95 #endif
96
97 char szWinDir[_MAX_PATH];
98 ::GetWindowsDirectory(szWinDir, _MAX_PATH);
99 str << szWinDir << "\\" << szFile;
100 if ( bNoExt )
101 str << ".ini";
102 #endif // UNIX/Win
103
104 return str;
105 }
106
107 wxString wxFileConfig::GetLocalFileName(const char *szFile)
108 {
109 wxString str;
110
111 #ifdef __UNIX__
112 const char *szHome = getenv("HOME");
113 if ( szHome == NULL ) {
114 // we're homeless...
115 wxLogWarning(_("can't find user's HOME, using current directory."));
116 szHome = ".";
117 }
118 str << szHome << "/." << szFile;
119 #else // Windows
120 #ifdef __WIN32__
121 const char *szHome = getenv("HOMEDRIVE");
122 if ( szHome != NULL )
123 str << szHome;
124 szHome = getenv("HOMEPATH");
125 if ( szHome != NULL )
126 str << szHome;
127 str << szFile;
128 if ( strchr(szFile, '.') == NULL )
129 str << ".ini";
130 #else // Win16
131 // Win16 has no idea about home, so use the current directory instead
132 str << ".\\" << szFile;
133 #endif // WIN16/32
134 #endif // UNIX/Win
135
136 return str;
137 }
138
139 // ----------------------------------------------------------------------------
140 // ctor
141 // ----------------------------------------------------------------------------
142
143 void wxFileConfig::Init()
144 {
145 m_pCurrentGroup =
146 m_pRootGroup = new ConfigGroup(NULL, "", this);
147
148 m_linesHead =
149 m_linesTail = NULL;
150
151 m_strPath.Empty();
152 }
153
154 wxFileConfig::wxFileConfig(const wxString& strLocal, const wxString& strGlobal)
155 : m_strLocalFile(strLocal), m_strGlobalFile(strGlobal)
156 {
157 Init();
158
159 // it's not an error if (one of the) file(s) doesn't exist
160
161 // parse the global file
162 if ( !strGlobal.IsEmpty() ) {
163 if ( wxFile::Exists(strGlobal) ) {
164 wxTextFile fileGlobal(strGlobal);
165
166 if ( fileGlobal.Open() ) {
167 Parse(fileGlobal, FALSE /* global */);
168 SetRootPath();
169 }
170 else
171 wxLogWarning(_("can't open global configuration file '%s'."),
172 strGlobal.c_str());
173 }
174 }
175
176 // parse the local file
177 if ( wxFile::Exists(strLocal) ) {
178 wxTextFile fileLocal(strLocal);
179 if ( fileLocal.Open() ) {
180 Parse(fileLocal, TRUE /* local */);
181 SetRootPath();
182 }
183 else
184 wxLogWarning(_("can't open user configuration file '%s'."),
185 strLocal.c_str());
186 }
187 }
188
189 wxFileConfig::~wxFileConfig()
190 {
191 Flush();
192 delete m_pRootGroup;
193
194 LineList *pCur = m_linesHead;
195 while ( pCur != NULL ) {
196 LineList *pNext = pCur->Next();
197 delete pCur;
198 pCur = pNext;
199 }
200 }
201
202 // ----------------------------------------------------------------------------
203 // parse a config file
204 // ----------------------------------------------------------------------------
205
206 void wxFileConfig::Parse(wxTextFile& file, bool bLocal)
207 {
208 const char *pStart;
209 const char *pEnd;
210 wxString strLine;
211
212 uint nLineCount = file.GetLineCount();
213 for ( uint n = 0; n < nLineCount; n++ ) {
214 strLine = file[n];
215
216 // add the line to linked list
217 if ( bLocal )
218 LineListAppend(strLine);
219
220 // skip leading spaces
221 for ( pStart = strLine; isspace(*pStart); pStart++ )
222 ;
223
224 // skip blank/comment lines
225 if ( *pStart == '\0'|| *pStart == ';' || *pStart == '#' )
226 continue;
227
228 if ( *pStart == '[' ) { // a new group
229 pEnd = pStart;
230
231 while ( *++pEnd != ']' ) {
232 if ( !IsValid(*pEnd) && *pEnd != ' ' ) // allow spaces in group names
233 break;
234 }
235
236 if ( *pEnd != ']' ) {
237 wxLogError(_("file '%s': unexpected character %c at line %d."),
238 file.GetName(), *pEnd, n + 1);
239 continue; // skip this line
240 }
241
242 // group name here is always considered as abs path
243 wxString strGroup;
244 pStart++;
245 strGroup << APPCONF_PATH_SEPARATOR << wxString(pStart, pEnd - pStart);
246
247 // will create it if doesn't yet exist
248 SetPath(strGroup);
249
250 if ( bLocal )
251 m_pCurrentGroup->SetLine(m_linesTail);
252
253 // check that there is nothing except comments left on this line
254 bool bCont = TRUE;
255 while ( *++pEnd != '\0' && bCont ) {
256 switch ( *pEnd ) {
257 case '#':
258 case ';':
259 bCont = FALSE;
260 break;
261
262 case ' ':
263 case '\t':
264 // ignore whitespace ('\n' impossible here)
265 break;
266
267 default:
268 wxLogWarning(_("file '%s', line %d: '%s' "
269 "ignored after group header."),
270 file.GetName(), n + 1, pEnd);
271 bCont = FALSE;
272 }
273 }
274 }
275 else { // a key
276 const char *pEnd = pStart;
277 while ( IsValid(*pEnd) )
278 pEnd++;
279
280 wxString strKey(pStart, pEnd);
281
282 // skip whitespace
283 while ( isspace(*pEnd) )
284 pEnd++;
285
286 if ( *pEnd++ != '=' ) {
287 wxLogError(_("file '%s', line %d: '=' expected."),
288 file.GetName(), n + 1);
289 }
290 else {
291 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strKey);
292
293 if ( pEntry == NULL ) {
294 // new entry
295 pEntry = m_pCurrentGroup->AddEntry(strKey, n);
296
297 if ( bLocal )
298 pEntry->SetLine(m_linesTail);
299 }
300 else {
301 if ( bLocal && pEntry->IsImmutable() ) {
302 // immutable keys can't be changed by user
303 wxLogWarning(_("file '%s', line %d: value for "
304 "immutable key '%s' ignored."),
305 file.GetName(), n + 1, strKey.c_str());
306 continue;
307 }
308 // the condition below catches the cases (a) and (b) but not (c):
309 // (a) global key found second time in global file
310 // (b) key found second (or more) time in local file
311 // (c) key from global file now found in local one
312 // which is exactly what we want.
313 else if ( !bLocal || pEntry->IsLocal() ) {
314 wxLogWarning(_("file '%s', line %d: key '%s' was first "
315 "found at line %d."),
316 file.GetName(), n + 1, strKey.c_str(), pEntry->Line());
317
318 if ( bLocal )
319 pEntry->SetLine(m_linesTail);
320 }
321 }
322
323 // skip whitespace
324 while ( isspace(*pEnd) )
325 pEnd++;
326
327 pEntry->SetValue(FilterIn(pEnd), FALSE /* read from file */);
328 }
329 }
330 }
331 }
332
333 // ----------------------------------------------------------------------------
334 // set/retrieve path
335 // ----------------------------------------------------------------------------
336
337 void wxFileConfig::SetRootPath()
338 {
339 m_strPath.Empty();
340 m_pCurrentGroup = m_pRootGroup;
341 }
342
343 void wxFileConfig::SetPath(const wxString& strPath)
344 {
345 wxArrayString aParts;
346
347 if ( strPath.IsEmpty() ) {
348 SetRootPath();
349 return;
350 }
351
352 if ( strPath[0] == APPCONF_PATH_SEPARATOR ) {
353 // absolute path
354 wxSplitPath(aParts, strPath);
355 }
356 else {
357 // relative path, combine with current one
358 wxString strFullPath = m_strPath;
359 strFullPath << APPCONF_PATH_SEPARATOR << strPath;
360 wxSplitPath(aParts, strFullPath);
361 }
362
363 // change current group
364 uint n;
365 m_pCurrentGroup = m_pRootGroup;
366 for ( n = 0; n < aParts.Count(); n++ ) {
367 ConfigGroup *pNextGroup = m_pCurrentGroup->FindSubgroup(aParts[n]);
368 if ( pNextGroup == NULL )
369 pNextGroup = m_pCurrentGroup->AddSubgroup(aParts[n]);
370 m_pCurrentGroup = pNextGroup;
371 }
372
373 // recombine path parts in one variable
374 m_strPath.Empty();
375 for ( n = 0; n < aParts.Count(); n++ ) {
376 m_strPath << APPCONF_PATH_SEPARATOR << aParts[n];
377 }
378 }
379
380 // ----------------------------------------------------------------------------
381 // enumeration
382 // ----------------------------------------------------------------------------
383
384 bool wxFileConfig::GetFirstGroup(wxString& str, long& lIndex)
385 {
386 lIndex = 0;
387 return GetNextGroup(str, lIndex);
388 }
389
390 bool wxFileConfig::GetNextGroup (wxString& str, long& lIndex)
391 {
392 if ( uint(lIndex) < m_pCurrentGroup->Groups().Count() ) {
393 str = m_pCurrentGroup->Groups()[lIndex++]->Name();
394 return TRUE;
395 }
396 else
397 return FALSE;
398 }
399
400 bool wxFileConfig::GetFirstEntry(wxString& str, long& lIndex)
401 {
402 lIndex = 0;
403 return GetNextEntry(str, lIndex);
404 }
405
406 bool wxFileConfig::GetNextEntry (wxString& str, long& lIndex)
407 {
408 if ( uint(lIndex) < m_pCurrentGroup->Entries().Count() ) {
409 str = m_pCurrentGroup->Entries()[lIndex++]->Name();
410 return TRUE;
411 }
412 else
413 return FALSE;
414 }
415
416 uint wxFileConfig::GetNumberOfEntries(bool bRecursive) const
417 {
418 uint n = m_pCurrentGroup->Entries().Count();
419 if ( bRecursive ) {
420 ConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
421 uint nSubgroups = m_pCurrentGroup->Groups().Count();
422 for ( uint nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
423 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
424 n += GetNumberOfEntries(TRUE);
425 CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
426 }
427 }
428
429 return n;
430 }
431
432 uint wxFileConfig::GetNumberOfGroups(bool bRecursive) const
433 {
434 uint n = m_pCurrentGroup->Groups().Count();
435 if ( bRecursive ) {
436 ConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
437 uint nSubgroups = m_pCurrentGroup->Groups().Count();
438 for ( uint nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
439 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
440 n += GetNumberOfGroups(TRUE);
441 CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
442 }
443 }
444
445 return n;
446 }
447
448 // ----------------------------------------------------------------------------
449 // tests for existence
450 // ----------------------------------------------------------------------------
451
452 bool wxFileConfig::HasGroup(const wxString& strName) const
453 {
454 PathChanger path(this, strName);
455
456 ConfigGroup *pGroup = m_pCurrentGroup->FindSubgroup(path.Name());
457 return pGroup != NULL;
458 }
459
460 bool wxFileConfig::HasEntry(const wxString& strName) const
461 {
462 PathChanger path(this, strName);
463
464 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
465 return pEntry != NULL;
466 }
467
468 // ----------------------------------------------------------------------------
469 // read/write values
470 // ----------------------------------------------------------------------------
471
472 bool wxFileConfig::Read(wxString *pstr,
473 const char *szKey,
474 const char *szDefault) const
475 {
476 PathChanger path(this, szKey);
477
478 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
479 if (pEntry == NULL) {
480 *pstr = ExpandEnvVars(szDefault);
481 return FALSE;
482 }
483 else {
484 *pstr = ExpandEnvVars(pEntry->Value());
485 return TRUE;
486 }
487 }
488
489 const char *wxFileConfig::Read(const char *szKey,
490 const char *szDefault) const
491 {
492 static wxString s_str;
493 Read(&s_str, szKey, szDefault);
494
495 return s_str.c_str();
496 }
497
498 bool wxFileConfig::Read(long *pl, const char *szKey, long lDefault) const
499 {
500 wxString str;
501 if ( Read(&str, szKey) ) {
502 *pl = atol(str);
503 return TRUE;
504 }
505 else {
506 *pl = lDefault;
507 return FALSE;
508 }
509 }
510
511 bool wxFileConfig::Write(const char *szKey, const char *szValue)
512 {
513 PathChanger path(this, szKey);
514
515 wxString strName = path.Name();
516 if ( strName.IsEmpty() ) {
517 // setting the value of a group is an error
518 wxASSERT_MSG( IsEmpty(szValue), "can't set value of a group!" );
519
520 // ... except if it's empty in which case it's a way to force it's creation
521 m_pCurrentGroup->SetDirty();
522
523 // this will add a line for this group if it didn't have it before
524 (void)m_pCurrentGroup->GetGroupLine();
525 }
526 else {
527 // writing an entry
528
529 // check that the name is reasonable
530 if ( strName[0u] == APPCONF_IMMUTABLE_PREFIX ) {
531 wxLogError(_("Entry name can't start with '%c'."),
532 APPCONF_IMMUTABLE_PREFIX);
533 return FALSE;
534 }
535
536 for ( const char *pc = strName; *pc != '\0'; pc++ ) {
537 if ( !IsValid(*pc) ) {
538 wxLogError(_("Character '%c' is invalid in a config entry name."),
539 *pc);
540 return FALSE;
541 }
542 }
543
544 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strName);
545 if ( pEntry == NULL )
546 pEntry = m_pCurrentGroup->AddEntry(strName);
547
548 pEntry->SetValue(szValue);
549 }
550
551 return TRUE;
552 }
553
554 bool wxFileConfig::Write(const char *szKey, long lValue)
555 {
556 // ltoa() is not ANSI :-(
557 char szBuf[40]; // should be good for sizeof(long) <= 16 (128 bits)
558 sprintf(szBuf, "%ld", lValue);
559 return Write(szKey, szBuf);
560 }
561
562 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
563 {
564 if ( LineListIsEmpty() || !m_pRootGroup->IsDirty() )
565 return TRUE;
566
567 wxTempFile file(m_strLocalFile);
568
569 if ( !file.IsOpened() ) {
570 wxLogError(_("can't open user configuration file."));
571 return FALSE;
572 }
573
574 // write all strings to file
575 for ( LineList *p = m_linesHead; p != NULL; p = p->Next() ) {
576 if ( !file.Write(p->Text() + wxTextFile::GetEOL()) ) {
577 wxLogError(_("can't write user configuration file."));
578 return FALSE;
579 }
580 }
581
582 return file.Commit();
583 }
584
585 // ----------------------------------------------------------------------------
586 // delete groups/entries
587 // ----------------------------------------------------------------------------
588
589 bool wxFileConfig::DeleteEntry(const char *szKey, bool bGroupIfEmptyAlso)
590 {
591 PathChanger path(this, szKey);
592
593 if ( !m_pCurrentGroup->DeleteEntry(path.Name()) )
594 return FALSE;
595
596 if ( bGroupIfEmptyAlso && m_pCurrentGroup->IsEmpty() ) {
597 if ( m_pCurrentGroup != m_pRootGroup ) {
598 ConfigGroup *pGroup = m_pCurrentGroup;
599 SetPath(".."); // changes m_pCurrentGroup!
600 m_pCurrentGroup->DeleteSubgroup(pGroup->Name());
601 }
602 //else: never delete the root group
603 }
604
605 return TRUE;
606 }
607
608 bool wxFileConfig::DeleteGroup(const char *szKey)
609 {
610 PathChanger path(this, szKey);
611
612 return m_pCurrentGroup->DeleteSubgroup(path.Name());
613 }
614
615 bool wxFileConfig::DeleteAll()
616 {
617 const char *szFile = m_strLocalFile;
618 delete m_pRootGroup;
619 Init();
620
621 if ( remove(szFile) == -1 )
622 wxLogSysError(_("can't delete user configuration file '%s'"), szFile);
623
624 szFile = m_strGlobalFile;
625 if ( remove(szFile) )
626 wxLogSysError(_("can't delete system configuration file '%s'"), szFile);
627
628 return TRUE;
629 }
630
631 // ----------------------------------------------------------------------------
632 // linked list functions
633 // ----------------------------------------------------------------------------
634
635 // append a new line to the end of the list
636 wxFileConfig::LineList *wxFileConfig::LineListAppend(const wxString& str)
637 {
638 LineList *pLine = new LineList(str);
639
640 if ( m_linesTail == NULL ) {
641 // list is empty
642 m_linesHead = pLine;
643 }
644 else {
645 // adjust pointers
646 m_linesTail->SetNext(pLine);
647 pLine->SetPrev(m_linesTail);
648 }
649
650 m_linesTail = pLine;
651 return m_linesTail;
652 }
653
654 // insert a new line after the given one or in the very beginning if !pLine
655 wxFileConfig::LineList *wxFileConfig::LineListInsert(const wxString& str,
656 LineList *pLine)
657 {
658 if ( pLine == m_linesTail )
659 return LineListAppend(str);
660
661 LineList *pNewLine = new LineList(str);
662 if ( pLine == NULL ) {
663 // prepend to the list
664 pNewLine->SetNext(m_linesHead);
665 m_linesHead->SetPrev(pNewLine);
666 m_linesHead = pNewLine;
667 }
668 else {
669 // insert before pLine
670 LineList *pNext = pLine->Next();
671 pNewLine->SetNext(pNext);
672 pNewLine->SetPrev(pLine);
673 pNext->SetPrev(pNewLine);
674 pLine->SetNext(pNewLine);
675 }
676
677 return pNewLine;
678 }
679
680 void wxFileConfig::LineListRemove(LineList *pLine)
681 {
682 LineList *pPrev = pLine->Prev(),
683 *pNext = pLine->Next();
684
685 // first entry?
686 if ( pPrev == NULL )
687 m_linesHead = pNext;
688 else
689 pPrev->SetNext(pNext);
690
691 // last entry?
692 if ( pNext == NULL )
693 m_linesTail = pPrev;
694 else
695 pNext->SetPrev(pPrev);
696
697 delete pLine;
698 }
699
700 bool wxFileConfig::LineListIsEmpty()
701 {
702 return m_linesHead == NULL;
703 }
704
705 // ============================================================================
706 // wxFileConfig::ConfigGroup
707 // ============================================================================
708
709 // ----------------------------------------------------------------------------
710 // ctor/dtor
711 // ----------------------------------------------------------------------------
712
713 // ctor
714 wxFileConfig::ConfigGroup::ConfigGroup(wxFileConfig::ConfigGroup *pParent,
715 const wxString& strName,
716 wxFileConfig *pConfig)
717 : m_aEntries(CompareEntries),
718 m_aSubgroups(CompareGroups),
719 m_strName(strName)
720 {
721 m_pConfig = pConfig;
722 m_pParent = pParent;
723 m_bDirty = FALSE;
724 m_pLine = NULL;
725
726 m_pLastEntry = NULL;
727 m_pLastGroup = NULL;
728 }
729
730 // dtor deletes all children
731 wxFileConfig::ConfigGroup::~ConfigGroup()
732 {
733 // entries
734 uint n, nCount = m_aEntries.Count();
735 for ( n = 0; n < nCount; n++ )
736 delete m_aEntries[n];
737
738 // subgroups
739 nCount = m_aSubgroups.Count();
740 for ( n = 0; n < nCount; n++ )
741 delete m_aSubgroups[n];
742 }
743
744 // ----------------------------------------------------------------------------
745 // line
746 // ----------------------------------------------------------------------------
747
748 void wxFileConfig::ConfigGroup::SetLine(LineList *pLine)
749 {
750 wxASSERT( m_pLine == NULL ); // shouldn't be called twice
751
752 m_pLine = pLine;
753 }
754
755 /*
756 This is a bit complicated, so let me explain it in details. All lines that
757 were read from the local file (the only one we will ever modify) are stored
758 in a (doubly) linked list. Our problem is to know at which position in this
759 list should we insert the new entries/subgroups. To solve it we keep three
760 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
761
762 m_pLine points to the line containing "[group_name]"
763 m_pLastEntry points to the last entry of this group in the local file.
764 m_pLastGroup subgroup
765
766 Initially, they're NULL all three. When the group (an entry/subgroup) is read
767 from the local file, the corresponding variable is set. However, if the group
768 was read from the global file and then modified or created by the application
769 these variables are still NULL and we need to create the corresponding lines.
770 See the following functions (and comments preceding them) for the details of
771 how we do it.
772
773 Also, when our last entry/group are deleted we need to find the new last
774 element - the code in DeleteEntry/Subgroup does this by backtracking the list
775 of lines until it either founds an entry/subgroup (and this is the new last
776 element) or the m_pLine of the group, in which case there are no more entries
777 (or subgroups) left and m_pLast<element> becomes NULL.
778
779 NB: This last problem could be avoided for entries if we added new entries
780 immediately after m_pLine, but in this case the entries would appear
781 backwards in the config file (OTOH, it's not that important) and as we
782 would still need to do it for the subgroups the code wouldn't have been
783 significantly less complicated.
784 */
785
786 // Return the line which contains "[our name]". If we're still not in the list,
787 // add our line to it immediately after the last line of our parent group if we
788 // have it or in the very beginning if we're the root group.
789 wxFileConfig::LineList *wxFileConfig::ConfigGroup::GetGroupLine()
790 {
791 if ( m_pLine == NULL ) {
792 ConfigGroup *pParent = Parent();
793
794 // this group wasn't present in local config file, add it now
795 if ( pParent != NULL ) {
796 wxString strFullName;
797 strFullName << "[" << GetFullName().c_str() + 1 << "]"; // +1: no '/'
798 m_pLine = m_pConfig->LineListInsert(strFullName,
799 pParent->GetLastGroupLine());
800 pParent->SetLastGroup(this); // we're surely after all the others
801 }
802 else {
803 // we return NULL, so that LineListInsert() will insert us in the
804 // very beginning
805 }
806 }
807
808 return m_pLine;
809 }
810
811 // Return the last line belonging to the subgroups of this group (after which
812 // we can add a new subgroup), if we don't have any subgroups or entries our
813 // last line is the group line (m_pLine) itself.
814 wxFileConfig::LineList *wxFileConfig::ConfigGroup::GetLastGroupLine()
815 {
816 // if we have any subgroups, our last line is the last line of the last
817 // subgroup
818 if ( m_pLastGroup != NULL ) {
819 wxFileConfig::LineList *pLine = m_pLastGroup->GetLastGroupLine();
820
821 wxASSERT( pLine != NULL ); // last group must have !NULL associated line
822 return pLine;
823 }
824
825 // no subgroups, so the last line is the line of thelast entry (if any)
826 return GetLastEntryLine();
827 }
828
829 // return the last line belonging to the entries of this group (after which
830 // we can add a new entry), if we don't have any entries we will add the new
831 // one immediately after the group line itself.
832 wxFileConfig::LineList *wxFileConfig::ConfigGroup::GetLastEntryLine()
833 {
834 if ( m_pLastEntry != NULL ) {
835 wxFileConfig::LineList *pLine = m_pLastEntry->GetLine();
836
837 wxASSERT( pLine != NULL ); // last entry must have !NULL associated line
838 return pLine;
839 }
840
841 // no entries: insert after the group header
842 return GetGroupLine();
843 }
844
845 // ----------------------------------------------------------------------------
846 // group name
847 // ----------------------------------------------------------------------------
848
849 wxString wxFileConfig::ConfigGroup::GetFullName() const
850 {
851 if ( Parent() )
852 return Parent()->GetFullName() + APPCONF_PATH_SEPARATOR + Name();
853 else
854 return "";
855 }
856
857 // ----------------------------------------------------------------------------
858 // find an item
859 // ----------------------------------------------------------------------------
860
861 // use binary search because the array is sorted
862 wxFileConfig::ConfigEntry *
863 wxFileConfig::ConfigGroup::FindEntry(const char *szName) const
864 {
865 uint i,
866 lo = 0,
867 hi = m_aEntries.Count();
868 int res;
869 wxFileConfig::ConfigEntry *pEntry;
870
871 while ( lo < hi ) {
872 i = (lo + hi)/2;
873 pEntry = m_aEntries[i];
874
875 #if APPCONF_CASE_SENSITIVE
876 res = strcmp(pEntry->Name(), szName);
877 #else
878 res = Stricmp(pEntry->Name(), szName);
879 #endif
880
881 if ( res > 0 )
882 hi = i;
883 else if ( res < 0 )
884 lo = i + 1;
885 else
886 return pEntry;
887 }
888
889 return NULL;
890 }
891
892 wxFileConfig::ConfigGroup *
893 wxFileConfig::ConfigGroup::FindSubgroup(const char *szName) const
894 {
895 uint i,
896 lo = 0,
897 hi = m_aSubgroups.Count();
898 int res;
899 wxFileConfig::ConfigGroup *pGroup;
900
901 while ( lo < hi ) {
902 i = (lo + hi)/2;
903 pGroup = m_aSubgroups[i];
904
905 #if APPCONF_CASE_SENSITIVE
906 res = strcmp(pGroup->Name(), szName);
907 #else
908 res = Stricmp(pGroup->Name(), szName);
909 #endif
910
911 if ( res > 0 )
912 hi = i;
913 else if ( res < 0 )
914 lo = i + 1;
915 else
916 return pGroup;
917 }
918
919 return NULL;
920 }
921
922 // ----------------------------------------------------------------------------
923 // create a new item
924 // ----------------------------------------------------------------------------
925
926 // create a new entry and add it to the current group
927 wxFileConfig::ConfigEntry *
928 wxFileConfig::ConfigGroup::AddEntry(const wxString& strName, int nLine)
929 {
930 wxASSERT( FindEntry(strName) == NULL );
931
932 ConfigEntry *pEntry = new ConfigEntry(this, strName, nLine);
933 m_aEntries.Add(pEntry);
934
935 return pEntry;
936 }
937
938 // create a new group and add it to the current group
939 wxFileConfig::ConfigGroup *
940 wxFileConfig::ConfigGroup::AddSubgroup(const wxString& strName)
941 {
942 wxASSERT( FindSubgroup(strName) == NULL );
943
944 ConfigGroup *pGroup = new ConfigGroup(this, strName, m_pConfig);
945 m_aSubgroups.Add(pGroup);
946
947 return pGroup;
948 }
949
950 // ----------------------------------------------------------------------------
951 // delete an item
952 // ----------------------------------------------------------------------------
953
954 /*
955 The delete operations are _very_ slow if we delete the last item of this
956 group (see comments before GetXXXLineXXX functions for more details),
957 so it's much better to start with the first entry/group if we want to
958 delete several of them.
959 */
960
961 bool wxFileConfig::ConfigGroup::DeleteSubgroup(const char *szName)
962 {
963 ConfigGroup *pGroup = FindSubgroup(szName);
964 wxCHECK( pGroup != NULL, FALSE ); // deleting non existing group?
965
966 // delete all entries
967 uint nCount = pGroup->m_aEntries.Count();
968 for ( uint nEntry = 0; nEntry < nCount; nEntry++ ) {
969 LineList *pLine = pGroup->m_aEntries[nEntry]->GetLine();
970 if ( pLine != NULL )
971 m_pConfig->LineListRemove(pLine);
972 }
973
974 LineList *pLine = pGroup->m_pLine;
975 if ( pLine != NULL ) {
976 // notice that we may do this test inside the previous "if" because the
977 // last entry's line is surely !NULL
978 if ( pGroup == m_pLastGroup ) {
979 // our last entry is being deleted - find the last one which stays
980 wxASSERT( m_pLine != NULL ); // we have a subgroup with !NULL pLine...
981
982 // go back until we find a subgroup or reach the group's line
983 ConfigGroup *pNewLast = NULL;
984 uint n, nSubgroups = m_aSubgroups.Count();
985 for ( LineList *pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() ) {
986 // is it our subgroup?
987 for ( n = 0; (pNewLast == NULL) && (n < nSubgroups); n++ ) {
988 // do _not_ call GetGroupLine! we don't want to add it to the local
989 // file if it's not already there
990 if ( m_aSubgroups[n]->m_pLine == m_pLine )
991 pNewLast = m_aSubgroups[n];
992 }
993
994 if ( pNewLast != NULL ) // found?
995 break;
996 }
997
998 if ( pl == m_pLine ) {
999 wxASSERT( !pNewLast ); // how comes it has the same line as we?
1000
1001 // we've reached the group line without finding any subgroups
1002 m_pLastGroup = NULL;
1003 }
1004 else
1005 m_pLastGroup = pNewLast;
1006 }
1007
1008 m_pConfig->LineListRemove(pLine);
1009 }
1010
1011 SetDirty();
1012
1013 m_aSubgroups.Remove(pGroup);
1014 delete pGroup;
1015
1016 return TRUE;
1017 }
1018
1019 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName)
1020 {
1021 ConfigEntry *pEntry = FindEntry(szName);
1022 wxCHECK( pEntry != NULL, FALSE ); // deleting non existing item?
1023
1024 LineList *pLine = pEntry->GetLine();
1025 if ( pLine != NULL ) {
1026 // notice that we may do this test inside the previous "if" because the
1027 // last entry's line is surely !NULL
1028 if ( pEntry == m_pLastEntry ) {
1029 // our last entry is being deleted - find the last one which stays
1030 wxASSERT( m_pLine != NULL ); // if we have an entry with !NULL pLine...
1031
1032 // go back until we find another entry or reach the group's line
1033 ConfigEntry *pNewLast = NULL;
1034 uint n, nEntries = m_aEntries.Count();
1035 for ( LineList *pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() ) {
1036 // is it our subgroup?
1037 for ( n = 0; (pNewLast == NULL) && (n < nEntries); n++ ) {
1038 if ( m_aEntries[n]->GetLine() == m_pLine )
1039 pNewLast = m_aEntries[n];
1040 }
1041
1042 if ( pNewLast != NULL ) // found?
1043 break;
1044 }
1045
1046 if ( pl == m_pLine ) {
1047 wxASSERT( !pNewLast ); // how comes it has the same line as we?
1048
1049 // we've reached the group line without finding any subgroups
1050 m_pLastEntry = NULL;
1051 }
1052 else
1053 m_pLastEntry = pNewLast;
1054 }
1055
1056 m_pConfig->LineListRemove(pLine);
1057 }
1058
1059 // we must be written back for the changes to be saved
1060 SetDirty();
1061
1062 m_aEntries.Remove(pEntry);
1063 delete pEntry;
1064
1065 return TRUE;
1066 }
1067
1068 // ----------------------------------------------------------------------------
1069 //
1070 // ----------------------------------------------------------------------------
1071 void wxFileConfig::ConfigGroup::SetDirty()
1072 {
1073 m_bDirty = TRUE;
1074 if ( Parent() != NULL ) // propagate upwards
1075 Parent()->SetDirty();
1076 }
1077
1078 // ============================================================================
1079 // wxFileConfig::ConfigEntry
1080 // ============================================================================
1081
1082 // ----------------------------------------------------------------------------
1083 // ctor
1084 // ----------------------------------------------------------------------------
1085 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup *pParent,
1086 const wxString& strName,
1087 int nLine)
1088 : m_strName(strName)
1089 {
1090 wxASSERT( !strName.IsEmpty() );
1091
1092 m_pParent = pParent;
1093 m_nLine = nLine;
1094 m_pLine = NULL;
1095
1096 m_bDirty = FALSE;
1097
1098 m_bImmutable = strName[0] == APPCONF_IMMUTABLE_PREFIX;
1099 if ( m_bImmutable )
1100 m_strName.erase(0, 1); // remove first character
1101 }
1102
1103 // ----------------------------------------------------------------------------
1104 // set value
1105 // ----------------------------------------------------------------------------
1106
1107 void wxFileConfig::ConfigEntry::SetLine(LineList *pLine)
1108 {
1109 if ( m_pLine != NULL ) {
1110 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1111 Name().c_str(), m_pParent->GetFullName().c_str());
1112 }
1113
1114 m_pLine = pLine;
1115 Group()->SetLastEntry(this);
1116 }
1117
1118 // second parameter is FALSE if we read the value from file and prevents the
1119 // entry from being marked as 'dirty'
1120 void wxFileConfig::ConfigEntry::SetValue(const wxString& strValue, bool bUser)
1121 {
1122 if ( bUser && IsImmutable() ) {
1123 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1124 Name().c_str());
1125 return;
1126 }
1127
1128 // do nothing if it's the same value
1129 if ( strValue == m_strValue )
1130 return;
1131
1132 m_strValue = strValue;
1133
1134 if ( bUser ) {
1135 wxString strVal = FilterOut(strValue);
1136 wxString strLine;
1137 strLine << m_strName << " = " << strVal;
1138
1139 if ( m_pLine != NULL ) {
1140 // entry was read from the local config file, just modify the line
1141 m_pLine->SetText(strLine);
1142 }
1143 else {
1144 // add a new line to the file
1145 wxASSERT( m_nLine == NOT_FOUND ); // consistency check
1146
1147 m_pLine = Group()->Config()->LineListInsert(strLine,
1148 Group()->GetLastEntryLine());
1149 Group()->SetLastEntry(this);
1150 }
1151
1152 SetDirty();
1153 }
1154 }
1155
1156 void wxFileConfig::ConfigEntry::SetDirty()
1157 {
1158 m_bDirty = TRUE;
1159 Group()->SetDirty();
1160 }
1161
1162 // ============================================================================
1163 // global functions
1164 // ============================================================================
1165
1166 // ----------------------------------------------------------------------------
1167 // compare functions for array sorting
1168 // ----------------------------------------------------------------------------
1169
1170 int CompareEntries(wxFileConfig::ConfigEntry *p1,
1171 wxFileConfig::ConfigEntry *p2)
1172 {
1173 #if APPCONF_CASE_SENSITIVE
1174 return strcmp(p1->Name(), p2->Name());
1175 #else
1176 return Stricmp(p1->Name(), p2->Name());
1177 #endif
1178 }
1179
1180 int CompareGroups(wxFileConfig::ConfigGroup *p1,
1181 wxFileConfig::ConfigGroup *p2)
1182 {
1183 #if APPCONF_CASE_SENSITIVE
1184 return strcmp(p1->Name(), p2->Name());
1185 #else
1186 return Stricmp(p1->Name(), p2->Name());
1187 #endif
1188 }
1189
1190 // ----------------------------------------------------------------------------
1191 // filter functions
1192 // ----------------------------------------------------------------------------
1193
1194 // undo FilterOut
1195 wxString FilterIn(const wxString& str)
1196 {
1197 wxString strResult;
1198 strResult.Alloc(str.Len());
1199
1200 bool bQuoted = !str.IsEmpty() && str[0] == '"';
1201
1202 for ( uint n = bQuoted ? 1 : 0; n < str.Len(); n++ ) {
1203 if ( str[n] == '\\' ) {
1204 switch ( str[++n] ) {
1205 case 'n':
1206 strResult += '\n';
1207 break;
1208
1209 case 'r':
1210 strResult += '\r';
1211 break;
1212
1213 case 't':
1214 strResult += '\t';
1215 break;
1216
1217 case '\\':
1218 strResult += '\\';
1219 break;
1220
1221 case '"':
1222 strResult += '"';
1223 break;
1224 }
1225 }
1226 else {
1227 if ( str[n] != '"' || !bQuoted )
1228 strResult += str[n];
1229 else if ( n != str.Len() - 1 ) {
1230 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1231 n, str.c_str());
1232 }
1233 //else: it's the last quote of a quoted string, ok
1234 }
1235 }
1236
1237 return strResult;
1238 }
1239
1240 // quote the string before writing it to file
1241 wxString FilterOut(const wxString& str)
1242 {
1243 wxString strResult;
1244 strResult.Alloc(str.Len());
1245
1246 // quoting is necessary to preserve spaces in the beginning of the string
1247 bool bQuote = isspace(str[0]) || str[0] == '"';
1248
1249 if ( bQuote )
1250 strResult += '"';
1251
1252 char c;
1253 for ( uint n = 0; n < str.Len(); n++ ) {
1254 switch ( str[n] ) {
1255 case '\n':
1256 c = 'n';
1257 break;
1258
1259 case '\r':
1260 c = 'r';
1261 break;
1262
1263 case '\t':
1264 c = 't';
1265 break;
1266
1267 case '\\':
1268 c = '\\';
1269 break;
1270
1271 case '"':
1272 if ( bQuote )
1273 c = '"';
1274 //else: fall through
1275
1276 default:
1277 strResult += str[n];
1278 continue; // nothing special to do
1279 }
1280
1281 // we get here only for special characters
1282 strResult << '\\' << c;
1283 }
1284
1285 if ( bQuote )
1286 strResult += '"';
1287
1288 return strResult;
1289 }