A sample container

To create the code which is detailed here, i first wrote the bonobo.idl file which is more or less the equivalent of the bonobo.idl file delivered with Bonobo. Just a few things were removed (ie: linking/saving and in-place activation stuff). Then, i ran orbit-idl on it with the --skeleton-impl option. This gave me a bonobo-skel-impl.c file. I then removed everything from this file which was a reference to the Embeddable interfaces and saved the result in the container.c file. I then created the component.c file by removing all references to container side stuff from bonobo-skel-impl.c. The resulting code with the Makefile is in Appendix D.

Then, all you need to do is to implement the different interfaces left in both files. The Makefile will build both files and the skeletons and stubs.

To build this code, you will need to latest CVS versions of Bonobo and ORBit. Once compiled, just launch the "container" executable and you will be able to add components and components' views. There is only One kind of component: a hand-made button... Here is a small screen shot from my PC with 2 different embeddables each having multiple views with different data.

Figure 7-2. Container/Button Containee

While this chapter concentrates on the inner workings of the container, the following chapter will answer all your questions on the button component.

Creating the Container interface.

The first step is to create the container interface. The following code is called when the "container" executable is launched. It will simply create a Container interface...

int main (int argc, char **argv)
{
  CORBA_Environment ev;
  CORBA_ORB orb;
  CORBA_Object root_poa;
  PortableServer_POAManager root_poa_manager;

  CORBA_Object obj;

  CORBA_exception_init (&ev);
  orb = gnome_CORBA_init ("a \"simple\" container", 
			  "1.0", 
			  &argc, 
			  argv, 
			  GNORBA_INIT_SERVER_FUNC, 
			  &ev);

  root_poa = CORBA_ORB_resolve_initial_references (orb, "RootPOA", &ev);

  root_poa_manager = PortableServer_POA__get_the_POAManager (
                     (PortableServer_POA) root_poa, &ev);
  PortableServer_POAManager_activate (root_poa_manager, &ev);

  /* this function will create all the other 
   * needed interfaces
   */
  obj = impl_GNOME_Container__create ((PortableServer_POA)
				      root_poa, 
				      &ev);


  CORBA_exception_free (&ev);
  gtk_main();
  return 0;
}

The impl_GNOME_Container__create was slightly modified from the automatically generated form to build the GUI to allow to user to interact with the program.

static GNOME_Container
impl_GNOME_Container__create(PortableServer_POA poa, 
			     CORBA_Environment * ev)
{
   GNOME_Container retval;
   impl_POA_GNOME_Container *newservant = 
     g_new0(impl_POA_GNOME_Container, 1);
   PortableServer_ObjectId *objid;
   GtkWidget *app;
   GnomeUIInfo menu_file[] = {
     GNOMEUIINFO_MENU_NEW_ITEM("New Component", 
			       "Add a new component",
			       add_component, 
			       newservant),
     GNOMEUIINFO_MENU_NEW_ITEM("New Component View", 
			       "Add a new View to a component",
			       add_component_view, 
			       newservant),

     GNOMEUIINFO_SEPARATOR,
     GNOMEUIINFO_MENU_EXIT_ITEM(app_quit, NULL),
     GNOMEUIINFO_END
   };
   GnomeUIInfo menu_help[] = {
     GNOMEUIINFO_END
   };
   GnomeUIInfo main_menu[] = {
     GNOMEUIINFO_SUBTREE("Files", menu_file),
     GNOMEUIINFO_SUBTREE("Help", menu_help),
     GNOMEUIINFO_END
   };


   newservant->servant.vepv = &impl_GNOME_Container_vepv;
   newservant->poa = poa;
   newservant->client_site_list = NULL;

   app = gnome_app_new ("container 1.0", "Container");
   gnome_app_create_menus (GNOME_APP(app), main_menu);
   gtk_window_set_default_size (GTK_WINDOW(app), 200, 200);
   newservant->box = gtk_vbox_new (TRUE, TRUE);
   gnome_app_set_contents (GNOME_APP(app), newservant->box);
   gtk_widget_show_all (app);

   POA_GNOME_Container__init((PortableServer_Servant) newservant, ev);
   objid = PortableServer_POA_activate_object(poa, newservant, ev);
   CORBA_free(objid);
   retval = PortableServer_POA_servant_to_reference(poa, newservant, ev);

   return retval;
}

This function simply creates a GnomeApp which contains a small menu. The "File" menu allows you to create a new component and embed it, create a new view for an already created component and exit. The exit callback is not fully implemented: it should destroy all allocated data and make sure embedded components are disconnected before quitting (i am lazy).

void app_quit (void)
{
  gtk_main_quit ();
}

The 2 other callbacks are much more complex and are discussed below.

Adding a Component

