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