/*
 * An embeddable "paint" component.
 *
 * FIXME: This is supposed to be an example.. describe
 *
 * Author:
 *   Nat Friedman (nat@nat.org)
 *
 */

#include <config.h>
#include <bonobo.h>
#include <bonobo/bonobo-print.h>
#include <libgnomeprint/gnome-print.h>

/*
 * The Embeddable data.
 *
 * This is where we store the document's abstract data.  Each
 * on-screen representation of the document (a BonoboView) will be
 * based on the data in this structure.
 */
typedef struct {
	BonoboEmbeddable     *embeddable;

	/*
	 * We store the image data internally in a GdkPixmap.
	 */
	GdkPixmap	    *pixmap;
	int		     width;
	int		     height;
} embeddable_data_t;

/*
 * The per-view data.
 */
typedef struct {
	BonoboView	    *view;
	embeddable_data_t   *embeddable_data;

	/*
	 * The widget used to render this view of the image data.
	 */
	GtkWidget	    *drawing_area;

	/*
	 * The width and height of this view.
	 */
	int		     width;
	int		     height;

	/*
	 * The drawing context for this view.  Each view can have a
	 * separate current pen color, and so each view maintains its
	 * own GC.
	 */
	GdkGC		    *gc;

	/*
	 * The last known x,y position of the mouse.
	 */
	int		     last_x;
	int		     last_y;
} view_data_t;

/*
 * Clean up our supplementary BonoboEmbeddable data sturctures.
 */
static void
embeddable_destroy_cb (BonoboEmbeddable *embeddable, embeddable_data_t *embeddable_data)
{
	gdk_pixmap_unref (embeddable_data->pixmap);
	g_free (embeddable_data); 
}

/*
 * This callback is invoked when the BonoboEmbeddable object
 * encounters a fatal CORBA exception.
 */
static void
embeddable_system_exception_cb (BonoboEmbeddable *embeddable, CORBA_Object corba_object,
				CORBA_Environment *ev, gpointer data)
{
	bonobo_object_unref (BONOBO_OBJECT (embeddable));
}

/*
 * The view encounters a fatal corba exception.
 */
static void
view_system_exception_cb (BonoboView *view, CORBA_Object corba_object,
			  CORBA_Environment *ev, gpointer data)
{
	bonobo_object_unref (BONOBO_OBJECT (view));
}

/*
 * The job of this function is to update the view from the
 * embeddable's representation of the image data.
 */
static void
view_update (view_data_t *view_data)
{
	gdk_draw_pixmap (view_data->drawing_area->window,
			 view_data->gc,
			 view_data->embeddable_data->pixmap,
			 0, 0,
			 0, 0,
			 MIN (view_data->width, view_data->embeddable_data->width),
			 MIN (view_data->height, view_data->embeddable_data->height));
}

static void 
update_view_foreach (BonoboView *view, void *data)
{
	view_data_t *view_data;

	view_data = gtk_object_get_data (GTK_OBJECT (view), "view_data");
	view_update (view_data);
}

/*
 * This function updates all of an embeddable's views to reflect the
 * image data stored in the embeddable.
 */
static void
embeddable_update_all_views (embeddable_data_t *embeddable_data)
{
	BonoboEmbeddable *embeddable;

	embeddable = embeddable_data->embeddable;

	bonobo_embeddable_foreach_view (embeddable, update_view_foreach, NULL);
}

/*
 * This function sets the view's current drawing color.
 */
static void
view_set_color (view_data_t *view_data, char *color)
{
	GdkColormap *colormap;
	GdkColor gdk_color;

	fprintf (stderr, "Set color to '%s'\n", color);

	colormap = gtk_widget_get_colormap (view_data->drawing_area);

	gdk_color_parse (color, &gdk_color);
	gdk_color_alloc (colormap, &gdk_color);

	gdk_gc_set_foreground (view_data->gc, &gdk_color);
}

static void
color_listener_cb (BonoboUIComponent *uic, const char *path,
		   Bonobo_UIComponent_EventType type,
		   const char *state, gpointer user_data)
{
	if (atoi (state)) {
		if (strstr (path, "Red") != NULL)
			view_set_color (user_data, "red");
		else if (strstr (path, "White") != NULL)
			view_set_color (user_data, "white");
		else if (strstr (path, "Green") != NULL)
			view_set_color (user_data, "green");
		else
			g_error ("set to unknown color");
	}
}

/*
 * When one of our views is activated, we merge our menus
 * in with our container's menus.
 */
