go-qml / qml

QML support for the Go language
Other
1.96k stars 189 forks source link

Add support for resource bundles #102

Closed niemeyer closed 9 years ago

niemeyer commented 9 years ago

Some references:

The qml package may leverage the public API behind this logic to load the resource from in-memory data:

Using Resources in a Library

If you have resources in a library, you need to force initialization of your resources by calling Q_INIT_RESOURCE() with the base name of the .qrc file. For example: (...) This ensures that the resources are linked into the final application binary in the case of static linking. You should put the initialization code close to where the resources are used in your library, so that clients of your library will only link in the resources if they use the feature of the library that depends on them.

That's the definition of these macros, from qglobal.h:

#define Q_INIT_RESOURCE(name) \
    do { extern int QT_MANGLE_NAMESPACE(qInitResources_ ## name) ();       \
        QT_MANGLE_NAMESPACE(qInitResources_ ## name) (); } while (0)
#define Q_CLEANUP_RESOURCE(name) \
    do { extern int QT_MANGLE_NAMESPACE(qCleanupResources_ ## name) ();    \
        QT_MANGLE_NAMESPACE(qCleanupResources_ ## name) (); } while (0)

These functions are automatically built by the rcc tool. At their bottom, logic similar to the following is used to actually do the registration at runtime:

qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data);

This function is defined in qtbase/src/corelib/io/qresource.cpp and is exported, but is not declared in any header. It's safe to do a local declaration and use it, though, because public code does the same and depends on it to work.

GeertJohan commented 9 years ago

From a usage point of view I would prefer a method like addImageProvider, but for resources. This means that the actual loading of the resources is up to the developer. It could be from disk, http, or appended with go.rice or other resource bundling tools for Go.

niemeyer commented 9 years ago

That's how it will end up. The points above are simply about how to implement that interface internally.

GeertJohan commented 9 years ago

Did some more reading in the Qt docs.

The QAbstractFileEngine class provides an abstraction for accessing the filesystem.

The QDir, QFile, and QFileInfo classes all make use of a QAbstractFileEngine internally. If you create your own QAbstractFileEngine subclass (and register it with Qt by creating a QAbstractFileEngineHandler subclass), your file engine will be used when the path is one that your file engine handles.

http://qt-project.org/doc/qt-4.8/qabstractfileengine.html

The QAbstractFileEngineHandler class provides a way to register custom file engines with your application.

QAbstractFileEngineHandler is a factory for creating QAbstractFileEngine objects (file engines), which are used internally by QFile, QFileInfo, and QDir when working with files and directories. When you open a file, Qt chooses a suitable file engine by passing the file name from QFile or QDir through an internal list of registered file engine handlers. The first handler to recognize the file name is used to create the engine. Qt provides internal file engines for working with regular files and resources, but you can also register your own QAbstractFileEngine subclasses.

http://qt-project.org/doc/qt-4.8/qabstractfileenginehandler.html

It looks like this is a better method to add this feature. I think we can implement a QAbstractFileEngineHandler subclass which calls a go function type ResourceProvider func(path string) Resource which is registered with qml.RegisterResourceProvider(ResourceProvider). ResourceProvider could return a nil Resource (at which point the next QFileEngineHandler is tried by Qt). Resource would look something like type Resource interface { Read(), Seek(), etc. }.

I think we could register one QAbstractFileEngineHandler and iterate over the registered ResourceProvider's within go-qml. Or register a new QAbstractFileEngineHandler for each ResourceProvider that is registered. In the first case we could add some path prefix selector, like ImageProvider's providerId

niemeyer commented 9 years ago

From http://qt-project.org/doc/qt-5/sourcebreaks.html

QFSFileEngine, QAbstractFileEngine, QAbstractFileEngineIterator, and QAbstractFileEngineHandler are no longer public. We recommend you to avoid using these classes as there is no compatibility promise.

GeertJohan commented 9 years ago

Oops... That kinda sucks :-1: Why would they have done that? There is no replacement for this I take.. So really the only way to register resources is with the rcc registration hack? This means that there is no way to let Qt request for resources right? I couldn't find too much about that, but to me it still looks like it will only let you register resources (including the binary blob) before they are used. So there's no way to register something like a ResourceProvider..?

GeertJohan commented 9 years ago

Also, the registerResourceData used by rcc generated code isn't in a .h file, so is there a compatibility promise on that?

niemeyer commented 9 years ago

Do you mean qRegisterResourceData? That's not in a .h but is depended upon by public code generated by the rcc tool. If they change that, every binary built that uses rcc and linked against qt is immediately broken.

GeertJohan commented 9 years ago

Looks great @niemeyer I've learned a lot about how it works by reading the code. Thanks!