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