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