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