]> git.saurik.com Git - wxWidgets.git/blob - src/common/fileconf.cpp
Update to docs incl. wxString first stab; added a couple of palette-related events...
[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 LineList *pl;
986 for ( pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() ) {
987 // is it our subgroup?
988 for ( n = 0; (pNewLast == NULL) && (n < nSubgroups); n++ ) {
989 // do _not_ call GetGroupLine! we don't want to add it to the local
990 // file if it's not already there
991 if ( m_aSubgroups[n]->m_pLine == m_pLine )
992 pNewLast = m_aSubgroups[n];
993 }
994
995 if ( pNewLast != NULL ) // found?
996 break;
997 }
998
999 if ( pl == m_pLine ) {
1000 wxASSERT( !pNewLast ); // how comes it has the same line as we?
1001
1002 // we've reached the group line without finding any subgroups
1003 m_pLastGroup = NULL;
1004 }
1005 else
1006 m_pLastGroup = pNewLast;
1007 }
1008
1009 m_pConfig->LineListRemove(pLine);
1010 }
1011
1012 SetDirty();
1013
1014 m_aSubgroups.Remove(pGroup);
1015 delete pGroup;
1016
1017 return TRUE;
1018 }
1019
1020 bool wxFileConfig::ConfigGroup::DeleteEntry(const char *szName)
1021 {
1022 ConfigEntry *pEntry = FindEntry(szName);
1023 wxCHECK( pEntry != NULL, FALSE ); // deleting non existing item?
1024
1025 LineList *pLine = pEntry->GetLine();
1026 if ( pLine != NULL ) {
1027 // notice that we may do this test inside the previous "if" because the
1028 // last entry's line is surely !NULL
1029 if ( pEntry == m_pLastEntry ) {
1030 // our last entry is being deleted - find the last one which stays
1031 wxASSERT( m_pLine != NULL ); // if we have an entry with !NULL pLine...
1032
1033 // go back until we find another entry or reach the group's line
1034 ConfigEntry *pNewLast = NULL;
1035 uint n, nEntries = m_aEntries.Count();
1036 LineList *pl;
1037 for ( pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() ) {
1038 // is it our subgroup?
1039 for ( n = 0; (pNewLast == NULL) && (n < nEntries); n++ ) {
1040 if ( m_aEntries[n]->GetLine() == m_pLine )
1041 pNewLast = m_aEntries[n];
1042 }
1043
1044 if ( pNewLast != NULL ) // found?
1045 break;
1046 }
1047
1048 if ( pl == m_pLine ) {
1049 wxASSERT( !pNewLast ); // how comes it has the same line as we?
1050
1051 // we've reached the group line without finding any subgroups
1052 m_pLastEntry = NULL;
1053 }
1054 else
1055 m_pLastEntry = pNewLast;
1056 }
1057
1058 m_pConfig->LineListRemove(pLine);
1059 }
1060
1061 // we must be written back for the changes to be saved
1062 SetDirty();
1063
1064 m_aEntries.Remove(pEntry);
1065 delete pEntry;
1066
1067 return TRUE;
1068 }
1069
1070 // ----------------------------------------------------------------------------
1071 //
1072 // ----------------------------------------------------------------------------
1073 void wxFileConfig::ConfigGroup::SetDirty()
1074 {
1075 m_bDirty = TRUE;
1076 if ( Parent() != NULL ) // propagate upwards
1077 Parent()->SetDirty();
1078 }
1079
1080 // ============================================================================
1081 // wxFileConfig::ConfigEntry
1082 // ============================================================================
1083
1084 // ----------------------------------------------------------------------------
1085 // ctor
1086 // ----------------------------------------------------------------------------
1087 wxFileConfig::ConfigEntry::ConfigEntry(wxFileConfig::ConfigGroup *pParent,
1088 const wxString& strName,
1089 int nLine)
1090 : m_strName(strName)
1091 {
1092 wxASSERT( !strName.IsEmpty() );
1093
1094 m_pParent = pParent;
1095 m_nLine = nLine;
1096 m_pLine = NULL;
1097
1098 m_bDirty = FALSE;
1099
1100 m_bImmutable = strName[0] == APPCONF_IMMUTABLE_PREFIX;
1101 if ( m_bImmutable )
1102 m_strName.erase(0, 1); // remove first character
1103 }
1104
1105 // ----------------------------------------------------------------------------
1106 // set value
1107 // ----------------------------------------------------------------------------
1108
1109 void wxFileConfig::ConfigEntry::SetLine(LineList *pLine)
1110 {
1111 if ( m_pLine != NULL ) {
1112 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1113 Name().c_str(), m_pParent->GetFullName().c_str());
1114 }
1115
1116 m_pLine = pLine;
1117 Group()->SetLastEntry(this);
1118 }
1119
1120 // second parameter is FALSE if we read the value from file and prevents the
1121 // entry from being marked as 'dirty'
1122 void wxFileConfig::ConfigEntry::SetValue(const wxString& strValue, bool bUser)
1123 {
1124 if ( bUser && IsImmutable() ) {
1125 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1126 Name().c_str());
1127 return;
1128 }
1129
1130 // do nothing if it's the same value
1131 if ( strValue == m_strValue )
1132 return;
1133
1134 m_strValue = strValue;
1135
1136 if ( bUser ) {
1137 wxString strVal = FilterOut(strValue);
1138 wxString strLine;
1139 strLine << m_strName << " = " << strVal;
1140
1141 if ( m_pLine != NULL ) {
1142 // entry was read from the local config file, just modify the line
1143 m_pLine->SetText(strLine);
1144 }
1145 else {
1146 // add a new line to the file
1147 wxASSERT( m_nLine == NOT_FOUND ); // consistency check
1148
1149 m_pLine = Group()->Config()->LineListInsert(strLine,
1150 Group()->GetLastEntryLine());
1151 Group()->SetLastEntry(this);
1152 }
1153
1154 SetDirty();
1155 }
1156 }
1157
1158 void wxFileConfig::ConfigEntry::SetDirty()
1159 {
1160 m_bDirty = TRUE;
1161 Group()->SetDirty();
1162 }
1163
1164 // ============================================================================
1165 // global functions
1166 // ============================================================================
1167
1168 // ----------------------------------------------------------------------------
1169 // compare functions for array sorting
1170 // ----------------------------------------------------------------------------
1171
1172 int CompareEntries(wxFileConfig::ConfigEntry *p1,
1173 wxFileConfig::ConfigEntry *p2)
1174 {
1175 #if APPCONF_CASE_SENSITIVE
1176 return strcmp(p1->Name(), p2->Name());
1177 #else
1178 return Stricmp(p1->Name(), p2->Name());
1179 #endif
1180 }
1181
1182 int CompareGroups(wxFileConfig::ConfigGroup *p1,
1183 wxFileConfig::ConfigGroup *p2)
1184 {
1185 #if APPCONF_CASE_SENSITIVE
1186 return strcmp(p1->Name(), p2->Name());
1187 #else
1188 return Stricmp(p1->Name(), p2->Name());
1189 #endif
1190 }
1191
1192 // ----------------------------------------------------------------------------
1193 // filter functions
1194 // ----------------------------------------------------------------------------
1195
1196 // undo FilterOut
1197 wxString FilterIn(const wxString& str)
1198 {
1199 wxString strResult;
1200 strResult.Alloc(str.Len());
1201
1202 bool bQuoted = !str.IsEmpty() && str[0] == '"';
1203
1204 for ( uint n = bQuoted ? 1 : 0; n < str.Len(); n++ ) {
1205 if ( str[n] == '\\' ) {
1206 switch ( str[++n] ) {
1207 case 'n':
1208 strResult += '\n';
1209 break;
1210
1211 case 'r':
1212 strResult += '\r';
1213 break;
1214
1215 case 't':
1216 strResult += '\t';
1217 break;
1218
1219 case '\\':
1220 strResult += '\\';
1221 break;
1222
1223 case '"':
1224 strResult += '"';
1225 break;
1226 }
1227 }
1228 else {
1229 if ( str[n] != '"' || !bQuoted )
1230 strResult += str[n];
1231 else if ( n != str.Len() - 1 ) {
1232 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1233 n, str.c_str());
1234 }
1235 //else: it's the last quote of a quoted string, ok
1236 }
1237 }
1238
1239 return strResult;
1240 }
1241
1242 // quote the string before writing it to file
1243 wxString FilterOut(const wxString& str)
1244 {
1245 wxString strResult;
1246 strResult.Alloc(str.Len());
1247
1248 // quoting is necessary to preserve spaces in the beginning of the string
1249 bool bQuote = isspace(str[0]) || str[0] == '"';
1250
1251 if ( bQuote )
1252 strResult += '"';
1253
1254 char c;
1255 for ( uint n = 0; n < str.Len(); n++ ) {
1256 switch ( str[n] ) {
1257 case '\n':
1258 c = 'n';
1259 break;
1260
1261 case '\r':
1262 c = 'r';
1263 break;
1264
1265 case '\t':
1266 c = 't';
1267 break;
1268
1269 case '\\':
1270 c = '\\';
1271 break;
1272
1273 case '"':
1274 if ( bQuote )
1275 c = '"';
1276 //else: fall through
1277
1278 default:
1279 strResult += str[n];
1280 continue; // nothing special to do
1281 }
1282
1283 // we get here only for special characters
1284 strResult << '\\' << c;
1285 }
1286
1287 if ( bQuote )
1288 strResult += '"';
1289
1290 return strResult;
1291 }