This document is out of date.  Refer to the TestPlugin CVS module and the user manual
for more up to date information.


Plugin class:

class TestPlugin : public Plugin {

 public:

  TestPlugin();
  ~TestPlugin();

  // Event functions
  void onEvent(Event *, Connection *); // To receive Connect and Disconnect Events.

  GtkWidget * getPrefsWidget(MUD *); // Function called when the user opens the preferences dialog
  void onPrefsOk(MUD *); // Function called when preferences Ok button clicked.
  void onPrefsApply(MUD *); // Function called when preferences Apply button is clicked.
  void onPrefsCancel(MUD *); // Function called when preferences Cancel button is clicked.

  // Data functions
  void output(Connection *, char *); // Function called when MUD output received.
  void input(Connection *, char *); // Function called when input from user received.
  void prompt(Connection *, char *); // Function called when a prompt line from a MUD is received.

};

Your plugin should extend the Plugin class in a manner similar to this:

---

#include "Plugin.h"
#include "version.h"
#include "PluginHandler.h"
#include "TurfProtocol.h" // Only if you intend to use the Turf Protocol

extern PluginHandler * phandler;
extern TurfProtocol * turf; // Only if you intend to use the Turf Protocol

static TestPlugin * test = NULL;

extern "C" char * plugin_query_name() {
	return "PluginName";
}

extern "C" char * plugin_query_description() {
	return "Description of plugin.";
}

extern "C" char * plugin_query_major() {
	return "major_version_number";
}

extern "C" char * plugin_query_minor() {
	return "minor_version_number";
}

extern "C" void plugin_init(void) {
	test = new TestPlugin();
}

extern "C" void plugin_cleanup(void) {
	delete test;
}

class TestPlugin : public Plugin {

	TestPlugin();
	~TestPlugin();

};

--

Constructor
===========

This is currently called with no argument.  It may be that at some point in
the future it becomes useful to pass an argument (probably via a void pointer)
to a plugin.

The constructor needs to do at a minimum:

TestPlugin::TestPlugin() {
  version = 1.0; // Floating point version number.
  name = strdup("Test Plugin"); // Brief name of plugin.

  phandler->registerPlugin(this, VERSION); // Tells the plugin handler about
                                           // this plugin.
}

Destructor
==========

Generally this will only delete things that were explicitly created in the
TestPlugin.  It should be written knowing that it's possible to unload and
reload a plugin whilst the client is still running.  Any timeouts the plugin
is using should be removed here, else BadThings will happen when that timeout
triggers.

TestPlugin::~TestPlugin() {
}

Events
======

A plugin may receive events from time to time - for example when a connection
is created or closed.

void TestPlugin::onEvent(Event * e, Connection * c) {
  if (e->getType() == EvConnect) {
    // A new connection, c, was created.
  }

  if (e->getType() == EvDisconnect) {
    // A connection, c, has been closed.
  }
}

Preference Events
=================

A plugin may need to allow the user to configure it through Papaya's standard
preferences dialog.  In order to do this, the following functions must be
provided:

GtkWidget * TestPlugin::getPrefsWidget(MUD * mud) {
  // Insert code to create a frame widget containing the widgets for
  // configuring the plugin's preferences.
  return frame_widget;
}

void onPrefsOk(MUD *) {
  // Look up the preferences set by the user from the widgets.
  // Destroy the widgets used by this plugin's preferences.
}

void onPrefsApply(MUD *) {
  // Look up the preferences set by the user from the widgets.
}

void onPrefsCancel(MUD *) {
  // Destroy the widgets used by this plugin's preferences.
}

Input Filter
============

This is the data inputted by the user.

void TestPlugin::input(Connection * c, char * buf) {
}

'buf' is a pointer to the text the user inputted.  The buf is guaranteed to be
16384 bytes in size (unless some git reallocs it in their plugin).

Avoid the use of copy operations unless it is absolutely necessary in your
plugin as these will slow the operation of the client down.  Mangle the
contents of buf as you like.  If you set buf[0] = '\0' it is then considered
empty, nothing is sent to the MUD and no further plugins will be allowed to
process it.

Output Filter
=============

This is the main window data returned by the MUD.

void TestPlugin::output(Connection * c, char * buf) {
}

'buf' is a pointer to the data returned by the MUD, and it's 16384 bytes in
size (though the string in it may be considerably less).

Avoid copy operations unless absolutely necessary as these slow the client
down.  If you set buf[0] = '\0' no text will be appended to the output area
and no other plugins will process the data.

Prompt Filter
=============

This is the data that is displayed in the prompt area.

void TestPlugin::prompt(Connection * c, char * buf) {
}

'buf' is a pointer to the data returned by the MUD, and it's 16384 bytes in
size (though the string in it may be considerably less).

