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