Scaffold API proposal



Hi,

Happy New Year everyone!

After some days of thought and elaboration (and New Year's celebrations
:-) I present you a first draft of the API proposal for Scaffold.  The
proposal tries to address all the issues I enumerated in the thread
"future of scaffold".

It's still a very rough draft, and there are lots of missing parts, but
I want to get the ball rolling and gather some feedback before
elaborating further.

To design the interfaces I made a list of plugins I'd like to have in
Scaffold and then tried to see the interactions between them.  My list
goes something like:

- a project manager
- a text document manager
- a VCS plugin
- text utilities
- a debugger
- glade plugin
- devhelp plugin
- a symbol browser
- a personal information/mail plugin

I think the proposed interfaces cover a good amount of the interactions
between those.  Surely I missed some interactions and some important
plugins (in fact, right now I'm thinking a couple more).  Please feel
free to append your wishlist :-)

Note that while there are 9 interfaces outlined in the document, the
actual number will be smaller.  This is because the shell interface
involves the EventBus, the FileOpen service and the ShellUI, and those
interfaces will not be implemented by any plugin.

Anyway, the document is attached.  Feedback of any kind is very welcome
:-)

Cheers,
Gustavo

			SCAFFOLD API PROPOSAL

			     Version 0.1

			    Jan 5th, 2004




1. BINARY INTERFACES
====================

[ NOTE: in all API declarations the first parameter which is a
reference to the object itself 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.
  

  /* sets a given value in the repository; returns TRUE if the value
     could be set */
  gboolean set_value (const gchar *path, GValue *value, GError **error);

  /* tries to retrieve a value for a given path, returning TRUE if the
     value was set */
  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.

       [ UNDECIDED: what's the policy for replacing an existing value? ]


----
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 listen_event (const gchar *event_pattern, GClosure *closure);

  /* unregister a listener */
  void unlisten_event (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.

	[ DECISION: should we have each visual component implement this
	interface, or have a single implementation at the shell, and
	somehow namespace the UI elements (popups)?

	I tend to prefer offering this interface and have each
	interested plugin implement it, since that allows greater
	flexibility.  For example, in the project manager we could
	have different popups depending on the row type and one place
	to add common actions to all types.  The individual types
	would be accessed as an interface in the shell's repository:
	'/Project/targets', '/Project/sources', '/Project/groups',
	while the common place through '/Project/all' (or similar).

	If we decide to go the other way, we could eventually ditch
	this interface and have a shell method which would return the
	GtkUIManager. ]


  /* 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 ]



---
The FileOpen Service:

This service is offered by the shell in principle since it's way too
common and will probably be used by almost all plugins.

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

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

	[ NOTE: Having the shell implement this service also solves
	the problem of opening a file from the commandline; I don't
	like the shell exposing the full command line in the value
	repository ]


  /* 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, 
                                      gboolean read_only,
                                      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, FileHandler handler);

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



---
The Document interface:

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

  gchar *get_uri ();

  gchar *get_mime_type ();

  gboolean get_editable ();

  /* users of this method? */
  save ();



---
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 RELATIVE_POS_SHIFT 0
  #define STEP_SIZE_SHIFT    2

  /* relative position */
  #define FROM_CURRENT 0 << RELATIVE_POS_SHIFT
  #define FROM_START   1 << RELATIVE_POS_SHIFT
  #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
  #define COLUMN 3 << 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_position (gint modifier);



---
ShellUI:

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 *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);


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



---
BuildTarget:

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 ();


---
[ FIXME: Design the project interface ]
[ FIXME: Design a reflection interface to discover runtime interfaces ]
[ FIXME: Other interfaces? ]



2. STRING INTERFACE (repository hierarchy and event names)
==========================================================

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:

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

/Documents/Current
	The currently (or last) active text document

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

/Project/Targets
	Targets collection

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


The following are standard event names:

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

Shell::Quit
	The user wants to exit the application

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

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



3. STARTUP AND SHUTDOWN SEQUENCE
================================

Note: 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.

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
- the shell opens all files given in the command line
- the Shell::Ready event it emitted
  - if there is a session plugin
    - on emission of the Shell::Ready event, it loads the (default or
      command line specified) session file 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
- all tools are unloaded in reversed order
- the shell is destroyed


	[ FIXME: what about loading a session file in the middle of
	another session? We need an event to signal all plugins to
	reset their contents. ]



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