/* * An embeddable "paint" component. * * FIXME: This is supposed to be an example.. describe * * Author: * Nat Friedman (nat@nat.org) * */ #include #include #include #include /* * 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 = "\n" " \n" " \n" " \n" "\n"; const char *ui_menus = "\n" " \n" " \n" " \n" " \n" " \n" "\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)