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