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