X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/b37e592bd78ba94abdff0bd964f16000043b3419..f822acceeee524d6e37fcbcaec3d795319c58d50:/src/xrc/xmlres.cpp?ds=sidebyside diff --git a/src/xrc/xmlres.cpp b/src/xrc/xmlres.cpp index 5b41a453aa..eebc913691 100644 --- a/src/xrc/xmlres.cpp +++ b/src/xrc/xmlres.cpp @@ -47,6 +47,7 @@ #include "wx/imaglist.h" #include "wx/dir.h" #include "wx/xml/xml.h" +#include "wx/hashset.h" class wxXmlResourceDataRecord @@ -71,6 +72,69 @@ class wxXmlResourceDataRecords : public wxVector // this is a class so that it can be forward-declared }; +WX_DECLARE_HASH_SET(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; + // Removes, if it exists, an entry from the XRCID table. Used in id-ranges + // to replace defunct or statically-initialised entries with current values + static void RemoveXRCIDEntry(const wxString& idstr); + +protected: + wxIdRange* FindRangeForItem(const wxXmlNode* node, + const wxString& item, + wxString& value) const; + wxVector m_IdRanges; + +private: + static wxIdRangeManager *ms_instance; +}; + namespace { @@ -90,7 +154,7 @@ const char *ATTR_INPUT_FILENAME = "__wx:filename"; // helper to get filename corresponding to an XML node wxString -GetFileNameFromNode(wxXmlNode *node, const wxXmlResourceDataRecords& files) +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' @@ -520,7 +584,31 @@ static void ProcessPlatformProperty(wxXmlNode *node) } } +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(); + } +} bool wxXmlResource::UpdateResources() { @@ -631,6 +719,8 @@ bool wxXmlResource::UpdateResources() } ProcessPlatformProperty(rec->Doc->GetRoot()); + PreprocessForIdRanges(rec->Doc->GetRoot()); + wxIdRangeManager::Get()->FinaliseRanges(rec->Doc->GetRoot()); #if wxUSE_DATETIME #if wxUSE_FILESYSTEM rec->Time = file->GetModificationTime(); @@ -744,7 +834,7 @@ wxXmlResource::GetResourceNodeAndLocation(const wxString& name, bool recursive, wxString *path) const { - // ensure everything is up-to-date: this is needed to support on-remand + // ensure everything is up-to-date: this is needed to support on-demand // reloading of XRC files const_cast(this)->UpdateResources(); @@ -916,6 +1006,331 @@ wxXmlResource::DoCreateResFromNode(wxXmlNode& node, 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" + ); + } +} + +void wxIdRange::NoteItem(const wxXmlNode* node, const wxString& item) +{ + // 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; + } + + if (content=="start") + { + // "start" means [0], so store that in the set + if (m_indices.count(0) == 0) + { + m_indices.insert(0); + } + else + { + wxXmlResource::Get()->ReportError + ( + node, + "duplicate id-range item found" + ); + } + } + 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" + ); + } + + } + 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) + { + // First clear any pre-existing XRCID + // Necessary for wxXmlResource::Unload() followed by Load() + wxIdRangeManager::RemoveXRCIDEntry( + m_name + wxString::Format("[%i]", i-m_start)); + + // Use the second parameter of GetXRCID to force it to take the value i + wxXmlResource::GetXRCID(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 + wxIdRangeManager::RemoveXRCIDEntry(m_name + "[start]"); + wxXmlResource::GetXRCID(m_name + "[start]", m_start); + wxIdRangeManager::RemoveXRCIDEntry(m_name + "[end]"); + wxXmlResource::GetXRCID(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; +} + +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; + } +} + +wxIdRange * +wxIdRangeManager::FindRangeForItem(const wxXmlNode* node, + const wxString& item, + wxString& value) const +{ + wxString basename = item.BeforeFirst('['); + wxCHECK_MSG( !basename.empty(), NULL, + "an id-range item without a range name" ); + + 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; + } + + 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; +} + +void +wxIdRangeManager::NotifyRangeOfItem(const wxXmlNode* node, + const wxString& item) const +{ + wxString value; + wxIdRange* range = FindRangeForItem(node, item, value); + if (range) + range->NoteItem(node, value); +} + +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); + } + } +} + class wxXmlSubclassFactories : public wxVector { @@ -1189,18 +1604,11 @@ float wxXmlResourceHandler::GetFloat(const wxString& param, float defaultv) { wxString str = GetParamValue(param); -#if wxUSE_INTL - // strings in XRC always use C locale but wxString::ToDouble() uses the - // current one, so transform the string to it supposing that the only - // difference between them is the decimal separator - // - // TODO: use wxString::ToCDouble() when we have it - str.Replace(wxT("."), wxLocale::GetInfo(wxLOCALE_DECIMAL_POINT, - wxLOCALE_CAT_NUMBER)); -#endif // wxUSE_INTL - + // 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; - if (!str.ToDouble(&value)) + if (!str.ToCDouble(&value)) value = defaultv; return wx_truncate_cast(float, value); @@ -1500,18 +1908,13 @@ wxImageList *wxXmlResourceHandler::GetImageList(const wxString& param) wxXmlNode * const oldnode = m_node; m_node = imagelist_node; - // size + // Get the size if we have it, otherwise we will use the size of the first + // list element. wxSize size = GetSize(); - size.SetDefaults(wxSize(wxSystemSettings::GetMetric(wxSYS_ICON_X), - wxSystemSettings::GetMetric(wxSYS_ICON_Y))); - - // mask: true by default - bool mask = HasParam(wxT("mask")) ? GetBool(wxT("mask"), true) : true; - // now we have everything we need to create the image list - wxImageList *imagelist = new wxImageList(size.x, size.y, mask); - - // add images + // Start adding images, we'll create the image list when adding the first + // one. + wxImageList * imagelist = NULL; wxString parambitmap = wxT("bitmap"); if ( HasParam(parambitmap) ) { @@ -1520,8 +1923,21 @@ wxImageList *wxXmlResourceHandler::GetImageList(const wxString& param) { if (n->GetType() == wxXML_ELEMENT_NODE && n->GetName() == parambitmap) { + wxIcon icon = GetIcon(n); + 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(GetIcon(n)); + imagelist->Add(icon); } n = n->GetNext(); } @@ -1554,9 +1970,10 @@ wxXmlNode *wxXmlResourceHandler::GetParamNode(const wxString& param) return NULL; } +/* static */ bool wxXmlResourceHandler::IsOfClass(wxXmlNode *node, const wxString& classname) { - return node->GetAttribute(wxT("class"), wxEmptyString) == classname; + return node->GetAttribute(wxT("class")) == classname; } @@ -1942,7 +2359,7 @@ void wxXmlResourceHandler::ReportParamError(const wxString& param, m_resource->ReportError(GetParamNode(param), message); } -void wxXmlResource::ReportError(wxXmlNode *context, const wxString& message) +void wxXmlResource::ReportError(const wxXmlNode *context, const wxString& message) { if ( !context ) { @@ -1958,7 +2375,7 @@ void wxXmlResource::ReportError(wxXmlNode *context, const wxString& message) DoReportError(filename, context, message); } -void wxXmlResource::DoReportError(const wxString& xrcFile, wxXmlNode *position, +void wxXmlResource::DoReportError(const wxString& xrcFile, const wxXmlNode *position, const wxString& message) { const int line = position ? position->GetLineNumber() : -1; @@ -1994,13 +2411,23 @@ struct XRCID_record static XRCID_record *XRCID_Records[XRCID_TABLE_SIZE] = {NULL}; -static int XRCID_Lookup(const char *str_id, int value_if_not_found = wxID_NONE) +// Extremely simplistic hash function which probably ought to be replaced with +// wxStringHash::stringHash(). +static inline unsigned XRCIdHash(const char *str_id) { - int index = 0; + unsigned index = 0; - for (const char *c = str_id; *c != '\0'; c++) index += (int)*c; + for (const char *c = str_id; *c != '\0'; c++) index += (unsigned int)*c; index %= XRCID_TABLE_SIZE; + return index; +} + +static int XRCID_Lookup(const char *str_id, int value_if_not_found = wxID_NONE) +{ + const unsigned index = XRCIdHash(str_id); + + XRCID_record *oldrec = NULL; for (XRCID_record *rec = XRCID_Records[index]; rec; rec = rec->next) { @@ -2201,6 +2628,30 @@ wxString wxXmlResource::FindXRCIDById(int numId) return wxString(); } +/* static */ +void wxIdRangeManager::RemoveXRCIDEntry(const wxString& idstr) +{ + const char *str_id = idstr.mb_str(); + + const unsigned index = XRCIdHash(str_id); + + XRCID_record **p_previousrec = &XRCID_Records[index]; + for (XRCID_record *rec = XRCID_Records[index]; rec; rec = rec->next) + { + if (wxStrcmp(rec->key, str_id) == 0) + { + // Found the item to be removed so delete its record; but first + // remove it from the linked list. + *p_previousrec = rec->next; + free(rec->key); + delete rec; + return; + } + + p_previousrec = &rec->next; + } +} + static void CleanXRCID_Record(XRCID_record *rec) { if (rec) @@ -2252,6 +2703,7 @@ public: void OnExit() { delete wxXmlResource::Set(NULL); + delete wxIdRangeManager::Set(NULL); if(wxXmlResource::ms_subclassFactories) { for ( wxXmlSubclassFactories::iterator i = wxXmlResource::ms_subclassFactories->begin();