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