Guido Plugin Author's Guide

For programmers wishing to create plugins for Guido, the Perl/Tk "GUI-Builder-GUI," this document is provided to outline what must be done, what can be done, and how to tell the difference between the two.

Guido's plugin architecture provides flexibility

The Guido plugin architecture gives the end user (the Perl/Tk developer, that is), a lot of control over how the application functions.

* Every built-in Guido IDE component -- except for the menu and toolbar -- is already a plugin
* Each of the built-ins can be overridden by a custom component that either inherits from the original or creates a whole new API of its own.
* The Guido application itself is represented by an object model that is exposed to every registered plugin at runtime, giving the plugins complete control over the application's functionality.
* The Guido project data is also represented by a separate object model, making it easy to parse and persist. The application object model provides references to each loaded project.

Every Guido plugin is an object-oriented Perl module and probably a composite widget, too

Every plugin used by Guido must be a Perl OO module. In order to present a user interface, they must also be a composite Perl/Tk widget. This stands in contrast to Guido macros, which can be non-OO Perl modules or simple snippets of Perl code, but which are not required to persist data beyond the current method call. Plugins are supposed to integrate with the Guido application and maintain a persistent presence and can optionally display information graphically to the user in the IDE.

Usually, composite widgets inherit from Tk::Frame or Tk::Derived, and Guido plugins are no exception. The large majority of them will be basic composite widgets, although it is not a restriction placed on them by Guido. Here is an example of a simple Guido plugin:

	package Guido::Plugin::MyPlugin;
	require Guido::Plugin;
	require Tk::Frame;
	use base qw(Guido::Plugin Tk::Frame);
	Construct Tk::Widget 'MyPlugin';
	sub Populate {
		my ($cw, $args) = @_;
		$cw->SUPER::Populate($args);
		$cw->Label(-text=>"Yo, I'm a plugin!")->pack();
	}
	
	sub init_plugin {
	}
	
	sub place_menus {
	}
	
	sub refresh {
	}

This plugin will do nothing more than display "Yo, I'm a plugin!" in the IDE, but it's still a valid plugin.

Note that the only real difference between this and a regular composite widget is the addition of Guido::Plugin to the inheritance heirarchy and the overriding of Guido::Plugin's 3 methods -- init_plugin(), place_menus(), and refresh()

Every Guido plugin inherits from Guido::Plugin

