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