static void
view_create_menus (view_data_t *view_data)
{
	Bonobo_UIContainer  remote_uic;
	BonoboView         *view = view_data->view;
	BonoboUIComponent  *uic;

	const char *ui_commands =
		"<commands>\n"
		"	<cmd name=\"ColorWhite\"  _label=\"White\" group=\"Color\"/>\n"
		"	<cmd name=\"ColorRed\"    _label=\"Red\"   group=\"Color\"/>\n"
		"	<cmd name=\"ColorGreen\"  _label=\"Green\" group=\"Color\"/>\n"
		"</commands>\n";

	const char *ui_menus =
		"<menu>\n"
		"	<submenu name=\"Colors\" _label=\"Colors\">\n"
		"		<menuitem name=\"ColorWhite\" type=\"radio\" verb=\"\"/>\n"
		"		<menuitem name=\"ColorRed\"   type=\"radio\" verb=\"\"/>\n"
		"		<menuitem name=\"ColorGreen\" type=\"radio\" verb=\"\"/>\n"
		"	</submenu>\n"
		"</menu>\n";

	/*
	 * Grab our BonoboUIComponent object.
	 */
	uic = bonobo_view_get_ui_component (view);

	/*
	 * Get our container's UIContainer server.
	 */
	remote_uic = bonobo_view_get_remote_ui_container (view);

	/*
	 * We have to deal gracefully with containers
	 * which don't have a UIContainer running.
	 */
	if (remote_uic == CORBA_OBJECT_NIL) {
		g_warning ("Can't get remote UIContainer");
		return;
	}

	/*
	 * Give our BonoboUIHandler object a reference to the
	 * container's UIContainer server.
	 */
	bonobo_ui_component_set_container (uic, remote_uic);

	bonobo_ui_component_set_translate (uic, "/", ui_commands, NULL);
	bonobo_ui_component_set_translate (uic, "/", ui_menus, NULL);

	bonobo_ui_component_add_listener (uic, "ColorWhite", color_listener_cb, view_data);
	bonobo_ui_component_add_listener (uic, "ColorRed",   color_listener_cb, view_data);
	bonobo_ui_component_add_listener (uic, "ColorGreen", color_listener_cb, view_data);

	bonobo_ui_component_thaw (uic, NULL);
}

/*
 * When this view is deactivated, we must remove our menu items.
 */
static void
view_remove_menus (view_data_t *view_data)
{
	BonoboView *view = view_data->view;
	BonoboUIComponent *uic;

	uic = bonobo_view_get_ui_component (view);

	bonobo_ui_component_unset_container (uic);
}

static void
view_activate_cb (BonoboView *view, gboolean activate, view_data_t *view_data)
{
	/*
	 * The ViewFrame has just asked the View (that's us) to be
	 * activated or deactivated.  We must reply to the ViewFrame
	 * and say whether or not we want our activation state to
	 * change.  We are an acquiescent BonoboView, so we just agree
	 * with whatever the ViewFrame told us.  Most components
	 * should behave this way.
	 */
	bonobo_view_activate_notify (view, activate);

	/*
	 * If we were just activated, we merge in our menu entries.
	 * If we were just deactivated, we remove them.
	 */
	if (activate)
		view_create_menus (view_data);
	else
		view_remove_menus (view_data);
}

/*
 * This callback is envoked when the view is destroyed.  We use it to
 * free up our ancillary view-centric data structures.
 */
static void
view_destroy_cb (BonoboView *view, view_data_t *view_data)
{
	gdk_gc_destroy (view_data->gc);
	g_free (view_data);
}

/*
 * Some parts of the View initialization must be forestalled until the
 * View window is actually realized.  We perform those here.
 */
static void
view_realize_cb (GtkWidget *drawing_area, view_data_t *view_data)
{
	view_set_color (view_data, "white");
	view_update (view_data);
}

/*
 * When a part of the window is exposed, we must redraw it.
 */
static void
view_expose_cb (GtkWidget *drawing_area, GdkEventExpose *event, view_data_t *view_data)
{
	view_update (view_data);
}

/*
 * This callback is invoked whenever the user moves the mouse
 * over the drawing area.
 */
static void
view_motion_notify_cb (GtkWidget *drawing_area, GdkEventMotion *event,
		       view_data_t *view_data)
{
	embeddable_data_t *embeddable_data = view_data->embeddable_data;

	/*
	 * If the mouse button is depressed, we update the internal
	 * representation of the image.  Then we update all the views.
	 */

	if (! (event->state & GDK_BUTTON1_MASK))
		return;

	/*
	 * First, update the internal representation of the image.  We
	 * store the image data internally in an off-screen GdkPixmap.
	 * This is just for the convenience of being able to use gdk
	 * routines to draw into it.  We could just as easily store
	 * this data in our own image buffer, or as a list of strokes
	 * which could be replayed, or whatever.
	 *
	 * We do the drawing using the view's graphics context.  This
	 * is because each view could have a different current drawing
	 * mode (pen color, style, etc).  In general, all views for a
	 * given embeddable are supposed to be identical, but they can
	 * differ in some small ways, such as the undo history and
	 * current editing mode.
	 */
	if (view_data->last_x != -1 && view_data->last_y != -1) {
		gdk_draw_line (embeddable_data->pixmap,
			       view_data->gc,
			       view_data->last_x, view_data->last_y,
			       (gint) event->x, (gint) event->y);
	}

	view_data->last_x = (gint) event->x;
	view_data->last_y = (gint) event->y;
		
	/*
	 * Now reflect this change to the image data in all the views.
	 */
	embeddable_update_all_views (embeddable_data);
}

