1/////////////////////////////////////////////////////////////////////////////// 2// Name: src/common/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: fileconf.cpp 50711 2007-12-15 02:57:58Z VZ $ 8// Copyright: (c) 1997 Karsten Ballueder & Vadim Zeitlin 9// Ballueder@usa.net <zeitlin@dptmaths.ens-cachan.fr> 10// Licence: wxWindows licence 11/////////////////////////////////////////////////////////////////////////////// 12 13// ---------------------------------------------------------------------------- 14// headers 15// ---------------------------------------------------------------------------- 16 17// For compilers that support precompilation, includes "wx.h". 18#include "wx/wxprec.h" 19 20#ifdef __BORLANDC__ 21 #pragma hdrstop 22#endif //__BORLANDC__ 23 24#if wxUSE_CONFIG && wxUSE_FILECONFIG 25 26#ifndef WX_PRECOMP 27 #include "wx/dynarray.h" 28 #include "wx/string.h" 29 #include "wx/intl.h" 30 #include "wx/log.h" 31 #include "wx/app.h" 32 #include "wx/utils.h" // for wxGetHomeDir 33 #if wxUSE_STREAMS 34 #include "wx/stream.h" 35 #endif // wxUSE_STREAMS 36#endif //WX_PRECOMP 37 38#include "wx/file.h" 39#include "wx/textfile.h" 40#include "wx/memtext.h" 41#include "wx/config.h" 42#include "wx/fileconf.h" 43#include "wx/filefn.h" 44 45#if defined(__WXMAC__) 46 #include "wx/mac/private.h" // includes mac headers 47 #include "wx/filename.h" // for MacSetTypeAndCreator 48#endif 49 50#if defined(__WXMSW__) 51 #include "wx/msw/private.h" 52#endif //windows.h 53#if defined(__WXPM__) 54 #define INCL_DOS 55 #include <os2.h> 56#endif 57 58#include <stdlib.h> 59#include <ctype.h> 60 61// ---------------------------------------------------------------------------- 62// macros 63// ---------------------------------------------------------------------------- 64#define CONST_CAST ((wxFileConfig *)this)-> 65 66// ---------------------------------------------------------------------------- 67// constants 68// ---------------------------------------------------------------------------- 69 70#ifndef MAX_PATH 71 #define MAX_PATH 512 72#endif 73 74#define FILECONF_TRACE_MASK _T("fileconf") 75 76// ---------------------------------------------------------------------------- 77// global functions declarations 78// ---------------------------------------------------------------------------- 79 80// compare functions for sorting the arrays 81static int LINKAGEMODE CompareEntries(wxFileConfigEntry *p1, wxFileConfigEntry *p2); 82static int LINKAGEMODE CompareGroups(wxFileConfigGroup *p1, wxFileConfigGroup *p2); 83 84// filter strings 85static wxString FilterInValue(const wxString& str); 86static wxString FilterOutValue(const wxString& str); 87 88static wxString FilterInEntryName(const wxString& str); 89static wxString FilterOutEntryName(const wxString& str); 90 91// get the name to use in wxFileConfig ctor 92static wxString GetAppName(const wxString& appname); 93 94// ============================================================================ 95// private classes 96// ============================================================================ 97 98// ---------------------------------------------------------------------------- 99// "template" array types 100// ---------------------------------------------------------------------------- 101 102#ifdef WXMAKINGDLL_BASE 103 WX_DEFINE_SORTED_USER_EXPORTED_ARRAY(wxFileConfigEntry *, ArrayEntries, 104 WXDLLIMPEXP_BASE); 105 WX_DEFINE_SORTED_USER_EXPORTED_ARRAY(wxFileConfigGroup *, ArrayGroups, 106 WXDLLIMPEXP_BASE); 107#else 108 WX_DEFINE_SORTED_ARRAY(wxFileConfigEntry *, ArrayEntries); 109 WX_DEFINE_SORTED_ARRAY(wxFileConfigGroup *, ArrayGroups); 110#endif 111 112// ---------------------------------------------------------------------------- 113// wxFileConfigLineList 114// ---------------------------------------------------------------------------- 115 116// we store all lines of the local config file as a linked list in memory 117class wxFileConfigLineList 118{ 119public: 120 void SetNext(wxFileConfigLineList *pNext) { m_pNext = pNext; } 121 void SetPrev(wxFileConfigLineList *pPrev) { m_pPrev = pPrev; } 122 123 // ctor 124 wxFileConfigLineList(const wxString& str, 125 wxFileConfigLineList *pNext = NULL) : m_strLine(str) 126 { SetNext(pNext); SetPrev(NULL); } 127 128 // next/prev nodes in the linked list 129 wxFileConfigLineList *Next() const { return m_pNext; } 130 wxFileConfigLineList *Prev() const { return m_pPrev; } 131 132 // get/change lines text 133 void SetText(const wxString& str) { m_strLine = str; } 134 const wxString& Text() const { return m_strLine; } 135 136private: 137 wxString m_strLine; // line contents 138 wxFileConfigLineList *m_pNext, // next node 139 *m_pPrev; // previous one 140 141 DECLARE_NO_COPY_CLASS(wxFileConfigLineList) 142}; 143 144// ---------------------------------------------------------------------------- 145// wxFileConfigEntry: a name/value pair 146// ---------------------------------------------------------------------------- 147 148class wxFileConfigEntry 149{ 150private: 151 wxFileConfigGroup *m_pParent; // group that contains us 152 153 wxString m_strName, // entry name 154 m_strValue; // value 155 bool m_bImmutable:1, // can be overriden locally? 156 m_bHasValue:1; // set after first call to SetValue() 157 158 int m_nLine; // used if m_pLine == NULL only 159 160 // pointer to our line in the linked list or NULL if it was found in global 161 // file (which we don't modify) 162 wxFileConfigLineList *m_pLine; 163 164public: 165 wxFileConfigEntry(wxFileConfigGroup *pParent, 166 const wxString& strName, int nLine); 167 168 // simple accessors 169 const wxString& Name() const { return m_strName; } 170 const wxString& Value() const { return m_strValue; } 171 wxFileConfigGroup *Group() const { return m_pParent; } 172 bool IsImmutable() const { return m_bImmutable; } 173 bool IsLocal() const { return m_pLine != 0; } 174 int Line() const { return m_nLine; } 175 wxFileConfigLineList * 176 GetLine() const { return m_pLine; } 177 178 // modify entry attributes 179 void SetValue(const wxString& strValue, bool bUser = true); 180 void SetLine(wxFileConfigLineList *pLine); 181 182 DECLARE_NO_COPY_CLASS(wxFileConfigEntry) 183}; 184 185// ---------------------------------------------------------------------------- 186// wxFileConfigGroup: container of entries and other groups 187// ---------------------------------------------------------------------------- 188 189class wxFileConfigGroup 190{ 191private: 192 wxFileConfig *m_pConfig; // config object we belong to 193 wxFileConfigGroup *m_pParent; // parent group (NULL for root group) 194 ArrayEntries m_aEntries; // entries in this group 195 ArrayGroups m_aSubgroups; // subgroups 196 wxString m_strName; // group's name 197 wxFileConfigLineList *m_pLine; // pointer to our line in the linked list 198 wxFileConfigEntry *m_pLastEntry; // last entry/subgroup of this group in the 199 wxFileConfigGroup *m_pLastGroup; // local file (we insert new ones after it) 200 201 // DeleteSubgroupByName helper 202 bool DeleteSubgroup(wxFileConfigGroup *pGroup); 203 204 // used by Rename() 205 void UpdateGroupAndSubgroupsLines(); 206 207public: 208 // ctor 209 wxFileConfigGroup(wxFileConfigGroup *pParent, const wxString& strName, wxFileConfig *); 210 211 // dtor deletes all entries and subgroups also 212 ~wxFileConfigGroup(); 213 214 // simple accessors 215 const wxString& Name() const { return m_strName; } 216 wxFileConfigGroup *Parent() const { return m_pParent; } 217 wxFileConfig *Config() const { return m_pConfig; } 218 219 const ArrayEntries& Entries() const { return m_aEntries; } 220 const ArrayGroups& Groups() const { return m_aSubgroups; } 221 bool IsEmpty() const { return Entries().IsEmpty() && Groups().IsEmpty(); } 222 223 // find entry/subgroup (NULL if not found) 224 wxFileConfigGroup *FindSubgroup(const wxChar *szName) const; 225 wxFileConfigEntry *FindEntry (const wxChar *szName) const; 226 227 // delete entry/subgroup, return false if doesn't exist 228 bool DeleteSubgroupByName(const wxChar *szName); 229 bool DeleteEntry(const wxChar *szName); 230 231 // create new entry/subgroup returning pointer to newly created element 232 wxFileConfigGroup *AddSubgroup(const wxString& strName); 233 wxFileConfigEntry *AddEntry (const wxString& strName, int nLine = wxNOT_FOUND); 234 235 void SetLine(wxFileConfigLineList *pLine); 236 237 // rename: no checks are done to ensure that the name is unique! 238 void Rename(const wxString& newName); 239 240 // 241 wxString GetFullName() const; 242 243 // get the last line belonging to an entry/subgroup of this group 244 wxFileConfigLineList *GetGroupLine(); // line which contains [group] 245 // may be NULL for "/" only 246 wxFileConfigLineList *GetLastEntryLine(); // after which our subgroups start 247 wxFileConfigLineList *GetLastGroupLine(); // after which the next group starts 248 249 // called by entries/subgroups when they're created/deleted 250 void SetLastEntry(wxFileConfigEntry *pEntry); 251 void SetLastGroup(wxFileConfigGroup *pGroup) 252 { m_pLastGroup = pGroup; } 253 254 DECLARE_NO_COPY_CLASS(wxFileConfigGroup) 255}; 256 257// ============================================================================ 258// implementation 259// ============================================================================ 260 261// ---------------------------------------------------------------------------- 262// static functions 263// ---------------------------------------------------------------------------- 264wxString wxFileConfig::GetGlobalDir() 265{ 266 wxString strDir; 267 268#ifdef __VMS__ // Note if __VMS is defined __UNIX is also defined 269 strDir = wxT("sys$manager:"); 270#elif defined(__WXMAC__) 271 strDir = wxMacFindFolder( (short) kOnSystemDisk, kPreferencesFolderType, kDontCreateFolder ) ; 272#elif defined( __UNIX__ ) 273 strDir = wxT("/etc/"); 274#elif defined(__OS2__) 275 ULONG aulSysInfo[QSV_MAX] = {0}; 276 UINT drive; 277 APIRET rc; 278 279 rc = DosQuerySysInfo( 1L, QSV_MAX, (PVOID)aulSysInfo, sizeof(ULONG)*QSV_MAX); 280 if (rc == 0) 281 { 282 drive = aulSysInfo[QSV_BOOT_DRIVE - 1]; 283 strDir.Printf(wxT("%c:\\OS2\\"), 'A'+drive-1); 284 } 285#elif defined(__WXSTUBS__) 286 wxFAIL_MSG( wxT("TODO") ); 287#elif defined(__DOS__) 288 // There's no such thing as global cfg dir in MS-DOS, let's return 289 // current directory (FIXME_MGL?) 290 strDir = wxT(".\\"); 291#elif defined(__WXWINCE__) 292 strDir = wxT("\\Windows\\"); 293#else // Windows 294 295 wxChar szWinDir[MAX_PATH]; 296 ::GetWindowsDirectory(szWinDir, MAX_PATH); 297 298 strDir = szWinDir; 299 strDir << wxT('\\'); 300#endif // Unix/Windows 301 302 return strDir; 303} 304 305wxString wxFileConfig::GetLocalDir() 306{ 307 wxString strDir; 308 309#if defined(__WXMAC__) || defined(__DOS__) 310 // no local dir concept on Mac OS 9 or MS-DOS 311 strDir << GetGlobalDir() ; 312#else 313 wxGetHomeDir(&strDir); 314 315 #ifdef __UNIX__ 316 if ( 317 (strDir.Last() != wxT('/')) 318 #ifdef __VMS 319 && (strDir.Last() != wxT(']')) 320 #endif 321 ) 322 strDir << wxT('/'); 323 #else 324 if (strDir.Last() != wxT('\\')) 325 strDir << wxT('\\'); 326 #endif 327#endif 328 329 return strDir; 330} 331 332wxString wxFileConfig::GetGlobalFileName(const wxChar *szFile) 333{ 334 wxString str = GetGlobalDir(); 335 str << szFile; 336 337 if ( wxStrchr(szFile, wxT('.')) == NULL ) 338#if defined( __WXMAC__ ) 339 str << wxT(" Preferences") ; 340#elif defined( __UNIX__ ) 341 str << wxT(".conf"); 342#else // Windows 343 str << wxT(".ini"); 344#endif // UNIX/Win 345 346 return str; 347} 348 349wxString wxFileConfig::GetLocalFileName(const wxChar *szFile) 350{ 351#ifdef __VMS__ 352 // On VMS I saw the problem that the home directory was appended 353 // twice for the configuration file. Does that also happen for 354 // other platforms? 355 wxString str = wxT( '.' ); 356#else 357 wxString str = GetLocalDir(); 358#endif 359 360#if defined( __UNIX__ ) && !defined( __VMS ) && !defined( __WXMAC__ ) 361 str << wxT('.'); 362#endif 363 364 str << szFile; 365 366#if defined(__WINDOWS__) || defined(__DOS__) 367 if ( wxStrchr(szFile, wxT('.')) == NULL ) 368 str << wxT(".ini"); 369#endif 370 371#ifdef __WXMAC__ 372 str << wxT(" Preferences") ; 373#endif 374 375 return str; 376} 377 378// ---------------------------------------------------------------------------- 379// ctor 380// ---------------------------------------------------------------------------- 381 382void wxFileConfig::Init() 383{ 384 m_pCurrentGroup = 385 m_pRootGroup = new wxFileConfigGroup(NULL, wxEmptyString, this); 386 387 m_linesHead = 388 m_linesTail = NULL; 389 390 // It's not an error if (one of the) file(s) doesn't exist. 391 392 // parse the global file 393 if ( !m_strGlobalFile.empty() && wxFile::Exists(m_strGlobalFile) ) 394 { 395 wxTextFile fileGlobal(m_strGlobalFile); 396 397 if ( fileGlobal.Open(*m_conv/*ignored in ANSI build*/) ) 398 { 399 Parse(fileGlobal, false /* global */); 400 SetRootPath(); 401 } 402 else 403 { 404 wxLogWarning(_("can't open global configuration file '%s'."), m_strGlobalFile.c_str()); 405 } 406 } 407 408 // parse the local file 409 if ( !m_strLocalFile.empty() && wxFile::Exists(m_strLocalFile) ) 410 { 411 wxTextFile fileLocal(m_strLocalFile); 412 if ( fileLocal.Open(*m_conv/*ignored in ANSI build*/) ) 413 { 414 Parse(fileLocal, true /* local */); 415 SetRootPath(); 416 } 417 else 418 { 419 wxLogWarning(_("can't open user configuration file '%s'."), m_strLocalFile.c_str() ); 420 } 421 } 422 423 m_isDirty = false; 424} 425 426// constructor supports creation of wxFileConfig objects of any type 427wxFileConfig::wxFileConfig(const wxString& appName, const wxString& vendorName, 428 const wxString& strLocal, const wxString& strGlobal, 429 long style, 430 const wxMBConv& conv) 431 : wxConfigBase(::GetAppName(appName), vendorName, 432 strLocal, strGlobal, 433 style), 434 m_strLocalFile(strLocal), m_strGlobalFile(strGlobal), 435 m_conv(conv.Clone()) 436{ 437 // Make up names for files if empty 438 if ( m_strLocalFile.empty() && (style & wxCONFIG_USE_LOCAL_FILE) ) 439 { 440 m_strLocalFile = GetLocalFileName(GetAppName()); 441#if defined(__UNIX__) && !defined(__VMS) 442 if ( style & wxCONFIG_USE_SUBDIR ) 443 m_strLocalFile << wxFILE_SEP_PATH << GetAppName() << _T(".conf"); 444#endif 445 } 446 447 if ( m_strGlobalFile.empty() && (style & wxCONFIG_USE_GLOBAL_FILE) ) 448 m_strGlobalFile = GetGlobalFileName(GetAppName()); 449 450 // Check if styles are not supplied, but filenames are, in which case 451 // add the correct styles. 452 if ( !m_strLocalFile.empty() ) 453 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE); 454 455 if ( !m_strGlobalFile.empty() ) 456 SetStyle(GetStyle() | wxCONFIG_USE_GLOBAL_FILE); 457 458 // if the path is not absolute, prepend the standard directory to it 459 // UNLESS wxCONFIG_USE_RELATIVE_PATH style is set 460 if ( !(style & wxCONFIG_USE_RELATIVE_PATH) ) 461 { 462 if ( !m_strLocalFile.empty() && !wxIsAbsolutePath(m_strLocalFile) ) 463 { 464 const wxString strLocalOrig = m_strLocalFile; 465 m_strLocalFile = GetLocalDir(); 466 m_strLocalFile << strLocalOrig; 467 } 468 469 if ( !m_strGlobalFile.empty() && !wxIsAbsolutePath(m_strGlobalFile) ) 470 { 471 const wxString strGlobalOrig = m_strGlobalFile; 472 m_strGlobalFile = GetGlobalDir(); 473 m_strGlobalFile << strGlobalOrig; 474 } 475 } 476 477 SetUmask(-1); 478 479 Init(); 480} 481 482#if wxUSE_STREAMS 483 484wxFileConfig::wxFileConfig(wxInputStream &inStream, const wxMBConv& conv) 485 : m_conv(conv.Clone()) 486{ 487 // always local_file when this constructor is called (?) 488 SetStyle(GetStyle() | wxCONFIG_USE_LOCAL_FILE); 489 490 m_pCurrentGroup = 491 m_pRootGroup = new wxFileConfigGroup(NULL, wxEmptyString, this); 492 493 m_linesHead = 494 m_linesTail = NULL; 495 496 // read the entire stream contents in memory 497 wxString str; 498 { 499 static const size_t chunkLen = 1024; 500 501 wxMemoryBuffer buf(chunkLen); 502 do 503 { 504 inStream.Read(buf.GetAppendBuf(chunkLen), chunkLen); 505 buf.UngetAppendBuf(inStream.LastRead()); 506 507 const wxStreamError err = inStream.GetLastError(); 508 509 if ( err != wxSTREAM_NO_ERROR && err != wxSTREAM_EOF ) 510 { 511 wxLogError(_("Error reading config options.")); 512 break; 513 } 514 } 515 while ( !inStream.Eof() ); 516 517#if wxUSE_UNICODE 518 size_t len; 519 str = conv.cMB2WC((char *)buf.GetData(), buf.GetDataLen(), &len); 520 if ( !len && buf.GetDataLen() ) 521 { 522 wxLogError(_("Failed to read config options.")); 523 } 524#else // !wxUSE_UNICODE 525 // no need for conversion 526 str.assign((char *)buf.GetData(), buf.GetDataLen()); 527#endif // wxUSE_UNICODE/!wxUSE_UNICODE 528 } 529 530 531 // translate everything to the current (platform-dependent) line 532 // termination character 533 str = wxTextBuffer::Translate(str); 534 535 wxMemoryText memText; 536 537 // Now we can add the text to the memory text. To do this we extract line 538 // by line from the translated string, until we've reached the end. 539 // 540 // VZ: all this is horribly inefficient, we should do the translation on 541 // the fly in one pass saving both memory and time (TODO) 542 543 const wxChar *pEOL = wxTextBuffer::GetEOL(wxTextBuffer::typeDefault); 544 const size_t EOLLen = wxStrlen(pEOL); 545 546 int posLineStart = str.Find(pEOL); 547 while ( posLineStart != -1 ) 548 { 549 wxString line(str.Left(posLineStart)); 550 551 memText.AddLine(line); 552 553 str = str.Mid(posLineStart + EOLLen); 554 555 posLineStart = str.Find(pEOL); 556 } 557 558 // also add whatever we have left in the translated string. 559 if ( !str.empty() ) 560 memText.AddLine(str); 561 562 // Finally we can parse it all. 563 Parse(memText, true /* local */); 564 565 SetRootPath(); 566 ResetDirty(); 567} 568 569#endif // wxUSE_STREAMS 570 571void wxFileConfig::CleanUp() 572{ 573 delete m_pRootGroup; 574 575 wxFileConfigLineList *pCur = m_linesHead; 576 while ( pCur != NULL ) { 577 wxFileConfigLineList *pNext = pCur->Next(); 578 delete pCur; 579 pCur = pNext; 580 } 581} 582 583wxFileConfig::~wxFileConfig() 584{ 585 Flush(); 586 587 CleanUp(); 588 589 delete m_conv; 590} 591 592// ---------------------------------------------------------------------------- 593// parse a config file 594// ---------------------------------------------------------------------------- 595 596void wxFileConfig::Parse(const wxTextBuffer& buffer, bool bLocal) 597{ 598 const wxChar *pStart; 599 const wxChar *pEnd; 600 wxString strLine; 601 602 size_t nLineCount = buffer.GetLineCount(); 603 604 for ( size_t n = 0; n < nLineCount; n++ ) 605 { 606 strLine = buffer[n]; 607 608 // add the line to linked list 609 if ( bLocal ) 610 LineListAppend(strLine); 611 612 613 // skip leading spaces 614 for ( pStart = strLine; wxIsspace(*pStart); pStart++ ) 615 ; 616 617 // skip blank/comment lines 618 if ( *pStart == wxT('\0')|| *pStart == wxT(';') || *pStart == wxT('#') ) 619 continue; 620 621 if ( *pStart == wxT('[') ) { // a new group 622 pEnd = pStart; 623 624 while ( *++pEnd != wxT(']') ) { 625 if ( *pEnd == wxT('\\') ) { 626 // the next char is escaped, so skip it even if it is ']' 627 pEnd++; 628 } 629 630 if ( *pEnd == wxT('\n') || *pEnd == wxT('\0') ) { 631 // we reached the end of line, break out of the loop 632 break; 633 } 634 } 635 636 if ( *pEnd != wxT(']') ) { 637 wxLogError(_("file '%s': unexpected character %c at line %d."), 638 buffer.GetName(), *pEnd, n + 1); 639 continue; // skip this line 640 } 641 642 // group name here is always considered as abs path 643 wxString strGroup; 644 pStart++; 645 strGroup << wxCONFIG_PATH_SEPARATOR 646 << FilterInEntryName(wxString(pStart, pEnd - pStart)); 647 648 // will create it if doesn't yet exist 649 SetPath(strGroup); 650 651 if ( bLocal ) 652 { 653 if ( m_pCurrentGroup->Parent() ) 654 m_pCurrentGroup->Parent()->SetLastGroup(m_pCurrentGroup); 655 m_pCurrentGroup->SetLine(m_linesTail); 656 } 657 658 // check that there is nothing except comments left on this line 659 bool bCont = true; 660 while ( *++pEnd != wxT('\0') && bCont ) { 661 switch ( *pEnd ) { 662 case wxT('#'): 663 case wxT(';'): 664 bCont = false; 665 break; 666 667 case wxT(' '): 668 case wxT('\t'): 669 // ignore whitespace ('\n' impossible here) 670 break; 671 672 default: 673 wxLogWarning(_("file '%s', line %d: '%s' ignored after group header."), 674 buffer.GetName(), n + 1, pEnd); 675 bCont = false; 676 } 677 } 678 } 679 else { // a key 680 pEnd = pStart; 681 while ( *pEnd && *pEnd != wxT('=') /* && !wxIsspace(*pEnd)*/ ) { 682 if ( *pEnd == wxT('\\') ) { 683 // next character may be space or not - still take it because it's 684 // quoted (unless there is nothing) 685 pEnd++; 686 if ( !*pEnd ) { 687 // the error message will be given below anyhow 688 break; 689 } 690 } 691 692 pEnd++; 693 } 694 695 wxString strKey(FilterInEntryName(wxString(pStart, pEnd).Trim())); 696 697 // skip whitespace 698 while ( wxIsspace(*pEnd) ) 699 pEnd++; 700 701 if ( *pEnd++ != wxT('=') ) { 702 wxLogError(_("file '%s', line %d: '=' expected."), 703 buffer.GetName(), n + 1); 704 } 705 else { 706 wxFileConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strKey); 707 708 if ( pEntry == NULL ) { 709 // new entry 710 pEntry = m_pCurrentGroup->AddEntry(strKey, n); 711 } 712 else { 713 if ( bLocal && pEntry->IsImmutable() ) { 714 // immutable keys can't be changed by user 715 wxLogWarning(_("file '%s', line %d: value for immutable key '%s' ignored."), 716 buffer.GetName(), n + 1, strKey.c_str()); 717 continue; 718 } 719 // the condition below catches the cases (a) and (b) but not (c): 720 // (a) global key found second time in global file 721 // (b) key found second (or more) time in local file 722 // (c) key from global file now found in local one 723 // which is exactly what we want. 724 else if ( !bLocal || pEntry->IsLocal() ) { 725 wxLogWarning(_("file '%s', line %d: key '%s' was first found at line %d."), 726 buffer.GetName(), n + 1, strKey.c_str(), pEntry->Line()); 727 728 } 729 } 730 731 if ( bLocal ) 732 pEntry->SetLine(m_linesTail); 733 734 // skip whitespace 735 while ( wxIsspace(*pEnd) ) 736 pEnd++; 737 738 wxString value = pEnd; 739 if ( !(GetStyle() & wxCONFIG_USE_NO_ESCAPE_CHARACTERS) ) 740 value = FilterInValue(value); 741 742 pEntry->SetValue(value, false); 743 } 744 } 745 } 746} 747 748// ---------------------------------------------------------------------------- 749// set/retrieve path 750// ---------------------------------------------------------------------------- 751 752void wxFileConfig::SetRootPath() 753{ 754 m_strPath.Empty(); 755 m_pCurrentGroup = m_pRootGroup; 756} 757 758bool 759wxFileConfig::DoSetPath(const wxString& strPath, bool createMissingComponents) 760{ 761 wxArrayString aParts; 762 763 if ( strPath.empty() ) { 764 SetRootPath(); 765 return true; 766 } 767 768 if ( strPath[0] == wxCONFIG_PATH_SEPARATOR ) { 769 // absolute path 770 wxSplitPath(aParts, strPath); 771 } 772 else { 773 // relative path, combine with current one 774 wxString strFullPath = m_strPath; 775 strFullPath << wxCONFIG_PATH_SEPARATOR << strPath; 776 wxSplitPath(aParts, strFullPath); 777 } 778 779 // change current group 780 size_t n; 781 m_pCurrentGroup = m_pRootGroup; 782 for ( n = 0; n < aParts.Count(); n++ ) { 783 wxFileConfigGroup *pNextGroup = m_pCurrentGroup->FindSubgroup(aParts[n]); 784 if ( pNextGroup == NULL ) 785 { 786 if ( !createMissingComponents ) 787 return false; 788 789 pNextGroup = m_pCurrentGroup->AddSubgroup(aParts[n]); 790 } 791 792 m_pCurrentGroup = pNextGroup; 793 } 794 795 // recombine path parts in one variable 796 m_strPath.Empty(); 797 for ( n = 0; n < aParts.Count(); n++ ) { 798 m_strPath << wxCONFIG_PATH_SEPARATOR << aParts[n]; 799 } 800 801 return true; 802} 803 804void wxFileConfig::SetPath(const wxString& strPath) 805{ 806 DoSetPath(strPath, true /* create missing path components */); 807} 808 809// ---------------------------------------------------------------------------- 810// enumeration 811// ---------------------------------------------------------------------------- 812 813bool wxFileConfig::GetFirstGroup(wxString& str, long& lIndex) const 814{ 815 lIndex = 0; 816 return GetNextGroup(str, lIndex); 817} 818 819bool wxFileConfig::GetNextGroup (wxString& str, long& lIndex) const 820{ 821 if ( size_t(lIndex) < m_pCurrentGroup->Groups().Count() ) { 822 str = m_pCurrentGroup->Groups()[(size_t)lIndex++]->Name(); 823 return true; 824 } 825 else 826 return false; 827} 828 829bool wxFileConfig::GetFirstEntry(wxString& str, long& lIndex) const 830{ 831 lIndex = 0; 832 return GetNextEntry(str, lIndex); 833} 834 835bool wxFileConfig::GetNextEntry (wxString& str, long& lIndex) const 836{ 837 if ( size_t(lIndex) < m_pCurrentGroup->Entries().Count() ) { 838 str = m_pCurrentGroup->Entries()[(size_t)lIndex++]->Name(); 839 return true; 840 } 841 else 842 return false; 843} 844 845size_t wxFileConfig::GetNumberOfEntries(bool bRecursive) const 846{ 847 size_t n = m_pCurrentGroup->Entries().Count(); 848 if ( bRecursive ) { 849 wxFileConfigGroup *pOldCurrentGroup = m_pCurrentGroup; 850 size_t nSubgroups = m_pCurrentGroup->Groups().Count(); 851 for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) { 852 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup]; 853 n += GetNumberOfEntries(true); 854 CONST_CAST m_pCurrentGroup = pOldCurrentGroup; 855 } 856 } 857 858 return n; 859} 860 861size_t wxFileConfig::GetNumberOfGroups(bool bRecursive) const 862{ 863 size_t n = m_pCurrentGroup->Groups().Count(); 864 if ( bRecursive ) { 865 wxFileConfigGroup *pOldCurrentGroup = m_pCurrentGroup; 866 size_t nSubgroups = m_pCurrentGroup->Groups().Count(); 867 for ( size_t nGroup = 0; nGroup < nSubgroups; nGroup++ ) { 868 CONST_CAST m_pCurrentGroup = m_pCurrentGroup->Groups()[nGroup]; 869 n += GetNumberOfGroups(true); 870 CONST_CAST m_pCurrentGroup = pOldCurrentGroup; 871 } 872 } 873 874 return n; 875} 876 877// ---------------------------------------------------------------------------- 878// tests for existence 879// ---------------------------------------------------------------------------- 880 881bool wxFileConfig::HasGroup(const wxString& strName) const 882{ 883 // special case: DoSetPath("") does work as it's equivalent to DoSetPath("/") 884 // but there is no group with empty name so treat this separately 885 if ( strName.empty() ) 886 return false; 887 888 const wxString pathOld = GetPath(); 889 890 wxFileConfig *self = wx_const_cast(wxFileConfig *, this); 891 const bool 892 rc = self->DoSetPath(strName, false /* don't create missing components */); 893 894 self->SetPath(pathOld); 895 896 return rc; 897} 898 899bool wxFileConfig::HasEntry(const wxString& entry) const 900{ 901 // path is the part before the last "/" 902 wxString path = entry.BeforeLast(wxCONFIG_PATH_SEPARATOR); 903 904 // except in the special case of "/keyname" when there is nothing before "/" 905 if ( path.empty() && *entry.c_str() == wxCONFIG_PATH_SEPARATOR ) 906 { 907 path = wxCONFIG_PATH_SEPARATOR; 908 } 909 910 // change to the path of the entry if necessary and remember the old path 911 // to restore it later 912 wxString pathOld; 913 wxFileConfig * const self = wx_const_cast(wxFileConfig *, this); 914 if ( !path.empty() ) 915 { 916 pathOld = GetPath(); 917 if ( pathOld.empty() ) 918 pathOld = wxCONFIG_PATH_SEPARATOR; 919 920 if ( !self->DoSetPath(path, false /* don't create if doesn't exist */) ) 921 { 922 return false; 923 } 924 } 925 926 // check if the entry exists in this group 927 const bool exists = m_pCurrentGroup->FindEntry( 928 entry.AfterLast(wxCONFIG_PATH_SEPARATOR)) != NULL; 929 930 // restore the old path if we changed it above 931 if ( !pathOld.empty() ) 932 { 933 self->SetPath(pathOld); 934 } 935 936 return exists; 937} 938 939// ---------------------------------------------------------------------------- 940// read/write values 941// ---------------------------------------------------------------------------- 942 943bool wxFileConfig::DoReadString(const wxString& key, wxString* pStr) const 944{ 945 wxConfigPathChanger path(this, key); 946 947 wxFileConfigEntry *pEntry = m_pCurrentGroup->FindEntry(path.Name()); 948 if (pEntry == NULL) { 949 return false; 950 } 951 952 *pStr = pEntry->Value(); 953 954 return true; 955} 956 957bool wxFileConfig::DoReadLong(const wxString& key, long *pl) const 958{ 959 wxString str; 960 if ( !Read(key, &str) ) 961 return false; 962 963 // extra spaces shouldn't prevent us from reading numeric values 964 str.Trim(); 965 966 return str.ToLong(pl); 967} 968 969bool wxFileConfig::DoWriteString(const wxString& key, const wxString& szValue) 970{ 971 wxConfigPathChanger path(this, key); 972 wxString strName = path.Name(); 973 974 wxLogTrace( FILECONF_TRACE_MASK, 975 _T(" Writing String '%s' = '%s' to Group '%s'"), 976 strName.c_str(), 977 szValue.c_str(), 978 GetPath().c_str() ); 979 980 if ( strName.empty() ) 981 { 982 // setting the value of a group is an error 983 984 wxASSERT_MSG( szValue.empty(), wxT("can't set value of a group!") ); 985 986 // ... except if it's empty in which case it's a way to force it's creation 987 988 wxLogTrace( FILECONF_TRACE_MASK, 989 _T(" Creating group %s"), 990 m_pCurrentGroup->Name().c_str() ); 991 992 SetDirty(); 993 994 // this will add a line for this group if it didn't have it before (or 995 // do nothing for the root but it's ok as it always exists anyhow) 996 (void)m_pCurrentGroup->GetGroupLine(); 997 } 998 else 999 { 1000 // writing an entry check that the name is reasonable 1001 if ( strName[0u] == wxCONFIG_IMMUTABLE_PREFIX ) 1002 { 1003 wxLogError( _("Config entry name cannot start with '%c'."), 1004 wxCONFIG_IMMUTABLE_PREFIX); 1005 return false; 1006 } 1007 1008 wxFileConfigEntry *pEntry = m_pCurrentGroup->FindEntry(strName); 1009 1010 if ( pEntry == 0 ) 1011 { 1012 wxLogTrace( FILECONF_TRACE_MASK, 1013 _T(" Adding Entry %s"), 1014 strName.c_str() ); 1015 pEntry = m_pCurrentGroup->AddEntry(strName); 1016 } 1017 1018 wxLogTrace( FILECONF_TRACE_MASK, 1019 _T(" Setting value %s"), 1020 szValue.c_str() ); 1021 pEntry->SetValue(szValue); 1022 1023 SetDirty(); 1024 } 1025 1026 return true; 1027} 1028 1029bool wxFileConfig::DoWriteLong(const wxString& key, long lValue) 1030{ 1031 return Write(key, wxString::Format(_T("%ld"), lValue)); 1032} 1033 1034bool wxFileConfig::Flush(bool /* bCurrentOnly */) 1035{ 1036 if ( !IsDirty() || !m_strLocalFile ) 1037 return true; 1038 1039 // set the umask if needed 1040 wxCHANGE_UMASK(m_umask); 1041 1042 wxTempFile file(m_strLocalFile); 1043 1044 if ( !file.IsOpened() ) 1045 { 1046 wxLogError(_("can't open user configuration file.")); 1047 return false; 1048 } 1049 1050 // write all strings to file 1051 wxString filetext; 1052 filetext.reserve(4096); 1053 for ( wxFileConfigLineList *p = m_linesHead; p != NULL; p = p->Next() ) 1054 { 1055 filetext << p->Text() << wxTextFile::GetEOL(); 1056 } 1057 1058 if ( !file.Write(filetext, *m_conv) ) 1059 { 1060 wxLogError(_("can't write user configuration file.")); 1061 return false; 1062 } 1063 1064 if ( !file.Commit() ) 1065 { 1066 wxLogError(_("Failed to update user configuration file.")); 1067 1068 return false; 1069 } 1070 1071 ResetDirty(); 1072 1073#if defined(__WXMAC__) 1074 wxFileName(m_strLocalFile).MacSetTypeAndCreator('TEXT', 'ttxt'); 1075#endif // __WXMAC__ 1076 1077 return true; 1078} 1079 1080#if wxUSE_STREAMS 1081 1082bool wxFileConfig::Save(wxOutputStream& os, const wxMBConv& conv) 1083{ 1084 // save unconditionally, even if not dirty 1085 for ( wxFileConfigLineList *p = m_linesHead; p != NULL; p = p->Next() ) 1086 { 1087 wxString line = p->Text(); 1088 line += wxTextFile::GetEOL(); 1089 1090 wxCharBuffer buf(line.mb_str(conv)); 1091 if ( !os.Write(buf, strlen(buf)) ) 1092 { 1093 wxLogError(_("Error saving user configuration data.")); 1094 1095 return false; 1096 } 1097 } 1098 1099 ResetDirty(); 1100 1101 return true; 1102} 1103 1104#endif // wxUSE_STREAMS 1105 1106// ---------------------------------------------------------------------------- 1107// renaming groups/entries 1108// ---------------------------------------------------------------------------- 1109 1110bool wxFileConfig::RenameEntry(const wxString& oldName, 1111 const wxString& newName) 1112{ 1113 wxASSERT_MSG( !wxStrchr(oldName, wxCONFIG_PATH_SEPARATOR), 1114 _T("RenameEntry(): paths are not supported") ); 1115 1116 // check that the entry exists 1117 wxFileConfigEntry *oldEntry = m_pCurrentGroup->FindEntry(oldName); 1118 if ( !oldEntry ) 1119 return false; 1120 1121 // check that the new entry doesn't already exist 1122 if ( m_pCurrentGroup->FindEntry(newName) ) 1123 return false; 1124 1125 // delete the old entry, create the new one 1126 wxString value = oldEntry->Value(); 1127 if ( !m_pCurrentGroup->DeleteEntry(oldName) ) 1128 return false; 1129 1130 SetDirty(); 1131 1132 wxFileConfigEntry *newEntry = m_pCurrentGroup->AddEntry(newName); 1133 newEntry->SetValue(value); 1134 1135 return true; 1136} 1137 1138bool wxFileConfig::RenameGroup(const wxString& oldName, 1139 const wxString& newName) 1140{ 1141 // check that the group exists 1142 wxFileConfigGroup *group = m_pCurrentGroup->FindSubgroup(oldName); 1143 if ( !group ) 1144 return false; 1145 1146 // check that the new group doesn't already exist 1147 if ( m_pCurrentGroup->FindSubgroup(newName) ) 1148 return false; 1149 1150 group->Rename(newName); 1151 1152 SetDirty(); 1153 1154 return true; 1155} 1156 1157// ---------------------------------------------------------------------------- 1158// delete groups/entries 1159// ---------------------------------------------------------------------------- 1160 1161bool wxFileConfig::DeleteEntry(const wxString& key, bool bGroupIfEmptyAlso) 1162{ 1163 wxConfigPathChanger path(this, key); 1164 1165 if ( !m_pCurrentGroup->DeleteEntry(path.Name()) ) 1166 return false; 1167 1168 SetDirty(); 1169 1170 if ( bGroupIfEmptyAlso && m_pCurrentGroup->IsEmpty() ) { 1171 if ( m_pCurrentGroup != m_pRootGroup ) { 1172 wxFileConfigGroup *pGroup = m_pCurrentGroup; 1173 SetPath(wxT("..")); // changes m_pCurrentGroup! 1174 m_pCurrentGroup->DeleteSubgroupByName(pGroup->Name()); 1175 } 1176 //else: never delete the root group 1177 } 1178 1179 return true; 1180} 1181 1182bool wxFileConfig::DeleteGroup(const wxString& key) 1183{ 1184 wxConfigPathChanger path(this, RemoveTrailingSeparator(key)); 1185 1186 if ( !m_pCurrentGroup->DeleteSubgroupByName(path.Name()) ) 1187 return false; 1188 1189 path.UpdateIfDeleted(); 1190 1191 SetDirty(); 1192 1193 return true; 1194} 1195 1196bool wxFileConfig::DeleteAll() 1197{ 1198 CleanUp(); 1199 1200 if ( !m_strLocalFile.empty() ) 1201 { 1202 if ( wxFile::Exists(m_strLocalFile) && wxRemove(m_strLocalFile) == -1 ) 1203 { 1204 wxLogSysError(_("can't delete user configuration file '%s'"), 1205 m_strLocalFile.c_str()); 1206 return false; 1207 } 1208 } 1209 1210 Init(); 1211 1212 return true; 1213} 1214 1215// ---------------------------------------------------------------------------- 1216// linked list functions 1217// ---------------------------------------------------------------------------- 1218 1219 // append a new line to the end of the list 1220 1221wxFileConfigLineList *wxFileConfig::LineListAppend(const wxString& str) 1222{ 1223 wxLogTrace( FILECONF_TRACE_MASK, 1224 _T(" ** Adding Line '%s'"), 1225 str.c_str() ); 1226 wxLogTrace( FILECONF_TRACE_MASK, 1227 _T(" head: %s"), 1228 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) ); 1229 wxLogTrace( FILECONF_TRACE_MASK, 1230 _T(" tail: %s"), 1231 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) ); 1232 1233 wxFileConfigLineList *pLine = new wxFileConfigLineList(str); 1234 1235 if ( m_linesTail == NULL ) 1236 { 1237 // list is empty 1238 m_linesHead = pLine; 1239 } 1240 else 1241 { 1242 // adjust pointers 1243 m_linesTail->SetNext(pLine); 1244 pLine->SetPrev(m_linesTail); 1245 } 1246 1247 m_linesTail = pLine; 1248 1249 wxLogTrace( FILECONF_TRACE_MASK, 1250 _T(" head: %s"), 1251 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) ); 1252 wxLogTrace( FILECONF_TRACE_MASK, 1253 _T(" tail: %s"), 1254 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) ); 1255 1256 return m_linesTail; 1257} 1258 1259// insert a new line after the given one or in the very beginning if !pLine 1260wxFileConfigLineList *wxFileConfig::LineListInsert(const wxString& str, 1261 wxFileConfigLineList *pLine) 1262{ 1263 wxLogTrace( FILECONF_TRACE_MASK, 1264 _T(" ** Inserting Line '%s' after '%s'"), 1265 str.c_str(), 1266 ((pLine) ? pLine->Text().c_str() : wxEmptyString) ); 1267 wxLogTrace( FILECONF_TRACE_MASK, 1268 _T(" head: %s"), 1269 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) ); 1270 wxLogTrace( FILECONF_TRACE_MASK, 1271 _T(" tail: %s"), 1272 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) ); 1273 1274 if ( pLine == m_linesTail ) 1275 return LineListAppend(str); 1276 1277 wxFileConfigLineList *pNewLine = new wxFileConfigLineList(str); 1278 if ( pLine == NULL ) 1279 { 1280 // prepend to the list 1281 pNewLine->SetNext(m_linesHead); 1282 m_linesHead->SetPrev(pNewLine); 1283 m_linesHead = pNewLine; 1284 } 1285 else 1286 { 1287 // insert before pLine 1288 wxFileConfigLineList *pNext = pLine->Next(); 1289 pNewLine->SetNext(pNext); 1290 pNewLine->SetPrev(pLine); 1291 pNext->SetPrev(pNewLine); 1292 pLine->SetNext(pNewLine); 1293 } 1294 1295 wxLogTrace( FILECONF_TRACE_MASK, 1296 _T(" head: %s"), 1297 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) ); 1298 wxLogTrace( FILECONF_TRACE_MASK, 1299 _T(" tail: %s"), 1300 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) ); 1301 1302 return pNewLine; 1303} 1304 1305void wxFileConfig::LineListRemove(wxFileConfigLineList *pLine) 1306{ 1307 wxLogTrace( FILECONF_TRACE_MASK, 1308 _T(" ** Removing Line '%s'"), 1309 pLine->Text().c_str() ); 1310 wxLogTrace( FILECONF_TRACE_MASK, 1311 _T(" head: %s"), 1312 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) ); 1313 wxLogTrace( FILECONF_TRACE_MASK, 1314 _T(" tail: %s"), 1315 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) ); 1316 1317 wxFileConfigLineList *pPrev = pLine->Prev(), 1318 *pNext = pLine->Next(); 1319 1320 // first entry? 1321 1322 if ( pPrev == NULL ) 1323 m_linesHead = pNext; 1324 else 1325 pPrev->SetNext(pNext); 1326 1327 // last entry? 1328 1329 if ( pNext == NULL ) 1330 m_linesTail = pPrev; 1331 else 1332 pNext->SetPrev(pPrev); 1333 1334 if ( m_pRootGroup->GetGroupLine() == pLine ) 1335 m_pRootGroup->SetLine(m_linesHead); 1336 1337 wxLogTrace( FILECONF_TRACE_MASK, 1338 _T(" head: %s"), 1339 ((m_linesHead) ? m_linesHead->Text().c_str() : wxEmptyString) ); 1340 wxLogTrace( FILECONF_TRACE_MASK, 1341 _T(" tail: %s"), 1342 ((m_linesTail) ? m_linesTail->Text().c_str() : wxEmptyString) ); 1343 1344 delete pLine; 1345} 1346 1347bool wxFileConfig::LineListIsEmpty() 1348{ 1349 return m_linesHead == NULL; 1350} 1351 1352// ============================================================================ 1353// wxFileConfig::wxFileConfigGroup 1354// ============================================================================ 1355 1356// ---------------------------------------------------------------------------- 1357// ctor/dtor 1358// ---------------------------------------------------------------------------- 1359 1360// ctor 1361wxFileConfigGroup::wxFileConfigGroup(wxFileConfigGroup *pParent, 1362 const wxString& strName, 1363 wxFileConfig *pConfig) 1364 : m_aEntries(CompareEntries), 1365 m_aSubgroups(CompareGroups), 1366 m_strName(strName) 1367{ 1368 m_pConfig = pConfig; 1369 m_pParent = pParent; 1370 m_pLine = NULL; 1371 1372 m_pLastEntry = NULL; 1373 m_pLastGroup = NULL; 1374} 1375 1376// dtor deletes all children 1377wxFileConfigGroup::~wxFileConfigGroup() 1378{ 1379 // entries 1380 size_t n, nCount = m_aEntries.Count(); 1381 for ( n = 0; n < nCount; n++ ) 1382 delete m_aEntries[n]; 1383 1384 // subgroups 1385 nCount = m_aSubgroups.Count(); 1386 for ( n = 0; n < nCount; n++ ) 1387 delete m_aSubgroups[n]; 1388} 1389 1390// ---------------------------------------------------------------------------- 1391// line 1392// ---------------------------------------------------------------------------- 1393 1394void wxFileConfigGroup::SetLine(wxFileConfigLineList *pLine) 1395{ 1396 // for a normal (i.e. not root) group this method shouldn't be called twice 1397 // unless we are resetting the line 1398 wxASSERT_MSG( !m_pParent || !m_pLine || !pLine, 1399 _T("changing line for a non-root group?") ); 1400 1401 m_pLine = pLine; 1402} 1403 1404/* 1405 This is a bit complicated, so let me explain it in details. All lines that 1406 were read from the local file (the only one we will ever modify) are stored 1407 in a (doubly) linked list. Our problem is to know at which position in this 1408 list should we insert the new entries/subgroups. To solve it we keep three 1409 variables for each group: m_pLine, m_pLastEntry and m_pLastGroup. 1410 1411 m_pLine points to the line containing "[group_name]" 1412 m_pLastEntry points to the last entry of this group in the local file. 1413 m_pLastGroup subgroup 1414 1415 Initially, they're NULL all three. When the group (an entry/subgroup) is read 1416 from the local file, the corresponding variable is set. However, if the group 1417 was read from the global file and then modified or created by the application 1418 these variables are still NULL and we need to create the corresponding lines. 1419 See the following functions (and comments preceding them) for the details of 1420 how we do it. 1421 1422 Also, when our last entry/group are deleted we need to find the new last 1423 element - the code in DeleteEntry/Subgroup does this by backtracking the list 1424 of lines until it either founds an entry/subgroup (and this is the new last 1425 element) or the m_pLine of the group, in which case there are no more entries 1426 (or subgroups) left and m_pLast<element> becomes NULL. 1427 1428 NB: This last problem could be avoided for entries if we added new entries 1429 immediately after m_pLine, but in this case the entries would appear 1430 backwards in the config file (OTOH, it's not that important) and as we 1431 would still need to do it for the subgroups the code wouldn't have been 1432 significantly less complicated. 1433*/ 1434 1435// Return the line which contains "[our name]". If we're still not in the list, 1436// add our line to it immediately after the last line of our parent group if we 1437// have it or in the very beginning if we're the root group. 1438wxFileConfigLineList *wxFileConfigGroup::GetGroupLine() 1439{ 1440 wxLogTrace( FILECONF_TRACE_MASK, 1441 _T(" GetGroupLine() for Group '%s'"), 1442 Name().c_str() ); 1443 1444 if ( !m_pLine ) 1445 { 1446 wxLogTrace( FILECONF_TRACE_MASK, 1447 _T(" Getting Line item pointer") ); 1448 1449 wxFileConfigGroup *pParent = Parent(); 1450 1451 // this group wasn't present in local config file, add it now 1452 if ( pParent ) 1453 { 1454 wxLogTrace( FILECONF_TRACE_MASK, 1455 _T(" checking parent '%s'"), 1456 pParent->Name().c_str() ); 1457 1458 wxString strFullName; 1459 1460 // add 1 to the name because we don't want to start with '/' 1461 strFullName << wxT("[") 1462 << FilterOutEntryName(GetFullName().c_str() + 1) 1463 << wxT("]"); 1464 m_pLine = m_pConfig->LineListInsert(strFullName, 1465 pParent->GetLastGroupLine()); 1466 pParent->SetLastGroup(this); // we're surely after all the others 1467 } 1468 //else: this is the root group and so we return NULL because we don't 1469 // have any group line 1470 } 1471 1472 return m_pLine; 1473} 1474 1475// Return the last line belonging to the subgroups of this group (after which 1476// we can add a new subgroup), if we don't have any subgroups or entries our 1477// last line is the group line (m_pLine) itself. 1478wxFileConfigLineList *wxFileConfigGroup::GetLastGroupLine() 1479{ 1480 // if we have any subgroups, our last line is the last line of the last 1481 // subgroup 1482 if ( m_pLastGroup ) 1483 { 1484 wxFileConfigLineList *pLine = m_pLastGroup->GetLastGroupLine(); 1485 1486 wxASSERT_MSG( pLine, _T("last group must have !NULL associated line") ); 1487 1488 return pLine; 1489 } 1490 1491 // no subgroups, so the last line is the line of thelast entry (if any) 1492 return GetLastEntryLine(); 1493} 1494 1495// return the last line belonging to the entries of this group (after which 1496// we can add a new entry), if we don't have any entries we will add the new 1497// one immediately after the group line itself. 1498wxFileConfigLineList *wxFileConfigGroup::GetLastEntryLine() 1499{ 1500 wxLogTrace( FILECONF_TRACE_MASK, 1501 _T(" GetLastEntryLine() for Group '%s'"), 1502 Name().c_str() ); 1503 1504 if ( m_pLastEntry ) 1505 { 1506 wxFileConfigLineList *pLine = m_pLastEntry->GetLine(); 1507 1508 wxASSERT_MSG( pLine, _T("last entry must have !NULL associated line") ); 1509 1510 return pLine; 1511 } 1512 1513 // no entries: insert after the group header, if any 1514 return GetGroupLine(); 1515} 1516 1517void wxFileConfigGroup::SetLastEntry(wxFileConfigEntry *pEntry) 1518{ 1519 m_pLastEntry = pEntry; 1520 1521 if ( !m_pLine ) 1522 { 1523 // the only situation in which a group without its own line can have 1524 // an entry is when the first entry is added to the initially empty 1525 // root pseudo-group 1526 wxASSERT_MSG( !m_pParent, _T("unexpected for non root group") ); 1527 1528 // let the group know that it does have a line in the file now 1529 m_pLine = pEntry->GetLine(); 1530 } 1531} 1532 1533// ---------------------------------------------------------------------------- 1534// group name 1535// ---------------------------------------------------------------------------- 1536 1537void wxFileConfigGroup::UpdateGroupAndSubgroupsLines() 1538{ 1539 // update the line of this group 1540 wxFileConfigLineList *line = GetGroupLine(); 1541 wxCHECK_RET( line, _T("a non root group must have a corresponding line!") ); 1542 1543 // +1: skip the leading '/' 1544 line->SetText(wxString::Format(_T("[%s]"), GetFullName().c_str() + 1)); 1545 1546 1547 // also update all subgroups as they have this groups name in their lines 1548 const size_t nCount = m_aSubgroups.Count(); 1549 for ( size_t n = 0; n < nCount; n++ ) 1550 { 1551 m_aSubgroups[n]->UpdateGroupAndSubgroupsLines(); 1552 } 1553} 1554 1555void wxFileConfigGroup::Rename(const wxString& newName) 1556{ 1557 wxCHECK_RET( m_pParent, _T("the root group can't be renamed") ); 1558 1559 if ( newName == m_strName ) 1560 return; 1561 1562 // we need to remove the group from the parent and it back under the new 1563 // name to keep the parents array of subgroups alphabetically sorted 1564 m_pParent->m_aSubgroups.Remove(this); 1565 1566 m_strName = newName; 1567 1568 m_pParent->m_aSubgroups.Add(this); 1569 1570 // update the group lines recursively 1571 UpdateGroupAndSubgroupsLines(); 1572} 1573 1574wxString wxFileConfigGroup::GetFullName() const 1575{ 1576 wxString fullname; 1577 if ( Parent() ) 1578 fullname = Parent()->GetFullName() + wxCONFIG_PATH_SEPARATOR + Name(); 1579 1580 return fullname; 1581} 1582 1583// ---------------------------------------------------------------------------- 1584// find an item 1585// ---------------------------------------------------------------------------- 1586 1587// use binary search because the array is sorted 1588wxFileConfigEntry * 1589wxFileConfigGroup::FindEntry(const wxChar *szName) const 1590{ 1591 size_t i, 1592 lo = 0, 1593 hi = m_aEntries.Count(); 1594 int res; 1595 wxFileConfigEntry *pEntry; 1596 1597 while ( lo < hi ) { 1598 i = (lo + hi)/2; 1599 pEntry = m_aEntries[i]; 1600 1601 #if wxCONFIG_CASE_SENSITIVE 1602 res = wxStrcmp(pEntry->Name(), szName); 1603 #else 1604 res = wxStricmp(pEntry->Name(), szName); 1605 #endif 1606 1607 if ( res > 0 ) 1608 hi = i; 1609 else if ( res < 0 ) 1610 lo = i + 1; 1611 else 1612 return pEntry; 1613 } 1614 1615 return NULL; 1616} 1617 1618wxFileConfigGroup * 1619wxFileConfigGroup::FindSubgroup(const wxChar *szName) const 1620{ 1621 size_t i, 1622 lo = 0, 1623 hi = m_aSubgroups.Count(); 1624 int res; 1625 wxFileConfigGroup *pGroup; 1626 1627 while ( lo < hi ) { 1628 i = (lo + hi)/2; 1629 pGroup = m_aSubgroups[i]; 1630 1631 #if wxCONFIG_CASE_SENSITIVE 1632 res = wxStrcmp(pGroup->Name(), szName); 1633 #else 1634 res = wxStricmp(pGroup->Name(), szName); 1635 #endif 1636 1637 if ( res > 0 ) 1638 hi = i; 1639 else if ( res < 0 ) 1640 lo = i + 1; 1641 else 1642 return pGroup; 1643 } 1644 1645 return NULL; 1646} 1647 1648// ---------------------------------------------------------------------------- 1649// create a new item 1650// ---------------------------------------------------------------------------- 1651 1652// create a new entry and add it to the current group 1653wxFileConfigEntry *wxFileConfigGroup::AddEntry(const wxString& strName, int nLine) 1654{ 1655 wxASSERT( FindEntry(strName) == 0 ); 1656 1657 wxFileConfigEntry *pEntry = new wxFileConfigEntry(this, strName, nLine); 1658 1659 m_aEntries.Add(pEntry); 1660 return pEntry; 1661} 1662 1663// create a new group and add it to the current group 1664wxFileConfigGroup *wxFileConfigGroup::AddSubgroup(const wxString& strName) 1665{ 1666 wxASSERT( FindSubgroup(strName) == 0 ); 1667 1668 wxFileConfigGroup *pGroup = new wxFileConfigGroup(this, strName, m_pConfig); 1669 1670 m_aSubgroups.Add(pGroup); 1671 return pGroup; 1672} 1673 1674// ---------------------------------------------------------------------------- 1675// delete an item 1676// ---------------------------------------------------------------------------- 1677 1678/* 1679 The delete operations are _very_ slow if we delete the last item of this 1680 group (see comments before GetXXXLineXXX functions for more details), 1681 so it's much better to start with the first entry/group if we want to 1682 delete several of them. 1683 */ 1684 1685bool wxFileConfigGroup::DeleteSubgroupByName(const wxChar *szName) 1686{ 1687 wxFileConfigGroup * const pGroup = FindSubgroup(szName); 1688 1689 return pGroup ? DeleteSubgroup(pGroup) : false; 1690} 1691 1692// Delete the subgroup and remove all references to it from 1693// other data structures. 1694bool wxFileConfigGroup::DeleteSubgroup(wxFileConfigGroup *pGroup) 1695{ 1696 wxCHECK_MSG( pGroup, false, _T("deleting non existing group?") ); 1697 1698 wxLogTrace( FILECONF_TRACE_MASK, 1699 _T("Deleting group '%s' from '%s'"), 1700 pGroup->Name().c_str(), 1701 Name().c_str() ); 1702 1703 wxLogTrace( FILECONF_TRACE_MASK, 1704 _T(" (m_pLine) = prev: %p, this %p, next %p"), 1705 m_pLine ? wx_static_cast(void*, m_pLine->Prev()) : 0, 1706 wx_static_cast(void*, m_pLine), 1707 m_pLine ? wx_static_cast(void*, m_pLine->Next()) : 0 ); 1708 wxLogTrace( FILECONF_TRACE_MASK, 1709 _T(" text: '%s'"), 1710 m_pLine ? m_pLine->Text().c_str() : wxEmptyString ); 1711 1712 // delete all entries... 1713 size_t nCount = pGroup->m_aEntries.Count(); 1714 1715 wxLogTrace(FILECONF_TRACE_MASK, 1716 _T("Removing %lu entries"), (unsigned long)nCount ); 1717 1718 for ( size_t nEntry = 0; nEntry < nCount; nEntry++ ) 1719 { 1720 wxFileConfigLineList *pLine = pGroup->m_aEntries[nEntry]->GetLine(); 1721 1722 if ( pLine ) 1723 { 1724 wxLogTrace( FILECONF_TRACE_MASK, 1725 _T(" '%s'"), 1726 pLine->Text().c_str() ); 1727 m_pConfig->LineListRemove(pLine); 1728 } 1729 } 1730 1731 // ...and subgroups of this subgroup 1732 nCount = pGroup->m_aSubgroups.Count(); 1733 1734 wxLogTrace( FILECONF_TRACE_MASK, 1735 _T("Removing %lu subgroups"), (unsigned long)nCount ); 1736 1737 for ( size_t nGroup = 0; nGroup < nCount; nGroup++ ) 1738 { 1739 pGroup->DeleteSubgroup(pGroup->m_aSubgroups[0]); 1740 } 1741 1742 // and then finally the group itself 1743 wxFileConfigLineList *pLine = pGroup->m_pLine; 1744 if ( pLine ) 1745 { 1746 wxLogTrace( FILECONF_TRACE_MASK, 1747 _T(" Removing line for group '%s' : '%s'"), 1748 pGroup->Name().c_str(), 1749 pLine->Text().c_str() ); 1750 wxLogTrace( FILECONF_TRACE_MASK, 1751 _T(" Removing from group '%s' : '%s'"), 1752 Name().c_str(), 1753 ((m_pLine) ? m_pLine->Text().c_str() : wxEmptyString) ); 1754 1755 // notice that we may do this test inside the previous "if" 1756 // because the last entry's line is surely !NULL 1757 if ( pGroup == m_pLastGroup ) 1758 { 1759 wxLogTrace( FILECONF_TRACE_MASK, 1760 _T(" Removing last group") ); 1761 1762 // our last entry is being deleted, so find the last one which 1763 // stays by going back until we find a subgroup or reach the 1764 // group line 1765 const size_t nSubgroups = m_aSubgroups.Count(); 1766 1767 m_pLastGroup = NULL; 1768 for ( wxFileConfigLineList *pl = pLine->Prev(); 1769 pl && !m_pLastGroup; 1770 pl = pl->Prev() ) 1771 { 1772 // does this line belong to our subgroup? 1773 for ( size_t n = 0; n < nSubgroups; n++ ) 1774 { 1775 // do _not_ call GetGroupLine! we don't want to add it to 1776 // the local file if it's not already there 1777 if ( m_aSubgroups[n]->m_pLine == pl ) 1778 { 1779 m_pLastGroup = m_aSubgroups[n]; 1780 break; 1781 } 1782 } 1783 1784 if ( pl == m_pLine ) 1785 break; 1786 } 1787 } 1788 1789 m_pConfig->LineListRemove(pLine); 1790 } 1791 else 1792 { 1793 wxLogTrace( FILECONF_TRACE_MASK, 1794 _T(" No line entry for Group '%s'?"), 1795 pGroup->Name().c_str() ); 1796 } 1797 1798 m_aSubgroups.Remove(pGroup); 1799 delete pGroup; 1800 1801 return true; 1802} 1803 1804bool wxFileConfigGroup::DeleteEntry(const wxChar *szName) 1805{ 1806 wxFileConfigEntry *pEntry = FindEntry(szName); 1807 if ( !pEntry ) 1808 { 1809 // entry doesn't exist, nothing to do 1810 return false; 1811 } 1812 1813 wxFileConfigLineList *pLine = pEntry->GetLine(); 1814 if ( pLine != NULL ) { 1815 // notice that we may do this test inside the previous "if" because the 1816 // last entry's line is surely !NULL 1817 if ( pEntry == m_pLastEntry ) { 1818 // our last entry is being deleted - find the last one which stays 1819 wxASSERT( m_pLine != NULL ); // if we have an entry with !NULL pLine... 1820 1821 // find the previous entry (if any) 1822 wxFileConfigEntry *pNewLast = NULL; 1823 const wxFileConfigLineList * const 1824 pNewLastLine = m_pLastEntry->GetLine()->Prev(); 1825 const size_t nEntries = m_aEntries.GetCount(); 1826 for ( size_t n = 0; n < nEntries; n++ ) { 1827 if ( m_aEntries[n]->GetLine() == pNewLastLine ) { 1828 pNewLast = m_aEntries[n]; 1829 break; 1830 } 1831 } 1832 1833 // pNewLast can be NULL here -- it's ok and can happen if we have no 1834 // entries left 1835 m_pLastEntry = pNewLast; 1836 } 1837 1838 m_pConfig->LineListRemove(pLine); 1839 } 1840 1841 m_aEntries.Remove(pEntry); 1842 delete pEntry; 1843 1844 return true; 1845} 1846 1847// ============================================================================ 1848// wxFileConfig::wxFileConfigEntry 1849// ============================================================================ 1850 1851// ---------------------------------------------------------------------------- 1852// ctor 1853// ---------------------------------------------------------------------------- 1854wxFileConfigEntry::wxFileConfigEntry(wxFileConfigGroup *pParent, 1855 const wxString& strName, 1856 int nLine) 1857 : m_strName(strName) 1858{ 1859 wxASSERT( !strName.empty() ); 1860 1861 m_pParent = pParent; 1862 m_nLine = nLine; 1863 m_pLine = NULL; 1864 1865 m_bHasValue = false; 1866 1867 m_bImmutable = strName[0] == wxCONFIG_IMMUTABLE_PREFIX; 1868 if ( m_bImmutable ) 1869 m_strName.erase(0, 1); // remove first character 1870} 1871 1872// ---------------------------------------------------------------------------- 1873// set value 1874// ---------------------------------------------------------------------------- 1875 1876void wxFileConfigEntry::SetLine(wxFileConfigLineList *pLine) 1877{ 1878 if ( m_pLine != NULL ) { 1879 wxLogWarning(_("entry '%s' appears more than once in group '%s'"), 1880 Name().c_str(), m_pParent->GetFullName().c_str()); 1881 } 1882 1883 m_pLine = pLine; 1884 Group()->SetLastEntry(this); 1885} 1886 1887// second parameter is false if we read the value from file and prevents the 1888// entry from being marked as 'dirty' 1889void wxFileConfigEntry::SetValue(const wxString& strValue, bool bUser) 1890{ 1891 if ( bUser && IsImmutable() ) 1892 { 1893 wxLogWarning( _("attempt to change immutable key '%s' ignored."), 1894 Name().c_str()); 1895 return; 1896 } 1897 1898 // do nothing if it's the same value: but don't test for it if m_bHasValue 1899 // hadn't been set yet or we'd never write empty values to the file 1900 if ( m_bHasValue && strValue == m_strValue ) 1901 return; 1902 1903 m_bHasValue = true; 1904 m_strValue = strValue; 1905 1906 if ( bUser ) 1907 { 1908 wxString strValFiltered; 1909 1910 if ( Group()->Config()->GetStyle() & wxCONFIG_USE_NO_ESCAPE_CHARACTERS ) 1911 { 1912 strValFiltered = strValue; 1913 } 1914 else { 1915 strValFiltered = FilterOutValue(strValue); 1916 } 1917 1918 wxString strLine; 1919 strLine << FilterOutEntryName(m_strName) << wxT('=') << strValFiltered; 1920 1921 if ( m_pLine ) 1922 { 1923 // entry was read from the local config file, just modify the line 1924 m_pLine->SetText(strLine); 1925 } 1926 else // this entry didn't exist in the local file 1927 { 1928 // add a new line to the file: note that line returned by 1929 // GetLastEntryLine() may be NULL if we're in the root group and it 1930 // doesn't have any entries yet, but this is ok as passing NULL 1931 // line to LineListInsert() means to prepend new line to the list 1932 wxFileConfigLineList *line = Group()->GetLastEntryLine(); 1933 m_pLine = Group()->Config()->LineListInsert(strLine, line); 1934 1935 Group()->SetLastEntry(this); 1936 } 1937 } 1938} 1939 1940// ============================================================================ 1941// global functions 1942// ============================================================================ 1943 1944// ---------------------------------------------------------------------------- 1945// compare functions for array sorting 1946// ---------------------------------------------------------------------------- 1947 1948int CompareEntries(wxFileConfigEntry *p1, wxFileConfigEntry *p2) 1949{ 1950#if wxCONFIG_CASE_SENSITIVE 1951 return wxStrcmp(p1->Name(), p2->Name()); 1952#else 1953 return wxStricmp(p1->Name(), p2->Name()); 1954#endif 1955} 1956 1957int CompareGroups(wxFileConfigGroup *p1, wxFileConfigGroup *p2) 1958{ 1959#if wxCONFIG_CASE_SENSITIVE 1960 return wxStrcmp(p1->Name(), p2->Name()); 1961#else 1962 return wxStricmp(p1->Name(), p2->Name()); 1963#endif 1964} 1965 1966// ---------------------------------------------------------------------------- 1967// filter functions 1968// ---------------------------------------------------------------------------- 1969 1970// undo FilterOutValue 1971static wxString FilterInValue(const wxString& str) 1972{ 1973 wxString strResult; 1974 strResult.Alloc(str.Len()); 1975 1976 bool bQuoted = !str.empty() && str[0] == '"'; 1977 1978 for ( size_t n = bQuoted ? 1 : 0; n < str.Len(); n++ ) { 1979 if ( str[n] == wxT('\\') ) { 1980 switch ( str[++n] ) { 1981 case wxT('n'): 1982 strResult += wxT('\n'); 1983 break; 1984 1985 case wxT('r'): 1986 strResult += wxT('\r'); 1987 break; 1988 1989 case wxT('t'): 1990 strResult += wxT('\t'); 1991 break; 1992 1993 case wxT('\\'): 1994 strResult += wxT('\\'); 1995 break; 1996 1997 case wxT('"'): 1998 strResult += wxT('"'); 1999 break; 2000 } 2001 } 2002 else { 2003 if ( str[n] != wxT('"') || !bQuoted ) 2004 strResult += str[n]; 2005 else if ( n != str.Len() - 1 ) { 2006 wxLogWarning(_("unexpected \" at position %d in '%s'."), 2007 n, str.c_str()); 2008 } 2009 //else: it's the last quote of a quoted string, ok 2010 } 2011 } 2012 2013 return strResult; 2014} 2015 2016// quote the string before writing it to file 2017static wxString FilterOutValue(const wxString& str) 2018{ 2019 if ( !str ) 2020 return str; 2021 2022 wxString strResult; 2023 strResult.Alloc(str.Len()); 2024 2025 // quoting is necessary to preserve spaces in the beginning of the string 2026 bool bQuote = wxIsspace(str[0]) || str[0] == wxT('"'); 2027 2028 if ( bQuote ) 2029 strResult += wxT('"'); 2030 2031 wxChar c; 2032 for ( size_t n = 0; n < str.Len(); n++ ) { 2033 switch ( str[n] ) { 2034 case wxT('\n'): 2035 c = wxT('n'); 2036 break; 2037 2038 case wxT('\r'): 2039 c = wxT('r'); 2040 break; 2041 2042 case wxT('\t'): 2043 c = wxT('t'); 2044 break; 2045 2046 case wxT('\\'): 2047 c = wxT('\\'); 2048 break; 2049 2050 case wxT('"'): 2051 if ( bQuote ) { 2052 c = wxT('"'); 2053 break; 2054 } 2055 //else: fall through 2056 2057 default: 2058 strResult += str[n]; 2059 continue; // nothing special to do 2060 } 2061 2062 // we get here only for special characters 2063 strResult << wxT('\\') << c; 2064 } 2065 2066 if ( bQuote ) 2067 strResult += wxT('"'); 2068 2069 return strResult; 2070} 2071 2072// undo FilterOutEntryName 2073static wxString FilterInEntryName(const wxString& str) 2074{ 2075 wxString strResult; 2076 strResult.Alloc(str.Len()); 2077 2078 for ( const wxChar *pc = str.c_str(); *pc != '\0'; pc++ ) { 2079 if ( *pc == wxT('\\') ) { 2080 // we need to test it here or we'd skip past the NUL in the loop line 2081 if ( *++pc == _T('\0') ) 2082 break; 2083 } 2084 2085 strResult += *pc; 2086 } 2087 2088 return strResult; 2089} 2090 2091// sanitize entry or group name: insert '\\' before any special characters 2092static wxString FilterOutEntryName(const wxString& str) 2093{ 2094 wxString strResult; 2095 strResult.Alloc(str.Len()); 2096 2097 for ( const wxChar *pc = str.c_str(); *pc != wxT('\0'); pc++ ) { 2098 const wxChar c = *pc; 2099 2100 // we explicitly allow some of "safe" chars and 8bit ASCII characters 2101 // which will probably never have special meaning and with which we can't 2102 // use isalnum() anyhow (in ASCII built, in Unicode it's just fine) 2103 // 2104 // NB: note that wxCONFIG_IMMUTABLE_PREFIX and wxCONFIG_PATH_SEPARATOR 2105 // should *not* be quoted 2106 if ( 2107#if !wxUSE_UNICODE 2108 ((unsigned char)c < 127) && 2109#endif // ANSI 2110 !wxIsalnum(c) && !wxStrchr(wxT("@_/-!.*%"), c) ) 2111 { 2112 strResult += wxT('\\'); 2113 } 2114 2115 strResult += c; 2116 } 2117 2118 return strResult; 2119} 2120 2121// we can't put ?: in the ctor initializer list because it confuses some 2122// broken compilers (Borland C++) 2123static wxString GetAppName(const wxString& appName) 2124{ 2125 if ( !appName && wxTheApp ) 2126 return wxTheApp->GetAppName(); 2127 else 2128 return appName; 2129} 2130 2131#endif // wxUSE_CONFIG 2132