Scaffold API proposal 0.2

Hi guys,

Here's a new version of my API proposal for Scaffold with changes due to
the feedback I got from you.  Attached is the html version.  You can get
the Docbook file from

There are still a bunch of missing/incorrect parts.  Most notably, the
ideas from John's last mail[1]: the layout switch, highlighting and the
markers interface.  Those are particularly hard and I haven't had much
time to think about them.

I think I'll start throwing out some code, so I'd like to get the basic
interfaces first (ValueContainer, EventBus, ShellUI and UIActionTarget).

Also, I want to have a document describing the expected implementation
and behavior of the fundamental plugins (namely project manager,
document manager and session).  I'll write a draft document in the next
few days (unless somebody else wants to do it ;-)

Comments, ideas, flames? :-)  All feedback appreciated.



Title: Scaffold API Proposal

Scaffold API Proposal

Version 0.2

Revision History
Revision 0.1Jan 5th, 2004
Initial version.
Revision 0.2Jan 17th, 2004
Added introduction to binary interfaces. Value removal notification. EventBus API rename. The UI merging interface can be implemented by any plugin which would support actions. The FileOpen service should be implemented by the Document Manager plugin and not the shell. Added method to create a new file to the FileOpen service. Added a description to the registered FileHandler. Added save as, close and clipboard operations to Document. Added stock_id parameter to ShellUI's add_widget. New section about runtime interfaces. Introduced command handlers.

Binary Interfaces

In the following sections the Scaffold interfaces are presented. Interface here implies the GInterface an object must implement to provide the functionality. That's why the defined methods are minimal. In the actual API to use these interfaces there will be wrappers defined when necessary for clarity or easy of use.

The set described below is the basic set of intefaces to the Scaffold IDE services. All these will be implemented in the libscaffold library. For further extensions two options are available:

  • create new GInterfaces and bundle them in another shared library which plugins can link to

  • use runtime interfaces (described at the end of the section) which are realized as properties and signals of the GObject system.


In all method declarations below the first parameter (which is a reference to the object implementing the interface) is omitted for clarity.

The ValueContainer interface

One of the main components of the shell is the ValueContainer. The ValueContainer is an interface which can also be implemented by plugins which want to “take over” certain subpaths of the main hierarchy.

The shell's ValueContainer exposes a tree hierarchy where each of the nodes can hold a single GValue. Paths in the tree are represented by strings, where each element of the path is separated by a special character or sequence of characters (still undecided, but the slash '/' will be used in the following examples). The root of the hierarchy is not accesible, and thus the initial slash in a given path can be omitted. Another way of looking at the model exposed by ValueContainer is as nested hashes.

An implementation of the ValueContainer interface can support delegation. This means, if a GValue which contains an object which in turn implements the ValueContainer interface is installed at a given path, all accesses to subpaths which have it as its parent will be delegated to the installed object. Delegation should be transparent.

E.g. an object A which realizes the ValueContainer interface is installed at the shell's path /Documents. An access to the path /Documents/Untitled0 the shell's object hierarchy will be delegated to A (by using the ValueContainer interface) with a path of /Untitled0.

There's no such thing as a relative path as in a filesystem. A path is an address, not a means of navigation.

  /* Value removal/replace notification callback signature. */
  typedef void (* ValueUnsetNotify) (const gchar *path, GValue *value);

  /* Sets a given value in the repository.  Returns TRUE if the value
     could be set.  If an unset notify is provided, it is called
     whenever the value is unset (i.e. either because it's being
     removed or replaced). */
  gboolean set_value (const gchar *path, 
                      GValue *value, 
                      ValueUnsetNotify unset_notify_cb,
                      GError **error);

  /* Tries to retrieve a value for a given path, returning TRUE if the
     value was set and the value itself in the value parameter. */
  gboolean get_value (const gchar *path, GValue *value, GError **error);

  /* removes a value from the repository */ 
  void unset_value (const gchar *path);

  /* gets a list of strings with the set immediate subpaths */ 
  GSList *get_children (const gchar *parent);

The repository offered by the shell's ValueContainer is the place where plugins should expose their services and go look for other plugins services. A standard hierarchy should be defined, which will become part of the API, along with all the interfaces.

The EventBus

The EventBus interface provides a shell-wide notification mechanism. Events emitted in the bus are considered of interest of several plugins. Notifications of lesser importance should be implemented using the standard GSignal mechanism.

Group of events are selectable through patterns by the use of wildcards (the g_pattern_spec_* API will be used for this).