static void
embeddable_clear_image (embeddable_data_t *embeddable_data)
{
	GdkGC *temp_gc;

	temp_gc = gdk_gc_new (embeddable_data->pixmap);

	gdk_draw_rectangle (embeddable_data->pixmap,
			    temp_gc,
			    TRUE, 0, 0,
			    embeddable_data->width,
			    embeddable_data->height);
	gdk_gc_destroy (temp_gc);
}

/*
 * This function is invoked whenever the container requests that a new
 * view be created for a given embeddable.  Its job is to constrct the
 * new view and return it.
 *
 * All views of a given Embeddable must be identical.  The purpose of
 * the "View" concept is to make it easy for containers to implement
 * split-screen editing, where the same document is displayed in two
 * different windows simultaneously.
 *
 * Views can differ in a few small ways: they can be at different
 * zooms, can have different undo histories, and so on.  But using
 * BonoboViews to implement two radically different representations of
 * a piece of data (e.g. a hexadecimal dump of an image and the image
 * itself) is a misuse of the classes.
 */
static BonoboView *
view_factory (BonoboEmbeddable       *embeddable,
	      const Bonobo_ViewFrame  view_frame,
	      embeddable_data_t      *embeddable_data)
{
	view_data_t *view_data;
	BonoboView  *view;
	GtkWidget   *vbox;

	/*
	 * Create the private view data.
	 */
	view_data = g_new0 (view_data_t, 1);
	view_data->embeddable_data = embeddable_data;

	view_data->last_x = -1;
	view_data->last_y = -1;
	view_data->width = embeddable_data->width;
	view_data->height = embeddable_data->width;

	/*
	 * Now create the drawing area which will be used to display
	 * the current image in this view.
	 */
	view_data->drawing_area = gtk_drawing_area_new ();

	gtk_drawing_area_size (GTK_DRAWING_AREA (view_data->drawing_area),
			       view_data->width,
			       view_data->height);

	gtk_widget_set_usize (view_data->drawing_area,
			      view_data->width,
			      view_data->height);

	/*
	 * We will use this event to actually draw into the
	 * Embeddable.
	 */
	gtk_widget_set_events (view_data->drawing_area,
			       gtk_widget_get_events (view_data->drawing_area) |
			       GDK_BUTTON_MOTION_MASK |
			       GDK_BUTTON_PRESS_MASK);

	gtk_signal_connect (GTK_OBJECT (view_data->drawing_area), "motion_notify_event",
			    GTK_SIGNAL_FUNC (view_motion_notify_cb), view_data);

	/*
	 * When the widget is realized, we will draw the current image
	 * data into it.
	 */
	gtk_signal_connect (GTK_OBJECT (view_data->drawing_area), "realize",
			    GTK_SIGNAL_FUNC (view_realize_cb), view_data);

	/*
	 * We have to redraw the view when we get expose events.
	 */
	gtk_signal_connect (GTK_OBJECT (view_data->drawing_area), "expose_event",
			    GTK_SIGNAL_FUNC (view_expose_cb), view_data);
	 

	/*
	 * Insert the drawing area into a vbox.
	 */
	vbox = gtk_vbox_new (FALSE, 0);

	gtk_box_pack_start (GTK_BOX (vbox),
			    view_data->drawing_area,
			    TRUE, TRUE, 0);

	gtk_widget_show_all (vbox);

	/*
	 * Each view has its own GC; we create that here.
	 */
	view_data->gc = gdk_gc_new (embeddable_data->pixmap);

	/*
	 * Create the BonoboView object.
	 */
	view = bonobo_view_new (vbox);
	view_data->view = view;
	gtk_object_set_data (GTK_OBJECT (view), "view_data", view_data);

	/*
	 * When our container wants to activate a given view of this
	 * component, we will get the "activate" signal.
	 */
	gtk_signal_connect (GTK_OBJECT (view), "activate",
			    GTK_SIGNAL_FUNC (view_activate_cb), view_data);

	/*
	 * The "system_exception" signal is raised when the BonoboView
	 * encounters a fatal CORBA exception.
	 */
	gtk_signal_connect (GTK_OBJECT (view), "system_exception",
			    GTK_SIGNAL_FUNC (view_system_exception_cb), view_data);

	/*
	 * We'll need to be able to cleanup when this view gets
	 * destroyed.
	 */
	gtk_signal_connect (GTK_OBJECT (view), "destroy",
			    GTK_SIGNAL_FUNC (view_destroy_cb), view_data);

	return view;
}

