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