Merge branch 'xrc-paths'
Allow loading XRC from wxXmlDocument and use it to add a test for bitmap paths URI encoding in XRC. See https://github.com/wxWidgets/wxWidgets/pull/2325
This commit is contained in:
@@ -341,8 +341,8 @@ or translations are done.
|
|||||||
@subsection overview_xrcformat_type_bitmap Bitmap
|
@subsection overview_xrcformat_type_bitmap Bitmap
|
||||||
|
|
||||||
Bitmap properties contain specification of a single bitmap or icon. In the most
|
Bitmap properties contain specification of a single bitmap or icon. In the most
|
||||||
basic form, their text value is simply a relative filename (or another
|
basic form, their text value is simply a relative URL of the bitmap to use.
|
||||||
wxFileSystem URL) of the bitmap to use. For example:
|
For example:
|
||||||
@code
|
@code
|
||||||
<object class="tool" name="wxID_NEW">
|
<object class="tool" name="wxID_NEW">
|
||||||
<tooltip>New</tooltip>
|
<tooltip>New</tooltip>
|
||||||
@@ -350,7 +350,15 @@ wxFileSystem URL) of the bitmap to use. For example:
|
|||||||
</object>
|
</object>
|
||||||
@endcode
|
@endcode
|
||||||
The value is interpreted as path relative to the location of XRC file where the
|
The value is interpreted as path relative to the location of XRC file where the
|
||||||
reference occurs.
|
reference occurs, but notice that it is still an URL and not just a filename,
|
||||||
|
which means that the characters special in the URLs, such as @c '#' must be
|
||||||
|
percent-encoded, e.g. here is the correct way to specify a bitmap with the path
|
||||||
|
@c "images/#1/tool.png" in XRC:
|
||||||
|
@code
|
||||||
|
<object class="tool" name="first">
|
||||||
|
<bitmap>images/%231/tool.png</bitmap>
|
||||||
|
</object>
|
||||||
|
@endcode
|
||||||
|
|
||||||
Bitmap file paths can include environment variables that are expanded if
|
Bitmap file paths can include environment variables that are expanded if
|
||||||
wxXRC_USE_ENVVARS was passed to the wxXmlResource constructor.
|
wxXRC_USE_ENVVARS was passed to the wxXmlResource constructor.
|
||||||
|
@@ -131,6 +131,13 @@ public:
|
|||||||
// Loads all XRC files from a directory.
|
// Loads all XRC files from a directory.
|
||||||
bool LoadAllFiles(const wxString& dirname);
|
bool LoadAllFiles(const wxString& dirname);
|
||||||
|
|
||||||
|
// Loads resources from the given XML document, taking ownership of it.
|
||||||
|
//
|
||||||
|
// The name argument is only used to Unload() the document later here and
|
||||||
|
// doesn't need to be an existing filename at all (but should be unique if
|
||||||
|
// specified, otherwise it's just synthesized internally).
|
||||||
|
bool LoadDocument(wxXmlDocument* doc, const wxString& name = wxString());
|
||||||
|
|
||||||
// Unload resource from the given XML file (wildcards not allowed)
|
// Unload resource from the given XML file (wildcards not allowed)
|
||||||
bool Unload(const wxString& filename);
|
bool Unload(const wxString& filename);
|
||||||
|
|
||||||
@@ -320,6 +327,9 @@ protected:
|
|||||||
// wxXmlDocument (which will be owned by caller) on success or NULL.
|
// wxXmlDocument (which will be owned by caller) on success or NULL.
|
||||||
wxXmlDocument *DoLoadFile(const wxString& file);
|
wxXmlDocument *DoLoadFile(const wxString& file);
|
||||||
|
|
||||||
|
// Load XRC from the given document and returns true on success.
|
||||||
|
bool DoLoadDocument(const wxXmlDocument& doc);
|
||||||
|
|
||||||
// Scans the resources list for unloaded files and loads them. Also reloads
|
// Scans the resources list for unloaded files and loads them. Also reloads
|
||||||
// files that have been modified since last loading.
|
// files that have been modified since last loading.
|
||||||
bool UpdateResources();
|
bool UpdateResources();
|
||||||
|
@@ -233,12 +233,58 @@ public:
|
|||||||
*/
|
*/
|
||||||
bool Load(const wxString& filemask);
|
bool Load(const wxString& filemask);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Load resources from the XML document containing them.
|
||||||
|
|
||||||
|
This can be useful when XRC contents comes from some place other than a
|
||||||
|
file or, more generally, an URL, as it can still be read into a
|
||||||
|
wxMemoryInputStream and then wxXmlDocument can be created from this
|
||||||
|
stream and used with this function.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
@code
|
||||||
|
const char* const xrc_data = ...; // Retrieve it from wherever.
|
||||||
|
wxMemoryInputStream mis(xrc_data, strlen(xrc_data));
|
||||||
|
wxScopedPtr<wxXmlDocument> xmlDoc(new wxXmlDocument(mis, "UTF-8"));
|
||||||
|
if ( !xmlDoc->IsOk() )
|
||||||
|
{
|
||||||
|
... handle invalid XML here ...
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !wxXmlResource::Get()->LoadDocument(xmlDoc.release()) )
|
||||||
|
{
|
||||||
|
... handle invalid XRC here ...
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
... use the just loaded XRC as usual ...
|
||||||
|
@endcode
|
||||||
|
|
||||||
|
@param doc A valid, i.e. non-null, document pointer ownership of which
|
||||||
|
is passed to wxXmlResource, i.e. this pointer can't be used after
|
||||||
|
this function rteturns.
|
||||||
|
@param name The name argument is optional, but may be provided if you
|
||||||
|
plan to call Unload() later. It doesn't need to be an existing file
|
||||||
|
or even conform to the usual form of file names as it is not
|
||||||
|
interpreted in any way by wxXmlResource, but it should be unique
|
||||||
|
among the other documents and file names used if specified.
|
||||||
|
@return @true on success, @false if the document couldn't be loaded
|
||||||
|
(note that @a doc is still destroyed in this case to avoid memory
|
||||||
|
leaks).
|
||||||
|
|
||||||
|
@see Load(), LoadFile()
|
||||||
|
|
||||||
|
@since 3.1.6
|
||||||
|
*/
|
||||||
|
bool LoadDocument(wxXmlDocument* doc, const wxString& name = wxString());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Simpler form of Load() for loading a single XRC file.
|
Simpler form of Load() for loading a single XRC file.
|
||||||
|
|
||||||
@since 2.9.0
|
@since 2.9.0
|
||||||
|
|
||||||
@see Load(), LoadAllFiles()
|
@see Load(), LoadAllFiles(), LoadDocument()
|
||||||
*/
|
*/
|
||||||
bool LoadFile(const wxFileName& file);
|
bool LoadFile(const wxFileName& file);
|
||||||
|
|
||||||
|
@@ -478,16 +478,17 @@ wxFSFile* wxFileSystem::OpenFile(const wxString& location, int flags)
|
|||||||
m_LastName.clear();
|
m_LastName.clear();
|
||||||
|
|
||||||
// try relative paths first :
|
// try relative paths first :
|
||||||
if (meta != wxT(':'))
|
if (meta != wxT(':') && !m_Path.empty())
|
||||||
{
|
{
|
||||||
|
const wxString fullloc = m_Path + loc;
|
||||||
node = m_Handlers.GetFirst();
|
node = m_Handlers.GetFirst();
|
||||||
while (node)
|
while (node)
|
||||||
{
|
{
|
||||||
wxFileSystemHandler *h = (wxFileSystemHandler*) node -> GetData();
|
wxFileSystemHandler *h = (wxFileSystemHandler*) node -> GetData();
|
||||||
if (h->CanOpen(m_Path + loc))
|
if (h->CanOpen(fullloc))
|
||||||
{
|
{
|
||||||
s = MakeLocal(h)->OpenFile(*this, m_Path + loc);
|
s = MakeLocal(h)->OpenFile(*this, fullloc);
|
||||||
if (s) { m_LastName = m_Path + loc; break; }
|
if (s) { m_LastName = fullloc; break; }
|
||||||
}
|
}
|
||||||
node = node->GetNext();
|
node = node->GetNext();
|
||||||
}
|
}
|
||||||
|
@@ -74,17 +74,43 @@ wxDateTime GetXRCFileModTime(const wxString& filename)
|
|||||||
// name.
|
// name.
|
||||||
static void XRCID_Assign(const wxString& str_id, int value);
|
static void XRCID_Assign(const wxString& str_id, int value);
|
||||||
|
|
||||||
|
// Flags indicating whether the XRC contents was loaded from file or URL, more
|
||||||
|
// generally: this is usually true but is not set for the records created
|
||||||
|
// directly from wxXmlDocument.
|
||||||
|
namespace XRCWhence
|
||||||
|
{
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
From_URL = 0,
|
||||||
|
From_Doc = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace // XRCWhence
|
||||||
|
|
||||||
class wxXmlResourceDataRecord
|
class wxXmlResourceDataRecord
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// Ctor takes ownership of the document pointer.
|
// Ctor takes ownership of the document pointer.
|
||||||
wxXmlResourceDataRecord(const wxString& File_,
|
wxXmlResourceDataRecord(const wxString& File_,
|
||||||
wxXmlDocument *Doc_
|
wxXmlDocument *Doc_,
|
||||||
|
int flags = XRCWhence::From_URL
|
||||||
)
|
)
|
||||||
: File(File_), Doc(Doc_)
|
: File(File_), Doc(Doc_)
|
||||||
{
|
{
|
||||||
#if wxUSE_DATETIME
|
#if wxUSE_DATETIME
|
||||||
Time = GetXRCFileModTime(File);
|
switch ( flags )
|
||||||
|
{
|
||||||
|
case XRCWhence::From_URL:
|
||||||
|
Time = GetXRCFileModTime(File);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case XRCWhence::From_Doc:
|
||||||
|
// Leave Time invalid.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
wxUnusedVar(flags);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -664,9 +690,14 @@ bool wxXmlResource::UpdateResources()
|
|||||||
if ( m_flags & wxXRC_NO_RELOADING )
|
if ( m_flags & wxXRC_NO_RELOADING )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// And we don't do it for the records that were not loaded from a
|
||||||
|
// file/URI (or at least not directly) in the first place.
|
||||||
|
if ( !rec->Time.IsValid() )
|
||||||
|
continue;
|
||||||
|
|
||||||
// Otherwise check its modification time if we can.
|
// Otherwise check its modification time if we can.
|
||||||
#if wxUSE_DATETIME
|
#if wxUSE_DATETIME
|
||||||
const wxDateTime lastModTime = GetXRCFileModTime(rec->File);
|
wxDateTime lastModTime = GetXRCFileModTime(rec->File);
|
||||||
|
|
||||||
if ( lastModTime.IsValid() && lastModTime <= rec->Time )
|
if ( lastModTime.IsValid() && lastModTime <= rec->Time )
|
||||||
#else // !wxUSE_DATETIME
|
#else // !wxUSE_DATETIME
|
||||||
@@ -749,7 +780,15 @@ wxXmlDocument *wxXmlResource::DoLoadFile(const wxString& filename)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
wxXmlNode * const root = doc->GetRoot();
|
if (!DoLoadDocument(*doc))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return doc.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wxXmlResource::DoLoadDocument(const wxXmlDocument& doc)
|
||||||
|
{
|
||||||
|
wxXmlNode * const root = doc.GetRoot();
|
||||||
if (root->GetName() != wxT("resource"))
|
if (root->GetName() != wxT("resource"))
|
||||||
{
|
{
|
||||||
ReportError
|
ReportError
|
||||||
@@ -757,7 +796,7 @@ wxXmlDocument *wxXmlResource::DoLoadFile(const wxString& filename)
|
|||||||
root,
|
root,
|
||||||
"invalid XRC resource, doesn't have root node <resource>"
|
"invalid XRC resource, doesn't have root node <resource>"
|
||||||
);
|
);
|
||||||
return NULL;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
long version;
|
long version;
|
||||||
@@ -778,7 +817,34 @@ wxXmlDocument *wxXmlResource::DoLoadFile(const wxString& filename)
|
|||||||
PreprocessForIdRanges(root);
|
PreprocessForIdRanges(root);
|
||||||
wxIdRangeManager::Get()->FinaliseRanges(root);
|
wxIdRangeManager::Get()->FinaliseRanges(root);
|
||||||
|
|
||||||
return doc.release();
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wxXmlResource::LoadDocument(wxXmlDocument* doc, const wxString& name)
|
||||||
|
{
|
||||||
|
wxCHECK_MSG( doc, false, wxS("must have a valid document") );
|
||||||
|
|
||||||
|
if ( !DoLoadDocument(*doc) )
|
||||||
|
{
|
||||||
|
// Still avoid memory leaks.
|
||||||
|
delete doc;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to use something instead of the file name, so if we were not
|
||||||
|
// given a name synthesize something ourselves.
|
||||||
|
wxString docname = name;
|
||||||
|
if ( docname.empty() )
|
||||||
|
{
|
||||||
|
static unsigned long s_xrcDocument = 0;
|
||||||
|
|
||||||
|
// Make it look different from any real file name.
|
||||||
|
docname = wxString::Format(wxS("<XML document #%lu>"), ++s_xrcDocument);
|
||||||
|
}
|
||||||
|
|
||||||
|
Data().push_back(new wxXmlResourceDataRecord(docname, doc, XRCWhence::From_Doc));
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
wxXmlNode *wxXmlResource::DoFindResource(wxXmlNode *parent,
|
wxXmlNode *wxXmlResource::DoFindResource(wxXmlNode *parent,
|
||||||
|
@@ -20,13 +20,18 @@
|
|||||||
#if wxUSE_XRC
|
#if wxUSE_XRC
|
||||||
|
|
||||||
#include "wx/fs_inet.h"
|
#include "wx/fs_inet.h"
|
||||||
|
#include "wx/imagxpm.h"
|
||||||
#include "wx/xml/xml.h"
|
#include "wx/xml/xml.h"
|
||||||
|
#include "wx/scopedptr.h"
|
||||||
#include "wx/sstream.h"
|
#include "wx/sstream.h"
|
||||||
#include "wx/wfstream.h"
|
#include "wx/wfstream.h"
|
||||||
#include "wx/xrc/xmlres.h"
|
#include "wx/xrc/xmlres.h"
|
||||||
|
#include "wx/xrc/xh_bmp.h"
|
||||||
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
#include "testfile.h"
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// helpers to create/save some xrc
|
// helpers to create/save some xrc
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@@ -36,9 +41,19 @@ namespace
|
|||||||
|
|
||||||
static const char *TEST_XRC_FILE = "test.xrc";
|
static const char *TEST_XRC_FILE = "test.xrc";
|
||||||
|
|
||||||
|
void LoadXrcFrom(const wxString& xrcText)
|
||||||
|
{
|
||||||
|
wxStringInputStream sis(xrcText);
|
||||||
|
wxScopedPtr<wxXmlDocument> xmlDoc(new wxXmlDocument(sis, "UTF-8"));
|
||||||
|
REQUIRE( xmlDoc->IsOk() );
|
||||||
|
|
||||||
|
// Load the xrc we've just created
|
||||||
|
REQUIRE( wxXmlResource::Get()->LoadDocument(xmlDoc.release(), TEST_XRC_FILE) );
|
||||||
|
}
|
||||||
|
|
||||||
// I'm hard-wiring the xrc into this function for now
|
// I'm hard-wiring the xrc into this function for now
|
||||||
// If different xrcs are wanted for future tests, it'll be easy to refactor
|
// If different xrcs are wanted for future tests, it'll be easy to refactor
|
||||||
void CreateXrc()
|
void LoadTestXrc()
|
||||||
{
|
{
|
||||||
const char *xrcText =
|
const char *xrcText =
|
||||||
"<?xml version=\"1.0\" ?>"
|
"<?xml version=\"1.0\" ?>"
|
||||||
@@ -116,13 +131,7 @@ void CreateXrc()
|
|||||||
"</resource>"
|
"</resource>"
|
||||||
;
|
;
|
||||||
|
|
||||||
// afaict there's no elegant way to load xrc direct from a string
|
LoadXrcFrom(wxString::FromAscii(xrcText));
|
||||||
// So save it as a file, from which it can be loaded
|
|
||||||
wxStringInputStream sis(xrcText);
|
|
||||||
wxFFileOutputStream fos(TEST_XRC_FILE);
|
|
||||||
REQUIRE(fos.IsOk());
|
|
||||||
fos.Write(sis);
|
|
||||||
REQUIRE(fos.Close());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // anon namespace
|
} // anon namespace
|
||||||
@@ -135,8 +144,7 @@ void CreateXrc()
|
|||||||
class XrcTestCase
|
class XrcTestCase
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
XrcTestCase() { CreateXrc(); }
|
XrcTestCase() { }
|
||||||
~XrcTestCase() { wxRemoveFile(TEST_XRC_FILE); }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
wxDECLARE_NO_COPY_CLASS(XrcTestCase);
|
wxDECLARE_NO_COPY_CLASS(XrcTestCase);
|
||||||
@@ -148,8 +156,7 @@ TEST_CASE_METHOD(XrcTestCase, "XRC::ObjectReferences", "[xrc]")
|
|||||||
|
|
||||||
for ( int n = 0; n < 2; ++n )
|
for ( int n = 0; n < 2; ++n )
|
||||||
{
|
{
|
||||||
// Load the xrc file we're just created
|
LoadTestXrc();
|
||||||
REQUIRE( wxXmlResource::Get()->Load(TEST_XRC_FILE) );
|
|
||||||
|
|
||||||
// In xrc there's now a dialog containing two panels, one an object
|
// In xrc there's now a dialog containing two panels, one an object
|
||||||
// reference of the other
|
// reference of the other
|
||||||
@@ -171,8 +178,7 @@ TEST_CASE_METHOD(XrcTestCase, "XRC::IDRanges", "[xrc]")
|
|||||||
// Tests ID ranges
|
// Tests ID ranges
|
||||||
for ( int n = 0; n < 2; ++n )
|
for ( int n = 0; n < 2; ++n )
|
||||||
{
|
{
|
||||||
// Load the xrc file we're just created
|
LoadTestXrc();
|
||||||
REQUIRE( wxXmlResource::Get()->Load(TEST_XRC_FILE) );
|
|
||||||
|
|
||||||
// foo[start] should == foo[0]
|
// foo[start] should == foo[0]
|
||||||
CHECK( XRCID("SecondCol[start]") == XRCID("SecondCol[0]") );
|
CHECK( XRCID("SecondCol[start]") == XRCID("SecondCol[0]") );
|
||||||
@@ -205,6 +211,52 @@ TEST_CASE_METHOD(XrcTestCase, "XRC::IDRanges", "[xrc]")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("XRC::PathWithFragment", "[xrc][uri]")
|
||||||
|
{
|
||||||
|
wxXmlResource::Get()->AddHandler(new wxBitmapXmlHandler);
|
||||||
|
wxImage::AddHandler(new wxXPMHandler);
|
||||||
|
|
||||||
|
const wxString filename = "image#1.xpm";
|
||||||
|
TempFile xpmFile(filename);
|
||||||
|
|
||||||
|
// Simplest possible XPM, just to have something to create a bitmap from.
|
||||||
|
static const char* xpm =
|
||||||
|
"/* XPM */\n"
|
||||||
|
"static const char *const xpm[] = {\n"
|
||||||
|
"/* columns rows colors chars-per-pixel */\n"
|
||||||
|
"\"1 1 1 1\",\n"
|
||||||
|
"\" c None\",\n"
|
||||||
|
"/* pixels */\n"
|
||||||
|
"\" \"\n"
|
||||||
|
;
|
||||||
|
|
||||||
|
wxFFile ff;
|
||||||
|
REQUIRE( ff.Open(filename, "w") );
|
||||||
|
REQUIRE( ff.Write(wxString::FromAscii(xpm)) );
|
||||||
|
REQUIRE( ff.Close() );
|
||||||
|
|
||||||
|
// Opening a percent-encoded URI should work.
|
||||||
|
wxString url = filename;
|
||||||
|
url.Replace("#", "%23");
|
||||||
|
|
||||||
|
LoadXrcFrom
|
||||||
|
(
|
||||||
|
wxString::Format
|
||||||
|
(
|
||||||
|
"<?xml version=\"1.0\" ?>"
|
||||||
|
"<resource>"
|
||||||
|
" <object class=\"wxBitmap\" name=\"bad\">%s</object>"
|
||||||
|
" <object class=\"wxBitmap\" name=\"good\">%s</object>"
|
||||||
|
"</resource>",
|
||||||
|
filename,
|
||||||
|
url
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
CHECK( wxXmlResource::Get()->LoadBitmap("good").IsOk() );
|
||||||
|
CHECK( !wxXmlResource::Get()->LoadBitmap("bad").IsOk() );
|
||||||
|
}
|
||||||
|
|
||||||
// This test is disabled by default as it requires the environment variable
|
// This test is disabled by default as it requires the environment variable
|
||||||
// below to be defined to point to a HTTP URL with the file to load.
|
// below to be defined to point to a HTTP URL with the file to load.
|
||||||
//
|
//
|
||||||
|
Reference in New Issue
Block a user