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