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