Creating a component is done with the add_component function which is passed as parameter the container servant. The first thing done is to ask the GOAD to create a component. Here, we ask for our hand-made component called "Component".

  list = goad_server_list_get ();
  embeddable = goad_server_activate_with_id (list, 
                                             "Component", 
                                             GOAD_ACTIVATE_NEW_ONLY, 
                                             NULL);
  goad_server_list_free (list);

Then, we create the ClientSite corresponding to this Embeddable.

  client_site_servant = impl_GNOME_ClientSite__create (poa, 
                                                       embeddable,
                                                       container_servant,
                                                       &ev);
  client_site = PortableServer_POA_servant_to_reference (poa, 
                                                         client_site_servant,
                                                         &ev);
  /* keep the client site ref in some safe place 
     for the container... */
  container_servant->client_site_list = 
    g_slist_append (container_servant->client_site_list, 
                    client_site_servant);

The impl_GNOME_ClientSite__create function was also modified from its automatically generated form: it now has 2 more parameters: "embeddable" and "container_servant".

static impl_POA_GNOME_ClientSite *
impl_GNOME_ClientSite__create(PortableServer_POA poa, 
                              GNOME_Embeddable embeddable, 
                              impl_POA_GNOME_Container *container_servant,
                              CORBA_Environment * ev)
{
   impl_POA_GNOME_ClientSite *retval;
   impl_POA_GNOME_ClientSite *newservant;
   PortableServer_ObjectId *objid;

   newservant = g_new0(impl_POA_GNOME_ClientSite, 1);
   newservant->servant.vepv = &impl_GNOME_ClientSite_vepv;
   newservant->poa = poa;
   newservant->embeddable = embeddable;
   newservant->container_servant = container_servant;

   POA_GNOME_ClientSite__init((PortableServer_Servant) newservant, ev);
   objid = PortableServer_POA_activate_object(poa, newservant, ev);
   CORBA_free(objid);
   retval = newservant;

   return retval;
}

The ClientSite stores a pointer to its embeddable and to its container servant in its embeddable and container_servant fields. (the corresponding structure is shown below.)

struct _impl_POA_GNOME_ClientSite
{
  POA_GNOME_ClientSite servant;
  PortableServer_POA poa;
  impl_POA_GNOME_Container *container_servant;
  impl_POA_GNOME_ViewFrame *active;
  GSList *view_frame_list;

  GNOME_Embeddable embeddable;
  CORBA_boolean show;
};

Once the ClientSite and its Embeddable are created, we must tell the Embeddable what ClientSite it will talk to:

  /* now, just give the newly created component 
     a ref to its ClientSite */
  GNOME_Embeddable_set_client_site (embeddable,
                                    client_site, 
                                    &ev);
  /* and set its host name. ie: the name which
     will be displayed on the handle of its
     editing window */
  GNOME_Embeddable_set_host_name (embeddable,
                                  "my_component", 
                                  "my_component", 
                                  &ev);

Last, we must create a ViewFrame/View so that the user has visual feedback of the fact the component was activated.

First, a ViewFrame is created. We give the newly created ViewFrame a pointer to its ClientSite with client_site_servant. Then, the ViewFrame is added to the ClientSite list of ViewFrames and then we add the ViewFrame GtkSocket to the container main window.

Once the ViewFrame is correctly initialized, we wan create the corresponding View with GNOME::Embeddable::new_view which is given its ViewFrame CORBA::Object reference as parameter and returns a GNOME::View object reference. The View object reference is stored in the ViewFrame structure.

Finally, we must give the GNOME::View its GtkSocket with GNOME::View::set_window. Now, the View should be displayed. and everything is fine :).

  view_frame_servant = (impl_POA_GNOME_ViewFrame *) 
    impl_GNOME_ViewFrame__create (poa, client_site_servant, &ev);
  view_frame = PortableServer_POA_servant_to_reference (poa, 
							view_frame_servant, 
							&ev);
  client_site_servant->view_frame_list = 
    g_slist_append (client_site_servant->view_frame_list, 
		    view_frame_servant);
  gtk_box_pack_start (GTK_BOX(container_servant->box), 
		      view_frame_servant->wrapper,
		      FALSE, 0, 0);
  /* realize the socket ... so that it has a GdkWindow 
     otherwise, the GdkWindow not existing will make the 
     prgm crash  when calling set_window...
     A hard to find bug...
  */
  gtk_widget_show_all (container_servant->box);

  /* now, create the View for this ViewFrame */
  view = GNOME_Embeddable_new_view (embeddable, view_frame, &ev);
  view_frame_servant->view = CORBA_Object_duplicate (view, &ev);


  /* Now, give the View its window */
  GNOME_View_set_window (view, 
			 GDK_WINDOW_XWINDOW(view_frame_servant->socket->window), 
			 &ev);

  /* pfew !! now, the view should be displayed ...*/