(If you don't understand Perl's object-oriented inheritance system, please refer to the perltoot man page (the section on Inheritance).

The Guido::Plugin module defines the interface that must be supported by each Guido plugin. To support the interface, each plugin must inherit from Guido::Plugin and override the required methods.

In order to ensure that all plugins support the interface, the Guido application checks each plugin at runtime to verify that it inherits from the Guido::Plugin module. The Guido::Plugin module helps enforce the interface by raising errors if a required method has not been overridden. There are currently 3 methods in Guido::Plugin that must be overridden by the inheriting plugin:

Although each plugin must override the methods implemented in Guido::Plugin, it is not necessary that the plugin actually do anything in any of the methods. For example, many plugins will not need to add menus to the application's menu structure, and so will not place any code in their place_menus() method.

In no case should you delegate to the SUPER class, since Guido::Plugin is your SUPER class, and it simply raises a fatal error if its corresponding method is called. If you want to be a do-nothing, just leave the method empty.

init_plugin() is called when the plugin is created

When Guido first starts, it will check its plugin configuration file to find the list of plugins it must load. As Guido instantiates each plugin, it either:

* uses its Tk MainWindow to instantiate the plugin (for Widget-based plugins)
* calls the plugin's new() method (for non-Widget-based plugins)

The defining factor that determines which of the above happens is whether the plugin inherits from the Tk::Widget class. If not, the new() method is called. Note that at this point, the plugin has no reference to the Guido::Application object, so this phase should concentrate on simple object instantiation, not interaction with Guido. Non-Widget-based plugins must be able to be instantiated by calling new() with no parameters.

Once the plugin is instantiated, Guido calls the init_plugin() method so the plugin can initialize itself. It passes a reference to the Guido::Application object, and it is recommended that the plugin retain this reference in a lexical variable (such as, my $app) for later use.

place_menus() is called when the plugin can add menus to Guido

Once instantiation and initialization are done, Guido calls place_menus() for each plugin to allow menu placement, if desired. The Guido::Application object's place_menu() method should be used for this purpose.

The place_menu() method accepts three parameters:

* The label of the menu to which the plugin's menu should be added. This must match the label that appears on the menu. For example, the plugin can pass in "File" to have its menu attached to the "File" menu.
* The label for the menu itself. This will be the label that appears in the parent menu.
* A reference to a pre-created Tk menu.

The method returns 1 on success, 0 on failure.

Here is a sample that adds a functional place_menus() to our MyPlugin class:

	sub place_menus {
		my($self, $app) = @_;
		
		#Create our menu
		my $menu_struct = [
			[Button => '~Do something cool', 
				-command=>\&cool_thing,
			],
		];
		$menu = $app->{mw}->Menu(-menuitems=>$menu_struct);
		
		#Place the menu
		$app->place_menu($self, "MyPlugin", $menu) or $app->ERROR(text=>"I couldn't place my menu! Waaah!");
	}

refresh() is called when application or project state changes

When the project being managed by Guido is modified in some way -- perhaps a new source file is added or removed -- the application will call the refresh() method on each plugin. Your plugin can simply choose to do nothing here, but your display might get out of sync with the state of the project if you do. It is sometimes best to do the same things you do when your plugin is initialized, only be sure to clean out any pre-existing data or widgets before you do. For example, if your plugin maintains a display of files in the project, be sure to remove the existing list elements before adding them all again. Or you might just scan the project file collections to see which files have been added and then append the new ones to the existing list.

The "plugin_data" hashes allow you to store global and project-specific plugin information

Plugins should not attempt to create their own configuration registries unless it is absolutely necessary. The Guido application provides two different locations to store plugin-related data: one in the Application object, and one in the Project object.

In both cases, the method of accessing and updating the data is the same. Simply treat the data store as a hash (because it is one!). For example, retrieving data from the Application object's plugin_data store, you would use the following code if your plugin was named "MyPlugin.pm" and your reference to the Application object was called $app:

	my $plugin_data = $app->{plugin_data}->{MyPlugin};

Using this hash reference stored in $plugin_data, you can access data you previously stored there. Perhaps you need to know what image to display in a particular part of your plugin:

	my $img_path = $plugin_data->{HeaderImagePath};

Any textual value can be stored in the hash. Whether you store a particular bit of information in the Application object or in the Project object depends on when and how you want to get it back.

For those interested in the gory details, Guido uses XML::Simple to store and retrieve the plugin_data hash. You can check out the documentation on that module (or the source) to see what goes on behind the curtain.

* The Application object's plugin_data store contains information that is constant no matter what project you are working on

The data in this hash is written to disk only when the user selects "Tools->Save Configuration" from the main menu. It is not updated when projects are loaded or modified, and is only re-read when Guido is restarted.

Examples of things you might store here are initialization parameters or other defaults for your plugin that you allow the user to configure.

* The Project object's plugin_data store contains information that is specific to each project

The data in this hash is written to disk whenever the project is saved. Because each project has its own plugin_data hash, you must access it using a reference to the Project object which contains the data store you want. If you only have a reference to the Application object, you'll have to use the projects collection to get to the hash:

	my $plugin_data = $app->projects("Big Tk App")->{plugin_data};

In the example above, the project's name is "Big Tk App", and $plugin_data now has a reference to the plugin_data hash for that project. The plugin can not only read, but write to the hash and when the project is saved, the updated information will be saved, as well.

WARNING!: Do not save object references in the plugin_data hash! Only textual data should be stored there. If you save an object reference to the hash, the system will not complain, but the next time the plugin_data store is loaded, you will not have the original object reference. You will only have a string containing the (now useless) hexadecimal memory address of the object you attempted to store. If you really must store an object in the hash, you'll have to investigate the Storable or Data::Dumper modules.

References

* The Guido::Plugin pod
* The Guido::Application pod
* The Guido::Project pod
* The perltoot pod
* The Tk composite pod