While not enforced, it's expected that events will be namespaced. E.g.: Shell::ready, Shell::quit, Build::start, Build::stop, Documents::change_active, etc.. The event names will be part of Scaffold's API too.

Event emission is synchronous.

  /* registers a listener for a specific event pattern; use the returned
     id to unregister the listener */
  guint add_listener (const gchar *event_pattern, GClosure *closure);

  /* unregister a listener */
  void remove_listener (guint id);

  /* emits an event in the bus */
  void emit_event (const gchar *event);

While presented as a separated interface, only the shell will implement the event bus.

The action UI merging interface UIActionTarget

This interface will allow a plugin to merge action-type UI elements (menus, toolbars). This API mirrors the GtkUIManager API.

The shell will implement this interface for the application menu bar and toolbars, but it can also be implemented by any plugin which has a visual component and wants to merge actions for popup menus.

  /* install an action group */
  void add_action_group	(GtkActionGroup *group);

  /* remove an action group */ 
  void remove_action_group (GtkActionGroup *group);

  /* merge UI elements to the component's UI; the returned id is used
     for unmerging */
  guint merge_ui (const gchar *ui);

  /* unmerge a previously merged UI */
  void unmerge_ui (guint merge_id);

The shell will have a <menu> element and a <toolbar> element at a minimum. Plugins implementing the interface will have a <popup> element.


UNDECIDED: how to register and use several toolbars


FIXME: add status bar API

The FileOpen Service

This service is one of the most important and will be implemented by the document manager plugin.

The concept is as follows: a plugin which can open a certain type of document (selected by the mimetype) registers itself with this interface. When an open request is made, the document manager checks to see if it has any registered provider and invokes the handler if so. Otherwise, it might open the file using Gnome's standard machinery (gnome_url_show() or whatever).

Different plugins are expected to open different types of documents: the glade plugin will open .glade files, the project manager will open .scaffold project files, etc..

  /* the open handler signature; if the opened document can be
     manipulated the function should return a reference to the created
     document, NULL otherwise */
  typedef Document * (* FileHandler) (const gchar *uri, 
                                      const gchar *mime_type,
                                      gboolean     read_only,
                                      GError     **error);

  /* create a new document of the given mime type */
  Document *new_file (const gchar *mime_type,
                      GError     **error);

  /* open a file and (if applicable) present it to the user; errors
     are indicated in the error parameter, and NULL is a valid result */
  Document *open_file (const gchar *uri, 
                       gboolean     read_only,
                       GError     **error);

  /* register an open handler for a mime type */
  void register_file_handler (const gchar *mime_type, 
                              const gchar *description,
                              FileHandler  handler);

  /* deregister an open handler */
  void unregister_file_handler (FileHandler handler);

The Document interface

The document interface represents a single file opened by the application.

  /* property accessors */
  gchar *get_uri ();
  gchar *get_mime_type ();
  gboolean get_editable ();

  /* save/close operations */
  void save (gboolean save_as);
  void close ();

  /* clipboard operations */
  void copy (GtkClipboard *clipboard, gboolean cut);
  void paste (GtkClipboard *clipboard);

The TextDocument interface

Implemented by textual documents, allows direct manipulation of the file contents.

Document regions are delimited by the point and the mark (ala emacs). Cut and copy operations affect the region. Insert occurs at the point always.

  /* movement modifiers: a combination of one value from the following
     mask values */

  #define STEP_SIZE_SHIFT    2

  /* relative position */
  #define FROM_END     2 << RELATIVE_POS_SHIFT

  /* steps */
  #define CHAR   0 << STEP_SIZE_SHIFT
  #define WORD   1 << STEP_SIZE_SHIFT
  #define LINE   2 << STEP_SIZE_SHIFT

  void set_mark (gboolean swap_with_point);

  gboolean move_point (gint modifier, gint offset);

  gchar *get_text (gboolean cut);

  void insert_text (const gchar *text);

  gint get_point_position (gint modifier);


The ShellUI interface deals with visual components in the shell's container (dock) and the preferences dialog.

  /* adds a widget to the shell's dock; returns FALSE if there already
     existed a widget with the same name */
  gboolean add_dock_widget (GtkWidget   *widget,
                            const gchar *name,
                            const gchar *title,
                            const gchar *stock_id,
                            const gchar *pos_hint);

  /* adds a preference page to the preferences dialog; returns FALSE
     if there already existed a page with the same name */
  gboolean add_preferences_widget (GtkWidget *page,
				   const gchar *name,
				   const gchar *title,
				   const gchar *category);

  /* removes a widget from the shell */
  void remove_widget (GtkWidget *widget);

  /* makes sure the widget is visible to the user */
  void present_widget (GtkWidget *widget);


