]> git.saurik.com Git - wxWidgets.git/blob - src/common/fileconf.cpp
Some minors changes, like adding empty lines at end of files, to be able to compile...
[wxWidgets.git] / src / common / fileconf.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: fileconf.cpp
3 // Purpose: implementation of wxFileConfig derivation of wxConfig
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 07.04.98 (adapted from appconf.cpp)
7 // RCS-ID: $Id$
8 // Copyright: (c) 1997 Karsten Ballüder & Vadim Zeitlin
9 // Ballueder@usa.net <zeitlin@dptmaths.ens-cachan.fr>
10 // Licence: wxWindows license
11 ///////////////////////////////////////////////////////////////////////////////
12
13 #ifdef __GNUG__
14 #pragma implementation "fileconf.h"
15 #endif
16
17 // ----------------------------------------------------------------------------
18 // headers
19 // ----------------------------------------------------------------------------
20
21 #include "wx/wxprec.h"
22
23 #ifdef __BORLANDC__
24 #pragma hdrstop
25 #endif //__BORLANDC__
26
27 #if wxUSE_CONFIG
28
29 #ifndef WX_PRECOMP
30 #include "wx/string.h"
31 #include "wx/intl.h"
32 #endif //WX_PRECOMP
33
34 #include "wx/app.h"
35 #include "wx/dynarray.h"
36 #include "wx/file.h"
37 #include "wx/log.h"
38 #include "wx/textfile.h"
39 #include "wx/config.h"
40 #include "wx/fileconf.h"
41
42 #include "wx/utils.h" // for wxGetHomeDir
43
44 // _WINDOWS_ is defined when windows.h is included,
45 // __WXMSW__ is defined for MS Windows compilation
46 #if defined(__WXMSW__) && !defined(_WINDOWS_)
47 #include <windows.h>
48 #endif //windows.h
49
50 #include <stdlib.h>
51 #include <ctype.h>
52
53 // ----------------------------------------------------------------------------
54 // macros
55 // ----------------------------------------------------------------------------
56 #define CONST_CAST ((wxFileConfig *)this)->
57
58 // ----------------------------------------------------------------------------
59 // constants
60 // ----------------------------------------------------------------------------
61 #ifndef MAX_PATH
62 #define MAX_PATH 512
63 #endif
64
65 // ----------------------------------------------------------------------------
66 // global functions declarations
67 // ----------------------------------------------------------------------------
68
69 // compare functions for sorting the arrays
70 static int CompareEntries(ConfigEntry *p1, ConfigEntry *p2);
71 static int CompareGroups(ConfigGroup *p1, ConfigGroup *p2);
72
73 // filter strings
74 static wxString FilterInValue(const wxString& str);
75 static wxString FilterOutValue(const wxString& str);
76
77 static wxString FilterInEntryName(const wxString& str);
78 static wxString FilterOutEntryName(const wxString& str);
79
80 // get the name to use in wxFileConfig ctor
81 static wxString GetAppName(const wxString& appname);
82
83 // ============================================================================
84 // implementation
85 // ============================================================================
86
87 // ----------------------------------------------------------------------------
88 // static functions
89 // ----------------------------------------------------------------------------
90 wxString wxFileConfig::GetGlobalDir()
91 {
92 wxString strDir;
93
94 #ifdef __UNIX__
95 strDir = _T("/etc/");
96 #elif defined(__WXSTUBS__)
97 wxASSERT_MSG( FALSE, _T("TODO") ) ;
98 #elif defined(__WXMAC__)
99 wxASSERT_MSG( FALSE, _T("TODO") ) ;
100 #else // Windows
101 wxChar szWinDir[MAX_PATH];
102 ::GetWindowsDirectory(szWinDir, MAX_PATH);
103
104 strDir = szWinDir;
105 strDir << _T('\\');
106 #endif // Unix/Windows
107
108 return strDir;
109 }
110
111 wxString wxFileConfig::GetLocalDir()
112 {
113 wxString strDir;
114
115 wxGetHomeDir(&strDir);
116
117 #ifdef __UNIX__
118 if (strDir.Last() != _T('/')) strDir << _T('/');
119 #else
120 if (strDir.Last() != _T('\\')) strDir << _T('\\');
121 #endif
122
123 return strDir;
124 }
125
126 wxString wxFileConfig::GetGlobalFileName(const wxChar *szFile)
127 {
128 wxString str = GetGlobalDir();
129 str << szFile;
130
131 if ( wxStrchr(szFile, _T('.')) == NULL )
132 #ifdef __UNIX__
133 str << _T(".conf");
134 #else // Windows
135 str << _T(".ini");
136 #endif // UNIX/Win
137
138 return str;
139 }
140
141 wxString wxFileConfig::GetLocalFileName(const wxChar *szFile)
142 {
143 wxString str = GetLocalDir();
144
145 #ifdef __UNIX__
146 str << _T('.');
147 #endif
148
149 str << szFile;
150
151 #ifdef __WXMSW__
152 if ( wxStrchr(szFile, _T('.')) == NULL )
153 str << _T(".ini");
154 #endif
155
156 return str;
157 }
158
159 // ----------------------------------------------------------------------------
160 // ctor
161 // ----------------------------------------------------------------------------
162
163 void wxFileConfig::Init()
164 {
165 m_pCurrentGroup =
166 m_pRootGroup = new ConfigGroup(NULL, "", this);
167
168 m_linesHead =
169 m_linesTail = NULL;
170
171 // it's not an error if (one of the) file(s) doesn't exist
172
173 // parse the global file
174 if ( !m_strGlobalFile.IsEmpty() && wxFile::Exists(m_strGlobalFile) ) {
175 wxTextFile fileGlobal(m_strGlobalFile);
176
177 if ( fileGlobal.Open() ) {
178 Parse(fileGlobal, FALSE /* global */);
179 SetRootPath();
180 }
181 else
182 wxLogWarning(_("can't open global configuration file '%s'."),
183 m_strGlobalFile.c_str());
184 }
185
186 // parse the local file
187 if ( !m_strLocalFile.IsEmpty() && wxFile::Exists(m_strLocalFile) ) {
188 wxTextFile fileLocal(m_strLocalFile);
189 if ( fileLocal.Open() ) {
190 Parse(fileLocal, TRUE /* local */);
191 SetRootPath();
192 }
193 else
194 wxLogWarning(_("can't open user configuration file '%s'."),
195 m_strLocalFile.c_str());
196 }
197 }
198
199 // constructor supports creation of wxFileConfig objects of any type
200 wxFileConfig::wxFileConfig(const wxString& appName, const wxString& vendorName,
201 const wxString& strLocal, const wxString& strGlobal,
202 long style)
203 : wxConfigBase(::GetAppName(appName), vendorName,
204 strLocal, strGlobal,
205 style),
206 m_strLocalFile(strLocal), m_strGlobalFile(strGlobal)
207 {
208 // Make up names for files if empty
209 if ( m_strLocalFile.IsEmpty() && (style & wxCONFIG_USE_LOCAL_FILE) )
210 {
211 m_strLocalFile = GetLocalFileName(GetAppName());
212 }
213
214 if ( m_strGlobalFile.IsEmpty() && (style & wxCONFIG_USE_GLOBAL_FILE) )
215 {
216 m_strGlobalFile = GetGlobalFileName(GetAppName());
217 }
218
219 // Check if styles are not supplied, but filenames are, in which case
220 // add the correct styles.
221 if ( !m_strLocalFile.IsEmpty() )
222 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE);
223
224 if ( !m_strGlobalFile.IsEmpty() )
225 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE);
226
227 // if the path is not absolute, prepend the standard directory to it
228 // UNLESS wxCONFIG_USE_RELATIVE_PATH style is set
229 if ( !(style & wxCONFIG_USE_RELATIVE_PATH) ){
230 if ( !m_strLocalFile.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile) )
231 {
232 wxString strLocal = m_strLocalFile;
233 m_strLocalFile = GetLocalDir();
234 m_strLocalFile << strLocal;
235 }
236
237 if ( !m_strGlobalFile.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile) )
238 {
239 wxString strGlobal = m_strGlobalFile;
240 m_strGlobalFile = GetGlobalDir();
241 m_strGlobalFile << strGlobal;
242 }
243 }
244
245 Init();
246 }
247
248 void wxFileConfig::CleanUp()
249 {
250 delete m_pRootGroup;
251
252 LineList *pCur = m_linesHead;
253 while ( pCur != NULL ) {
254 LineList *pNext = pCur->Next();
255 delete pCur;
256 pCur = pNext;
257 }
258 }
259
260 wxFileConfig::~wxFileConfig()
261 {
262 Flush();
263
264 CleanUp();
265 }
266
267 // ----------------------------------------------------------------------------
268 // parse a config file
269 // ----------------------------------------------------------------------------
270
271 void wxFileConfig::Parse(wxTextFile& file, bool bLocal)
272 {
273 const wxChar *pStart;
274 const wxChar *pEnd;
275 wxString strLine;
276
277 size_t nLineCount = file.GetLineCount();
278 for ( size_t n = 0; n < nLineCount; n++ ) {
279 strLine = file[n];
280
281 // add the line to linked list
282 if ( bLocal )
283 LineListAppend(strLine);
284
285 // skip leading spaces
286 for ( pStart = strLine; wxIsspace(*pStart); pStart++ )
287 ;
288
289 // skip blank/comment lines
290 if ( *pStart == _T('\0')|| *pStart == _T(';') || *pStart == _T('#') )
291 continue;
292
293 if ( *pStart == _T('[') ) { // a new group
294 pEnd = pStart;
295
296 while ( *++pEnd != _T(']') ) {
297 if ( *pEnd == _T('\n') || *pEnd == _T('\0') )
298 break;
299 }
300
301 if ( *pEnd != _T(']') ) {
302 wxLogError(_("file '%s': unexpected character %c at line %d."),
303 file.GetName(), *pEnd, n + 1);
304 continue; // skip this line
305 }
306
307 // group name here is always considered as abs path
308 wxString strGroup;
309 pStart++;
310 strGroup << wxCONFIG_PATH_SEPARATOR
311 << FilterInEntryName(wxString(pStart, pEnd - pStart));
312
313 // will create it if doesn't yet exist
314 SetPath(strGroup);
315
316 if ( bLocal )
317 m_pCurrentGroup->SetLine(m_linesTail);
318
319 // check that there is nothing except comments left on this line
320 bool bCont = TRUE;
321 while ( *++pEnd != _T('\0') && bCont ) {
322 switch ( *pEnd ) {
323 case _T('#'):
324 case _T(';'):
325 bCont = FALSE;
326 break;
327
328 case _T(' '):
329 case _T('\t'):
330 // ignore whitespace ('\n' impossible here)
331 break;
332
333 default:
334 wxLogWarning(_("file '%s', line %d: '%s' "
335 "ignored after group header."),
336 file.GetName(), n + 1, pEnd);
337 bCont = FALSE;
338 }
339 }
340 }
341 else { // a key
342 const wxChar *pEnd = pStart;
343 while ( *pEnd != _T('=') && !wxIsspace(*pEnd) ) {
344 if ( *pEnd == _T('\\') ) {
345 // next character may be space or not - still take it because it's
346 // quoted
347 pEnd++;
348 }
349
350 pEnd++;
351 }
352
353 wxString strKey(FilterInEntryName(wxString(pStart, pEnd)));
354
355 // skip whitespace
356 while ( isspace(*pEnd) )
357 pEnd++;
358
359 if ( *pEnd++ != _T('=') ) {
360 wxLogError(_("file '%s', line %d: '=' expected."),
361 file.GetName(), n + 1);
362 }
363 else {
364 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strKey);
365
366 if ( pEntry == NULL ) {
367 // new entry
368 pEntry = m_pCurrentGroup->AddEntry(strKey, n);
369
370 if ( bLocal )
371 pEntry->SetLine(m_linesTail);
372 }
373 else {
374 if ( bLocal && pEntry->IsImmutable() ) {
375 // immutable keys can't be changed by user
376 wxLogWarning(_("file '%s', line %d: value for "
377 "immutable key '%s' ignored."),
378 file.GetName(), n + 1, strKey.c_str());
379 continue;
380 }
381 // the condition below catches the cases (a) and (b) but not (c):
382 // (a) global key found second time in global file
383 // (b) key found second (or more) time in local file
384 // (c) key from global file now found in local one
385 // which is exactly what we want.
386 else if ( !bLocal || pEntry->IsLocal() ) {
387 wxLogWarning(_("file '%s', line %d: key '%s' was first "
388 "found at line %d."),
389 file.GetName(), n + 1, strKey.c_str(), pEntry->Line());
390
391 if ( bLocal )
392 pEntry->SetLine(m_linesTail);
393 }
394 }
395
396 // skip whitespace
397 while ( wxIsspace(*pEnd) )
398 pEnd++;
399
400 pEntry->SetValue(FilterInValue(pEnd), FALSE /* read from file */);
401 }
402 }
403 }
404 }
405
406 // ----------------------------------------------------------------------------
407 // set/retrieve path
408 // ----------------------------------------------------------------------------
409
410 void wxFileConfig::SetRootPath()
411 {
412 m_strPath.Empty();
413 m_pCurrentGroup = m_pRootGroup;
414 }
415
416 void wxFileConfig::SetPath(const wxString& strPath)
417 {
418 wxArrayString aParts;
419
420 if ( strPath.IsEmpty() ) {
421 SetRootPath();
422 return;
423 }
424
425 if ( strPath[0] == wxCONFIG_PATH_SEPARATOR ) {
426 // absolute path
427 wxSplitPath(aParts, strPath);
428 }
429 else {
430 // relative path, combine with current one
431 wxString strFullPath = m_strPath;
432 strFullPath << wxCONFIG_PATH_SEPARATOR << strPath;
433 wxSplitPath(aParts, strFullPath);
434 }
435
436 // change current group
437 size_t n;
438 m_pCurrentGroup = m_pRootGroup;
439 for ( n = 0; n < aParts.Count(); n++ ) {
440 ConfigGroup *pNextGroup = m_pCurrentGroup->FindSubgroup(aParts[n]);
441 if ( pNextGroup == NULL )
442 pNextGroup = m_pCurrentGroup->AddSubgroup(aParts[n]);
443 m_pCurrentGroup = pNextGroup;
444 }
445
446 // recombine path parts in one variable
447 m_strPath.Empty();
448 for ( n = 0; n < aParts.Count(); n++ ) {
449 m_strPath << wxCONFIG_PATH_SEPARATOR << aParts[n];
450 }
451 }
452
453 // ----------------------------------------------------------------------------
454 // enumeration
455 // ----------------------------------------------------------------------------
456
457 bool wxFileConfig::GetFirstGroup(wxString& str, long& lIndex) const
458 {
459 lIndex = 0;
460 return GetNextGroup(str, lIndex);
461 }
462
463 bool wxFileConfig::GetNextGroup (wxString& str, long& lIndex) const
464 {
465 if ( size_t(lIndex) < m_pCurrentGroup->Groups().Count() ) {
466 str = m_pCurrentGroup->Groups()[lIndex++]->Name();
467 return TRUE;
468 }
469 else
470 return FALSE;
471 }
472
473 bool wxFileConfig::GetFirstEntry(wxString& str, long& lIndex) const
474 {
475 lIndex = 0;
476 return GetNextEntry(str, lIndex);
477 }
478
479 bool wxFileConfig::GetNextEntry (wxString& str, long& lIndex) const
480 {
481 if ( size_t(lIndex) < m_pCurrentGroup->Entries().Count() ) {
482 str = m_pCurrentGroup->Entries()[lIndex++]->Name();
483 return TRUE;
484 }
485 else
486 return FALSE;
487 }
488
489 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive) const
490 {
491 size_t n = m_pCurrentGroup->Entries().Count();
492 if ( bRecursive ) {
493 ConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
494 size_t nSubgroups = m_pCurrentGroup->Groups().Count();
495 for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
496 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
497 n += GetNumberOfEntries(TRUE);
498 CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
499 }
500 }
501
502 return n;
503 }
504
505 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive) const
506 {
507 size_t n = m_pCurrentGroup->Groups().Count();
508 if ( bRecursive ) {
509 ConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
510 size_t nSubgroups = m_pCurrentGroup->Groups().Count();
511 for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
512 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
513 n += GetNumberOfGroups(TRUE);
514 CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
515 }
516 }
517
518 return n;
519 }
520
521 // ----------------------------------------------------------------------------
522 // tests for existence
523 // ----------------------------------------------------------------------------
524
525 bool wxFileConfig::HasGroup(const wxString& strName) const
526 {
527 wxConfigPathChanger path(this, strName);
528
529 ConfigGroup *pGroup = m_pCurrentGroup->FindSubgroup(path.Name());
530 return pGroup != NULL;
531 }
532
533 bool wxFileConfig::HasEntry(const wxString& strName) const
534 {
535 wxConfigPathChanger path(this, strName);
536
537 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
538 return pEntry != NULL;
539 }
540
541 // ----------------------------------------------------------------------------
542 // read/write values
543 // ----------------------------------------------------------------------------
544
545 bool wxFileConfig::Read(const wxString& key,
546 wxString* pStr) const
547 {
548 wxConfigPathChanger path(this, key);
549
550 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
551 if (pEntry == NULL) {
552 return FALSE;
553 }
554 else {
555 *pStr = ExpandEnvVars(pEntry->Value());
556 return TRUE;
557 }
558 }
559
560 bool wxFileConfig::Read(const wxString& key,
561 wxString* pStr, const wxString& defVal) const
562 {
563 wxConfigPathChanger path(this, key);
564
565 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
566 if (pEntry == NULL) {
567 if( IsRecordingDefaults() )
568 ((wxFileConfig *)this)->Write(key,defVal);
569 *pStr = ExpandEnvVars(defVal);
570 return FALSE;
571 }
572 else {
573 *pStr = ExpandEnvVars(pEntry->Value());
574 return TRUE;
575 }
576 }
577
578 bool wxFileConfig::Read(const wxString& key, long *pl) const
579 {
580 wxString str;
581 if ( Read(key, & str) ) {
582 *pl = wxAtol(str);
583 return TRUE;
584 }
585 else {
586 return FALSE;
587 }
588 }
589
590 bool wxFileConfig::Write(const wxString& key, const wxString& szValue)
591 {
592 wxConfigPathChanger path(this, key);
593
594 wxString strName = path.Name();
595 if ( strName.IsEmpty() ) {
596 // setting the value of a group is an error
597 wxASSERT_MSG( wxIsEmpty(szValue), _T("can't set value of a group!") );
598
599 // ... except if it's empty in which case it's a way to force it's creation
600 m_pCurrentGroup->SetDirty();
601
602 // this will add a line for this group if it didn't have it before
603 (void)m_pCurrentGroup->GetGroupLine();
604 }
605 else {
606 // writing an entry
607
608 // check that the name is reasonable
609 if ( strName[0u] == wxCONFIG_IMMUTABLE_PREFIX ) {
610 wxLogError(_("Config entry name cannot start with '%c'."),
611 wxCONFIG_IMMUTABLE_PREFIX);
612 return FALSE;
613 }
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("[")
910 // +1: no '/'
911 << FilterOutEntryName(GetFullName().c_str() + 1)
912 << _T("]");
913 m_pLine = m_pConfig->LineListInsert(strFullName,
914 pParent->GetLastGroupLine());
915 pParent->SetLastGroup(this); // we're surely after all the others
916 }
917 else {
918 // we return NULL, so that LineListInsert() will insert us in the
919 // very beginning
920 }
921 }
922
923 return m_pLine;
924 }
925
926 // Return the last line belonging to the subgroups of this group (after which
927 // we can add a new subgroup), if we don't have any subgroups or entries our
928 // last line is the group line (m_pLine) itself.
929 LineList *ConfigGroup::GetLastGroupLine()
930 {
931 // if we have any subgroups, our last line is the last line of the last
932 // subgroup
933 if ( m_pLastGroup != NULL ) {
934 LineList *pLine = m_pLastGroup->GetLastGroupLine();
935
936 wxASSERT( pLine != NULL ); // last group must have !NULL associated line
937 return pLine;
938 }
939
940 // no subgroups, so the last line is the line of thelast entry (if any)
941 return GetLastEntryLine();
942 }
943
944 // return the last line belonging to the entries of this group (after which
945 // we can add a new entry), if we don't have any entries we will add the new
946 // one immediately after the group line itself.
947 LineList *ConfigGroup::GetLastEntryLine()
948 {
949 if ( m_pLastEntry != NULL ) {
950 LineList *pLine = m_pLastEntry->GetLine();
951
952 wxASSERT( pLine != NULL ); // last entry must have !NULL associated line
953 return pLine;
954 }
955
956 // no entries: insert after the group header
957 return GetGroupLine();
958 }
959
960 // ----------------------------------------------------------------------------
961 // group name
962 // ----------------------------------------------------------------------------
963
964 void ConfigGroup::Rename(const wxString& newName)
965 {
966 m_strName = newName;
967
968 LineList *line = GetGroupLine();
969 wxString strFullName;
970 strFullName << _T("[") << (GetFullName().c_str() + 1) << _T("]"); // +1: no '/'
971 line->SetText(strFullName);
972
973 SetDirty();
974 }
975
976 wxString ConfigGroup::GetFullName() const
977 {
978 if ( Parent() )
979 return Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR + Name();
980 else
981 return _T("");
982 }
983
984 // ----------------------------------------------------------------------------
985 // find an item
986 // ----------------------------------------------------------------------------
987
988 // use binary search because the array is sorted
989 ConfigEntry *
990 ConfigGroup::FindEntry(const wxChar *szName) const
991 {
992 size_t i,
993 lo = 0,
994 hi = m_aEntries.Count();
995 int res;
996 ConfigEntry *pEntry;
997
998 while ( lo < hi ) {
999 i = (lo + hi)/2;
1000 pEntry = m_aEntries[i];
1001
1002 #if wxCONFIG_CASE_SENSITIVE
1003 res = wxStrcmp(pEntry->Name(), szName);
1004 #else
1005 res = wxStricmp(pEntry->Name(), szName);
1006 #endif
1007
1008 if ( res > 0 )
1009 hi = i;
1010 else if ( res < 0 )
1011 lo = i + 1;
1012 else
1013 return pEntry;
1014 }
1015
1016 return NULL;
1017 }
1018
1019 ConfigGroup *
1020 ConfigGroup::FindSubgroup(const wxChar *szName) const
1021 {
1022 size_t i,
1023 lo = 0,
1024 hi = m_aSubgroups.Count();
1025 int res;
1026 ConfigGroup *pGroup;
1027
1028 while ( lo < hi ) {
1029 i = (lo + hi)/2;
1030 pGroup = m_aSubgroups[i];
1031
1032 #if wxCONFIG_CASE_SENSITIVE
1033 res = wxStrcmp(pGroup->Name(), szName);
1034 #else
1035 res = wxStricmp(pGroup->Name(), szName);
1036 #endif
1037
1038 if ( res > 0 )
1039 hi = i;
1040 else if ( res < 0 )
1041 lo = i + 1;
1042 else
1043 return pGroup;
1044 }
1045
1046 return NULL;
1047 }
1048
1049 // ----------------------------------------------------------------------------
1050 // create a new item
1051 // ----------------------------------------------------------------------------
1052
1053 // create a new entry and add it to the current group
1054 ConfigEntry *
1055 ConfigGroup::AddEntry(const wxString& strName, int nLine)
1056 {
1057 wxASSERT( FindEntry(strName) == NULL );
1058
1059 ConfigEntry *pEntry = new ConfigEntry(this, strName, nLine);
1060 m_aEntries.Add(pEntry);
1061
1062 return pEntry;
1063 }
1064
1065 // create a new group and add it to the current group
1066 ConfigGroup *
1067 ConfigGroup::AddSubgroup(const wxString& strName)
1068 {
1069 wxASSERT( FindSubgroup(strName) == NULL );
1070
1071 ConfigGroup *pGroup = new ConfigGroup(this, strName, m_pConfig);
1072 m_aSubgroups.Add(pGroup);
1073
1074 return pGroup;
1075 }
1076
1077 // ----------------------------------------------------------------------------
1078 // delete an item
1079 // ----------------------------------------------------------------------------
1080
1081 /*
1082 The delete operations are _very_ slow if we delete the last item of this
1083 group (see comments before GetXXXLineXXX functions for more details),
1084 so it's much better to start with the first entry/group if we want to
1085 delete several of them.
1086 */
1087
1088 bool ConfigGroup::DeleteSubgroupByName(const wxChar *szName)
1089 {
1090 return DeleteSubgroup(FindSubgroup(szName));
1091 }
1092
1093 // doesn't delete the subgroup itself, but does remove references to it from
1094 // all other data structures (and normally the returned pointer should be
1095 // deleted a.s.a.p. because there is nothing much to be done with it anyhow)
1096 bool ConfigGroup::DeleteSubgroup(ConfigGroup *pGroup)
1097 {
1098 wxCHECK( pGroup != NULL, FALSE ); // deleting non existing group?
1099
1100 // delete all entries
1101 size_t nCount = pGroup->m_aEntries.Count();
1102 for ( size_t nEntry = 0; nEntry < nCount; nEntry++ ) {
1103 LineList *pLine = pGroup->m_aEntries[nEntry]->GetLine();
1104 if ( pLine != NULL )
1105 m_pConfig->LineListRemove(pLine);
1106 }
1107
1108 // and subgroups of this sungroup
1109 nCount = pGroup->m_aSubgroups.Count();
1110 for ( size_t nGroup = 0; nGroup < nCount; nGroup++ ) {
1111 pGroup->DeleteSubgroup(pGroup->m_aSubgroups[0]);
1112 }
1113
1114 LineList *pLine = pGroup->m_pLine;
1115 if ( pLine != NULL ) {
1116 // notice that we may do this test inside the previous "if" because the
1117 // last entry's line is surely !NULL
1118 if ( pGroup == m_pLastGroup ) {
1119 // our last entry is being deleted - find the last one which stays
1120 wxASSERT( m_pLine != NULL ); // we have a subgroup with !NULL pLine...
1121
1122 // go back until we find a subgroup or reach the group's line
1123 ConfigGroup *pNewLast = NULL;
1124 size_t n, nSubgroups = m_aSubgroups.Count();
1125 LineList *pl;
1126 for ( pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() ) {
1127 // is it our subgroup?
1128 for ( n = 0; (pNewLast == NULL) && (n < nSubgroups); n++ ) {
1129 // do _not_ call GetGroupLine! we don't want to add it to the local
1130 // file if it's not already there
1131 if ( m_aSubgroups[n]->m_pLine == m_pLine )
1132 pNewLast = m_aSubgroups[n];
1133 }
1134
1135 if ( pNewLast != NULL ) // found?
1136 break;
1137 }
1138
1139 if ( pl == m_pLine ) {
1140 wxASSERT( !pNewLast ); // how comes it has the same line as we?
1141
1142 // we've reached the group line without finding any subgroups
1143 m_pLastGroup = NULL;
1144 }
1145 else
1146 m_pLastGroup = pNewLast;
1147 }
1148
1149 m_pConfig->LineListRemove(pLine);
1150 }
1151
1152 SetDirty();
1153
1154 m_aSubgroups.Remove(pGroup);
1155 delete pGroup;
1156
1157 return TRUE;
1158 }
1159
1160 bool ConfigGroup::DeleteEntry(const wxChar *szName)
1161 {
1162 ConfigEntry *pEntry = FindEntry(szName);
1163 wxCHECK( pEntry != NULL, FALSE ); // deleting non existing item?
1164
1165 LineList *pLine = pEntry->GetLine();
1166 if ( pLine != NULL ) {
1167 // notice that we may do this test inside the previous "if" because the
1168 // last entry's line is surely !NULL
1169 if ( pEntry == m_pLastEntry ) {
1170 // our last entry is being deleted - find the last one which stays
1171 wxASSERT( m_pLine != NULL ); // if we have an entry with !NULL pLine...
1172
1173 // go back until we find another entry or reach the group's line
1174 ConfigEntry *pNewLast = NULL;
1175 size_t n, nEntries = m_aEntries.Count();
1176 LineList *pl;
1177 for ( pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() ) {
1178 // is it our subgroup?
1179 for ( n = 0; (pNewLast == NULL) && (n < nEntries); n++ ) {
1180 if ( m_aEntries[n]->GetLine() == m_pLine )
1181 pNewLast = m_aEntries[n];
1182 }
1183
1184 if ( pNewLast != NULL ) // found?
1185 break;
1186 }
1187
1188 if ( pl == m_pLine ) {
1189 wxASSERT( !pNewLast ); // how comes it has the same line as we?
1190
1191 // we've reached the group line without finding any subgroups
1192 m_pLastEntry = NULL;
1193 }
1194 else
1195 m_pLastEntry = pNewLast;
1196 }
1197
1198 m_pConfig->LineListRemove(pLine);
1199 }
1200
1201 // we must be written back for the changes to be saved
1202 SetDirty();
1203
1204 m_aEntries.Remove(pEntry);
1205 delete pEntry;
1206
1207 return TRUE;
1208 }
1209
1210 // ----------------------------------------------------------------------------
1211 //
1212 // ----------------------------------------------------------------------------
1213 void ConfigGroup::SetDirty()
1214 {
1215 m_bDirty = TRUE;
1216 if ( Parent() != NULL ) // propagate upwards
1217 Parent()->SetDirty();
1218 }
1219
1220 // ============================================================================
1221 // wxFileConfig::ConfigEntry
1222 // ============================================================================
1223
1224 // ----------------------------------------------------------------------------
1225 // ctor
1226 // ----------------------------------------------------------------------------
1227 ConfigEntry::ConfigEntry(ConfigGroup *pParent,
1228 const wxString& strName,
1229 int nLine)
1230 : m_strName(strName)
1231 {
1232 wxASSERT( !strName.IsEmpty() );
1233
1234 m_pParent = pParent;
1235 m_nLine = nLine;
1236 m_pLine = NULL;
1237
1238 m_bDirty = FALSE;
1239
1240 m_bImmutable = strName[0] == wxCONFIG_IMMUTABLE_PREFIX;
1241 if ( m_bImmutable )
1242 m_strName.erase(0, 1); // remove first character
1243 }
1244
1245 // ----------------------------------------------------------------------------
1246 // set value
1247 // ----------------------------------------------------------------------------
1248
1249 void ConfigEntry::SetLine(LineList *pLine)
1250 {
1251 if ( m_pLine != NULL ) {
1252 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1253 Name().c_str(), m_pParent->GetFullName().c_str());
1254 }
1255
1256 m_pLine = pLine;
1257 Group()->SetLastEntry(this);
1258 }
1259
1260 // second parameter is FALSE if we read the value from file and prevents the
1261 // entry from being marked as 'dirty'
1262 void ConfigEntry::SetValue(const wxString& strValue, bool bUser)
1263 {
1264 if ( bUser && IsImmutable() ) {
1265 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1266 Name().c_str());
1267 return;
1268 }
1269
1270 // do nothing if it's the same value
1271 if ( strValue == m_strValue )
1272 return;
1273
1274 m_strValue = strValue;
1275
1276 if ( bUser ) {
1277 wxString strVal = FilterOutValue(strValue);
1278 wxString strLine;
1279 strLine << FilterOutEntryName(m_strName) << _T('=') << strVal;
1280
1281 if ( m_pLine != NULL ) {
1282 // entry was read from the local config file, just modify the line
1283 m_pLine->SetText(strLine);
1284 }
1285 else {
1286 // add a new line to the file
1287 wxASSERT( m_nLine == wxNOT_FOUND ); // consistency check
1288
1289 m_pLine = Group()->Config()->LineListInsert(strLine,
1290 Group()->GetLastEntryLine());
1291 Group()->SetLastEntry(this);
1292 }
1293
1294 SetDirty();
1295 }
1296 }
1297
1298 void ConfigEntry::SetDirty()
1299 {
1300 m_bDirty = TRUE;
1301 Group()->SetDirty();
1302 }
1303
1304 // ============================================================================
1305 // global functions
1306 // ============================================================================
1307
1308 // ----------------------------------------------------------------------------
1309 // compare functions for array sorting
1310 // ----------------------------------------------------------------------------
1311
1312 int CompareEntries(ConfigEntry *p1,
1313 ConfigEntry *p2)
1314 {
1315 #if wxCONFIG_CASE_SENSITIVE
1316 return wxStrcmp(p1->Name(), p2->Name());
1317 #else
1318 return wxStricmp(p1->Name(), p2->Name());
1319 #endif
1320 }
1321
1322 int CompareGroups(ConfigGroup *p1,
1323 ConfigGroup *p2)
1324 {
1325 #if wxCONFIG_CASE_SENSITIVE
1326 return wxStrcmp(p1->Name(), p2->Name());
1327 #else
1328 return wxStricmp(p1->Name(), p2->Name());
1329 #endif
1330 }
1331
1332 // ----------------------------------------------------------------------------
1333 // filter functions
1334 // ----------------------------------------------------------------------------
1335
1336 // undo FilterOutValue
1337 static wxString FilterInValue(const wxString& str)
1338 {
1339 wxString strResult;
1340 strResult.Alloc(str.Len());
1341
1342 bool bQuoted = !str.IsEmpty() && str[0] == '"';
1343
1344 for ( size_t n = bQuoted ? 1 : 0; n < str.Len(); n++ ) {
1345 if ( str[n] == _T('\\') ) {
1346 switch ( str[++n] ) {
1347 case _T('n'):
1348 strResult += _T('\n');
1349 break;
1350
1351 case _T('r'):
1352 strResult += _T('\r');
1353 break;
1354
1355 case _T('t'):
1356 strResult += _T('\t');
1357 break;
1358
1359 case _T('\\'):
1360 strResult += _T('\\');
1361 break;
1362
1363 case _T('"'):
1364 strResult += _T('"');
1365 break;
1366 }
1367 }
1368 else {
1369 if ( str[n] != _T('"') || !bQuoted )
1370 strResult += str[n];
1371 else if ( n != str.Len() - 1 ) {
1372 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1373 n, str.c_str());
1374 }
1375 //else: it's the last quote of a quoted string, ok
1376 }
1377 }
1378
1379 return strResult;
1380 }
1381
1382 // quote the string before writing it to file
1383 static wxString FilterOutValue(const wxString& str)
1384 {
1385 if ( !str )
1386 return str;
1387
1388 wxString strResult;
1389 strResult.Alloc(str.Len());
1390
1391 // quoting is necessary to preserve spaces in the beginning of the string
1392 bool bQuote = wxIsspace(str[0]) || str[0] == _T('"');
1393
1394 if ( bQuote )
1395 strResult += _T('"');
1396
1397 wxChar c;
1398 for ( size_t n = 0; n < str.Len(); n++ ) {
1399 switch ( str[n] ) {
1400 case _T('\n'):
1401 c = _T('n');
1402 break;
1403
1404 case _T('\r'):
1405 c = _T('r');
1406 break;
1407
1408 case _T('\t'):
1409 c = _T('t');
1410 break;
1411
1412 case _T('\\'):
1413 c = _T('\\');
1414 break;
1415
1416 case _T('"'):
1417 if ( bQuote ) {
1418 c = _T('"');
1419 break;
1420 }
1421 //else: fall through
1422
1423 default:
1424 strResult += str[n];
1425 continue; // nothing special to do
1426 }
1427
1428 // we get here only for special characters
1429 strResult << _T('\\') << c;
1430 }
1431
1432 if ( bQuote )
1433 strResult += _T('"');
1434
1435 return strResult;
1436 }
1437
1438 // undo FilterOutEntryName
1439 static wxString FilterInEntryName(const wxString& str)
1440 {
1441 wxString strResult;
1442 strResult.Alloc(str.Len());
1443
1444 for ( const wxChar *pc = str.c_str(); *pc != '\0'; pc++ ) {
1445 if ( *pc == _T('\\') )
1446 pc++;
1447
1448 strResult += *pc;
1449 }
1450
1451 return strResult;
1452 }
1453
1454 // sanitize entry or group name: insert '\\' before any special characters
1455 static wxString FilterOutEntryName(const wxString& str)
1456 {
1457 wxString strResult;
1458 strResult.Alloc(str.Len());
1459
1460 for ( const wxChar *pc = str.c_str(); *pc != _T('\0'); pc++ ) {
1461 wxChar c = *pc;
1462
1463 // we explicitly allow some of "safe" chars and 8bit ASCII characters
1464 // which will probably never have special meaning
1465 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
1466 // should *not* be quoted
1467 if ( !wxIsalnum(c) && !wxStrchr(_T("@_/-!.*%"), c) && ((c & 0x80) == 0) )
1468 strResult += _T('\\');
1469
1470 strResult += c;
1471 }
1472
1473 return strResult;
1474 }
1475
1476 // we can't put ?: in the ctor initializer list because it confuses some
1477 // broken compilers (Borland C++)
1478 static wxString GetAppName(const wxString& appName)
1479 {
1480 if ( !appName && wxTheApp )
1481 return wxTheApp->GetAppName();
1482 else
1483 return appName;
1484 }
1485
1486 #endif // wxUSE_CONFIG
1487