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