X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/77b2f9b185e5643c009c8a294ee9819d3950c423..43f4e852a1b2ac37c3db6a2b87315192ac549191:/src/xrc/xmlres.cpp?ds=inline diff --git a/src/xrc/xmlres.cpp b/src/xrc/xmlres.cpp index c5f7a8c8ed..a600a58db3 100644 --- a/src/xrc/xmlres.cpp +++ b/src/xrc/xmlres.cpp @@ -1,6 +1,6 @@ ///////////////////////////////////////////////////////////////////////////// -// Name: xmlres.cpp -// Purpose: XML resources +// Name: src/xrc/xmlres.cpp +// Purpose: XRC resources // Author: Vaclav Slavik // Created: 2000/03/05 // RCS-ID: $Id$ @@ -8,10 +8,6 @@ // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// -#ifdef __GNUG__ -#pragma implementation "xmlres.h" -#endif - // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" @@ -19,54 +15,338 @@ #pragma hdrstop #endif -#include "wx/dialog.h" -#include "wx/panel.h" -#include "wx/frame.h" +#if wxUSE_XRC + +#include "wx/xrc/xmlres.h" + +#ifndef WX_PRECOMP + #include "wx/intl.h" + #include "wx/log.h" + #include "wx/panel.h" + #include "wx/frame.h" + #include "wx/dialog.h" + #include "wx/settings.h" + #include "wx/bitmap.h" + #include "wx/image.h" + #include "wx/module.h" + #include "wx/wxcrtvararg.h" +#endif + +#ifndef __WXWINCE__ + #include +#endif + +#include "wx/vector.h" #include "wx/wfstream.h" #include "wx/filesys.h" -#include "wx/log.h" -#include "wx/intl.h" +#include "wx/filename.h" #include "wx/tokenzr.h" #include "wx/fontenum.h" -#include "wx/module.h" -#include "wx/bitmap.h" -#include "wx/image.h" #include "wx/fontmap.h" +#include "wx/artprov.h" +#include "wx/imaglist.h" +#include "wx/dir.h" +#include "wx/xml/xml.h" +#include "wx/hashset.h" +#include "wx/scopedptr.h" + +namespace +{ + +// Helper function to get modification time of either a wxFileSystem URI or +// just a normal file name, depending on the build. +#if wxUSE_DATETIME + +wxDateTime GetXRCFileModTime(const wxString& filename) +{ +#if wxUSE_FILESYSTEM + wxFileSystem fsys; + wxScopedPtr file(fsys.OpenFile(filename)); + + return file ? file->GetModificationTime() : wxDateTime(); +#else // wxUSE_FILESYSTEM + return wxDateTime(wxFileModificationTime(filename)); +#endif // wxUSE_FILESYSTEM +} + +#endif // wxUSE_DATETIME + +} // anonymous namespace + +// Assign the given value to the specified entry or add a new value with this +// name. +static void XRCID_Assign(const wxString& str_id, int value); + +class wxXmlResourceDataRecord +{ +public: + // Ctor takes ownership of the document pointer. + wxXmlResourceDataRecord(const wxString& File_, + wxXmlDocument *Doc_ + ) + : File(File_), Doc(Doc_) + { +#if wxUSE_DATETIME + Time = GetXRCFileModTime(File); +#endif + } + + ~wxXmlResourceDataRecord() {delete Doc;} + + wxString File; + wxXmlDocument *Doc; +#if wxUSE_DATETIME + wxDateTime Time; +#endif + + wxDECLARE_NO_COPY_CLASS(wxXmlResourceDataRecord); +}; + +class wxXmlResourceDataRecords : public wxVector +{ + // this is a class so that it can be forward-declared +}; + +WX_DECLARE_HASH_SET_PTR(int, wxIntegerHash, wxIntegerEqual, wxHashSetInt); + +class wxIdRange // Holds data for a particular rangename +{ +protected: + wxIdRange(const wxXmlNode* node, + const wxString& rname, + const wxString& startno, + const wxString& rsize); + + // Note the existence of an item within the range + void NoteItem(const wxXmlNode* node, const wxString& item); + + // The manager is telling us that it's finished adding items + void Finalise(const wxXmlNode* node); + + wxString GetName() const { return m_name; } + bool IsFinalised() const { return m_finalised; } + + const wxString m_name; + int m_start; + int m_end; + unsigned int m_size; + bool m_item_end_found; + bool m_finalised; + wxHashSetInt m_indices; + + friend class wxIdRangeManager; +}; + +class wxIdRangeManager +{ +public: + ~wxIdRangeManager(); + // Gets the global resources object or creates one if none exists. + static wxIdRangeManager *Get(); + + // Sets the global resources object and returns a pointer to the previous + // one (may be NULL). + static wxIdRangeManager *Set(wxIdRangeManager *res); + + // Create a new IDrange from this node + void AddRange(const wxXmlNode* node); + // Tell the IdRange that this item exists, and should be pre-allocated an ID + void NotifyRangeOfItem(const wxXmlNode* node, const wxString& item) const; + // Tells all IDranges that they're now complete, and can create their IDs + void FinaliseRanges(const wxXmlNode* node) const; + // Searches for a known IdRange matching 'name', returning its index or -1 + int Find(const wxString& rangename) const; + +protected: + wxIdRange* FindRangeForItem(const wxXmlNode* node, + const wxString& item, + wxString& value) const; + wxVector m_IdRanges; + +private: + static wxIdRangeManager *ms_instance; +}; + +namespace +{ + +// helper used by DoFindResource() and elsewhere: returns true if this is an +// object or object_ref node +// +// node must be non-NULL +inline bool IsObjectNode(wxXmlNode *node) +{ + return node->GetType() == wxXML_ELEMENT_NODE && + (node->GetName() == wxS("object") || + node->GetName() == wxS("object_ref")); +} + +// special XML attribute with name of input file, see GetFileNameFromNode() +const char *ATTR_INPUT_FILENAME = "__wx:filename"; + +// helper to get filename corresponding to an XML node +wxString +GetFileNameFromNode(const wxXmlNode *node, const wxXmlResourceDataRecords& files) +{ + // this loop does two things: it looks for ATTR_INPUT_FILENAME among + // parents and if it isn't used, it finds the root of the XML tree 'node' + // is in + for ( ;; ) + { + // in some rare cases (specifically, when an is used, see + // wxXmlResource::CreateResFromNode() and MergeNodesOver()), we work + // with XML nodes that are not rooted in any document from 'files' + // (because a new node was created by CreateResFromNode() to merge the + // content of and the referenced ); in that case, + // we hack around the problem by putting the information about input + // file into a custom attribute + if ( node->HasAttribute(ATTR_INPUT_FILENAME) ) + return node->GetAttribute(ATTR_INPUT_FILENAME); + + if ( !node->GetParent() ) + break; // we found the root of this XML tree + + node = node->GetParent(); + } + + // NB: 'node' now points to the root of XML document + + for ( wxXmlResourceDataRecords::const_iterator i = files.begin(); + i != files.end(); ++i ) + { + if ( (*i)->Doc->GetRoot() == node ) + { + return (*i)->File; + } + } + + return wxEmptyString; // not found +} + +} // anonymous namespace -#include "wx/xrc/xml.h" -#include "wx/xrc/xmlres.h" -#include "wx/arrimpl.cpp" -WX_DEFINE_OBJARRAY(wxXmlResourceDataRecords); +wxXmlResource *wxXmlResource::ms_instance = NULL; +/*static*/ wxXmlResource *wxXmlResource::Get() +{ + if ( !ms_instance ) + ms_instance = new wxXmlResource; + return ms_instance; +} -wxXmlResource::wxXmlResource(bool use_locale) +/*static*/ wxXmlResource *wxXmlResource::Set(wxXmlResource *res) { - m_handlers.DeleteContents(TRUE); - m_useLocale = use_locale; + wxXmlResource *old = ms_instance; + ms_instance = res; + return old; +} + +wxXmlResource::wxXmlResource(int flags, const wxString& domain) +{ + m_flags = flags; m_version = -1; + m_data = new wxXmlResourceDataRecords; + SetDomain(domain); } -wxXmlResource::wxXmlResource(const wxString& filemask, bool use_locale) +wxXmlResource::wxXmlResource(const wxString& filemask, int flags, const wxString& domain) { - m_useLocale = use_locale; + m_flags = flags; m_version = -1; - m_handlers.DeleteContents(TRUE); + m_data = new wxXmlResourceDataRecords; + SetDomain(domain); Load(filemask); } wxXmlResource::~wxXmlResource() { ClearHandlers(); + + for ( wxXmlResourceDataRecords::iterator i = m_data->begin(); + i != m_data->end(); ++i ) + { + delete *i; + } + delete m_data; +} + +void wxXmlResource::SetDomain(const wxString& domain) +{ + m_domain = domain; } -bool wxXmlResource::Load(const wxString& filemask) +/* static */ +wxString wxXmlResource::ConvertFileNameToURL(const wxString& filename) { - wxString fnd; - wxXmlResourceDataRecord *drec; - bool iswild = wxIsWild(filemask); - bool rt = TRUE; + wxString fnd(filename); + + // NB: as Load() and Unload() accept both filenames and URLs (should + // probably be changed to filenames only, but embedded resources + // currently rely on its ability to handle URLs - FIXME) we need to + // determine whether found name is filename and not URL and this is the + // fastest/simplest way to do it + if (wxFileName::FileExists(fnd)) + { + // Make the name absolute filename, because the app may + // change working directory later: + wxFileName fn(fnd); + if (fn.IsRelative()) + { + fn.MakeAbsolute(); + fnd = fn.GetFullPath(); + } +#if wxUSE_FILESYSTEM + fnd = wxFileSystem::FileNameToURL(fnd); +#endif + } + + return fnd; +} + +#if wxUSE_FILESYSTEM + +/* static */ +bool wxXmlResource::IsArchive(const wxString& filename) +{ + const wxString fnd = filename.Lower(); + + return fnd.Matches(wxT("*.zip")) || fnd.Matches(wxT("*.xrs")); +} + +#endif // wxUSE_FILESYSTEM + +bool wxXmlResource::LoadFile(const wxFileName& file) +{ +#if wxUSE_FILESYSTEM + return Load(wxFileSystem::FileNameToURL(file)); +#else + return Load(file.GetFullPath()); +#endif +} + +bool wxXmlResource::LoadAllFiles(const wxString& dirname) +{ + bool ok = true; + wxArrayString files; + + wxDir::GetAllFiles(dirname, &files, "*.xrc"); + + for ( wxArrayString::const_iterator i = files.begin(); i != files.end(); ++i ) + { + if ( !LoadFile(*i) ) + ok = false; + } + + return ok; +} + +bool wxXmlResource::Load(const wxString& filemask_) +{ + wxString filemask = ConvertFileNameToURL(filemask_); + + bool allOK = true; #if wxUSE_FILESYSTEM wxFileSystem fsys; @@ -76,42 +356,94 @@ bool wxXmlResource::Load(const wxString& filemask) # define wxXmlFindFirst wxFindFirstFile(filemask, wxFILE) # define wxXmlFindNext wxFindNextFile() #endif - if (iswild) - fnd = wxXmlFindFirst; - else - fnd = filemask; - while (!!fnd) + wxString fnd = wxXmlFindFirst; + if ( fnd.empty() ) + { + wxLogError(_("Cannot load resources from '%s'."), filemask); + return false; + } + + while (!fnd.empty()) { #if wxUSE_FILESYSTEM - if (filemask.Lower().Matches(wxT("*.zip")) || - filemask.Lower().Matches(wxT("*.rsc"))) + if ( IsArchive(fnd) ) { - rt = rt && Load(fnd + wxT("#zip:*.xmb")); - rt = rt && Load(fnd + wxT("#zip:*.xrc")); + if ( !Load(fnd + wxT("#zip:*.xrc")) ) + allOK = false; } - else -#endif + else // a single resource URL +#endif // wxUSE_FILESYSTEM { - drec = new wxXmlResourceDataRecord; - drec->File = fnd; - m_data.Add(drec); + wxXmlDocument * const doc = DoLoadFile(fnd); + if ( !doc ) + allOK = false; + else + Data().push_back(new wxXmlResourceDataRecord(fnd, doc)); } - if (iswild) - fnd = wxXmlFindNext; - else - fnd = wxEmptyString; + fnd = wxXmlFindNext; } # undef wxXmlFindFirst # undef wxXmlFindNext - return rt; + + return allOK; } +bool wxXmlResource::Unload(const wxString& filename) +{ + wxASSERT_MSG( !wxIsWild(filename), + wxT("wildcards not supported by wxXmlResource::Unload()") ); + + wxString fnd = ConvertFileNameToURL(filename); +#if wxUSE_FILESYSTEM + const bool isArchive = IsArchive(fnd); + if ( isArchive ) + fnd += wxT("#zip:"); +#endif // wxUSE_FILESYSTEM + + bool unloaded = false; + for ( wxXmlResourceDataRecords::iterator i = Data().begin(); + i != Data().end(); ++i ) + { +#if wxUSE_FILESYSTEM + if ( isArchive ) + { + if ( (*i)->File.StartsWith(fnd) ) + unloaded = true; + // don't break from the loop, we can have other matching files + } + else // a single resource URL +#endif // wxUSE_FILESYSTEM + { + if ( (*i)->File == fnd ) + { + delete *i; + Data().erase(i); + unloaded = true; + + // no sense in continuing, there is only one file with this URL + break; + } + } + } + + return unloaded; +} void wxXmlResource::AddHandler(wxXmlResourceHandler *handler) { - m_handlers.Append(handler); + wxXmlResourceHandlerImpl *impl = new wxXmlResourceHandlerImpl(handler); + handler->SetImpl(impl); + m_handlers.push_back(handler); + handler->SetParentResource(this); +} + +void wxXmlResource::InsertHandler(wxXmlResourceHandler *handler) +{ + wxXmlResourceHandlerImpl *impl = new wxXmlResourceHandlerImpl(handler); + handler->SetImpl(impl); + m_handlers.insert(m_handlers.begin(), handler); handler->SetParentResource(this); } @@ -119,11 +451,13 @@ void wxXmlResource::AddHandler(wxXmlResourceHandler *handler) void wxXmlResource::ClearHandlers() { - m_handlers.Clear(); + for ( wxVector::iterator i = m_handlers.begin(); + i != m_handlers.end(); ++i ) + delete *i; + m_handlers.clear(); } - wxMenu *wxXmlResource::LoadMenu(const wxString& name) { return (wxMenu*)CreateResFromNode(FindResource(name, wxT("wxMenu")), NULL, NULL); @@ -131,26 +465,24 @@ wxMenu *wxXmlResource::LoadMenu(const wxString& name) -wxMenuBar *wxXmlResource::LoadMenuBar(const wxString& name) +wxMenuBar *wxXmlResource::LoadMenuBar(wxWindow *parent, const wxString& name) { - return (wxMenuBar*)CreateResFromNode(FindResource(name, wxT("wxMenuBar")), NULL, NULL); + return (wxMenuBar*)CreateResFromNode(FindResource(name, wxT("wxMenuBar")), parent, NULL); } +#if wxUSE_TOOLBAR wxToolBar *wxXmlResource::LoadToolBar(wxWindow *parent, const wxString& name) { return (wxToolBar*)CreateResFromNode(FindResource(name, wxT("wxToolBar")), parent, NULL); } - +#endif wxDialog *wxXmlResource::LoadDialog(wxWindow *parent, const wxString& name) { - wxDialog *dialog = new wxDialog; - if (!LoadDialog(dialog, parent, name)) - { delete dialog; return NULL; } - else return dialog; + return (wxDialog*)CreateResFromNode(FindResource(name, wxT("wxDialog")), parent, NULL); } bool wxXmlResource::LoadDialog(wxDialog *dlg, wxWindow *parent, const wxString& name) @@ -170,6 +502,11 @@ bool wxXmlResource::LoadPanel(wxPanel *panel, wxWindow *parent, const wxString& return CreateResFromNode(FindResource(name, wxT("wxPanel")), parent, panel) != NULL; } +wxFrame *wxXmlResource::LoadFrame(wxWindow* parent, const wxString& name) +{ + return (wxFrame*)CreateResFromNode(FindResource(name, wxT("wxFrame")), parent, NULL); +} + bool wxXmlResource::LoadFrame(wxFrame* frame, wxWindow *parent, const wxString& name) { return CreateResFromNode(FindResource(name, wxT("wxFrame")), parent, frame) != NULL; @@ -195,6 +532,31 @@ wxIcon wxXmlResource::LoadIcon(const wxString& name) return rt; } + +wxObject * +wxXmlResource::DoLoadObject(wxWindow *parent, + const wxString& name, + const wxString& classname, + bool recursive) +{ + wxXmlNode * const node = FindResource(name, classname, recursive); + + return node ? DoCreateResFromNode(*node, parent, NULL) : NULL; +} + +bool +wxXmlResource::DoLoadObject(wxObject *instance, + wxWindow *parent, + const wxString& name, + const wxString& classname, + bool recursive) +{ + wxXmlNode * const node = FindResource(name, classname, recursive); + + return node && DoCreateResFromNode(*node, parent, instance) != NULL; +} + + bool wxXmlResource::AttachUnknownControl(const wxString& name, wxWindow *control, wxWindow *parent) { @@ -203,8 +565,8 @@ bool wxXmlResource::AttachUnknownControl(const wxString& name, wxWindow *container = parent->FindWindow(name + wxT("_container")); if (!container) { - wxLogError(_("Cannot find container for unknown control '%s'."), name.c_str()); - return FALSE; + wxLogError("Cannot find container for unknown control '%s'.", name); + return false; } return control->Reparent(container); } @@ -218,458 +580,1438 @@ static void ProcessPlatformProperty(wxXmlNode *node) wxXmlNode *c = node->GetChildren(); while (c) { - isok = FALSE; - if (!c->GetPropVal(wxT("platform"), &s)) - isok = TRUE; + isok = false; + if (!c->GetAttribute(wxT("platform"), &s)) + isok = true; else { - wxStringTokenizer tkn(s, " |"); + wxStringTokenizer tkn(s, wxT(" |")); while (tkn.HasMoreTokens()) { s = tkn.GetNextToken(); - if ( -#ifdef __WXMSW__ - s == wxString(wxT("win")) +#ifdef __WINDOWS__ + if (s == wxT("win")) isok = true; +#endif +#if defined(__MAC__) || defined(__APPLE__) + if (s == wxT("mac")) isok = true; #elif defined(__UNIX__) - s == wxString(wxT("unix")) -#elif defined(__MAC__) - s == wxString(wxT("mac")) -#elif defined(__OS2__) - s == wxString(wxT("os2")) -#else - FALSE + if (s == wxT("unix")) isok = true; #endif - ) isok = TRUE; +#ifdef __OS2__ + if (s == wxT("os2")) isok = true; +#endif + + if (isok) + break; } } if (isok) + { ProcessPlatformProperty(c); + c = c->GetNext(); + } else { + wxXmlNode *c2 = c->GetNext(); node->RemoveChild(c); delete c; + c = c2; } + } +} +static void PreprocessForIdRanges(wxXmlNode *rootnode) +{ + // First go through the top level, looking for the names of ID ranges + // as processing items is a lot easier if names are already known + wxXmlNode *c = rootnode->GetChildren(); + while (c) + { + if (c->GetName() == wxT("ids-range")) + wxIdRangeManager::Get()->AddRange(c); c = c->GetNext(); } -} + // Next, examine every 'name' for the '[' that denotes an ID in a range + c = rootnode->GetChildren(); + while (c) + { + wxString name = c->GetAttribute(wxT("name")); + if (name.find('[') != wxString::npos) + wxIdRangeManager::Get()->NotifyRangeOfItem(rootnode, name); + // Do any children by recursion, then proceed to the next sibling + PreprocessForIdRanges(c); + c = c->GetNext(); + } +} -void wxXmlResource::UpdateResources() +bool wxXmlResource::UpdateResources() { - bool modif; -# if wxUSE_FILESYSTEM - wxFSFile *file = NULL; - wxFileSystem fsys; -# endif + bool rt = true; - for (size_t i = 0; i < m_data.GetCount(); i++) + for ( wxXmlResourceDataRecords::iterator i = Data().begin(); + i != Data().end(); ++i ) { - modif = (m_data[i].Doc == NULL); + wxXmlResourceDataRecord* const rec = *i; + + // Check if we need to reload this one. + + // We never do it if this flag is specified. + if ( m_flags & wxXRC_NO_RELOADING ) + continue; + + // Otherwise check its modification time if we can. +#if wxUSE_DATETIME + const wxDateTime lastModTime = GetXRCFileModTime(rec->File); + + if ( lastModTime.IsValid() && lastModTime <= rec->Time ) +#else // !wxUSE_DATETIME + // Never reload the file contents: we can't know whether it changed or + // not in this build configuration and it would be unexpected and + // counter-productive to get a performance hit (due to constant + // reloading of XRC files) in a minimal wx build which is presumably + // used because of resource constraints of the current platform. +#endif // wxUSE_DATETIME/!wxUSE_DATETIME + { + // No need to reload, the file wasn't modified since we did it + // last. + continue; + } - if (!modif) + wxXmlDocument * const doc = DoLoadFile(rec->File); + if ( !doc ) { -# if wxUSE_FILESYSTEM - file = fsys.OpenFile(m_data[i].File); - modif = file && file->GetModificationTime() > m_data[i].Time; - if (!file) - wxLogError(_("Cannot open file '%s'."), m_data[i].File.c_str()); - wxDELETE(file); -# else - modif = wxDateTime(wxFileModificationTime(m_data[i].File)) > m_data[i].Time; -# endif + // Notice that we keep the old XML document: it seems better to + // preserve it instead of throwing it away if we have nothing to + // replace it with. + rt = false; + continue; } - if (modif) + // Replace the old resource contents with the new one. + delete rec->Doc; + rec->Doc = doc; + + // And, now that we loaded it successfully, update the last load time. +#if wxUSE_DATETIME + rec->Time = lastModTime.IsValid() ? lastModTime : wxDateTime::Now(); +#endif // wxUSE_DATETIME + } + + return rt; +} + +wxXmlDocument *wxXmlResource::DoLoadFile(const wxString& filename) +{ + wxLogTrace(wxT("xrc"), wxT("opening file '%s'"), filename); + + wxInputStream *stream = NULL; + +#if wxUSE_FILESYSTEM + wxFileSystem fsys; + wxScopedPtr file(fsys.OpenFile(filename)); + if (file) + { + // Notice that we don't have ownership of the stream in this case, it + // remains owned by wxFSFile. + stream = file->GetStream(); + } +#else // !wxUSE_FILESYSTEM + wxFileInputStream fstream(filename); + stream = &fstream; +#endif // wxUSE_FILESYSTEM/!wxUSE_FILESYSTEM + + if ( !stream || !stream->IsOk() ) + { + wxLogError(_("Cannot open resources file '%s'."), filename); + return NULL; + } + + wxString encoding(wxT("UTF-8")); +#if !wxUSE_UNICODE && wxUSE_INTL + if ( (GetFlags() & wxXRC_USE_LOCALE) == 0 ) + { + // In case we are not using wxLocale to translate strings, convert the + // strings GUI's charset. This must not be done when wxXRC_USE_LOCALE + // is on, because it could break wxGetTranslation lookup. + encoding = wxLocale::GetSystemEncodingName(); + } +#endif + + wxScopedPtr doc(new wxXmlDocument); + if (!doc->Load(*stream, encoding)) + { + wxLogError(_("Cannot load resources from file '%s'."), filename); + return NULL; + } + + wxXmlNode * const root = doc->GetRoot(); + if (root->GetName() != wxT("resource")) + { + ReportError + ( + root, + "invalid XRC resource, doesn't have root node " + ); + return NULL; + } + + long version; + int v1, v2, v3, v4; + wxString verstr = root->GetAttribute(wxT("version"), wxT("0.0.0.0")); + if (wxSscanf(verstr, wxT("%i.%i.%i.%i"), &v1, &v2, &v3, &v4) == 4) + version = v1*256*256*256+v2*256*256+v3*256+v4; + else + version = 0; + if (m_version == -1) + m_version = version; + if (m_version != version) + { + wxLogWarning("Resource files must have same version number."); + } + + ProcessPlatformProperty(root); + PreprocessForIdRanges(root); + wxIdRangeManager::Get()->FinaliseRanges(root); + + return doc.release(); +} + +wxXmlNode *wxXmlResource::DoFindResource(wxXmlNode *parent, + const wxString& name, + const wxString& classname, + bool recursive) const +{ + wxXmlNode *node; + + // first search for match at the top-level nodes (as this is + // where the resource is most commonly looked for): + for (node = parent->GetChildren(); node; node = node->GetNext()) + { + if ( IsObjectNode(node) && node->GetAttribute(wxS("name")) == name ) { - wxInputStream *stream = NULL; + // empty class name matches everything + if ( classname.empty() ) + return node; -# if wxUSE_FILESYSTEM - file = fsys.OpenFile(m_data[i].File); - if (file) - stream = file->GetStream(); -# else - stream = new wxFileInputStream(m_data[i].File); -# endif + wxString cls(node->GetAttribute(wxS("class"))); - if (stream) - { - delete m_data[i].Doc; - m_data[i].Doc = new wxXmlDocument; - } - if (!stream || !m_data[i].Doc->Load(*stream)) + // object_ref may not have 'class' attribute: + if (cls.empty() && node->GetName() == wxS("object_ref")) { - wxLogError(_("Cannot load resources from file '%s'."), m_data[i].File.c_str()); - wxDELETE(m_data[i].Doc); + wxString refName = node->GetAttribute(wxS("ref")); + if (refName.empty()) + continue; + + const wxXmlNode * const refNode = GetResourceNode(refName); + if ( refNode ) + cls = refNode->GetAttribute(wxS("class")); } - else if (m_data[i].Doc->GetRoot()->GetName() != wxT("resource")) + + if ( cls == classname ) + return node; + } + } + + // then recurse in child nodes + if ( recursive ) + { + for (node = parent->GetChildren(); node; node = node->GetNext()) + { + if ( IsObjectNode(node) ) { - wxLogError(_("Invalid XML resource '%s': doesn't have root node 'resource'."), m_data[i].File.c_str()); - wxDELETE(m_data[i].Doc); + wxXmlNode* found = DoFindResource(node, name, classname, true); + if ( found ) + return found; } - else - { - long version; - int v1, v2, v3, v4; - wxString verstr = m_data[i].Doc->GetRoot()->GetPropVal( - wxT("version"), wxT("0.0.0.0")); - if (wxSscanf(verstr.c_str(), wxT("%i.%i.%i.%i"), - &v1, &v2, &v3, &v4) == 4) - version = v1*256*256*256+v2*256*256+v3*256+v4; - else - version = 0; - if (m_version == -1) - m_version = version; - if (m_version != version) - wxLogError(_("Resource files must have same version number!")); + } + } - ProcessPlatformProperty(m_data[i].Doc->GetRoot()); - m_data[i].Time = file->GetModificationTime(); - } + return NULL; +} -# if wxUSE_FILESYSTEM - wxDELETE(file); -# else - wxDELETE(stream); -# endif - } +wxXmlNode *wxXmlResource::FindResource(const wxString& name, + const wxString& classname, + bool recursive) +{ + wxString path; + wxXmlNode * const + node = GetResourceNodeAndLocation(name, classname, recursive, &path); + + if ( !node ) + { + ReportError + ( + NULL, + wxString::Format + ( + "XRC resource \"%s\" (class \"%s\") not found", + name, classname + ) + ); } +#if wxUSE_FILESYSTEM + else // node was found + { + // ensure that relative paths work correctly when loading this node + // (which should happen as soon as we return as FindResource() result + // is always passed to CreateResFromNode()) + m_curFileSystem.ChangePathTo(path); + } +#endif // wxUSE_FILESYSTEM + + return node; } +wxXmlNode * +wxXmlResource::GetResourceNodeAndLocation(const wxString& name, + const wxString& classname, + bool recursive, + wxString *path) const +{ + // ensure everything is up-to-date: this is needed to support on-demand + // reloading of XRC files + const_cast(this)->UpdateResources(); + + for ( wxXmlResourceDataRecords::const_iterator f = Data().begin(); + f != Data().end(); ++f ) + { + wxXmlResourceDataRecord *const rec = *f; + wxXmlDocument * const doc = rec->Doc; + if ( !doc || !doc->GetRoot() ) + continue; + + wxXmlNode * const + found = DoFindResource(doc->GetRoot(), name, classname, recursive); + if ( found ) + { + if ( path ) + *path = rec->File; + + return found; + } + } + return NULL; +} -wxXmlNode *wxXmlResource::FindResource(const wxString& name, const wxString& classname) +static void MergeNodesOver(wxXmlNode& dest, wxXmlNode& overwriteWith, + const wxString& overwriteFilename) { - UpdateResources(); //ensure everything is up-to-date + // Merge attributes: + for ( wxXmlAttribute *attr = overwriteWith.GetAttributes(); + attr; attr = attr->GetNext() ) + { + wxXmlAttribute *dattr; + for (dattr = dest.GetAttributes(); dattr; dattr = dattr->GetNext()) + { - wxString dummy; - for (size_t f = 0; f < m_data.GetCount(); f++) + if ( dattr->GetName() == attr->GetName() ) + { + dattr->SetValue(attr->GetValue()); + break; + } + } + + if ( !dattr ) + dest.AddAttribute(attr->GetName(), attr->GetValue()); + } + + // Merge child nodes: + for (wxXmlNode* node = overwriteWith.GetChildren(); node; node = node->GetNext()) { - if (m_data[f].Doc == NULL || m_data[f].Doc->GetRoot() == NULL) continue; - for (wxXmlNode *node = m_data[f].Doc->GetRoot()->GetChildren(); - node; node = node->GetNext()) - if (node->GetType() == wxXML_ELEMENT_NODE && - (!classname || - node->GetPropVal(wxT("class"), wxEmptyString) == classname) && - node->GetName() == wxT("object") && - node->GetPropVal(wxT("name"), &dummy) && - dummy == name) + wxString name = node->GetAttribute(wxT("name"), wxEmptyString); + wxXmlNode *dnode; + + for (dnode = dest.GetChildren(); dnode; dnode = dnode->GetNext() ) + { + if ( dnode->GetName() == node->GetName() && + dnode->GetAttribute(wxT("name"), wxEmptyString) == name && + dnode->GetType() == node->GetType() ) { -#if wxUSE_FILESYSTEM - m_curFileSystem.ChangePathTo(m_data[f].File); -#endif - return node; + MergeNodesOver(*dnode, *node, overwriteFilename); + break; + } + } + + if ( !dnode ) + { + wxXmlNode *copyOfNode = new wxXmlNode(*node); + // remember referenced object's file, see GetFileNameFromNode() + copyOfNode->AddAttribute(ATTR_INPUT_FILENAME, overwriteFilename); + + static const wxChar *AT_END = wxT("end"); + wxString insert_pos = node->GetAttribute(wxT("insert_at"), AT_END); + if ( insert_pos == AT_END ) + { + dest.AddChild(copyOfNode); } + else if ( insert_pos == wxT("begin") ) + { + dest.InsertChild(copyOfNode, dest.GetChildren()); + } + } + } + + if ( dest.GetType() == wxXML_TEXT_NODE && overwriteWith.GetContent().length() ) + dest.SetContent(overwriteWith.GetContent()); +} + +wxObject * +wxXmlResource::DoCreateResFromNode(wxXmlNode& node, + wxObject *parent, + wxObject *instance, + wxXmlResourceHandler *handlerToUse) +{ + // handling of referenced resource + if ( node.GetName() == wxT("object_ref") ) + { + wxString refName = node.GetAttribute(wxT("ref"), wxEmptyString); + wxXmlNode* refNode = FindResource(refName, wxEmptyString, true); + + if ( !refNode ) + { + ReportError + ( + &node, + wxString::Format + ( + "referenced object node with ref=\"%s\" not found", + refName + ) + ); + return NULL; + } + + const bool hasOnlyRefAttr = node.GetAttributes() != NULL && + node.GetAttributes()->GetNext() == NULL; + + if ( hasOnlyRefAttr && !node.GetChildren() ) + { + // In the typical, simple case, is used to link + // to another node and doesn't have any content of its own that + // would overwrite linked object's properties. In this case, + // we can simply create the resource from linked node. + + return DoCreateResFromNode(*refNode, parent, instance); + } + else + { + // In the more complicated (but rare) case, has + // subnodes that partially overwrite content of the referenced + // object. In this case, we need to merge both XML trees and + // load the resource from result of the merge. + + wxXmlNode copy(*refNode); + MergeNodesOver(copy, node, GetFileNameFromNode(&node, Data())); + + // remember referenced object's file, see GetFileNameFromNode() + copy.AddAttribute(ATTR_INPUT_FILENAME, + GetFileNameFromNode(refNode, Data())); + + return DoCreateResFromNode(copy, parent, instance); + } + } + + if (handlerToUse) + { + if (handlerToUse->CanHandle(&node)) + { + return handlerToUse->CreateResource(&node, parent, instance); + } + } + else if (node.GetName() == wxT("object")) + { + for ( wxVector::iterator h = m_handlers.begin(); + h != m_handlers.end(); ++h ) + { + wxXmlResourceHandler *handler = *h; + if (handler->CanHandle(&node)) + return handler->CreateResource(&node, parent, instance); + } } - wxLogError(_("XML resource '%s' (class '%s') not found!"), - name.c_str(), classname.c_str()); + ReportError + ( + &node, + wxString::Format + ( + "no handler found for XML node \"%s\" (class \"%s\")", + node.GetName(), + node.GetAttribute("class", wxEmptyString) + ) + ); return NULL; } +wxIdRange::wxIdRange(const wxXmlNode* node, + const wxString& rname, + const wxString& startno, + const wxString& rsize) + : m_name(rname), + m_start(0), + m_size(0), + m_item_end_found(0), + m_finalised(0) +{ + long l; + if ( startno.ToLong(&l) ) + { + if ( l >= 0 ) + { + m_start = l; + } + else + { + wxXmlResource::Get()->ReportError + ( + node, + "a negative id-range start parameter was given" + ); + } + } + else + { + wxXmlResource::Get()->ReportError + ( + node, + "the id-range start parameter was malformed" + ); + } + unsigned long ul; + if ( rsize.ToULong(&ul) ) + { + m_size = ul; + } + else + { + wxXmlResource::Get()->ReportError + ( + node, + "the id-range size parameter was malformed" + ); + } +} -wxObject *wxXmlResource::CreateResFromNode(wxXmlNode *node, wxObject *parent, wxObject *instance) +void wxIdRange::NoteItem(const wxXmlNode* node, const wxString& item) { - if (node == NULL) return NULL; + // Nothing gets added here, but the existence of each item is noted + // thus getting an accurate count. 'item' will be either an integer e.g. + // [0] [123]: will eventually create an XRCID as start+integer or [start] + // or [end] which are synonyms for [0] or [range_size-1] respectively. + wxString content(item.Mid(1, item.length()-2)); + + // Check that basename+item wasn't foo[] + if (content.empty()) + { + wxXmlResource::Get()->ReportError(node, "an empty id-range item found"); + return; + } - wxXmlResourceHandler *handler; - wxObject *ret; - wxNode * ND = m_handlers.GetFirst(); - while (ND) + if (content=="start") { - handler = (wxXmlResourceHandler*)ND->GetData(); - if (node->GetName() == wxT("object") && handler->CanHandle(node)) + // "start" means [0], so store that in the set + if (m_indices.count(0) == 0) + { + m_indices.insert(0); + } + else { - ret = handler->CreateResource(node, parent, instance); - if (ret) return ret; + wxXmlResource::Get()->ReportError + ( + node, + "duplicate id-range item found" + ); } - ND = ND->GetNext(); } + else if (content=="end") + { + // We can't yet be certain which XRCID this will be equivalent to, so + // just note that there's an item with this name, in case we need to + // inc the range size + m_item_end_found = true; + } + else + { + // Anything else will be an integer, or rubbish + unsigned long l; + if ( content.ToULong(&l) ) + { + if (m_indices.count(l) == 0) + { + m_indices.insert(l); + // Check that this item wouldn't fall outside the current range + // extent + if (l >= m_size) + { + m_size = l + 1; + } + } + else + { + wxXmlResource::Get()->ReportError + ( + node, + "duplicate id-range item found" + ); + } - wxLogError(_("No handler found for XML node '%s', class '%s'!"), - node->GetName().c_str(), - node->GetPropVal(wxT("class"), wxEmptyString).c_str()); - return NULL; + } + else + { + wxXmlResource::Get()->ReportError + ( + node, + "an id-range item had a malformed index" + ); + } + } } +void wxIdRange::Finalise(const wxXmlNode* node) +{ + wxCHECK_RET( !IsFinalised(), + "Trying to finalise an already-finalised range" ); + + // Now we know about all the items, we can get an accurate range size + // Expand any requested range-size if there were more items than would fit + m_size = wxMax(m_size, m_indices.size()); + + // If an item is explicitly called foo[end], ensure it won't clash with + // another item + if ( m_item_end_found && m_indices.count(m_size-1) ) + ++m_size; + if (m_size == 0) + { + // This will happen if someone creates a range but no items in this xrc + // file Report the error and abort, but don't finalise, in case items + // appear later + wxXmlResource::Get()->ReportError + ( + node, + "trying to create an empty id-range" + ); + return; + } + if (m_start==0) + { + // This is the usual case, where the user didn't specify a start ID + // So get the range using NewControlId(). + // + // NB: negative numbers, but NewControlId already returns the most + // negative + m_start = wxWindow::NewControlId(m_size); + wxCHECK_RET( m_start != wxID_NONE, + "insufficient IDs available to create range" ); + m_end = m_start + m_size - 1; + } + else + { + // The user already specified a start value, which must be positive + m_end = m_start + m_size - 1; + } + // Create the XRCIDs + for (int i=m_start; i <= m_end; ++i) + { + // Ensure that we overwrite any existing value as otherwise + // wxXmlResource::Unload() followed by Load() wouldn't work correctly. + XRCID_Assign(m_name + wxString::Format("[%i]", i-m_start), i); + + wxLogTrace("xrcrange", + "integer = %i %s now returns %i", + i, + m_name + wxString::Format("[%i]", i-m_start), + XRCID((m_name + wxString::Format("[%i]", i-m_start)).mb_str())); + } + // and these special ones + XRCID_Assign(m_name + "[start]", m_start); + XRCID_Assign(m_name + "[end]", m_end); + wxLogTrace("xrcrange","%s[start] = %i %s[end] = %i", + m_name.mb_str(),XRCID(wxString(m_name+"[start]").mb_str()), + m_name.mb_str(),XRCID(wxString(m_name+"[end]").mb_str())); + + m_finalised = true; +} +wxIdRangeManager *wxIdRangeManager::ms_instance = NULL; +/*static*/ wxIdRangeManager *wxIdRangeManager::Get() +{ + if ( !ms_instance ) + ms_instance = new wxIdRangeManager; + return ms_instance; +} +/*static*/ wxIdRangeManager *wxIdRangeManager::Set(wxIdRangeManager *res) +{ + wxIdRangeManager *old = ms_instance; + ms_instance = res; + return old; +} +wxIdRangeManager::~wxIdRangeManager() +{ + for ( wxVector::iterator i = m_IdRanges.begin(); + i != m_IdRanges.end(); ++i ) + { + delete *i; + } + m_IdRanges.clear(); + delete ms_instance; +} -wxXmlResourceHandler::wxXmlResourceHandler() - : m_node(NULL), m_parent(NULL), m_instance(NULL), - m_parentAsWindow(NULL), m_instanceAsWindow(NULL) -{} +void wxIdRangeManager::AddRange(const wxXmlNode* node) +{ + wxString name = node->GetAttribute("name"); + wxString start = node->GetAttribute("start", "0"); + wxString size = node->GetAttribute("size", "0"); + if (name.empty()) + { + wxXmlResource::Get()->ReportError + ( + node, + "xrc file contains an id-range without a name" + ); + return; + } + int index = Find(name); + if (index == wxNOT_FOUND) + { + wxLogTrace("xrcrange", + "Adding ID range, name=%s start=%s size=%s", + name, start, size); + m_IdRanges.push_back(new wxIdRange(node, name, start, size)); + } + else + { + // There was already a range with this name. Let's hope this is + // from an Unload()/(re)Load(), not an unintentional duplication + wxLogTrace("xrcrange", + "Replacing ID range, name=%s start=%s size=%s", + name, start, size); + + wxIdRange* oldrange = m_IdRanges.at(index); + m_IdRanges.at(index) = new wxIdRange(node, name, start, size); + delete oldrange; + } +} -wxObject *wxXmlResourceHandler::CreateResource(wxXmlNode *node, wxObject *parent, wxObject *instance) +wxIdRange * +wxIdRangeManager::FindRangeForItem(const wxXmlNode* node, + const wxString& item, + wxString& value) const { - wxXmlNode *myNode = m_node; - wxString myClass = m_class; - wxObject *myParent = m_parent, *myInstance = m_instance; - wxWindow *myParentAW = m_parentAsWindow, *myInstanceAW = m_instanceAsWindow; + wxString basename = item.BeforeFirst('['); + wxCHECK_MSG( !basename.empty(), NULL, + "an id-range item without a range name" ); - m_node = node; - m_class = node->GetPropVal(wxT("class"), wxEmptyString); - m_parent = parent; - m_instance = instance; - m_parentAsWindow = wxDynamicCast(m_parent, wxWindow); - m_instanceAsWindow = wxDynamicCast(m_instance, wxWindow); + int index = Find(basename); + if (index == wxNOT_FOUND) + { + // Don't assert just because we've found an unexpected foo[123] + // Someone might just want such a name, nothing to do with ranges + return NULL; + } - wxObject *returned = DoCreateResource(); + value = item.Mid(basename.Len()); + if (value.at(value.length()-1)==']') + { + return m_IdRanges.at(index); + } + wxXmlResource::Get()->ReportError(node, "a malformed id-range item"); + return NULL; +} - m_node = myNode; - m_class = myClass; - m_parent = myParent; m_parentAsWindow = myParentAW; - m_instance = myInstance; m_instanceAsWindow = myInstanceAW; +void +wxIdRangeManager::NotifyRangeOfItem(const wxXmlNode* node, + const wxString& item) const +{ + wxString value; + wxIdRange* range = FindRangeForItem(node, item, value); + if (range) + range->NoteItem(node, value); +} - return returned; +int wxIdRangeManager::Find(const wxString& rangename) const +{ + for ( int i=0; i < (int)m_IdRanges.size(); ++i ) + { + if (m_IdRanges.at(i)->GetName() == rangename) + return i; + } + + return wxNOT_FOUND; +} + +void wxIdRangeManager::FinaliseRanges(const wxXmlNode* node) const +{ + for ( wxVector::const_iterator i = m_IdRanges.begin(); + i != m_IdRanges.end(); ++i ) + { + // Check if this range has already been finalised. Quite possible, + // as FinaliseRanges() gets called for each .xrc file loaded + if (!(*i)->IsFinalised()) + { + wxLogTrace("xrcrange", "Finalising ID range %s", (*i)->GetName()); + (*i)->Finalise(node); + } + } } -void wxXmlResourceHandler::AddStyle(const wxString& name, int value) +class wxXmlSubclassFactories : public wxVector { - m_styleNames.Add(name); - m_styleValues.Add(value); + // this is a class so that it can be forward-declared +}; + +wxXmlSubclassFactories *wxXmlResource::ms_subclassFactories = NULL; + +/*static*/ void wxXmlResource::AddSubclassFactory(wxXmlSubclassFactory *factory) +{ + if (!ms_subclassFactories) + { + ms_subclassFactories = new wxXmlSubclassFactories; + } + ms_subclassFactories->push_back(factory); } +class wxXmlSubclassFactoryCXX : public wxXmlSubclassFactory +{ +public: + ~wxXmlSubclassFactoryCXX() {} + + wxObject *Create(const wxString& className) + { + wxClassInfo* classInfo = wxClassInfo::FindClass(className); + + if (classInfo) + return classInfo->CreateObject(); + else + return NULL; + } +}; + + -void wxXmlResourceHandler::AddWindowStyles() +wxXmlResourceHandlerImpl::wxXmlResourceHandlerImpl(wxXmlResourceHandler *handler) + :wxXmlResourceHandlerImplBase(handler) { - ADD_STYLE(wxSIMPLE_BORDER); - ADD_STYLE(wxSUNKEN_BORDER); - ADD_STYLE(wxDOUBLE_BORDER); - ADD_STYLE(wxRAISED_BORDER); - ADD_STYLE(wxSTATIC_BORDER); - ADD_STYLE(wxNO_BORDER); - ADD_STYLE(wxTRANSPARENT_WINDOW); - ADD_STYLE(wxWANTS_CHARS); - ADD_STYLE(wxNO_FULL_REPAINT_ON_RESIZE); } +wxObject *wxXmlResourceHandlerImpl::CreateResFromNode(wxXmlNode *node, + wxObject *parent, wxObject *instance) +{ + return m_handler->m_resource->CreateResFromNode(node, parent, instance); +} + +#if wxUSE_FILESYSTEM +wxFileSystem& wxXmlResourceHandlerImpl::GetCurFileSystem() +{ + return m_handler->m_resource->GetCurFileSystem(); +} +#endif -bool wxXmlResourceHandler::HasParam(const wxString& param) +wxObject *wxXmlResourceHandlerImpl::CreateResource(wxXmlNode *node, wxObject *parent, wxObject *instance) +{ + wxXmlNode *myNode = m_handler->m_node; + wxString myClass = m_handler->m_class; + wxObject *myParent = m_handler->m_parent, *myInstance = m_handler->m_instance; + wxWindow *myParentAW = m_handler->m_parentAsWindow; + + m_handler->m_instance = instance; + if (!m_handler->m_instance && node->HasAttribute(wxT("subclass")) && + !(m_handler->m_resource->GetFlags() & wxXRC_NO_SUBCLASSING)) + { + wxString subclass = node->GetAttribute(wxT("subclass"), wxEmptyString); + if (!subclass.empty()) + { + for (wxXmlSubclassFactories::iterator i = wxXmlResource::ms_subclassFactories->begin(); + i != wxXmlResource::ms_subclassFactories->end(); ++i) + { + m_handler->m_instance = (*i)->Create(subclass); + if (m_handler->m_instance) + break; + } + + if (!m_handler->m_instance) + { + wxString name = node->GetAttribute(wxT("name"), wxEmptyString); + ReportError + ( + node, + wxString::Format + ( + "subclass \"%s\" not found for resource \"%s\", not subclassing", + subclass, name + ) + ); + } + } + } + + m_handler->m_node = node; + m_handler->m_class = node->GetAttribute(wxT("class"), wxEmptyString); + m_handler->m_parent = parent; + m_handler->m_parentAsWindow = wxDynamicCast(m_handler->m_parent, wxWindow); + + wxObject *returned = GetHandler()->DoCreateResource(); + + m_handler->m_node = myNode; + m_handler->m_class = myClass; + m_handler->m_parent = myParent; m_handler->m_parentAsWindow = myParentAW; + m_handler->m_instance = myInstance; + + return returned; +} + +bool wxXmlResourceHandlerImpl::HasParam(const wxString& param) { return (GetParamNode(param) != NULL); } -int wxXmlResourceHandler::GetStyle(const wxString& param, int defaults) +int wxXmlResourceHandlerImpl::GetStyle(const wxString& param, int defaults) { wxString s = GetParamValue(param); if (!s) return defaults; - wxStringTokenizer tkn(s, wxT("| "), wxTOKEN_STRTOK); + wxStringTokenizer tkn(s, wxT("| \t\n"), wxTOKEN_STRTOK); int style = 0; int index; wxString fl; while (tkn.HasMoreTokens()) { fl = tkn.GetNextToken(); - index = m_styleNames.Index(fl); + index = m_handler->m_styleNames.Index(fl); if (index != wxNOT_FOUND) - style |= m_styleValues[index]; + { + style |= m_handler->m_styleValues[index]; + } else - wxLogError(_("Unknown style flag ") + fl); + { + ReportParamError + ( + param, + wxString::Format("unknown style flag \"%s\"", fl) + ); + } } return style; } -wxString wxXmlResourceHandler::GetText(const wxString& param) +wxString wxXmlResourceHandlerImpl::GetText(const wxString& param, bool translate) { - wxString str1 = GetParamValue(param); + wxXmlNode *parNode = GetParamNode(param); + wxString str1(GetNodeContent(parNode)); wxString str2; - const wxChar *dt; - wxChar amp_char; - - // VS: First version of XML resources used $ instead of & (which is illegal in XML), - // but later I realized that '_' fits this purpose much better (because - // &File means "File with F underlined"). - if (m_resource->CompareVersion(2,3,0,1) < 0) - amp_char = wxT('$'); - else - amp_char = wxT('_'); - for (dt = str1.c_str(); *dt; dt++) + // "\\" wasn't translated to "\" prior to 2.5.3.0: + const bool escapeBackslash = (m_handler->m_resource->CompareVersion(2,5,3,0) >= 0); + + // VS: First version of XRC resources used $ instead of & (which is + // illegal in XML), but later I realized that '_' fits this purpose + // much better (because &File means "File with F underlined"). + const wxChar amp_char = (m_handler->m_resource->CompareVersion(2,3,0,1) < 0) + ? '$' : '_'; + + for ( wxString::const_iterator dt = str1.begin(); dt != str1.end(); ++dt ) { // Remap amp_char to &, map double amp_char to amp_char (for things // like "&File..." -- this is illegal in XML, so we use "_File..."): - if (*dt == amp_char) + if ( *dt == amp_char ) { - if ( *(++dt) == amp_char ) + if ( dt+1 == str1.end() || *(++dt) == amp_char ) str2 << amp_char; else str2 << wxT('&') << *dt; } - // Remap \n to CR, \r to LF, \t to TAB: - else if (*dt == wxT('\\')) - switch (*(++dt)) - { - case wxT('n') : str2 << wxT('\n'); break; - case wxT('t') : str2 << wxT('\t'); break; - case wxT('r') : str2 << wxT('\r'); break; - default : str2 << wxT('\\') << *dt; break; - } - else str2 << *dt; + // Remap \n to CR, \r to LF, \t to TAB, \\ to \: + else if ( *dt == wxT('\\') ) + { + switch ( (*(++dt)).GetValue() ) + { + case wxT('n'): + str2 << wxT('\n'); + break; + + case wxT('t'): + str2 << wxT('\t'); + break; + + case wxT('r'): + str2 << wxT('\r'); + break; + + case wxT('\\') : + // "\\" wasn't translated to "\" prior to 2.5.3.0: + if ( escapeBackslash ) + { + str2 << wxT('\\'); + break; + } + // else fall-through to default: branch below + + default: + str2 << wxT('\\') << *dt; + break; + } + } + else + { + str2 << *dt; + } + } + + if (m_handler->m_resource->GetFlags() & wxXRC_USE_LOCALE) + { + if (translate && parNode && + parNode->GetAttribute(wxT("translate"), wxEmptyString) != wxT("0")) + { + return wxGetTranslation(str2, m_handler->m_resource->GetDomain()); + } + else + { +#if wxUSE_UNICODE + return str2; +#else + // The string is internally stored as UTF-8, we have to convert + // it into system's default encoding so that it can be displayed: + return wxString(str2.wc_str(wxConvUTF8), wxConvLocal); +#endif + } } - if (m_resource->GetUseLocale()) - return wxGetTranslation(str2); - else - return str2; + // If wxXRC_USE_LOCALE is not set, then the string is already in + // system's default encoding in ANSI build, so we don't have to + // do anything special here. + return str2; } -long wxXmlResourceHandler::GetLong(const wxString& param, long defaultv) +long wxXmlResourceHandlerImpl::GetLong(const wxString& param, long defaultv) { - long value; + long value = defaultv; wxString str1 = GetParamValue(param); - if (!str1.ToLong(&value)) - value = defaultv; + if (!str1.empty()) + { + if (!str1.ToLong(&value)) + { + ReportParamError + ( + param, + wxString::Format("invalid long specification \"%s\"", str1) + ); + } + } return value; } +float wxXmlResourceHandlerImpl::GetFloat(const wxString& param, float defaultv) +{ + wxString str = GetParamValue(param); -int wxXmlResourceHandler::GetID() -{ - wxString sid = GetName(); - long num; - - if (sid == wxT("-1")) return -1; - else if (sid.IsNumber() && sid.ToLong(&num)) return num; -#define stdID(id) else if (sid == wxT(#id)) return id - stdID(wxID_OPEN); stdID(wxID_CLOSE); stdID(wxID_NEW); - stdID(wxID_SAVE); stdID(wxID_SAVEAS); stdID(wxID_REVERT); - stdID(wxID_EXIT); stdID(wxID_UNDO); stdID(wxID_REDO); - stdID(wxID_HELP); stdID(wxID_PRINT); stdID(wxID_PRINT_SETUP); - stdID(wxID_PREVIEW); stdID(wxID_ABOUT); stdID(wxID_HELP_CONTENTS); - stdID(wxID_HELP_COMMANDS); stdID(wxID_HELP_PROCEDURES); - stdID(wxID_CUT); stdID(wxID_COPY); stdID(wxID_PASTE); - stdID(wxID_CLEAR); stdID(wxID_FIND); stdID(wxID_DUPLICATE); - stdID(wxID_SELECTALL); stdID(wxID_OK); stdID(wxID_CANCEL); - stdID(wxID_APPLY); stdID(wxID_YES); stdID(wxID_NO); - stdID(wxID_STATIC); stdID(wxID_FORWARD); stdID(wxID_BACKWARD); - stdID(wxID_DEFAULT); stdID(wxID_MORE); stdID(wxID_SETUP); - stdID(wxID_RESET); stdID(wxID_HELP_CONTEXT); -#undef stdID - else return wxXmlResource::GetXMLID(sid); + // strings in XRC always use C locale so make sure to use the + // locale-independent wxString::ToCDouble() and not ToDouble() which uses + // the current locale with a potentially different decimal point character + double value = defaultv; + if (!str.empty()) + { + if (!str.ToCDouble(&value)) + { + ReportParamError + ( + param, + wxString::Format("invalid float specification \"%s\"", str) + ); + } + } + + return wx_truncate_cast(float, value); } -wxString wxXmlResourceHandler::GetName() +int wxXmlResourceHandlerImpl::GetID() { - return m_node->GetPropVal(wxT("name"), wxT("-1")); + return wxXmlResource::GetXRCID(GetName()); } -bool wxXmlResourceHandler::GetBool(const wxString& param, bool defaultv) +wxString wxXmlResourceHandlerImpl::GetName() { - wxString v = GetParamValue(param); - v.MakeLower(); - if (!v) return defaultv; - else return (v == wxT("1")); + return m_handler->m_node->GetAttribute(wxT("name"), wxT("-1")); } -wxColour wxXmlResourceHandler::GetColour(const wxString& param) +bool wxXmlResourceHandlerImpl::GetBoolAttr(const wxString& attr, bool defaultv) +{ + wxString v; + return m_handler->m_node->GetAttribute(attr, &v) ? v == '1' : defaultv; +} + +bool wxXmlResourceHandlerImpl::GetBool(const wxString& param, bool defaultv) +{ + const wxString v = GetParamValue(param); + + return v.empty() ? defaultv : (v == '1'); +} + + +static wxColour GetSystemColour(const wxString& name) +{ + if (!name.empty()) + { + #define SYSCLR(clr) \ + if (name == wxT(#clr)) return wxSystemSettings::GetColour(clr); + SYSCLR(wxSYS_COLOUR_SCROLLBAR) + SYSCLR(wxSYS_COLOUR_BACKGROUND) + SYSCLR(wxSYS_COLOUR_DESKTOP) + SYSCLR(wxSYS_COLOUR_ACTIVECAPTION) + SYSCLR(wxSYS_COLOUR_INACTIVECAPTION) + SYSCLR(wxSYS_COLOUR_MENU) + SYSCLR(wxSYS_COLOUR_WINDOW) + SYSCLR(wxSYS_COLOUR_WINDOWFRAME) + SYSCLR(wxSYS_COLOUR_MENUTEXT) + SYSCLR(wxSYS_COLOUR_WINDOWTEXT) + SYSCLR(wxSYS_COLOUR_CAPTIONTEXT) + SYSCLR(wxSYS_COLOUR_ACTIVEBORDER) + SYSCLR(wxSYS_COLOUR_INACTIVEBORDER) + SYSCLR(wxSYS_COLOUR_APPWORKSPACE) + SYSCLR(wxSYS_COLOUR_HIGHLIGHT) + SYSCLR(wxSYS_COLOUR_HIGHLIGHTTEXT) + SYSCLR(wxSYS_COLOUR_BTNFACE) + SYSCLR(wxSYS_COLOUR_3DFACE) + SYSCLR(wxSYS_COLOUR_BTNSHADOW) + SYSCLR(wxSYS_COLOUR_3DSHADOW) + SYSCLR(wxSYS_COLOUR_GRAYTEXT) + SYSCLR(wxSYS_COLOUR_BTNTEXT) + SYSCLR(wxSYS_COLOUR_INACTIVECAPTIONTEXT) + SYSCLR(wxSYS_COLOUR_BTNHIGHLIGHT) + SYSCLR(wxSYS_COLOUR_BTNHILIGHT) + SYSCLR(wxSYS_COLOUR_3DHIGHLIGHT) + SYSCLR(wxSYS_COLOUR_3DHILIGHT) + SYSCLR(wxSYS_COLOUR_3DDKSHADOW) + SYSCLR(wxSYS_COLOUR_3DLIGHT) + SYSCLR(wxSYS_COLOUR_INFOTEXT) + SYSCLR(wxSYS_COLOUR_INFOBK) + SYSCLR(wxSYS_COLOUR_LISTBOX) + SYSCLR(wxSYS_COLOUR_HOTLIGHT) + SYSCLR(wxSYS_COLOUR_GRADIENTACTIVECAPTION) + SYSCLR(wxSYS_COLOUR_GRADIENTINACTIVECAPTION) + SYSCLR(wxSYS_COLOUR_MENUHILIGHT) + SYSCLR(wxSYS_COLOUR_MENUBAR) + #undef SYSCLR + } + + return wxNullColour; +} + +wxColour wxXmlResourceHandlerImpl::GetColour(const wxString& param, const wxColour& defaultv) { wxString v = GetParamValue(param); - unsigned long tmp = 0; - if (v.Length() != 7 || v[0u] != wxT('#') || - wxSscanf(v.c_str(), wxT("#%lX"), &tmp) != 1) + if ( v.empty() ) + return defaultv; + + wxColour clr; + + // wxString -> wxColour conversion + if (!clr.Set(v)) { - wxLogError(_("XML resource: Incorrect colour specification '%s' for property '%s'."), - v.c_str(), param.c_str()); + // the colour doesn't use #RRGGBB format, check if it is symbolic + // colour name: + clr = GetSystemColour(v); + if (clr.IsOk()) + return clr; + + ReportParamError + ( + param, + wxString::Format("incorrect colour specification \"%s\"", v) + ); return wxNullColour; } - return wxColour((unsigned char) ((tmp & 0xFF0000) >> 16) , - (unsigned char) ((tmp & 0x00FF00) >> 8), - (unsigned char) ((tmp & 0x0000FF))); + return clr; +} + +namespace +{ + +// if 'param' has stock_id/stock_client, extracts them and returns true +bool GetStockArtAttrs(const wxXmlNode *paramNode, + const wxString& defaultArtClient, + wxString& art_id, wxString& art_client) +{ + if ( paramNode ) + { + art_id = paramNode->GetAttribute("stock_id", ""); + + if ( !art_id.empty() ) + { + art_id = wxART_MAKE_ART_ID_FROM_STR(art_id); + + art_client = paramNode->GetAttribute("stock_client", ""); + if ( art_client.empty() ) + art_client = defaultArtClient; + else + art_client = wxART_MAKE_CLIENT_ID_FROM_STR(art_client); + + return true; + } + } + + return false; } +} // anonymous namespace + +wxBitmap wxXmlResourceHandlerImpl::GetBitmap(const wxString& param, + const wxArtClient& defaultArtClient, + wxSize size) +{ + // it used to be possible to pass an empty string here to indicate that the + // bitmap name should be read from this node itself but this is not + // supported any more because GetBitmap(m_node) can be used directly + // instead + wxASSERT_MSG( !param.empty(), "bitmap parameter name can't be empty" ); + + const wxXmlNode* const node = GetParamNode(param); + + if ( !node ) + { + // this is not an error as bitmap parameter could be optional + return wxNullBitmap; + } + return GetBitmap(node, defaultArtClient, size); +} -wxBitmap wxXmlResourceHandler::GetBitmap(const wxString& param, wxSize size) +wxBitmap wxXmlResourceHandlerImpl::GetBitmap(const wxXmlNode* node, + const wxArtClient& defaultArtClient, + wxSize size) { - wxString name = GetParamValue(param); - if (name.IsEmpty()) return wxNullBitmap; + wxCHECK_MSG( node, wxNullBitmap, "bitmap node can't be NULL" ); + + /* If the bitmap is specified as stock item, query wxArtProvider for it: */ + wxString art_id, art_client; + if ( GetStockArtAttrs(node, defaultArtClient, + art_id, art_client) ) + { + wxBitmap stockArt(wxArtProvider::GetBitmap(art_id, art_client, size)); + if ( stockArt.IsOk() ) + return stockArt; + } + + /* ...or load the bitmap from file: */ + wxString name = GetParamValue(node); + if (name.empty()) return wxNullBitmap; #if wxUSE_FILESYSTEM - wxFSFile *fsfile = GetCurFileSystem().OpenFile(name); + wxFSFile *fsfile = GetCurFileSystem().OpenFile(name, wxFS_READ | wxFS_SEEKABLE); if (fsfile == NULL) { - wxLogError(_("XML resource: Cannot create bitmap from '%s'."), param.c_str()); + ReportParamError + ( + node->GetName(), + wxString::Format("cannot open bitmap resource \"%s\"", name) + ); return wxNullBitmap; } wxImage img(*(fsfile->GetStream())); delete fsfile; #else - wxImage img(GetParamValue(wxT("bitmap"))); + wxImage img(name); #endif - if (!img.Ok()) + + if (!img.IsOk()) { - wxLogError(_("XML resource: Cannot create bitmap from '%s'."), param.c_str()); + ReportParamError + ( + node->GetName(), + wxString::Format("cannot create bitmap from \"%s\"", name) + ); return wxNullBitmap; } if (!(size == wxDefaultSize)) img.Rescale(size.x, size.y); - return img.ConvertToBitmap(); + return wxBitmap(img); } +wxIcon wxXmlResourceHandlerImpl::GetIcon(const wxString& param, + const wxArtClient& defaultArtClient, + wxSize size) +{ + // see comment in GetBitmap(wxString) overload + wxASSERT_MSG( !param.empty(), "icon parameter name can't be empty" ); + + const wxXmlNode* const node = GetParamNode(param); + + if ( !node ) + { + // this is not an error as icon parameter could be optional + return wxIcon(); + } + + return GetIcon(node, defaultArtClient, size); +} -wxIcon wxXmlResourceHandler::GetIcon(const wxString& param, wxSize size) +wxIcon wxXmlResourceHandlerImpl::GetIcon(const wxXmlNode* node, + const wxArtClient& defaultArtClient, + wxSize size) { -#if wxCHECK_VERSION(2,3,0) || defined(__WXMSW__) wxIcon icon; - icon.CopyFromBitmap(GetBitmap(param, size)); + icon.CopyFromBitmap(GetBitmap(node, defaultArtClient, size)); + return icon; +} + + +wxIconBundle wxXmlResourceHandlerImpl::GetIconBundle(const wxString& param, + const wxArtClient& defaultArtClient) +{ + wxString art_id, art_client; + if ( GetStockArtAttrs(GetParamNode(param), defaultArtClient, + art_id, art_client) ) + { + wxIconBundle stockArt(wxArtProvider::GetIconBundle(art_id, art_client)); + if ( stockArt.IsOk() ) + return stockArt; + } + + const wxString name = GetParamValue(param); + if ( name.empty() ) + return wxNullIconBundle; + +#if wxUSE_FILESYSTEM + wxFSFile *fsfile = GetCurFileSystem().OpenFile(name, wxFS_READ | wxFS_SEEKABLE); + if ( fsfile == NULL ) + { + ReportParamError + ( + param, + wxString::Format("cannot open icon resource \"%s\"", name) + ); + return wxNullIconBundle; + } + + wxIconBundle bundle(*(fsfile->GetStream())); + delete fsfile; #else - wxIcon *iconpt; - wxBitmap bmppt = GetBitmap(param, size); - iconpt = (wxIcon*)(&bmppt); - wxIcon icon(*iconpt); + wxIconBundle bundle(name); #endif - return icon; + + if ( !bundle.IsOk() ) + { + ReportParamError + ( + param, + wxString::Format("cannot create icon from \"%s\"", name) + ); + return wxNullIconBundle; + } + + return bundle; } +wxImageList *wxXmlResourceHandlerImpl::GetImageList(const wxString& param) +{ + wxXmlNode * const imagelist_node = GetParamNode(param); + if ( !imagelist_node ) + return NULL; + + wxXmlNode * const oldnode = m_handler->m_node; + m_handler->m_node = imagelist_node; + + // Get the size if we have it, otherwise we will use the size of the first + // list element. + wxSize size = GetSize(); + + // Start adding images, we'll create the image list when adding the first + // one. + wxImageList * imagelist = NULL; + wxString parambitmap = wxT("bitmap"); + if ( HasParam(parambitmap) ) + { + wxXmlNode *n = m_handler->m_node->GetChildren(); + while (n) + { + if (n->GetType() == wxXML_ELEMENT_NODE && n->GetName() == parambitmap) + { + wxIcon icon = GetIcon(n, wxART_OTHER, size); + if ( !imagelist ) + { + // We need the real image list size to create it. + if ( size == wxDefaultSize ) + size = icon.GetSize(); + + // We use the mask by default. + bool mask = !HasParam(wxS("mask")) || GetBool(wxS("mask")); + + imagelist = new wxImageList(size.x, size.y, mask); + } + + // add icon instead of bitmap to keep the bitmap mask + imagelist->Add(icon); + } + n = n->GetNext(); + } + } + + m_handler->m_node = oldnode; + return imagelist; +} -wxXmlNode *wxXmlResourceHandler::GetParamNode(const wxString& param) +wxXmlNode *wxXmlResourceHandlerImpl::GetParamNode(const wxString& param) { - wxXmlNode *n = m_node->GetChildren(); + wxCHECK_MSG(m_handler->m_node, NULL, wxT("You can't access handler data before it was initialized!")); + + wxXmlNode *n = m_handler->m_node->GetChildren(); while (n) { if (n->GetType() == wxXML_ELEMENT_NODE && n->GetName() == param) + { + // TODO: check that there are no other properties/parameters with + // the same name and log an error if there are (can't do this + // right now as I'm not sure if it's not going to break code + // using this function in unintentional way (i.e. for + // accessing other things than properties), for example + // wxBitmapComboBoxXmlHandler almost surely does return n; + } n = n->GetNext(); } return NULL; } +bool wxXmlResourceHandlerImpl::IsOfClass(wxXmlNode *node, const wxString& classname) const +{ + return node->GetAttribute(wxT("class")) == classname; +} + -wxString wxXmlResourceHandler::GetNodeContent(wxXmlNode *node) + +wxString wxXmlResourceHandlerImpl::GetNodeContent(const wxXmlNode *node) { - wxXmlNode *n = node; + const wxXmlNode *n = node; if (n == NULL) return wxEmptyString; n = n->GetChildren(); @@ -685,51 +2027,69 @@ wxString wxXmlResourceHandler::GetNodeContent(wxXmlNode *node) -wxString wxXmlResourceHandler::GetParamValue(const wxString& param) +wxString wxXmlResourceHandlerImpl::GetParamValue(const wxString& param) { - if (param.IsEmpty()) - return GetNodeContent(m_node); + if (param.empty()) + return GetNodeContent(m_handler->m_node); else return GetNodeContent(GetParamNode(param)); } +wxString wxXmlResourceHandlerImpl::GetParamValue(const wxXmlNode* node) +{ + return GetNodeContent(node); +} -wxSize wxXmlResourceHandler::GetSize(const wxString& param) +wxSize wxXmlResourceHandlerImpl::GetSize(const wxString& param, + wxWindow *windowToUse) { wxString s = GetParamValue(param); - if (s.IsEmpty()) s = wxT("-1,-1"); + if (s.empty()) s = wxT("-1,-1"); bool is_dlg; - long sx, sy; + long sx, sy = 0; - is_dlg = s[s.Length()-1] == wxT('d'); + is_dlg = s[s.length()-1] == wxT('d'); if (is_dlg) s.RemoveLast(); if (!s.BeforeFirst(wxT(',')).ToLong(&sx) || !s.AfterLast(wxT(',')).ToLong(&sy)) { - wxLogError(_("Cannot parse coordinates from '%s'."), s.c_str()); + ReportParamError + ( + param, + wxString::Format("cannot parse coordinates value \"%s\"", s) + ); return wxDefaultSize; } if (is_dlg) { - if (m_instanceAsWindow) - return wxDLG_UNIT(m_instanceAsWindow, wxSize(sx, sy)); - else if (m_parentAsWindow) - return wxDLG_UNIT(m_parentAsWindow, wxSize(sx, sy)); + if (windowToUse) + { + return wxDLG_UNIT(windowToUse, wxSize(sx, sy)); + } + else if (m_handler->m_parentAsWindow) + { + return wxDLG_UNIT(m_handler->m_parentAsWindow, wxSize(sx, sy)); + } else { - wxLogError(_("Cannot convert dialog units: dialog unknown.")); + ReportParamError + ( + param, + "cannot convert dialog units: dialog unknown" + ); return wxDefaultSize; } } - else return wxSize(sx, sy); + + return wxSize(sx, sy); } -wxPoint wxXmlResourceHandler::GetPosition(const wxString& param) +wxPoint wxXmlResourceHandlerImpl::GetPosition(const wxString& param) { wxSize sz = GetSize(param); return wxPoint(sz.x, sz.y); @@ -737,152 +2097,365 @@ wxPoint wxXmlResourceHandler::GetPosition(const wxString& param) -wxCoord wxXmlResourceHandler::GetDimension(const wxString& param, wxCoord defaultv) +wxCoord wxXmlResourceHandlerImpl::GetDimension(const wxString& param, + wxCoord defaultv, + wxWindow *windowToUse) { wxString s = GetParamValue(param); - if (s.IsEmpty()) return defaultv; + if (s.empty()) return defaultv; bool is_dlg; long sx; - is_dlg = s[s.Length()-1] == wxT('d'); + is_dlg = s[s.length()-1] == wxT('d'); if (is_dlg) s.RemoveLast(); if (!s.ToLong(&sx)) { - wxLogError(_("Cannot parse dimension from '%s'."), s.c_str()); + ReportParamError + ( + param, + wxString::Format("cannot parse dimension value \"%s\"", s) + ); return defaultv; } if (is_dlg) { - if (m_instanceAsWindow) - return wxDLG_UNIT(m_instanceAsWindow, wxSize(sx, 0)).x; - else if (m_parentAsWindow) - return wxDLG_UNIT(m_parentAsWindow, wxSize(sx, 0)).x; + if (windowToUse) + { + return wxDLG_UNIT(windowToUse, wxSize(sx, 0)).x; + } + else if (m_handler->m_parentAsWindow) + { + return wxDLG_UNIT(m_handler->m_parentAsWindow, wxSize(sx, 0)).x; + } else { - wxLogError(_("Cannot convert dialog units: dialog unknown.")); + ReportParamError + ( + param, + "cannot convert dialog units: dialog unknown" + ); return defaultv; } } - else return sx; + + return sx; +} + +wxDirection +wxXmlResourceHandlerImpl::GetDirection(const wxString& param, wxDirection dirDefault) +{ + wxDirection dir; + + const wxString dirstr = GetParamValue(param); + if ( dirstr.empty() ) + dir = dirDefault; + else if ( dirstr == "wxLEFT" ) + dir = wxLEFT; + else if ( dirstr == "wxRIGHT" ) + dir = wxRIGHT; + else if ( dirstr == "wxTOP" ) + dir = wxTOP; + else if ( dirstr == "wxBOTTOM" ) + dir = wxBOTTOM; + else + { + ReportError + ( + GetParamNode(param), + wxString::Format + ( + "Invalid direction \"%s\": must be one of " + "wxLEFT|wxRIGHT|wxTOP|wxBOTTOM.", + dirstr + ) + ); + + dir = dirDefault; + } + + return dir; } +// Get system font index using indexname +static wxFont GetSystemFont(const wxString& name) +{ + if (!name.empty()) + { + #define SYSFNT(fnt) \ + if (name == wxT(#fnt)) return wxSystemSettings::GetFont(fnt); + SYSFNT(wxSYS_OEM_FIXED_FONT) + SYSFNT(wxSYS_ANSI_FIXED_FONT) + SYSFNT(wxSYS_ANSI_VAR_FONT) + SYSFNT(wxSYS_SYSTEM_FONT) + SYSFNT(wxSYS_DEVICE_DEFAULT_FONT) + SYSFNT(wxSYS_SYSTEM_FIXED_FONT) + SYSFNT(wxSYS_DEFAULT_GUI_FONT) + #undef SYSFNT + } + return wxNullFont; +} -wxFont wxXmlResourceHandler::GetFont(const wxString& param) +wxFont wxXmlResourceHandlerImpl::GetFont(const wxString& param, wxWindow* parent) { wxXmlNode *font_node = GetParamNode(param); if (font_node == NULL) { - wxLogError(_("Cannot find font node '%s'."), param.c_str()); + ReportError( + wxString::Format("cannot find font node \"%s\"", param)); return wxNullFont; } - wxXmlNode *oldnode = m_node; - m_node = font_node; + wxXmlNode *oldnode = m_handler->m_node; + m_handler->m_node = font_node; - long size = GetLong(wxT("size"), 12); + // font attributes: - wxString style = GetParamValue(wxT("style")); - wxString weight = GetParamValue(wxT("weight")); - int istyle = wxNORMAL, iweight = wxNORMAL; - if (style == wxT("italic")) istyle = wxITALIC; - else if (style == wxT("slant")) istyle = wxSLANT; - if (weight == wxT("bold")) iweight = wxBOLD; - else if (weight == wxT("light")) iweight = wxLIGHT; + // size + int isize = -1; + bool hasSize = HasParam(wxT("size")); + if (hasSize) + isize = GetLong(wxT("size"), -1); - wxString family = GetParamValue(wxT("family")); + // style + int istyle = wxNORMAL; + bool hasStyle = HasParam(wxT("style")); + if (hasStyle) + { + wxString style = GetParamValue(wxT("style")); + if (style == wxT("italic")) + istyle = wxITALIC; + else if (style == wxT("slant")) + istyle = wxSLANT; + else if (style != wxT("normal")) + { + ReportParamError + ( + param, + wxString::Format("unknown font style \"%s\"", style) + ); + } + } + + // weight + int iweight = wxNORMAL; + bool hasWeight = HasParam(wxT("weight")); + if (hasWeight) + { + wxString weight = GetParamValue(wxT("weight")); + if (weight == wxT("bold")) + iweight = wxBOLD; + else if (weight == wxT("light")) + iweight = wxLIGHT; + else if (weight != wxT("normal")) + { + ReportParamError + ( + param, + wxString::Format("unknown font weight \"%s\"", weight) + ); + } + } + + // underline + bool hasUnderlined = HasParam(wxT("underlined")); + bool underlined = hasUnderlined ? GetBool(wxT("underlined"), false) : false; + + // family and facename int ifamily = wxDEFAULT; - if (family == wxT("decorative")) ifamily = wxDECORATIVE; - else if (family == wxT("roman")) ifamily = wxROMAN; - else if (family == wxT("script")) ifamily = wxSCRIPT; - else if (family == wxT("swiss")) ifamily = wxSWISS; - else if (family == wxT("modern")) ifamily = wxMODERN; + bool hasFamily = HasParam(wxT("family")); + if (hasFamily) + { + wxString family = GetParamValue(wxT("family")); + if (family == wxT("decorative")) ifamily = wxDECORATIVE; + else if (family == wxT("roman")) ifamily = wxROMAN; + else if (family == wxT("script")) ifamily = wxSCRIPT; + else if (family == wxT("swiss")) ifamily = wxSWISS; + else if (family == wxT("modern")) ifamily = wxMODERN; + else if (family == wxT("teletype")) ifamily = wxTELETYPE; + else + { + ReportParamError + ( + param, + wxString::Format("unknown font family \"%s\"", family) + ); + } + } - bool underlined = GetBool(wxT("underlined"), FALSE); - wxString encoding = GetParamValue(wxT("encoding")); - wxFontMapper mapper; + wxString facename; + bool hasFacename = HasParam(wxT("face")); + if (hasFacename) + { + wxString faces = GetParamValue(wxT("face")); + wxStringTokenizer tk(faces, wxT(",")); +#if wxUSE_FONTENUM + wxArrayString facenames(wxFontEnumerator::GetFacenames()); + while (tk.HasMoreTokens()) + { + int index = facenames.Index(tk.GetNextToken(), false); + if (index != wxNOT_FOUND) + { + facename = facenames[index]; + break; + } + } +#else // !wxUSE_FONTENUM + // just use the first face name if we can't check its availability: + if (tk.HasMoreTokens()) + facename = tk.GetNextToken(); +#endif // wxUSE_FONTENUM/!wxUSE_FONTENUM + } + + // encoding wxFontEncoding enc = wxFONTENCODING_DEFAULT; - if (!encoding.IsEmpty()) enc = mapper.CharsetToEncoding(encoding); - if (enc == wxFONTENCODING_SYSTEM) enc = wxFONTENCODING_SYSTEM; + bool hasEncoding = HasParam(wxT("encoding")); +#if wxUSE_FONTMAP + if (hasEncoding) + { + wxString encoding = GetParamValue(wxT("encoding")); + wxFontMapper mapper; + if (!encoding.empty()) + enc = mapper.CharsetToEncoding(encoding); + if (enc == wxFONTENCODING_SYSTEM) + enc = wxFONTENCODING_DEFAULT; + } +#endif // wxUSE_FONTMAP + + wxFont font; - wxString faces = GetParamValue(wxT("face")); - wxString facename = wxEmptyString; - wxFontEnumerator enu; - enu.EnumerateFacenames(); - wxStringTokenizer tk(faces, wxT(",")); - while (tk.HasMoreTokens()) + // is this font based on a system font? + if (HasParam(wxT("sysfont"))) { - int index = enu.GetFacenames()->Index(tk.GetNextToken(), FALSE); - if (index != wxNOT_FOUND) + font = GetSystemFont(GetParamValue(wxT("sysfont"))); + if (HasParam(wxT("inherit"))) + { + ReportParamError + ( + param, + "double specification of \"sysfont\" and \"inherit\"" + ); + } + } + // or should the font of the widget be used? + else if (GetBool(wxT("inherit"), false)) + { + if (parent) + font = parent->GetFont(); + else { - facename = (*enu.GetFacenames())[index]; - break; + ReportParamError + ( + param, + "no parent window specified to derive the font from" + ); } } - m_node = oldnode; + if (font.IsOk()) + { + if (hasSize && isize != -1) + { + font.SetPointSize(isize); + if (HasParam(wxT("relativesize"))) + { + ReportParamError + ( + param, + "double specification of \"size\" and \"relativesize\"" + ); + } + } + else if (HasParam(wxT("relativesize"))) + font.SetPointSize(int(font.GetPointSize() * + GetFloat(wxT("relativesize")))); + + if (hasStyle) + font.SetStyle(istyle); + if (hasWeight) + font.SetWeight(iweight); + if (hasUnderlined) + font.SetUnderlined(underlined); + if (hasFamily) + font.SetFamily(ifamily); + if (hasFacename) + font.SetFaceName(facename); + if (hasEncoding) + font.SetDefaultEncoding(enc); + } + else // not based on system font + { + font = wxFont(isize == -1 ? wxNORMAL_FONT->GetPointSize() : isize, + ifamily, istyle, iweight, + underlined, facename, enc); + } - wxFont font(size, ifamily, istyle, iweight, underlined, facename, enc); + m_handler->m_node = oldnode; return font; } -void wxXmlResourceHandler::SetupWindow(wxWindow *wnd) +void wxXmlResourceHandlerImpl::SetupWindow(wxWindow *wnd) { //FIXME : add cursor if (HasParam(wxT("exstyle"))) - wnd->SetExtraStyle(GetStyle(wxT("exstyle"))); + // Have to OR it with existing style, since + // some implementations (e.g. wxGTK) use the extra style + // during creation + wnd->SetExtraStyle(wnd->GetExtraStyle() | GetStyle(wxT("exstyle"))); if (HasParam(wxT("bg"))) wnd->SetBackgroundColour(GetColour(wxT("bg"))); + if (HasParam(wxT("ownbg"))) + wnd->SetOwnBackgroundColour(GetColour(wxT("ownbg"))); if (HasParam(wxT("fg"))) wnd->SetForegroundColour(GetColour(wxT("fg"))); + if (HasParam(wxT("ownfg"))) + wnd->SetOwnForegroundColour(GetColour(wxT("ownfg"))); if (GetBool(wxT("enabled"), 1) == 0) - wnd->Enable(FALSE); + wnd->Enable(false); if (GetBool(wxT("focused"), 0) == 1) wnd->SetFocus(); if (GetBool(wxT("hidden"), 0) == 1) - wnd->Show(FALSE); + wnd->Show(false); #if wxUSE_TOOLTIPS if (HasParam(wxT("tooltip"))) wnd->SetToolTip(GetText(wxT("tooltip"))); #endif if (HasParam(wxT("font"))) - wnd->SetFont(GetFont()); + wnd->SetFont(GetFont(wxT("font"), wnd)); + if (HasParam(wxT("ownfont"))) + wnd->SetOwnFont(GetFont(wxT("ownfont"), wnd)); + if (HasParam(wxT("help"))) + wnd->SetHelpText(GetText(wxT("help"))); } -void wxXmlResourceHandler::CreateChildren(wxObject *parent, bool this_hnd_only) +void wxXmlResourceHandlerImpl::CreateChildren(wxObject *parent, bool this_hnd_only) { - wxXmlNode *n = m_node->GetChildren(); - - while (n) + for ( wxXmlNode *n = m_handler->m_node->GetChildren(); n; n = n->GetNext() ) { - if (n->GetType() == wxXML_ELEMENT_NODE && - n->GetName() == wxT("object")) + if ( IsObjectNode(n) ) { - if (this_hnd_only && CanHandle(n)) - CreateResource(n, parent, NULL); - else - m_resource->CreateResFromNode(n, parent, NULL); - } - n = n->GetNext(); + m_handler->m_resource->DoCreateResFromNode(*n, parent, NULL, + this_hnd_only ? this->GetHandler() : NULL); + } } } -void wxXmlResourceHandler::CreateChildrenPrivately(wxObject *parent, wxXmlNode *rootnode) +void wxXmlResourceHandlerImpl::CreateChildrenPrivately(wxObject *parent, wxXmlNode *rootnode) { wxXmlNode *root; - if (rootnode == NULL) root = m_node; else root = rootnode; + if (rootnode == NULL) root = m_handler->m_node; else root = rootnode; wxXmlNode *n = root->GetChildren(); while (n) { - if (n->GetType() == wxXML_ELEMENT_NODE && CanHandle(n)) + if (n->GetType() == wxXML_ELEMENT_NODE && GetHandler()->CanHandle(n)) { CreateResource(n, parent, NULL); } @@ -891,98 +2464,403 @@ void wxXmlResourceHandler::CreateChildrenPrivately(wxObject *parent, wxXmlNode * } +//----------------------------------------------------------------------------- +// errors reporting +//----------------------------------------------------------------------------- + +void wxXmlResourceHandlerImpl::ReportError(const wxString& message) +{ + m_handler->m_resource->ReportError(m_handler->m_node, message); +} + +void wxXmlResourceHandlerImpl::ReportError(wxXmlNode *context, + const wxString& message) +{ + m_handler->m_resource->ReportError(context ? context : m_handler->m_node, message); +} + +void wxXmlResourceHandlerImpl::ReportParamError(const wxString& param, + const wxString& message) +{ + m_handler->m_resource->ReportError(GetParamNode(param), message); +} + +void wxXmlResource::ReportError(const wxXmlNode *context, const wxString& message) +{ + if ( !context ) + { + DoReportError("", NULL, message); + return; + } + + // We need to find out the file that 'context' is part of. Performance of + // this code is not critical, so we simply find the root XML node and + // compare it with all loaded XRC files. + const wxString filename = GetFileNameFromNode(context, Data()); + + DoReportError(filename, context, message); +} +void wxXmlResource::DoReportError(const wxString& xrcFile, const wxXmlNode *position, + const wxString& message) +{ + const int line = position ? position->GetLineNumber() : -1; + wxString loc; + if ( !xrcFile.empty() ) + loc = xrcFile + ':'; + if ( line != -1 ) + loc += wxString::Format("%d:", line); + if ( !loc.empty() ) + loc += ' '; + wxLogError("XRC error: %s%s", loc, message); +} -// --------------- XMLID implementation ----------------------------- +//----------------------------------------------------------------------------- +// XRCID implementation +//----------------------------------------------------------------------------- -#define XMLID_TABLE_SIZE 1024 +#define XRCID_TABLE_SIZE 1024 -struct XMLID_record +struct XRCID_record { - int id; - wxChar *key; - XMLID_record *next; + /* Hold the id so that once an id is allocated for a name, it + does not get created again by NewControlId at least + until we are done with it */ + wxWindowIDRef id; + char *key; + XRCID_record *next; }; -static XMLID_record *XMLID_Records[XMLID_TABLE_SIZE] = {NULL}; +static XRCID_record *XRCID_Records[XRCID_TABLE_SIZE] = {NULL}; + +// Extremely simplistic hash function which probably ought to be replaced with +// wxStringHash::stringHash(). +static inline unsigned XRCIdHash(const char *str_id) +{ + unsigned index = 0; + + for (const char *c = str_id; *c != '\0'; c++) index += (unsigned int)*c; + index %= XRCID_TABLE_SIZE; + + return index; +} -/*static*/ int wxXmlResource::GetXMLID(const wxChar *str_id) +static void XRCID_Assign(const wxString& str_id, int value) { - static int XMLID_LastID = wxID_HIGHEST; + const wxCharBuffer buf_id(str_id.mb_str()); + const unsigned index = XRCIdHash(buf_id); + + + XRCID_record *oldrec = NULL; + for (XRCID_record *rec = XRCID_Records[index]; rec; rec = rec->next) + { + if (wxStrcmp(rec->key, buf_id) == 0) + { + rec->id = value; + return; + } + oldrec = rec; + } + + XRCID_record **rec_var = (oldrec == NULL) ? + &XRCID_Records[index] : &oldrec->next; + *rec_var = new XRCID_record; + (*rec_var)->key = wxStrdup(str_id); + (*rec_var)->id = value; + (*rec_var)->next = NULL; +} - int index = 0; +static int XRCID_Lookup(const char *str_id, int value_if_not_found = wxID_NONE) +{ + const unsigned index = XRCIdHash(str_id); - for (const wxChar *c = str_id; *c != wxT('\0'); c++) index += (int)*c; - index %= XMLID_TABLE_SIZE; - XMLID_record *oldrec = NULL; - int matchcnt = 0; - for (XMLID_record *rec = XMLID_Records[index]; rec; rec = rec->next) + XRCID_record *oldrec = NULL; + for (XRCID_record *rec = XRCID_Records[index]; rec; rec = rec->next) { if (wxStrcmp(rec->key, str_id) == 0) { return rec->id; } - matchcnt++; oldrec = rec; } - XMLID_record **rec_var = (oldrec == NULL) ? - &XMLID_Records[index] : &oldrec->next; - *rec_var = new XMLID_record; - (*rec_var)->id = ++XMLID_LastID; + XRCID_record **rec_var = (oldrec == NULL) ? + &XRCID_Records[index] : &oldrec->next; + *rec_var = new XRCID_record; (*rec_var)->key = wxStrdup(str_id); (*rec_var)->next = NULL; + char *end; + if (value_if_not_found != wxID_NONE) + (*rec_var)->id = value_if_not_found; + else + { + int asint = wxStrtol(str_id, &end, 10); + if (*str_id && *end == 0) + { + // if str_id was integer, keep it verbosely: + (*rec_var)->id = asint; + } + else + { + (*rec_var)->id = wxWindowBase::NewControlId(); + } + } + return (*rec_var)->id; } - -static void CleanXMLID_Record(XMLID_record *rec) +namespace { - if (rec) - { - CleanXMLID_Record(rec->next); - free(rec->key); - delete rec; - } -} -static void CleanXMLID_Records() +// flag indicating whether standard XRC ids were already initialized +static bool gs_stdIDsAdded = false; + +void AddStdXRCID_Records() { - for (int i = 0; i < XMLID_TABLE_SIZE; i++) - CleanXMLID_Record(XMLID_Records[i]); +#define stdID(id) XRCID_Lookup(#id, id) + stdID(-1); + + stdID(wxID_ANY); + stdID(wxID_SEPARATOR); + + stdID(wxID_OPEN); + stdID(wxID_CLOSE); + stdID(wxID_NEW); + stdID(wxID_SAVE); + stdID(wxID_SAVEAS); + stdID(wxID_REVERT); + stdID(wxID_EXIT); + stdID(wxID_UNDO); + stdID(wxID_REDO); + stdID(wxID_HELP); + stdID(wxID_PRINT); + stdID(wxID_PRINT_SETUP); + stdID(wxID_PAGE_SETUP); + stdID(wxID_PREVIEW); + stdID(wxID_ABOUT); + stdID(wxID_HELP_CONTENTS); + stdID(wxID_HELP_INDEX), + stdID(wxID_HELP_SEARCH), + stdID(wxID_HELP_COMMANDS); + stdID(wxID_HELP_PROCEDURES); + stdID(wxID_HELP_CONTEXT); + stdID(wxID_CLOSE_ALL); + stdID(wxID_PREFERENCES); + + stdID(wxID_EDIT); + stdID(wxID_CUT); + stdID(wxID_COPY); + stdID(wxID_PASTE); + stdID(wxID_CLEAR); + stdID(wxID_FIND); + stdID(wxID_DUPLICATE); + stdID(wxID_SELECTALL); + stdID(wxID_DELETE); + stdID(wxID_REPLACE); + stdID(wxID_REPLACE_ALL); + stdID(wxID_PROPERTIES); + + stdID(wxID_VIEW_DETAILS); + stdID(wxID_VIEW_LARGEICONS); + stdID(wxID_VIEW_SMALLICONS); + stdID(wxID_VIEW_LIST); + stdID(wxID_VIEW_SORTDATE); + stdID(wxID_VIEW_SORTNAME); + stdID(wxID_VIEW_SORTSIZE); + stdID(wxID_VIEW_SORTTYPE); + + + stdID(wxID_FILE1); + stdID(wxID_FILE2); + stdID(wxID_FILE3); + stdID(wxID_FILE4); + stdID(wxID_FILE5); + stdID(wxID_FILE6); + stdID(wxID_FILE7); + stdID(wxID_FILE8); + stdID(wxID_FILE9); + + + stdID(wxID_OK); + stdID(wxID_CANCEL); + stdID(wxID_APPLY); + stdID(wxID_YES); + stdID(wxID_NO); + stdID(wxID_STATIC); + stdID(wxID_FORWARD); + stdID(wxID_BACKWARD); + stdID(wxID_DEFAULT); + stdID(wxID_MORE); + stdID(wxID_SETUP); + stdID(wxID_RESET); + stdID(wxID_CONTEXT_HELP); + stdID(wxID_YESTOALL); + stdID(wxID_NOTOALL); + stdID(wxID_ABORT); + stdID(wxID_RETRY); + stdID(wxID_IGNORE); + stdID(wxID_ADD); + stdID(wxID_REMOVE); + + stdID(wxID_UP); + stdID(wxID_DOWN); + stdID(wxID_HOME); + stdID(wxID_REFRESH); + stdID(wxID_STOP); + stdID(wxID_INDEX); + + stdID(wxID_BOLD); + stdID(wxID_ITALIC); + stdID(wxID_JUSTIFY_CENTER); + stdID(wxID_JUSTIFY_FILL); + stdID(wxID_JUSTIFY_RIGHT); + stdID(wxID_JUSTIFY_LEFT); + stdID(wxID_UNDERLINE); + stdID(wxID_INDENT); + stdID(wxID_UNINDENT); + stdID(wxID_ZOOM_100); + stdID(wxID_ZOOM_FIT); + stdID(wxID_ZOOM_IN); + stdID(wxID_ZOOM_OUT); + stdID(wxID_UNDELETE); + stdID(wxID_REVERT_TO_SAVED); + stdID(wxID_CDROM); + stdID(wxID_CONVERT); + stdID(wxID_EXECUTE); + stdID(wxID_FLOPPY); + stdID(wxID_HARDDISK); + stdID(wxID_BOTTOM); + stdID(wxID_FIRST); + stdID(wxID_LAST); + stdID(wxID_TOP); + stdID(wxID_INFO); + stdID(wxID_JUMP_TO); + stdID(wxID_NETWORK); + stdID(wxID_SELECT_COLOR); + stdID(wxID_SELECT_FONT); + stdID(wxID_SORT_ASCENDING); + stdID(wxID_SORT_DESCENDING); + stdID(wxID_SPELL_CHECK); + stdID(wxID_STRIKETHROUGH); + + + stdID(wxID_SYSTEM_MENU); + stdID(wxID_CLOSE_FRAME); + stdID(wxID_MOVE_FRAME); + stdID(wxID_RESIZE_FRAME); + stdID(wxID_MAXIMIZE_FRAME); + stdID(wxID_ICONIZE_FRAME); + stdID(wxID_RESTORE_FRAME); + + + + stdID(wxID_MDI_WINDOW_CASCADE); + stdID(wxID_MDI_WINDOW_TILE_HORZ); + stdID(wxID_MDI_WINDOW_TILE_VERT); + stdID(wxID_MDI_WINDOW_ARRANGE_ICONS); + stdID(wxID_MDI_WINDOW_PREV); + stdID(wxID_MDI_WINDOW_NEXT); +#undef stdID } +} // anonymous namespace +/*static*/ +int wxXmlResource::DoGetXRCID(const char *str_id, int value_if_not_found) +{ + if ( !gs_stdIDsAdded ) + { + gs_stdIDsAdded = true; + AddStdXRCID_Records(); + } + + return XRCID_Lookup(str_id, value_if_not_found); +} +/* static */ +wxString wxXmlResource::FindXRCIDById(int numId) +{ + for ( int i = 0; i < XRCID_TABLE_SIZE; i++ ) + { + for ( XRCID_record *rec = XRCID_Records[i]; rec; rec = rec->next ) + { + if ( rec->id == numId ) + return wxString(rec->key); + } + } + return wxString(); +} +static void CleanXRCID_Record(XRCID_record *rec) +{ + if (rec) + { + CleanXRCID_Record(rec->next); + free(rec->key); + delete rec; + } +} -// --------------- module and globals ----------------------------- +static void CleanXRCID_Records() +{ + for (int i = 0; i < XRCID_TABLE_SIZE; i++) + { + CleanXRCID_Record(XRCID_Records[i]); + XRCID_Records[i] = NULL; + } + gs_stdIDsAdded = false; +} -static wxXmlResource gs_XmlResource; -wxXmlResource *wxTheXmlResource = &gs_XmlResource; +//----------------------------------------------------------------------------- +// module and globals +//----------------------------------------------------------------------------- +// normally we would do the cleanup from wxXmlResourceModule::OnExit() but it +// can happen that some XRC records have been created because of the use of +// XRCID() in event tables, which happens during static objects initialization, +// but then the application initialization failed and so the wx modules were +// neither initialized nor cleaned up -- this static object does the cleanup in +// this case +static struct wxXRCStaticCleanup +{ + ~wxXRCStaticCleanup() { CleanXRCID_Records(); } +} s_staticCleanup; class wxXmlResourceModule: public wxModule { DECLARE_DYNAMIC_CLASS(wxXmlResourceModule) public: wxXmlResourceModule() {} - bool OnInit() {return TRUE;} + bool OnInit() + { + wxXmlResource::AddSubclassFactory(new wxXmlSubclassFactoryCXX); + return true; + } void OnExit() { - wxTheXmlResource->ClearHandlers(); - CleanXMLID_Records(); + delete wxXmlResource::Set(NULL); + delete wxIdRangeManager::Set(NULL); + if(wxXmlResource::ms_subclassFactories) + { + for ( wxXmlSubclassFactories::iterator i = wxXmlResource::ms_subclassFactories->begin(); + i != wxXmlResource::ms_subclassFactories->end(); ++i ) + { + delete *i; + } + wxDELETE(wxXmlResource::ms_subclassFactories); + } + CleanXRCID_Records(); } }; @@ -998,4 +2876,4 @@ void wxXmlInitResourceModule() wxModule::RegisterModule(module); } - +#endif // wxUSE_XRC