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