For your pleasure, here is the ViewFrame structure:

struct _impl_POA_GNOME_ViewFrame
{
   POA_GNOME_ViewFrame servant;
   PortableServer_POA poa;
   impl_POA_GNOME_ClientSite *client_site_servant;
   GNOME_View view;
   GtkWidget *socket;
   GtkWidget *wrapper;
   gint activate;
   GtkRequisition request;
   GtkAllocation allocation;

};

Adding a Component View

The function responsible for this task is obviously add_component_view. It receives as parameter a pointer to the container servant.

I decided to add a View to the last ClientSite of container->client_site_list. The first step is thus to find this ClientSite.

  last = g_slist_last (container_servant->client_site_list);
  client_site_servant = (impl_POA_GNOME_ClientSite *) last->data;
  client_site = PortableServer_POA_servant_to_reference (poa, 
                                                         client_site_servant, 
                                                         &ev);

Then, we create a new ViewFrame for our future View and update the ViewFrame list of the ClientSite. We also pack the new ViewFrame into the container main window and make sure the GtkSocket is realized (ie: it is given an X window by GTK).

  view_frame_servant = (impl_POA_GNOME_ViewFrame *) 
    impl_GNOME_ViewFrame__create (poa, client_site_servant, &ev);
  view_frame = PortableServer_POA_servant_to_reference (poa, 
                                                        view_frame_servant, 
                                                        &ev);
  client_site_servant->view_frame_list = 
    g_slist_append (client_site_servant->view_frame_list, 
                    view_frame_servant);
  gtk_box_pack_start (GTK_BOX(container_servant->box), 
                      view_frame_servant->wrapper,
                      FALSE, 0, 0);
  gtk_widget_show_all (container_servant->box);

Last, we create the Embeddable new view and we give the newly created View its X window.

  view = GNOME_Embeddable_new_view (client_site_servant->embeddable, 
                                    view_frame, 
                                    &ev);
  view_frame_servant->view = CORBA_Object_duplicate (view, &ev);
  GNOME_View_set_window (view, 
                         GDK_WINDOW_XWINDOW(view_frame_servant->socket->window), 
                         &ev);

Now, we have a brand new window displayed in our container window.

Activating a View

Once all views are created, we must allow the user to activate a View to edit it. Obviously, this is done with the view_frame_cb callback which is called every time the user double clicks on one of the inactive view.

There are 2 possibilities: either we have already an activated view or we have no other activated view. In the first case, we just deactivate the active view and we activate the requested view. In the second simpler case, we just activate the requested view.

void view_frame_cb (GtkWidget *widget, 
	            GdkEvent *event, 
                    gpointer data)
{
  impl_POA_GNOME_ViewFrame *servant = 
    (impl_POA_GNOME_ViewFrame*) data;
  impl_POA_GNOME_ClientSite *active_client_site = 
    servant->client_site_servant->container_servant->active;
  
  if (event->type == GDK_2BUTTON_PRESS 
      && active_client_site == NULL)
    activate_view (servant);
  /* here, no other view in the container was activated */
  else if (event->type == GDK_2BUTTON_PRESS 
	   && active_client_site != NULL) {
    /* here, another view is active. */
    deactivate_view (active_client_site->active);
    activate_view (servant);
  }
}

The activation is done with the activate_view function. We simply update the fields of our Container and ClientSite to be able to track active views and we call GNOME::View::activate to tell the newly activated view that it is now active.

void 
activate_view (impl_POA_GNOME_ViewFrame *servant)
{
  CORBA_Environment ev;

  CORBA_exception_init (&ev);
  GNOME_View_activate (servant->view,
                       1, 
                       &ev);
  servant->client_site_servant->active = servant;
  servant->client_site_servant->container_servant->active = 
    servant->client_site_servant;
  CORBA_exception_free (&ev);
}

deactivation is done with deactivate_view.

void 
deactivate_view (impl_POA_GNOME_ViewFrame *servant)
{
  CORBA_Environment ev;

  CORBA_exception_init (&ev);
  GNOME_View_activate (servant->view,
                       0, 
                       &ev);
  CORBA_exception_free (&ev);
}

Finally, the ViewFrame cover must be removed and painted to show the user that the editing took place. This is done by the GNOME::View which must call its GNOME::ViewFrame::view_activated.

static void
impl_GNOME_ViewFrame_view_activated(impl_POA_GNOME_ViewFrame * servant,
                                    CORBA_boolean state,
                                    CORBA_Environment * ev)
{
  servant->activate = state;
  if (state == 1)
    gnome_wrapper_set_covered (GNOME_WRAPPER(servant->wrapper), 
                               0);
  else if (state == 0    )
    gnome_wrapper_set_covered (GNOME_WRAPPER(servant->wrapper), 
                               1);

}

The user should now be able to edit the button text.