static void
render_fn (GnomePrintContext         *ctx,
	   double                     width,
	   double                     height,
	   const Bonobo_PrintScissor *scissor,
	   gpointer                   user_data)
{
	GnomeFont         *font;
	double             w;
	const char         str [] = "Hello World";

	gnome_print_setlinewidth (ctx, 2);
	font = gnome_font_new ("Helvetica", 12.0);
	g_return_if_fail (font != NULL);
	gnome_print_setrgbcolor (ctx, 0.0, 0.0, 0.0);
	gnome_print_setfont (ctx, font);

	w = gnome_font_get_width_string (font, str);
	gnome_print_moveto (ctx, (width / 2) - (w / 2),
			    height / 2);
	gnome_print_show (ctx, str);
	gtk_object_unref (GTK_OBJECT (font));

	gnome_print_moveto (ctx, 0, 0);
	gnome_print_lineto (ctx, width, height);
	gnome_print_stroke (ctx);

/* We need a sensible internal representation in order to find the rowstride etc. */
/*	gnome_print_rgbimage (ctx, embeddable_data->
						  embeddable_data->width,
						  embeddable_data->height,
						  gdk_visual_get_best_depth ());*/
}


/*
 * When a container asks our GenericFactory for a new paint
 * component, this function is called.  It creates the new
 * BonoboEmbeddable object and returns it.
 */
static BonoboObject *
embeddable_factory (BonoboGenericFactory *this,
		    void *data)
{
	BonoboEmbeddable *embeddable;
	BonoboObject     *print;
	embeddable_data_t *embeddable_data;

	/*
	 * Create a data structure in which we can store
	 * Embeddable-object-specific data about this document.
	 */
	embeddable_data = g_new0 (embeddable_data_t, 1);
	if (embeddable_data == NULL)
		return NULL;

	/*
	 * Our paint component is very simple.  It only works with one
	 * size of image.
	 */
	embeddable_data->width = 100;
	embeddable_data->height = 100;

	/*
	 * The embeddable must maintain an internal representation of
	 * the data for its document.  In our case, that document is
	 * an image, and it so happens that the most convenient way of
	 * storing an image for us is a GdkPixmap.
	 */
	embeddable_data->pixmap = gdk_pixmap_new (NULL,
						  embeddable_data->width,
						  embeddable_data->height,
						  gdk_visual_get_best_depth ());

	/*
	 * Blank the pixmap.
	 */
	embeddable_clear_image (embeddable_data);
	
	/*
	 * Create the BonoboEmbeddable object.
	 */
	embeddable = bonobo_embeddable_new (BONOBO_VIEW_FACTORY (view_factory),
					    embeddable_data);

	print = BONOBO_OBJECT (bonobo_print_new (render_fn, embeddable_data));
	if (!print)
		g_warning ("Serious error creating print interface");
	else
		bonobo_object_add_interface (BONOBO_OBJECT (embeddable),
					     BONOBO_OBJECT (print));

	if (embeddable == NULL) {
		g_free (embeddable_data);
		return NULL;
	}
	
	embeddable_data->embeddable = embeddable;

	/*
	 * If the Embeddable encounters a fatal CORBA exception, it
	 * will emit a "system_exception" signal, notifying us that
	 * the object is defunct.  Our callback --
	 * embeddable_system_exception_cb() -- destroys the defunct
	 * BonoboEmbeddable object.
	 */
	gtk_signal_connect (GTK_OBJECT (embeddable), "system_exception",
			    GTK_SIGNAL_FUNC (embeddable_system_exception_cb),
			    embeddable_data);

	/*
	 * Catch the destroy signal so that we can free up resources.
	 * When an Embeddable is destroyed, its views will
	 * automatically be destroyed.
	 */
	gtk_signal_connect (GTK_OBJECT (embeddable), "destroy",
			    GTK_SIGNAL_FUNC (embeddable_destroy_cb),
			    embeddable_data);

	return BONOBO_OBJECT (embeddable);
}

BONOBO_OAF_FACTORY ("OAFIID:Bonobo_Sample_Paint_EmbeddableFactory",
		    "bonobo-simple-paint", VERSION,
		    embeddable_factory,
		    NULL)


syntax highlighted by Code2HTML, v. 0.9.1