UNDECIDED: The preferences dialog should be unique among the application, but there might exist more than one shell at any given time. How do we deal with the preference pages?


UNDECIDED: present_widget() should open the preferences dialog if the widget is a preference page?

The data UI merging interface UIDataTarget

Plugins which have visual elements which show information that can be extended by other plugins (e.g. the vcs plugin can add information to the project manager plugin) can implement this interface, which is roughly a stripped down GtkCellLayout.

GtkCellRenderers can be packed in groups. Groups have an id and can have a title. The main difference with the GtkCellLayout interface is the absence of a GtkTreeModel/GtkTreeIter pair to feed the set data function. Instead a ValueContainer and a path into it is given.

  /* set data function for the renderers */
  typedef void (* CellDataFunc) (CellRenderer *cell, 
	 		         ValueContainer *container, 
			         const gchar *path, 
			         gpointer user_data);

  /* create a new group of renderers to pack cells into */
  guint add_group (const gchar *group_title);

  /* pack a cell into the given group using the func to fill the data */
  void pack_cell (guint cell_group,
                  GtkCellRenderer *cell,
                  gboolean expand,
                  CellDataFunc func,
                  gpointer user_data);

  /* destroy the cell group */
  void remove_group (guint id);


I'm not really convinced with this interface, but I can't find another example (besides the vcs<->project interaction) which would use it.


The interface presented by a buildable and (possibly) executable project target.


FIXME: this needs a lot of thought

  GList *list_sources ();

  add_source (const gchar *uri);

  remove_source (const gchar *uri);

  gboolean is_executable ();

Other Interfaces


FIXME: Design the project interface


Command handler interface: this will almost surely be implemented by the shell only.


FIXME: Other interfaces?

Runtime discovered interfaces

Users of these runtime interfaces will be mostly plugins written in higher level languages (Python, C#, etc.).

To expose runtime interfaces the GObject's properties and signals will be used, so there's no need to defined yet another interface.

Wrappers in C (as well as in higher level languages) will be provided to install, access and discover GObject's properties and signals created specifically with the purpose of being exposed to other plugins.

String Interfaces

Value Repository Hierarchy

Whatever the implementation of the above interfaces, there must be a standard way for plugins to look for specific well-known interfaces. As stated above, the value repository is the place for plugins to publish their services. The missing part is how to name the services.

The following paths expressed from the shell's repository should lead to the specified services:


Text documents collection (each child of this node implements the TextDocument interface)


The currently (or last) active text document


The project interface and starting point to access all project elements such as targets and sources


Targets collection


Session saved data. This should present a ValueContainer interface. Only serializable data should be allowed here.

Standard Events

The following are standard event names:


The shell has finished loading the plugins and it's ready to begin operations


The user wants to exit the application


The session plugin has finished loading the session data, which is now available at /Session


The session plugin requests that all interested parties save session data to /Session

Startup and Shutdown sequence


The session management is implemented by a plugin which also has the capability of opening session files (i.e. it registers an open handler). The session data itself is sustained and accessed by a ValueContainer interface installed at /Session.


The plugin which installs the default command handler will be responsible for opening the files provided in the command line. Most likely this will be the document manager plugin (which should implement the FileOpen service).

The creation of a new shell involves the following operations and events:

  • the shell is created

  • all the tools in the shell toolset are loaded in order

  • if this is the first shell created, it invokes all registered command handlers using the provided command line parameters

  • the Shell::Ready event it emitted

    • if there is a session plugin

      • on emission of the Shell::Ready event, it loads the session file (either the default, or one specified in the command line) and emits Session::Loaded

      • all interested plugins can now load the session settings by registering to listen to the Session::Loaded event

On shutdown (when the user quits the application or closes the shell window), the following sequence takes place:

  • the event Shell::Quit is emitted

  • all plugins can perform shutdown operations, including asking the user to save unsaved data

  • if there is a session plugin

    • emits the Session::Save event

      • all registered plugins save session data to /Session

    • save the session file and perform shutdown operations

  • any plugin might cancel the shutdown by calling a method on the shell interface (FIXME: define this somewhere)

  • all tools are unloaded in reversed order

  • the shell is destroyed

Loading a new session implies creating a new shell with the specified session file. (FIXME: define a shell service method to create a new shell).

[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]