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