]> git.saurik.com Git - wxWidgets.git/blob - src/common/fileconf.cpp
4750baf77602f7082fff4ff36cbfbe0b5282b13c
[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 // is 'c' a valid character in group name?
71 // NB: wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR must be valid chars,
72 // but _not_ ']' (group name delimiter)
73 inline bool IsValid(char c) { return isalnum(c) || strchr("@_/-!.*%", c); }
74
75 // compare functions for sorting the arrays
76 static int CompareEntries(ConfigEntry *p1, ConfigEntry *p2);
77 static int CompareGroups(ConfigGroup *p1, ConfigGroup *p2);
78
79 // filter strings
80 static wxString FilterIn(const wxString& str);
81 static wxString FilterOut(const wxString& str);
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 = "/etc/";
96 #elif defined(__WXSTUBS__)
97 wxASSERT_MSG( FALSE, "TODO" ) ;
98 #elif defined(__WXMAC__)
99 wxASSERT_MSG( FALSE, "TODO" ) ;
100 #else // Windows
101 char szWinDir[MAX_PATH];
102 ::GetWindowsDirectory(szWinDir, MAX_PATH);
103
104 strDir = szWinDir;
105 strDir << '\\';
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() != '/') strDir << '/';
119 #else
120 if (strDir.Last() != '\\') strDir << '\\';
121 #endif
122
123 return strDir;
124 }
125
126 wxString wxFileConfig::GetGlobalFileName(const char *szFile)
127 {
128 wxString str = GetGlobalDir();
129 str << szFile;
130
131 if ( strchr(szFile, '.') == NULL )
132 #ifdef __UNIX__
133 str << ".conf";
134 #else // Windows
135 str << ".ini";
136 #endif // UNIX/Win
137
138 return str;
139 }
140
141 wxString wxFileConfig::GetLocalFileName(const char *szFile)
142 {
143 wxString str = GetLocalDir();
144
145 #ifdef __UNIX__
146 str << '.';
147 #endif
148
149 str << szFile;
150
151 #ifdef __WXMSW__
152 if ( strchr(szFile, '.') == NULL )
153 str << ".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(appName, vendorName, strLocal, strGlobal, style),
204 m_strLocalFile(strLocal), m_strGlobalFile(strGlobal)
205 {
206 // Make up an application name if not supplied
207 if (appName.IsEmpty() && wxTheApp)
208 {
209 SetAppName(wxTheApp->GetAppName());
210 }
211
212 // Make up names for files if empty
213 if ( m_strLocalFile.IsEmpty() && (style & wxCONFIG_USE_LOCAL_FILE) )
214 {
215 m_strLocalFile = GetLocalFileName(GetAppName());
216 }
217
218 if ( m_strGlobalFile.IsEmpty() && (style & wxCONFIG_USE_GLOBAL_FILE) )
219 {
220 m_strGlobalFile = GetGlobalFileName(GetAppName());
221 }
222
223 // Check if styles are not supplied, but filenames are, in which case
224 // add the correct styles.
225 if ( !m_strLocalFile.IsEmpty() )
226 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE);
227
228 if ( !m_strGlobalFile.IsEmpty() )
229 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE);
230
231 // if the path is not absolute, prepend the standard directory to it
232 if ( !m_strLocalFile.IsEmpty() && !wxIsAbsolutePath(m_strLocalFile) )
233 {
234 wxString strLocal = m_strLocalFile;
235 m_strLocalFile = GetLocalDir();
236 m_strLocalFile << strLocal;
237 }
238
239 if ( !m_strGlobalFile.IsEmpty() && !wxIsAbsolutePath(m_strGlobalFile) )
240 {
241 wxString strGlobal = m_strGlobalFile;
242 m_strGlobalFile = GetGlobalDir();
243 m_strGlobalFile << strGlobal;
244 }
245
246 Init();
247 }
248
249 void wxFileConfig::CleanUp()
250 {
251 delete m_pRootGroup;
252
253 LineList *pCur = m_linesHead;
254 while ( pCur != NULL ) {
255 LineList *pNext = pCur->Next();
256 delete pCur;
257 pCur = pNext;
258 }
259 }
260
261 wxFileConfig::~wxFileConfig()
262 {
263 Flush();
264
265 CleanUp();
266 }
267
268 // ----------------------------------------------------------------------------
269 // parse a config file
270 // ----------------------------------------------------------------------------
271
272 void wxFileConfig::Parse(wxTextFile& file, bool bLocal)
273 {
274 const char *pStart;
275 const char *pEnd;
276 wxString strLine;
277
278 size_t nLineCount = file.GetLineCount();
279 for ( size_t n = 0; n < nLineCount; n++ ) {
280 strLine = file[n];
281
282 // add the line to linked list
283 if ( bLocal )
284 LineListAppend(strLine);
285
286 // skip leading spaces
287 for ( pStart = strLine; isspace(*pStart); pStart++ )
288 ;
289
290 // skip blank/comment lines
291 if ( *pStart == '\0'|| *pStart == ';' || *pStart == '#' )
292 continue;
293
294 if ( *pStart == '[' ) { // a new group
295 pEnd = pStart;
296
297 while ( *++pEnd != ']' ) {
298 if ( !IsValid(*pEnd) && *pEnd != ' ' ) // allow spaces in group names
299 break;
300 }
301
302 if ( *pEnd != ']' ) {
303 wxLogError(_("file '%s': unexpected character %c at line %d."),
304 file.GetName(), *pEnd, n + 1);
305 continue; // skip this line
306 }
307
308 // group name here is always considered as abs path
309 wxString strGroup;
310 pStart++;
311 strGroup << wxCONFIG_PATH_SEPARATOR << 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 != '\0' && bCont ) {
322 switch ( *pEnd ) {
323 case '#':
324 case ';':
325 bCont = FALSE;
326 break;
327
328 case ' ':
329 case '\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 char *pEnd = pStart;
343 while ( IsValid(*pEnd) )
344 pEnd++;
345
346 wxString strKey(pStart, pEnd);
347
348 // skip whitespace
349 while ( isspace(*pEnd) )
350 pEnd++;
351
352 if ( *pEnd++ != '=' ) {
353 wxLogError(_("file '%s', line %d: '=' expected."),
354 file.GetName(), n + 1);
355 }
356 else {
357 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strKey);
358
359 if ( pEntry == NULL ) {
360 // new entry
361 pEntry = m_pCurrentGroup->AddEntry(strKey, n);
362
363 if ( bLocal )
364 pEntry->SetLine(m_linesTail);
365 }
366 else {
367 if ( bLocal && pEntry->IsImmutable() ) {
368 // immutable keys can't be changed by user
369 wxLogWarning(_("file '%s', line %d: value for "
370 "immutable key '%s' ignored."),
371 file.GetName(), n + 1, strKey.c_str());
372 continue;
373 }
374 // the condition below catches the cases (a) and (b) but not (c):
375 // (a) global key found second time in global file
376 // (b) key found second (or more) time in local file
377 // (c) key from global file now found in local one
378 // which is exactly what we want.
379 else if ( !bLocal || pEntry->IsLocal() ) {
380 wxLogWarning(_("file '%s', line %d: key '%s' was first "
381 "found at line %d."),
382 file.GetName(), n + 1, strKey.c_str(), pEntry->Line());
383
384 if ( bLocal )
385 pEntry->SetLine(m_linesTail);
386 }
387 }
388
389 // skip whitespace
390 while ( isspace(*pEnd) )
391 pEnd++;
392
393 pEntry->SetValue(FilterIn(pEnd), FALSE /* read from file */);
394 }
395 }
396 }
397 }
398
399 // ----------------------------------------------------------------------------
400 // set/retrieve path
401 // ----------------------------------------------------------------------------
402
403 void wxFileConfig::SetRootPath()
404 {
405 m_strPath.Empty();
406 m_pCurrentGroup = m_pRootGroup;
407 }
408
409 void wxFileConfig::SetPath(const wxString& strPath)
410 {
411 wxArrayString aParts;
412
413 if ( strPath.IsEmpty() ) {
414 SetRootPath();
415 return;
416 }
417
418 if ( strPath[0] == wxCONFIG_PATH_SEPARATOR ) {
419 // absolute path
420 wxSplitPath(aParts, strPath);
421 }
422 else {
423 // relative path, combine with current one
424 wxString strFullPath = m_strPath;
425 strFullPath << wxCONFIG_PATH_SEPARATOR << strPath;
426 wxSplitPath(aParts, strFullPath);
427 }
428
429 // change current group
430 size_t n;
431 m_pCurrentGroup = m_pRootGroup;
432 for ( n = 0; n < aParts.Count(); n++ ) {
433 ConfigGroup *pNextGroup = m_pCurrentGroup->FindSubgroup(aParts[n]);
434 if ( pNextGroup == NULL )
435 pNextGroup = m_pCurrentGroup->AddSubgroup(aParts[n]);
436 m_pCurrentGroup = pNextGroup;
437 }
438
439 // recombine path parts in one variable
440 m_strPath.Empty();
441 for ( n = 0; n < aParts.Count(); n++ ) {
442 m_strPath << wxCONFIG_PATH_SEPARATOR << aParts[n];
443 }
444 }
445
446 // ----------------------------------------------------------------------------
447 // enumeration
448 // ----------------------------------------------------------------------------
449
450 bool wxFileConfig::GetFirstGroup(wxString& str, long& lIndex) const
451 {
452 lIndex = 0;
453 return GetNextGroup(str, lIndex);
454 }
455
456 bool wxFileConfig::GetNextGroup (wxString& str, long& lIndex) const
457 {
458 if ( size_t(lIndex) < m_pCurrentGroup->Groups().Count() ) {
459 str = m_pCurrentGroup->Groups()[lIndex++]->Name();
460 return TRUE;
461 }
462 else
463 return FALSE;
464 }
465
466 bool wxFileConfig::GetFirstEntry(wxString& str, long& lIndex) const
467 {
468 lIndex = 0;
469 return GetNextEntry(str, lIndex);
470 }
471
472 bool wxFileConfig::GetNextEntry (wxString& str, long& lIndex) const
473 {
474 if ( size_t(lIndex) < m_pCurrentGroup->Entries().Count() ) {
475 str = m_pCurrentGroup->Entries()[lIndex++]->Name();
476 return TRUE;
477 }
478 else
479 return FALSE;
480 }
481
482 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive) const
483 {
484 size_t n = m_pCurrentGroup->Entries().Count();
485 if ( bRecursive ) {
486 ConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
487 size_t nSubgroups = m_pCurrentGroup->Groups().Count();
488 for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
489 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
490 n += GetNumberOfEntries(TRUE);
491 CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
492 }
493 }
494
495 return n;
496 }
497
498 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive) const
499 {
500 size_t n = m_pCurrentGroup->Groups().Count();
501 if ( bRecursive ) {
502 ConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
503 size_t nSubgroups = m_pCurrentGroup->Groups().Count();
504 for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
505 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
506 n += GetNumberOfGroups(TRUE);
507 CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
508 }
509 }
510
511 return n;
512 }
513
514 // ----------------------------------------------------------------------------
515 // tests for existence
516 // ----------------------------------------------------------------------------
517
518 bool wxFileConfig::HasGroup(const wxString& strName) const
519 {
520 wxConfigPathChanger path(this, strName);
521
522 ConfigGroup *pGroup = m_pCurrentGroup->FindSubgroup(path.Name());
523 return pGroup != NULL;
524 }
525
526 bool wxFileConfig::HasEntry(const wxString& strName) const
527 {
528 wxConfigPathChanger path(this, strName);
529
530 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
531 return pEntry != NULL;
532 }
533
534 // ----------------------------------------------------------------------------
535 // read/write values
536 // ----------------------------------------------------------------------------
537
538 bool wxFileConfig::Read(const wxString& key,
539 wxString* pStr) const
540 {
541 wxConfigPathChanger path(this, key);
542
543 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
544 if (pEntry == NULL) {
545 return FALSE;
546 }
547 else {
548 *pStr = ExpandEnvVars(pEntry->Value());
549 return TRUE;
550 }
551 }
552
553 bool wxFileConfig::Read(const wxString& key,
554 wxString* pStr, const wxString& defVal) const
555 {
556 wxConfigPathChanger path(this, key);
557
558 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
559 if (pEntry == NULL) {
560 if( IsRecordingDefaults() )
561 ((wxFileConfig *)this)->Write(key,defVal);
562 *pStr = ExpandEnvVars(defVal);
563 return FALSE;
564 }
565 else {
566 *pStr = ExpandEnvVars(pEntry->Value());
567 return TRUE;
568 }
569 }
570
571 bool wxFileConfig::Read(const wxString& key, long *pl) const
572 {
573 wxString str;
574 if ( Read(key, & str) ) {
575 *pl = atol(str);
576 return TRUE;
577 }
578 else {
579 return FALSE;
580 }
581 }
582
583 bool wxFileConfig::Write(const wxString& key, const wxString& szValue)
584 {
585 wxConfigPathChanger path(this, key);
586
587 wxString strName = path.Name();
588 if ( strName.IsEmpty() ) {
589 // setting the value of a group is an error
590 wxASSERT_MSG( IsEmpty(szValue), _("can't set value of a group!") );
591
592 // ... except if it's empty in which case it's a way to force it's creation
593 m_pCurrentGroup->SetDirty();
594
595 // this will add a line for this group if it didn't have it before
596 (void)m_pCurrentGroup->GetGroupLine();
597 }
598 else {
599 // writing an entry
600
601 // check that the name is reasonable
602 if ( strName[0u] == wxCONFIG_IMMUTABLE_PREFIX ) {
603 wxLogError(_("Entry name can't start with '%c'."),
604 wxCONFIG_IMMUTABLE_PREFIX);
605 return FALSE;
606 }
607
608 for ( const char *pc = strName; *pc != '\0'; pc++ ) {
609 if ( !IsValid(*pc) ) {
610 wxLogError(_("Character '%c' is invalid in a config entry name."),
611 *pc);
612 return FALSE;
613 }
614 }
615
616 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strName);
617 if ( pEntry == NULL )
618 pEntry = m_pCurrentGroup->AddEntry(strName);
619
620 pEntry->SetValue(szValue);
621 }
622
623 return TRUE;
624 }
625
626 bool wxFileConfig::Write(const wxString& key, long lValue)
627 {
628 // ltoa() is not ANSI :-(
629 wxString buf;
630 buf.Printf("%ld", lValue);
631 return Write(key, buf);
632 }
633
634 bool wxFileConfig::Flush(bool /* bCurrentOnly */)
635 {
636 if ( LineListIsEmpty() || !m_pRootGroup->IsDirty() )
637 return TRUE;
638
639 wxTempFile file(m_strLocalFile);
640
641 if ( !file.IsOpened() ) {
642 wxLogError(_("can't open user configuration file."));
643 return FALSE;
644 }
645
646 // write all strings to file
647 for ( LineList *p = m_linesHead; p != NULL; p = p->Next() ) {
648 if ( !file.Write(p->Text() + wxTextFile::GetEOL()) ) {
649 wxLogError(_("can't write user configuration file."));
650 return FALSE;
651 }
652 }
653
654 return file.Commit();
655 }
656
657 // ----------------------------------------------------------------------------
658 // renaming groups/entries
659 // ----------------------------------------------------------------------------
660
661 bool wxFileConfig::RenameEntry(const wxString& oldName,
662 const wxString& newName)
663 {
664 // check that the entry exists
665 ConfigEntry *oldEntry = m_pCurrentGroup->FindEntry(oldName);
666 if ( !oldEntry )
667 return FALSE;
668
669 // check that the new entry doesn't already exist
670 if ( m_pCurrentGroup->FindEntry(newName) )
671 return FALSE;
672
673 // delete the old entry, create the new one
674 wxString value = oldEntry->Value();
675 if ( !m_pCurrentGroup->DeleteEntry(oldName) )
676 return FALSE;
677
678 ConfigEntry *newEntry = m_pCurrentGroup->AddEntry(newName);
679 newEntry->SetValue(value);
680
681 return TRUE;
682 }
683
684 bool wxFileConfig::RenameGroup(const wxString& oldName,
685 const wxString& newName)
686 {
687 // check that the group exists
688 ConfigGroup *group = m_pCurrentGroup->FindSubgroup(oldName);
689 if ( !group )
690 return FALSE;
691
692 // check that the new group doesn't already exist
693 if ( m_pCurrentGroup->FindSubgroup(newName) )
694 return FALSE;
695
696 group->Rename(newName);
697
698 return TRUE;
699 }
700
701 // ----------------------------------------------------------------------------
702 // delete groups/entries
703 // ----------------------------------------------------------------------------
704
705 bool wxFileConfig::DeleteEntry(const wxString& key, bool bGroupIfEmptyAlso)
706 {
707 wxConfigPathChanger path(this, key);
708
709 if ( !m_pCurrentGroup->DeleteEntry(path.Name()) )
710 return FALSE;
711
712 if ( bGroupIfEmptyAlso && m_pCurrentGroup->IsEmpty() ) {
713 if ( m_pCurrentGroup != m_pRootGroup ) {
714 ConfigGroup *pGroup = m_pCurrentGroup;
715 SetPath(".."); // changes m_pCurrentGroup!
716 m_pCurrentGroup->DeleteSubgroupByName(pGroup->Name());
717 }
718 //else: never delete the root group
719 }
720
721 return TRUE;
722 }
723
724 bool wxFileConfig::DeleteGroup(const wxString& key)
725 {
726 wxConfigPathChanger path(this, key);
727
728 return m_pCurrentGroup->DeleteSubgroupByName(path.Name());
729 }
730
731 bool wxFileConfig::DeleteAll()
732 {
733 CleanUp();
734
735 const char *szFile = m_strLocalFile;
736
737 if ( remove(szFile) == -1 )
738 wxLogSysError(_("can't delete user configuration file '%s'"), szFile);
739
740 m_strLocalFile = m_strGlobalFile = "";
741 Init();
742
743 return TRUE;
744 }
745
746 // ----------------------------------------------------------------------------
747 // linked list functions
748 // ----------------------------------------------------------------------------
749
750 // append a new line to the end of the list
751 LineList *wxFileConfig::LineListAppend(const wxString& str)
752 {
753 LineList *pLine = new LineList(str);
754
755 if ( m_linesTail == NULL ) {
756 // list is empty
757 m_linesHead = pLine;
758 }
759 else {
760 // adjust pointers
761 m_linesTail->SetNext(pLine);
762 pLine->SetPrev(m_linesTail);
763 }
764
765 m_linesTail = pLine;
766 return m_linesTail;
767 }
768
769 // insert a new line after the given one or in the very beginning if !pLine
770 LineList *wxFileConfig::LineListInsert(const wxString& str,
771 LineList *pLine)
772 {
773 if ( pLine == m_linesTail )
774 return LineListAppend(str);
775
776 LineList *pNewLine = new LineList(str);
777 if ( pLine == NULL ) {
778 // prepend to the list
779 pNewLine->SetNext(m_linesHead);
780 m_linesHead->SetPrev(pNewLine);
781 m_linesHead = pNewLine;
782 }
783 else {
784 // insert before pLine
785 LineList *pNext = pLine->Next();
786 pNewLine->SetNext(pNext);
787 pNewLine->SetPrev(pLine);
788 pNext->SetPrev(pNewLine);
789 pLine->SetNext(pNewLine);
790 }
791
792 return pNewLine;
793 }
794
795 void wxFileConfig::LineListRemove(LineList *pLine)
796 {
797 LineList *pPrev = pLine->Prev(),
798 *pNext = pLine->Next();
799
800 // first entry?
801 if ( pPrev == NULL )
802 m_linesHead = pNext;
803 else
804 pPrev->SetNext(pNext);
805
806 // last entry?
807 if ( pNext == NULL )
808 m_linesTail = pPrev;
809 else
810 pNext->SetPrev(pPrev);
811
812 delete pLine;
813 }
814
815 bool wxFileConfig::LineListIsEmpty()
816 {
817 return m_linesHead == NULL;
818 }
819
820 // ============================================================================
821 // wxFileConfig::ConfigGroup
822 // ============================================================================
823
824 // ----------------------------------------------------------------------------
825 // ctor/dtor
826 // ----------------------------------------------------------------------------
827
828 // ctor
829 ConfigGroup::ConfigGroup(ConfigGroup *pParent,
830 const wxString& strName,
831 wxFileConfig *pConfig)
832 : m_aEntries(CompareEntries),
833 m_aSubgroups(CompareGroups),
834 m_strName(strName)
835 {
836 m_pConfig = pConfig;
837 m_pParent = pParent;
838 m_bDirty = FALSE;
839 m_pLine = NULL;
840
841 m_pLastEntry = NULL;
842 m_pLastGroup = NULL;
843 }
844
845 // dtor deletes all children
846 ConfigGroup::~ConfigGroup()
847 {
848 // entries
849 size_t n, nCount = m_aEntries.Count();
850 for ( n = 0; n < nCount; n++ )
851 delete m_aEntries[n];
852
853 // subgroups
854 nCount = m_aSubgroups.Count();
855 for ( n = 0; n < nCount; n++ )
856 delete m_aSubgroups[n];
857 }
858
859 // ----------------------------------------------------------------------------
860 // line
861 // ----------------------------------------------------------------------------
862
863 void ConfigGroup::SetLine(LineList *pLine)
864 {
865 wxASSERT( m_pLine == NULL ); // shouldn't be called twice
866
867 m_pLine = pLine;
868 }
869
870 /*
871 This is a bit complicated, so let me explain it in details. All lines that
872 were read from the local file (the only one we will ever modify) are stored
873 in a (doubly) linked list. Our problem is to know at which position in this
874 list should we insert the new entries/subgroups. To solve it we keep three
875 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup.
876
877 m_pLine points to the line containing "[group_name]"
878 m_pLastEntry points to the last entry of this group in the local file.
879 m_pLastGroup subgroup
880
881 Initially, they're NULL all three. When the group (an entry/subgroup) is read
882 from the local file, the corresponding variable is set. However, if the group
883 was read from the global file and then modified or created by the application
884 these variables are still NULL and we need to create the corresponding lines.
885 See the following functions (and comments preceding them) for the details of
886 how we do it.
887
888 Also, when our last entry/group are deleted we need to find the new last
889 element - the code in DeleteEntry/Subgroup does this by backtracking the list
890 of lines until it either founds an entry/subgroup (and this is the new last
891 element) or the m_pLine of the group, in which case there are no more entries
892 (or subgroups) left and m_pLast<element> becomes NULL.
893
894 NB: This last problem could be avoided for entries if we added new entries
895 immediately after m_pLine, but in this case the entries would appear
896 backwards in the config file (OTOH, it's not that important) and as we
897 would still need to do it for the subgroups the code wouldn't have been
898 significantly less complicated.
899 */
900
901 // Return the line which contains "[our name]". If we're still not in the list,
902 // add our line to it immediately after the last line of our parent group if we
903 // have it or in the very beginning if we're the root group.
904 LineList *ConfigGroup::GetGroupLine()
905 {
906 if ( m_pLine == NULL ) {
907 ConfigGroup *pParent = Parent();
908
909 // this group wasn't present in local config file, add it now
910 if ( pParent != NULL ) {
911 wxString strFullName;
912 strFullName << "[" << (GetFullName().c_str() + 1) << "]"; // +1: no '/'
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 << "[" << (GetFullName().c_str() + 1) << "]"; // +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 "";
982 }
983
984 // ----------------------------------------------------------------------------
985 // find an item
986 // ----------------------------------------------------------------------------
987
988 // use binary search because the array is sorted
989 ConfigEntry *
990 ConfigGroup::FindEntry(const char *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 = strcmp(pEntry->Name(), szName);
1004 #else
1005 res = Stricmp(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 char *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 = strcmp(pGroup->Name(), szName);
1034 #else
1035 res = Stricmp(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 char *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[nGroup]);
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 char *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 = FilterOut(strValue);
1278 wxString strLine;
1279 strLine << m_strName << " = " << 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 strcmp(p1->Name(), p2->Name());
1317 #else
1318 return Stricmp(p1->Name(), p2->Name());
1319 #endif
1320 }
1321
1322 int CompareGroups(ConfigGroup *p1,
1323 ConfigGroup *p2)
1324 {
1325 #if wxCONFIG_CASE_SENSITIVE
1326 return strcmp(p1->Name(), p2->Name());
1327 #else
1328 return Stricmp(p1->Name(), p2->Name());
1329 #endif
1330 }
1331
1332 // ----------------------------------------------------------------------------
1333 // filter functions
1334 // ----------------------------------------------------------------------------
1335
1336 // undo FilterOut
1337 wxString FilterIn(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] == '\\' ) {
1346 switch ( str[++n] ) {
1347 case 'n':
1348 strResult += '\n';
1349 break;
1350
1351 case 'r':
1352 strResult += '\r';
1353 break;
1354
1355 case 't':
1356 strResult += '\t';
1357 break;
1358
1359 case '\\':
1360 strResult += '\\';
1361 break;
1362
1363 case '"':
1364 strResult += '"';
1365 break;
1366 }
1367 }
1368 else {
1369 if ( str[n] != '"' || !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 wxString FilterOut(const wxString& str)
1384 {
1385 if(str.IsEmpty())
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 = isspace(str[0]) || str[0] == '"';
1393
1394 if ( bQuote )
1395 strResult += '"';
1396
1397 char c;
1398 for ( size_t n = 0; n < str.Len(); n++ ) {
1399 switch ( str[n] ) {
1400 case '\n':
1401 c = 'n';
1402 break;
1403
1404 case '\r':
1405 c = 'r';
1406 break;
1407
1408 case '\t':
1409 c = 't';
1410 break;
1411
1412 case '\\':
1413 c = '\\';
1414 break;
1415
1416 case '"':
1417 if ( bQuote ) {
1418 c = '"';
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 << '\\' << c;
1430 }
1431
1432 if ( bQuote )
1433 strResult += '"';
1434
1435 return strResult;
1436 }
1437
1438
1439
1440
1441
1442
1443