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