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