Writing a Bonobo Container

Now, what's a control without a container? I will now show you how to write a control container to contain the pathetic little ipctrl, or any control you like. The code for the container you can find in test-container.c of the source code that accompanies this article.

Initializing

The control container is a lot like a normal GTK+/GNOME applications, with some specifics, on which I will focus. The main() looks a bit like this:

int 
main (int argc, char** argv)
{
	CORBA_ORB orb;
	
	gnome_init_with_popt_table ("test-container", "0.0",
				     argc, argv, oaf_popt_options, 0, NULL);

	/*
	 * initialize CORBA, OAF  and bonobo
	 */
	orb = oaf_init (argc,argv);
	if (!orb)
		g_error ("initializing orb failed");
	if (!bonobo_init (orb, NULL, NULL))
		g_error ("could not initialize Bonobo");


	/*
	 * We can't make any CORBA calls unless we're in the main
	 * loop.  So we delay creating the container here.
	 */
	gtk_idle_add ((GtkFunction) create_app, NULL);
	bonobo_main ();

	return 0;
}

      
As you can see, it's not that difficult. You need to initialize the ORB for use with OAF and you need to initialize Bonobo. And finally, you cannot do a normal gtk_main as we need to wait for Bonobo to initialize the (remote) control. Therefore, we need gtk_idle_add, and start with bonobo_main().

Filling the application

The interesting stuff of the program is done in container_create. Again, most of it is standard GTK+/GNOME stuff, so let's focus on the Bonobo specifics.
static guint
create_app (void)
{
	GtkWidget *box, *control, *label, *button;

	BonoboUIContainer *uic;
	
	/*
	 * create a bonobo application (window)
	 */
	bonobo_win = bonobo_window_new ("ipctrl-container",
					"a container for ipctrl's");
	gtk_widget_set_usize (GTK_WIDGET(bonobo_win), 200, 120);

        /*
	 * connect a ui container to the application
	 */
	uic = bonobo_ui_container_new ();
	bonobo_ui_container_set_win (uic, BONOBO_WINDOW(bonobo_win));
	

	/* get a widget, containing the control */
	control = bonobo_widget_new_control (IPCTRL_OAFIID, BONOBO_OBJREF (uic));
	if (!control) 
		g_error ("Can't create control\n");


	  (...)


	/* lights, camera, action */
	gtk_widget_show_all (GTK_WIDGET(bonobo_win));

	return FALSE; /* putting TRUE here is a bad idea. I warned you. */
}

	  
We start our application with bonobo_window_new (which creates a BonoboWindow, a GTKWindow subclass. (yes - that's a global variable. I'm sorry). Then, there's probably the most important line:
	  /* get a widget, containing the control */
	  control = bonobo_widget_new_control (IPCTRL_OAFIID,
					     bonobo_object_corba_objref(BONOBO_OBJECT(uic)));
	  if (!control) 
		g_error ("Can't create control\n");
	    
Here, Bonobo returns a widget containing the ipctrl. Notice how easy this is. We ignore the uic argument for now. When we have this widget, we can easily add it to our application, as shown in the remaining lines in create_app. These remaining lines are pretty standard GTK+ programming, so we won't discuss it any further.

Using the property bag on the container side

In the control, we made a property bag to manipulate the octets of the ipctrl. In the container application, we must get a reference to this property bag. In create_app there's the magic line (which we omitted in the previous):
	  /* add listener to this control's property bag */
	install_property_bag_listener (BONOBO_WIDGET(control), BONOBO_WINDOW(bonobo_win));
	
The magic function install_property_bag_listener does the following:

static void
install_property_bag_listener (BonoboWidget *control, BonoboWindow *bonobo_win)
{
	Bonobo_Listener corba_listener;
	BonoboListener *listener;
	BonoboControlFrame *control_frame;
	CORBA_Environment ev;

	CORBA_exception_init (&ev);
	
	 /*
	  *  the property bag is associated with the control frame, so get the
	  *  control frame first
	  */
	control_frame = bonobo_widget_get_control_frame (BONOBO_WIDGET(control));
	if (!control_frame) 
		g_error ("can't find control frame\n");

	/* 
	 * now, get a ref to the property bag
	 */
	prop_bag = bonobo_control_frame_get_control_property_bag (control_frame, NULL);
	if (prop_bag == CORBA_OBJECT_NIL)
		g_error ("can't connect to property bag\n");

	/*
	 * connect a listener to the property-bag
	 */
	bonobo_event_source_client_add_listener (prop_bag, on_prop_changed,
						 "Bonobo/Property:change:octet1", NULL, bonobo_win);
	CORBA_exception_free (&ev);	
}

	
The first part of this function deals with getting a reference to property bag. We can get such a reference through the control frame of the embedded control, and from this control frame, we can get a reference to the property bag.

Then, we can create a new property listener through bonobo_property_listener_new. We then do two things with the newly created 'listener'. First, we define a GTK+ signal handler, which will call on_prop_change when anything changes in the property bag. Second, we get a reference to the CORBA object that's wrapped by the listener. Using the bare CORBA object reference, we notify the property bag we are interested in notifications of any changes, through Bonobo_PropertyBag_addChangeListener. Note that the second argument to this function (""), means that we are interested in changes for any property.

Now, the on_prop_change is, again, very straightforward, and will update the title of the container window whenever it is called (whenever there's change in the property bag). The code doesn't contain too many surprises, the only interesting thing is probably the call to retrieve a value from the property bag, for example:
	  octets[0] = (guint8) bonobo_property_bag_client_get_value_gint (prop_bag, "octet1", NULL);
	

We also have a function on_clear which will set values. Very easy:
	  bonobo_property_bag_client_set_value_gint (prop_bag, "octet1", 0, NULL);
	
This concludes the discussion about property bag listeners; for more information, have a look at the source code, and try to understand it... also the relevant Bonobo header files and IDL files are very interesting reading material.