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