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