]> git.saurik.com Git - wxWidgets.git/blob - src/common/fileconf.cpp
8c6ddeac2e6df86048e051957a50bf8dad1612a9
[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 = "/etc/";
94 #elif defined(__WXSTUBS__)
95 wxASSERT_MSG( FALSE, "TODO" ) ;
96 #elif defined(__WXMAC__)
97 wxASSERT_MSG( FALSE, "TODO" ) ;
98 #else // Windows
99 char szWinDir[MAX_PATH];
100 ::GetWindowsDirectory(szWinDir, MAX_PATH);
101
102 strDir = szWinDir;
103 strDir << '\\';
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() != '/') strDir << '/';
117 #else
118 if (strDir.Last() != '\\') strDir << '\\';
119 #endif
120
121 return strDir;
122 }
123
124 wxString wxFileConfig::GetGlobalFileName(const char *szFile)
125 {
126 wxString str = GetGlobalDir();
127 str << szFile;
128
129 if ( strchr(szFile, '.') == NULL )
130 #ifdef __UNIX__
131 str << ".conf";
132 #else // Windows
133 str << ".ini";
134 #endif // UNIX/Win
135
136 return str;
137 }
138
139 wxString wxFileConfig::GetLocalFileName(const char *szFile)
140 {
141 wxString str = GetLocalDir();
142
143 #ifdef __UNIX__
144 str << '.';
145 #endif
146
147 str << szFile;
148
149 #ifdef __WXMSW__
150 if ( strchr(szFile, '.') == NULL )
151 str << ".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, vendorName, strLocal, strGlobal, style),
202 m_strLocalFile(strLocal), m_strGlobalFile(strGlobal)
203 {
204 // Make up an application name if not supplied
205 if (appName.IsEmpty() && wxTheApp)
206 {
207 SetAppName(wxTheApp->GetAppName());
208 }
209
210 // Make up names for files if empty
211 if ( m_strLocalFile.IsEmpty() && (style & wxCONFIG_USE_LOCAL_FILE) )
212 {
213 m_strLocalFile = GetLocalFileName(GetAppName());
214 }
215
216 if ( m_strGlobalFile.IsEmpty() && (style & wxCONFIG_USE_GLOBAL_FILE) )
217 {
218 m_strGlobalFile = GetGlobalFileName(GetAppName());
219 }
220
221 // Check if styles are not supplied, but filenames are, in which case
222 // add the correct styles.
223 if ( !m_strLocalFile.IsEmpty() )
224 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE);
225
226 if ( !m_strGlobalFile.IsEmpty() )
227 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE);
228
229 // if the path is not absolute, prepend the standard directory to it
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 Init();
245 }
246
247 void wxFileConfig::CleanUp()
248 {
249 delete m_pRootGroup;
250
251 LineList *pCur = m_linesHead;
252 while ( pCur != NULL ) {
253 LineList *pNext = pCur->Next();
254 delete pCur;
255 pCur = pNext;
256 }
257 }
258
259 wxFileConfig::~wxFileConfig()
260 {
261 Flush();
262
263 CleanUp();
264 }
265
266 // ----------------------------------------------------------------------------
267 // parse a config file
268 // ----------------------------------------------------------------------------
269
270 void wxFileConfig::Parse(wxTextFile& file, bool bLocal)
271 {
272 const char *pStart;
273 const char *pEnd;
274 wxString strLine;
275
276 size_t nLineCount = file.GetLineCount();
277 for ( size_t n = 0; n < nLineCount; n++ ) {
278 strLine = file[n];
279
280 // add the line to linked list
281 if ( bLocal )
282 LineListAppend(strLine);
283
284 // skip leading spaces
285 for ( pStart = strLine; isspace(*pStart); pStart++ )
286 ;
287
288 // skip blank/comment lines
289 if ( *pStart == '\0'|| *pStart == ';' || *pStart == '#' )
290 continue;
291
292 if ( *pStart == '[' ) { // a new group
293 pEnd = pStart;
294
295 while ( *++pEnd != ']' ) {
296 if ( *pEnd == '\n' || *pEnd == '\0' )
297 break;
298 }
299
300 if ( *pEnd != ']' ) {
301 wxLogError(_("file '%s': unexpected character %c at line %d."),
302 file.GetName(), *pEnd, n + 1);
303 continue; // skip this line
304 }
305
306 // group name here is always considered as abs path
307 wxString strGroup;
308 pStart++;
309 strGroup << wxCONFIG_PATH_SEPARATOR
310 << FilterInEntryName(wxString(pStart, pEnd - pStart));
311
312 // will create it if doesn't yet exist
313 SetPath(strGroup);
314
315 if ( bLocal )
316 m_pCurrentGroup->SetLine(m_linesTail);
317
318 // check that there is nothing except comments left on this line
319 bool bCont = TRUE;
320 while ( *++pEnd != '\0' && bCont ) {
321 switch ( *pEnd ) {
322 case '#':
323 case ';':
324 bCont = FALSE;
325 break;
326
327 case ' ':
328 case '\t':
329 // ignore whitespace ('\n' impossible here)
330 break;
331
332 default:
333 wxLogWarning(_("file '%s', line %d: '%s' "
334 "ignored after group header."),
335 file.GetName(), n + 1, pEnd);
336 bCont = FALSE;
337 }
338 }
339 }
340 else { // a key
341 const char *pEnd = pStart;
342 while ( !isspace(*pEnd) ) {
343 if ( *pEnd == '\\' ) {
344 // next character may be space or not - still take it because it's
345 // quoted
346 pEnd++;
347 }
348
349 pEnd++;
350 }
351
352 wxString strKey(FilterInEntryName(wxString(pStart, pEnd)));
353
354 // skip whitespace
355 while ( isspace(*pEnd) )
356 pEnd++;
357
358 if ( *pEnd++ != '=' ) {
359 wxLogError(_("file '%s', line %d: '=' expected."),
360 file.GetName(), n + 1);
361 }
362 else {
363 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strKey);
364
365 if ( pEntry == NULL ) {
366 // new entry
367 pEntry = m_pCurrentGroup->AddEntry(strKey, n);
368
369 if ( bLocal )
370 pEntry->SetLine(m_linesTail);
371 }
372 else {
373 if ( bLocal && pEntry->IsImmutable() ) {
374 // immutable keys can't be changed by user
375 wxLogWarning(_("file '%s', line %d: value for "
376 "immutable key '%s' ignored."),
377 file.GetName(), n + 1, strKey.c_str());
378 continue;
379 }
380 // the condition below catches the cases (a) and (b) but not (c):
381 // (a) global key found second time in global file
382 // (b) key found second (or more) time in local file
383 // (c) key from global file now found in local one
384 // which is exactly what we want.
385 else if ( !bLocal || pEntry->IsLocal() ) {
386 wxLogWarning(_("file '%s', line %d: key '%s' was first "
387 "found at line %d."),
388 file.GetName(), n + 1, strKey.c_str(), pEntry->Line());
389
390 if ( bLocal )
391 pEntry->SetLine(m_linesTail);
392 }
393 }
394
395 // skip whitespace
396 while ( isspace(*pEnd) )
397 pEnd++;
398
399 pEntry->SetValue(FilterInValue(pEnd), FALSE /* read from file */);
400 }
401 }
402 }
403 }
404
405 // ----------------------------------------------------------------------------
406 // set/retrieve path
407 // ----------------------------------------------------------------------------
408
409 void wxFileConfig::SetRootPath()
410 {
411 m_strPath.Empty();
412 m_pCurrentGroup = m_pRootGroup;
413 }
414
415 void wxFileConfig::SetPath(const wxString& strPath)
416 {
417 wxArrayString aParts;
418
419 if ( strPath.IsEmpty() ) {
420 SetRootPath();
421 return;
422 }
423
424 if ( strPath[0] == wxCONFIG_PATH_SEPARATOR ) {
425 // absolute path
426 wxSplitPath(aParts, strPath);
427 }
428 else {
429 // relative path, combine with current one
430 wxString strFullPath = m_strPath;
431 strFullPath << wxCONFIG_PATH_SEPARATOR << strPath;
432 wxSplitPath(aParts, strFullPath);
433 }
434
435 // change current group
436 size_t n;
437 m_pCurrentGroup = m_pRootGroup;
438 for ( n = 0; n < aParts.Count(); n++ ) {
439 ConfigGroup *pNextGroup = m_pCurrentGroup->FindSubgroup(aParts[n]);
440 if ( pNextGroup == NULL )
441 pNextGroup = m_pCurrentGroup->AddSubgroup(aParts[n]);
442 m_pCurrentGroup = pNextGroup;
443 }
444
445 // recombine path parts in one variable
446 m_strPath.Empty();
447 for ( n = 0; n < aParts.Count(); n++ ) {
448 m_strPath << wxCONFIG_PATH_SEPARATOR << aParts[n];
449 }
450 }
451
452 // ----------------------------------------------------------------------------
453 // enumeration
454 // ----------------------------------------------------------------------------
455
456 bool wxFileConfig::GetFirstGroup(wxString& str, long& lIndex) const
457 {
458 lIndex = 0;
459 return GetNextGroup(str, lIndex);
460 }
461
462 bool wxFileConfig::GetNextGroup (wxString& str, long& lIndex) const
463 {
464 if ( size_t(lIndex) < m_pCurrentGroup->Groups().Count() ) {
465 str = m_pCurrentGroup->Groups()[lIndex++]->Name();
466 return TRUE;
467 }
468 else
469 return FALSE;
470 }
471
472 bool wxFileConfig::GetFirstEntry(wxString& str, long& lIndex) const
473 {
474 lIndex = 0;
475 return GetNextEntry(str, lIndex);
476 }
477
478 bool wxFileConfig::GetNextEntry (wxString& str, long& lIndex) const
479 {
480 if ( size_t(lIndex) < m_pCurrentGroup->Entries().Count() ) {
481 str = m_pCurrentGroup->Entries()[lIndex++]->Name();
482 return TRUE;
483 }
484 else
485 return FALSE;
486 }
487
488 size_t wxFileConfig::GetNumberOfEntries(bool bRecursive) const
489 {
490 size_t n = m_pCurrentGroup->Entries().Count();
491 if ( bRecursive ) {
492 ConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
493 size_t nSubgroups = m_pCurrentGroup->Groups().Count();
494 for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
495 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
496 n += GetNumberOfEntries(TRUE);
497 CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
498 }
499 }
500
501 return n;
502 }
503
504 size_t wxFileConfig::GetNumberOfGroups(bool bRecursive) const
505 {
506 size_t n = m_pCurrentGroup->Groups().Count();
507 if ( bRecursive ) {
508 ConfigGroup *pOldCurrentGroup = m_pCurrentGroup;
509 size_t nSubgroups = m_pCurrentGroup->Groups().Count();
510 for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) {
511 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup];
512 n += GetNumberOfGroups(TRUE);
513 CONST_CAST m_pCurrentGroup = pOldCurrentGroup;
514 }
515 }
516
517 return n;
518 }
519
520 // ----------------------------------------------------------------------------
521 // tests for existence
522 // ----------------------------------------------------------------------------
523
524 bool wxFileConfig::HasGroup(const wxString& strName) const
525 {
526 wxConfigPathChanger path(this, strName);
527
528 ConfigGroup *pGroup = m_pCurrentGroup->FindSubgroup(path.Name());
529 return pGroup != NULL;
530 }
531
532 bool wxFileConfig::HasEntry(const wxString& strName) const
533 {
534 wxConfigPathChanger path(this, strName);
535
536 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
537 return pEntry != NULL;
538 }
539
540 // ----------------------------------------------------------------------------
541 // read/write values
542 // ----------------------------------------------------------------------------
543
544 bool wxFileConfig::Read(const wxString& key,
545 wxString* pStr) const
546 {
547 wxConfigPathChanger path(this, key);
548
549 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
550 if (pEntry == NULL) {
551 return FALSE;
552 }
553 else {
554 *pStr = ExpandEnvVars(pEntry->Value());
555 return TRUE;
556 }
557 }
558
559 bool wxFileConfig::Read(const wxString& key,
560 wxString* pStr, const wxString& defVal) const
561 {
562 wxConfigPathChanger path(this, key);
563
564 ConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name());
565 if (pEntry == NULL) {
566 if( IsRecordingDefaults() )
567 ((wxFileConfig *)this)->Write(key,defVal);
568 *pStr = ExpandEnvVars(defVal);
569 return FALSE;
570 }
571 else {
572 *pStr = ExpandEnvVars(pEntry->Value());
573 return TRUE;
574 }
575 }
576
577 bool wxFileConfig::Read(const wxString& key, long *pl) const
578 {
579 wxString str;
580 if ( Read(key, & str) ) {
581 *pl = atol(str);
582 return TRUE;
583 }
584 else {
585 return FALSE;
586 }
587 }
588
589 bool wxFileConfig::Write(const wxString& key, const wxString& szValue)
590 {
591 wxConfigPathChanger path(this, key);
592
593 wxString strName = path.Name();
594 if ( strName.IsEmpty() ) {
595 // setting the value of a group is an error
596 wxASSERT_MSG( IsEmpty(szValue), "can't set value of a group!" );
597
598 // ... except if it's empty in which case it's a way to force it's creation
599 m_pCurrentGroup->SetDirty();
600
601 // this will add a line for this group if it didn't have it before
602 (void)m_pCurrentGroup->GetGroupLine();
603 }
604 else {
605 // writing an entry
606
607 // check that the name is reasonable
608 if ( strName[0u] == wxCONFIG_IMMUTABLE_PREFIX ) {
609 wxLogError(_("Config entry name cannot start with '%c'."),
610 wxCONFIG_IMMUTABLE_PREFIX);
611 return FALSE;
612 }
613
614 strName = FilterOutEntryName(strName);
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 #if 0
1110 // pGroup->m_aSubgroups.Count() gets decremented in DeleteSubgroup(),
1111 // so we cannot do this.
1112
1113 nCount = pGroup->m_aSubgroups.Count();
1114 for ( size_t nGroup = 0; nGroup < nCount; nGroup++ ) {
1115 pGroup->DeleteSubgroup(pGroup->m_aSubgroups[nGroup]);
1116 }
1117 #endif
1118 while(pGroup->m_aSubgroups.Count() > 0)
1119 pGroup->DeleteSubgroup(pGroup->m_aSubgroups[0]);
1120
1121 LineList *pLine = pGroup->m_pLine;
1122 if ( pLine != NULL ) {
1123 // notice that we may do this test inside the previous "if" because the
1124 // last entry's line is surely !NULL
1125 if ( pGroup == m_pLastGroup ) {
1126 // our last entry is being deleted - find the last one which stays
1127 wxASSERT( m_pLine != NULL ); // we have a subgroup with !NULL pLine...
1128
1129 // go back until we find a subgroup or reach the group's line
1130 ConfigGroup *pNewLast = NULL;
1131 size_t n, nSubgroups = m_aSubgroups.Count();
1132 LineList *pl;
1133 for ( pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() ) {
1134 // is it our subgroup?
1135 for ( n = 0; (pNewLast == NULL) && (n < nSubgroups); n++ ) {
1136 // do _not_ call GetGroupLine! we don't want to add it to the local
1137 // file if it's not already there
1138 if ( m_aSubgroups[n]->m_pLine == m_pLine )
1139 pNewLast = m_aSubgroups[n];
1140 }
1141
1142 if ( pNewLast != NULL ) // found?
1143 break;
1144 }
1145
1146 if ( pl == m_pLine ) {
1147 wxASSERT( !pNewLast ); // how comes it has the same line as we?
1148
1149 // we've reached the group line without finding any subgroups
1150 m_pLastGroup = NULL;
1151 }
1152 else
1153 m_pLastGroup = pNewLast;
1154 }
1155
1156 m_pConfig->LineListRemove(pLine);
1157 }
1158
1159 SetDirty();
1160
1161 m_aSubgroups.Remove(pGroup);
1162 delete pGroup;
1163
1164 return TRUE;
1165 }
1166
1167 bool ConfigGroup::DeleteEntry(const char *szName)
1168 {
1169 ConfigEntry *pEntry = FindEntry(szName);
1170 wxCHECK( pEntry != NULL, FALSE ); // deleting non existing item?
1171
1172 LineList *pLine = pEntry->GetLine();
1173 if ( pLine != NULL ) {
1174 // notice that we may do this test inside the previous "if" because the
1175 // last entry's line is surely !NULL
1176 if ( pEntry == m_pLastEntry ) {
1177 // our last entry is being deleted - find the last one which stays
1178 wxASSERT( m_pLine != NULL ); // if we have an entry with !NULL pLine...
1179
1180 // go back until we find another entry or reach the group's line
1181 ConfigEntry *pNewLast = NULL;
1182 size_t n, nEntries = m_aEntries.Count();
1183 LineList *pl;
1184 for ( pl = pLine->Prev(); pl != m_pLine; pl = pl->Prev() ) {
1185 // is it our subgroup?
1186 for ( n = 0; (pNewLast == NULL) && (n < nEntries); n++ ) {
1187 if ( m_aEntries[n]->GetLine() == m_pLine )
1188 pNewLast = m_aEntries[n];
1189 }
1190
1191 if ( pNewLast != NULL ) // found?
1192 break;
1193 }
1194
1195 if ( pl == m_pLine ) {
1196 wxASSERT( !pNewLast ); // how comes it has the same line as we?
1197
1198 // we've reached the group line without finding any subgroups
1199 m_pLastEntry = NULL;
1200 }
1201 else
1202 m_pLastEntry = pNewLast;
1203 }
1204
1205 m_pConfig->LineListRemove(pLine);
1206 }
1207
1208 // we must be written back for the changes to be saved
1209 SetDirty();
1210
1211 m_aEntries.Remove(pEntry);
1212 delete pEntry;
1213
1214 return TRUE;
1215 }
1216
1217 // ----------------------------------------------------------------------------
1218 //
1219 // ----------------------------------------------------------------------------
1220 void ConfigGroup::SetDirty()
1221 {
1222 m_bDirty = TRUE;
1223 if ( Parent() != NULL ) // propagate upwards
1224 Parent()->SetDirty();
1225 }
1226
1227 // ============================================================================
1228 // wxFileConfig::ConfigEntry
1229 // ============================================================================
1230
1231 // ----------------------------------------------------------------------------
1232 // ctor
1233 // ----------------------------------------------------------------------------
1234 ConfigEntry::ConfigEntry(ConfigGroup *pParent,
1235 const wxString& strName,
1236 int nLine)
1237 : m_strName(strName)
1238 {
1239 wxASSERT( !strName.IsEmpty() );
1240
1241 m_pParent = pParent;
1242 m_nLine = nLine;
1243 m_pLine = NULL;
1244
1245 m_bDirty = FALSE;
1246
1247 m_bImmutable = strName[0] == wxCONFIG_IMMUTABLE_PREFIX;
1248 if ( m_bImmutable )
1249 m_strName.erase(0, 1); // remove first character
1250 }
1251
1252 // ----------------------------------------------------------------------------
1253 // set value
1254 // ----------------------------------------------------------------------------
1255
1256 void ConfigEntry::SetLine(LineList *pLine)
1257 {
1258 if ( m_pLine != NULL ) {
1259 wxLogWarning(_("entry '%s' appears more than once in group '%s'"),
1260 Name().c_str(), m_pParent->GetFullName().c_str());
1261 }
1262
1263 m_pLine = pLine;
1264 Group()->SetLastEntry(this);
1265 }
1266
1267 // second parameter is FALSE if we read the value from file and prevents the
1268 // entry from being marked as 'dirty'
1269 void ConfigEntry::SetValue(const wxString& strValue, bool bUser)
1270 {
1271 if ( bUser && IsImmutable() ) {
1272 wxLogWarning(_("attempt to change immutable key '%s' ignored."),
1273 Name().c_str());
1274 return;
1275 }
1276
1277 // do nothing if it's the same value
1278 if ( strValue == m_strValue )
1279 return;
1280
1281 m_strValue = strValue;
1282
1283 if ( bUser ) {
1284 wxString strVal = FilterOutValue(strValue);
1285 wxString strLine;
1286 strLine << m_strName << " = " << strVal;
1287
1288 if ( m_pLine != NULL ) {
1289 // entry was read from the local config file, just modify the line
1290 m_pLine->SetText(strLine);
1291 }
1292 else {
1293 // add a new line to the file
1294 wxASSERT( m_nLine == wxNOT_FOUND ); // consistency check
1295
1296 m_pLine = Group()->Config()->LineListInsert(strLine,
1297 Group()->GetLastEntryLine());
1298 Group()->SetLastEntry(this);
1299 }
1300
1301 SetDirty();
1302 }
1303 }
1304
1305 void ConfigEntry::SetDirty()
1306 {
1307 m_bDirty = TRUE;
1308 Group()->SetDirty();
1309 }
1310
1311 // ============================================================================
1312 // global functions
1313 // ============================================================================
1314
1315 // ----------------------------------------------------------------------------
1316 // compare functions for array sorting
1317 // ----------------------------------------------------------------------------
1318
1319 int CompareEntries(ConfigEntry *p1,
1320 ConfigEntry *p2)
1321 {
1322 #if wxCONFIG_CASE_SENSITIVE
1323 return strcmp(p1->Name(), p2->Name());
1324 #else
1325 return Stricmp(p1->Name(), p2->Name());
1326 #endif
1327 }
1328
1329 int CompareGroups(ConfigGroup *p1,
1330 ConfigGroup *p2)
1331 {
1332 #if wxCONFIG_CASE_SENSITIVE
1333 return strcmp(p1->Name(), p2->Name());
1334 #else
1335 return Stricmp(p1->Name(), p2->Name());
1336 #endif
1337 }
1338
1339 // ----------------------------------------------------------------------------
1340 // filter functions
1341 // ----------------------------------------------------------------------------
1342
1343 // undo FilterOutValue
1344 static wxString FilterInValue(const wxString& str)
1345 {
1346 wxString strResult;
1347 strResult.Alloc(str.Len());
1348
1349 bool bQuoted = !str.IsEmpty() && str[0] == '"';
1350
1351 for ( size_t n = bQuoted ? 1 : 0; n < str.Len(); n++ ) {
1352 if ( str[n] == '\\' ) {
1353 switch ( str[++n] ) {
1354 case 'n':
1355 strResult += '\n';
1356 break;
1357
1358 case 'r':
1359 strResult += '\r';
1360 break;
1361
1362 case 't':
1363 strResult += '\t';
1364 break;
1365
1366 case '\\':
1367 strResult += '\\';
1368 break;
1369
1370 case '"':
1371 strResult += '"';
1372 break;
1373 }
1374 }
1375 else {
1376 if ( str[n] != '"' || !bQuoted )
1377 strResult += str[n];
1378 else if ( n != str.Len() - 1 ) {
1379 wxLogWarning(_("unexpected \" at position %d in '%s'."),
1380 n, str.c_str());
1381 }
1382 //else: it's the last quote of a quoted string, ok
1383 }
1384 }
1385
1386 return strResult;
1387 }
1388
1389 // quote the string before writing it to file
1390 static wxString FilterOutValue(const wxString& str)
1391 {
1392 if ( !str )
1393 return str;
1394
1395 wxString strResult;
1396 strResult.Alloc(str.Len());
1397
1398 // quoting is necessary to preserve spaces in the beginning of the string
1399 bool bQuote = isspace(str[0]) || str[0] == '"';
1400
1401 if ( bQuote )
1402 strResult += '"';
1403
1404 char c;
1405 for ( size_t n = 0; n < str.Len(); n++ ) {
1406 switch ( str[n] ) {
1407 case '\n':
1408 c = 'n';
1409 break;
1410
1411 case '\r':
1412 c = 'r';
1413 break;
1414
1415 case '\t':
1416 c = 't';
1417 break;
1418
1419 case '\\':
1420 c = '\\';
1421 break;
1422
1423 case '"':
1424 if ( bQuote ) {
1425 c = '"';
1426 break;
1427 }
1428 //else: fall through
1429
1430 default:
1431 strResult += str[n];
1432 continue; // nothing special to do
1433 }
1434
1435 // we get here only for special characters
1436 strResult << '\\' << c;
1437 }
1438
1439 if ( bQuote )
1440 strResult += '"';
1441
1442 return strResult;
1443 }
1444
1445 // undo FilterOutEntryName
1446 static wxString FilterInEntryName(const wxString& str)
1447 {
1448 wxString strResult;
1449 strResult.Alloc(str.Len());
1450
1451 for ( const char *pc = str.c_str(); *pc != '\0'; pc++ ) {
1452 if ( *pc == '\\' )
1453 pc++;
1454
1455 strResult += *pc;
1456 }
1457
1458 return strResult;
1459 }
1460
1461 // sanitize entry or group name: insert '\\' before any special characters
1462 static wxString FilterOutEntryName(const wxString& str)
1463 {
1464 wxString strResult;
1465 strResult.Alloc(str.Len());
1466
1467 for ( const char *pc = str.c_str(); *pc != '\0'; pc++ ) {
1468 char c = *pc;
1469
1470 // we explicitly allow some of "safe" chars and 8bit ASCII characters
1471 // which will probably never have special meaning
1472 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR
1473 // should *not* be quoted
1474 if ( !isalnum(c) && !strchr("@_/-!.*%", c) && ((c & 0x80) == 0) )
1475 strResult += '\\';
1476
1477 strResult += c;
1478 }
1479
1480 return strResult;
1481 }
1482