Add support for id ranges to XRC.

Allow to declare ranges of consecutive IDs in XRC by using the "id[n]" syntax.
Show this functionality in the xrc sample and test it in the new unit test.

Also show and test the "object reference" XRC functionality.

Closes #11431.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@66059 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Vadim Zeitlin
2010-11-07 14:00:59 +00:00
parent 1f6ea93556
commit 0526c8cc07
37 changed files with 1671 additions and 23 deletions

View File

@@ -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<wxXmlResourceDataRecord*>
// 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 char *str_id);
protected:
wxIdRange* FindRangeForItem(const wxXmlNode* node,
const wxString& item,
wxString& value) const;
wxVector<wxIdRange*> m_IdRanges;
private:
static wxIdRangeManager *ms_instance;
};
namespace
{
@@ -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<wxXmlResource *>(this)->UpdateResources();
@@ -916,6 +1006,329 @@ 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).mb_str(),
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<wxIdRange*>::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<wxIdRange*>::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<wxXmlSubclassFactory*>
{
@@ -2203,6 +2616,33 @@ wxString wxXmlResource::FindXRCIDById(int numId)
return wxString();
}
/* static */
void wxIdRangeManager::RemoveXRCIDEntry(const char *str_id)
{
int index = 0;
for (const char *c = str_id; *c != '\0'; c++) index += (int)*c;
index %= XRCID_TABLE_SIZE;
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
// replace it in the table with any rec->next (usually == NULL)
(*p_previousrec) = rec->next;
free(rec->key);
delete rec;
return;
}
else
{
(*p_previousrec) = rec;
}
}
}
static void CleanXRCID_Record(XRCID_record *rec)
{
if (rec)
@@ -2254,6 +2694,7 @@ public:
void OnExit()
{
delete wxXmlResource::Set(NULL);
delete wxIdRangeManager::Set(NULL);
if(wxXmlResource::ms_subclassFactories)
{
for ( wxXmlSubclassFactories::iterator i = wxXmlResource::ms_subclassFactories->begin();