Overview

The primary goal of the feature is to be able to create an interactive preview of a given file, in the form of a GtkWidget, from a generic interface that is extensible to different mime types. “Interactive” means that a set of actions can be performed on the preview itself. For instance, when previewing a PDF document it is desirable to switch to previous/next page; when previewing a video or a song it’s desirable to play/pause, etc. The feature lives ideally inside GTK.

Architecture

The requirement of supporting the widest variety of formats possible in the library calls for an extensible system, where a set of modules are registered as preview “providers”, each handling a list of content types. This also avoids the need for GTK to directly link to all the dependencies directly, but some providers could be distributed as part of GTK (e.g. a GdkPixbuf-based one for images).

The most straightforward way to implement this is through an extension point with a well-defined interface that can be implemented by shared objects modules installed by the different libraries or applications that want to extend the functionality. It should also be possible for implementations to use a DBus interface to transfer the data. This makes it possible for an actual application to be spawned in response to a preview request, and send the data over the wire, which will be not as expensive as it is now when kdbus enters the picture. Doing this in a generic “dbus implementation” module might be hard though, because it’s not possible to export a GtkWidget over DBus, and is outside the scope of this proposal. I imagine e.g. a Totem-provided viewer to be able to stream the video to the library, but the Totem module would still know how to draw back the data it receives from the Totem process.

Registration

Registration of modules associate their metadata to the .so file that will be used to drive the preview implementation. Desirable metadata for a first version include at least:

  • content types handled
  • priority
  • id/name

Once the .so file is installed to disk into a known location, a textual descriptor is installed along with it, providing metadata about the module capabilities; a different design is possible that uses methods on the module interface to define that without the need of an extra file. An advantage of the descriptor file is that it makes it easy to e.g. load modules in priority order (by just sorting the descriptors alphabetically), giving the library a mechanism to predictably resolve conflicts in case two modules claim to handle the same content type. It also makes the metadata fields that are exposed easily extensible.

Note that the content type description should also make it possible to implement module associations that are not achieved with traditional content-type sniffing. For instance, a module could handle the "x-gnome-preview/gdata" mime type and an application that is dealing with libgdata objects would use the stream-based API described below to request a preview widget for the document.

Interface

We need two separate interfaces; one used by the client of the library and one between the library and the modules.

Client

GtkWidget * gtk_preview_widget_new (void)
GtkWidget * gtk_preview_widget_new_from_file (GFile *)
void        gtk_preview_widget_set_file (GtkPreviewWidget *, GFile *)
GFile     * gtk_preview_widget_get_file (GtkPreviewWidget *)

These are all pretty obvious; to create a preview widget you need a file.

GtkWidget    * gtk_preview_widget_new_from_stream (GInputStream *, gchar * content_type)
void           gtk_preview_widget_set_stream (GtkPreviewWidget *, GInputStream *, gchar * content_type)
GInputStream * gtk_preview_widget_get_stream (GtkPreviewWidget *, gchar ** content_type_out)

Some documents are not possible to represent in a GFile - for instance, Google Docs accessed through libgdata or Onedrive documents accessed through libzapojit. Also there are use cases to preview aggregates of data that do not map to a document at all. A stream-based API is offered as well to cover those cases. The content type parameter is provided to associate the stream with an appropriate module. As an implementation detail, if the provided stream is a GFileInputStream, and no content type is provided, it would be possible to use g_file_input_stream_query_info() to automatically fetch it.

GtkPreviewContext * gtk_preview_widget_get_context (GtkPreviewWidget *)

Everything that relates to the widget but is not part of the widget itself is split into a GtkPreviewContext object. This is effectively the entry point for all the actions-related operations.

GtkPreviewContext implements GActionGroup

GIcon * gtk_preview_context_get_icon (GtkPreviewContext *, gchar * action)
gchar * gtk_preview_context_get_label (GtkPreviewContext *, gchar * action)
gchar * gtk_preview_context_get_description (GtkPreviewContext *, gchar * action)

By implementing GActionGroup, GtkPreviewContext can be used by the client to enumerate actions and create UI elements with them. There are no assumptions on where the actions come from; for instance, GtkPreviewContext could add by default an “open” action that is handled in a generic way entirely outside of the module. Similarly, applications could add their own actions on top of the stock.

Higher level objects can be built on top of these two simple classes. For instance, a GtkPreviewDialog could be created that displays the preview widget in a GtkScrolledWindow with actions in a GtkActionBar below it.

Module

GLib and GIO offer low-level and higher-level APIs to setup a library extension point. Assuming that part of the problem is solved, let’s define GtkPreviewModule as the interface that all modules need to implement.

GtkPreviewModule * gtk_preview_get_module_for_content_type (gchar *)

This is where the type detection mechanism is plugged in; GTK will internally keep knowledge of the available modules and return the right one compatible with the passed-in content type.

GtkPreviewWidget * gtk_preview_module_create_widget_for_file (GtkPreviewModule *, GFile *)
GtkPreviewWidget * gtk_preview_module_create_widget_for_stream (GtkPreviewModule *, GInputStream *, gchar * content_type)

These are the main entry points for the module to return the widget. A variation of this design confines the GFile-based API to the client side and always passes down a stream and content type, doing the detection before calling into the module.

GIcon * gtk_preview_module_get_icon (GtkPreviewModule *, gchar * action)
gchar * gtk_preview_module_get_label (GtkPreviewModule *, gchar * action)
gchar * gtk_preview_module_get_description (GtkPreviewModule *, gchar * action)

These methods are useful to return the information needed by the context object exposed to the client.

GtkPreviewContext * gtk_preview_context_new (GtkPreviewModule *)
void gtk_preview_widget_set_context (GtkPreviewWidget *, GtkPreviewContext *)

These methods tie the module and the context together, and are only used for module implementations.

Depending on how mimetype detection is implemented, modules might need to expose a method like gboolean gtk_preview_module_supports_type (GtkPreviewModule *). However, I’m leaning towards the text file descriptor for this kind of information, as it allows more easily a distributor or system administrator to give priority to certain modules over others or blacklist them.

Projects/GTK/Preview (last edited 2018-12-05 15:47:02 by EmmanueleBassi)