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