Avoid copy operations unless absolutely necessary as these slow the client
down.  If you set buf[0] = '\0' the prompt will be blank and no other plugins
will process the data.

Turf Client Protocol Filter
===========================

When the TurfProtocol plugin is loaded, certain messages from an aware MUD
will be filtered and presented as client messages.  If a plugin wishes to
receive these messages it needs to implement the following interface.

void TestPlugin::clientMessage(Connection * c, char * buf) {
}

'buf' is a pointer to the message sent from the MUD, minus the 'c15 c'
header, and it is 16384 bytes in size (though the string in it is probably
much less).

Avoid copy operations unless absolutely necessary as these slow the client
down.  Setting buf[0] = '\0' indicates that you have processed the data
and that no plugins running after this one should process it.

Threading
=========

It is possible to thread plugins in Papaya.  Be aware that the main core of
Papaya is _not_ thread safe and funny things may happen if you try to write
to Connections within a threaded plugin.

The TkPlugin used to provide an example of a threaded plugin, and it can be
found in the 'old/' subdirectory within the papaya-plugins directory.  Any
plugins that require control of the thread of execution (Eg Tk/Tcl) will
need to use threads to achieve this.  GTK/C(++) plugins are the exception
to this as Papaya already provides a gtk_main() which will service plugins.

Plugin Tray Area
================

If your plugin requires a small visible presence (no more than 30 pixels high)
then it could be placed in the tray area at the bottom of the client.  A
larger visible presence would probably warrant another window, or use an
extra sheet on the tabbed notepad.

VT::addToTray(GtkWidget * widget, GtkWidget ** frame);

  Note that the frame will be created for you.  You need to store the area
  in memory where the frame is so that you can tell the removeFromTray function
  about it.

VT::removeFromTray(GtkWidget * widget, GtkWidget * frame);

GtkWidget * widget = NULL;

void TestPlugin::onEvent(Event * e, Connection * c) {

  if (e->getType() == EvConnect) {
    widget = gtk_button_new_with_label("A Button");
    c->getVT()->addToTray(widget, &frame);
  }

  if (e->getType() == EvDisconnect) {
    c->getVT()->removeFromTray(widget, frame);
  }
}

Any plugin that has a presence in the plugin tray must provide a tooltip that
activates when the user's mouse hovers over the plugin's graphical presence.
For plugins whose graphical presence is provided by a label or drawing area
a 'hack' using an event box will be required to use tooltips.  See the
PromptPlugin for an example of this.

Awareness of Multiple Connections
=================================

If you need to store state information for each Connection then you will need
to construct a list or other data type to store the information.  To help this
a generic List class has been provided.

/* Data structure to store the start time of the connection. */
struct test_store {
  time_t start;
};

TestPlugin::TestPlugin() {
  list = new List(); // Create a new List object to store connection data.
}

void TestPlugin::onEvent(Event * e, Connection * c) {

  // When we get a connection create a new ListElement containing a test_store
  if (e->getType() == EvConnect) {
    struct test_store * tstore = (struct test_store *)malloc(sizeof(struct test_store));
    ListElement * tmp = list->findEntry(c);
    if (!tmp)
      tmp = list->newEntry(c);

    // Store the current time in seconds since the epoch in the test_store
    time(tstore->start);
    tmp->data = tstore;
    return;
  }

  // Connection closed.
  if (e->getType() == EvDisconnect) {
    ListElement * tmp = list->findEntry(c);
    time_t end;
    struct test_store * tstore;
    if (!tmp)
      return;

    // Retrieve the connection's start time
    tstore = tmp->data;
    time(&end);

    // Calculate how long we've been connected for
    printf("Connected for %d seconds.\n", end - tstore->start);

    // Delete the list entry. This will also delete the test_store object
    // (but not its subobjects)
    list->deleteEntry(tmp);
    return;
  }
}

Triggers
========

Regex triggers in C are much more CPU intensive than basic strstr and strchr
functions, especially for large volumes of output from the MUD.  However,
triggers do provide an easy way to look for certain strings and they can
be used, preferably with regex strings optimised for speed.

You can add triggers to plugins, instead of using the intensive output method.

#include "TriggerHandler.h"
extern TriggerHandler * thandler;

TestPlugin::TestPlugin() {
  Trigger * t = new Trigger("!!SOUND\\(([^ ]+).*\\)", MSP_Callback, NULL);
  thandler->addTrigger(t);
}

You can disable a trigger in the program if you keep a handle to your trigger
object, and then call thandler->removeTrigger(t);

You should remove plugins in a plugin's destructor as it may be unloaded
while the client is running.

Be aware that if a user is running with Triggers disabled, plugin triggers
will not be checked for or executed.
