/* -*- mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Bonobo control object
*
* Authors:
* Michael Meeks (michael@ximian.com)
* Nat Friedman (nat@ximian.com)
* Miguel de Icaza (miguel@ximian.com)
* Maciej Stachowiak (mjs@eazel.com)
*
* Copyright 1999, 2001 Ximian, Inc.
* 2000 Eazel, Inc.
*/
#include <config.h>
#include <stdlib.h>
#include <string.h>
#include <gdkconfig.h>
#include <gtk/gtksignal.h>
#if defined (GDK_WINDOWING_X11)
#include <gdk/gdkx.h>
#elif defined (GDK_WINDOWING_WIN32)
#define interface _win32_interface
#include <gdk/gdkwin32.h>
#undef interface
#else
#error Port to this GDK backend
#endif
#include <bonobo/bonobo-main.h>
#include <bonobo/bonobo-plug.h>
#include <bonobo/bonobo-control.h>
#include <bonobo/bonobo-exception.h>
#include <bonobo/bonobo-ui-sync-menu.h>
#include <bonobo/bonobo-control-internal.h>
#include <bonobo/bonobo-property-bag-client.h>
enum {
PLUG_CREATED,
DISCONNECTED,
SET_FRAME,
ACTIVATE,
LAST_SIGNAL
};
static guint control_signals [LAST_SIGNAL];
static GObjectClass *bonobo_control_parent_class = NULL;
struct _BonoboControlPrivate {
Bonobo_ControlFrame frame;
BonoboControlFrame *inproc_frame;
BonoboUIComponent *ui_component;
Bonobo_PropertyBag propbag;
BonoboUIContainer *popup_ui_container;
BonoboUIComponent *popup_ui_component;
BonoboUIEngine *popup_ui_engine;
BonoboUISync *popup_ui_sync;
GtkWidget *plug;
GtkWidget *widget;
guint no_frame_timeout_id;
guint active : 1;
guint automerge : 1;
guint sent_disconnect : 1;
};
static void
bonobo_control_disconnected (BonoboControl *control)
{
g_return_if_fail (control != NULL);
g_return_if_fail (control->priv != NULL);
if (!control->priv->sent_disconnect) {
control->priv->sent_disconnect = TRUE;
g_signal_emit (control, control_signals [DISCONNECTED], 0);
}
}
static void
control_frame_connection_died_cb (gpointer connection,
gpointer user_data)
{
BonoboControl *control = BONOBO_CONTROL (user_data);
g_return_if_fail (control != NULL);
bonobo_control_disconnected (control);
dbgprintf ("The remote control frame died unexpectedly");
bonobo_object_unref (BONOBO_OBJECT (control));
}
void
bonobo_control_add_listener (CORBA_Object object,
GCallback fn,
gpointer user_data,
CORBA_Environment *ev)
{
ORBitConnectionStatus status;
if (object == CORBA_OBJECT_NIL)
return;
status = ORBit_small_listen_for_broken (
object, fn, user_data);
switch (status) {
case ORBIT_CONNECTION_CONNECTED:
break;
default:
dbgprintf ("premature CORBA_Object death");
bonobo_exception_general_error_set (
ev, NULL, "Control died prematurely");
break;
}
}
/**
* bonobo_control_window_id_from_x11:
* @x11_id: the x11 window id or Windows HWND.
*
* Return value: the window id or handle as a string; free after use.
**/
Bonobo_Gdk_WindowId
bonobo_control_window_id_from_x11 (guint32 x11_id)
{
gchar str[32];
snprintf (str, 31, "%u", x11_id);
str[31] = '\0';
/* printf ("Mangled %d to '%s'\n", x11_id, str);*/
return CORBA_string_dup (str);
}
/**
* bonobo_control_x11_from_window_id:
* @id: CORBA_char *
*
* De-mangle a window id string,
* fields are separated by ':' character,
* currently only the first field is used.
*
* Return value: the native window id.
**/
guint32
bonobo_control_x11_from_window_id (const CORBA_char *id)
{
guint32 x11_id;
char **elements;
/* printf ("ID string '%s'\n", id);*/
elements = g_strsplit (id, ":", -1);
if (elements && elements [0])
x11_id = strtol (elements [0], NULL, 10);
else {
#if defined (GDK_WINDOWING_X11)
g_warning ("Serious X id mangling error");
#elif defined (GDK_WINDOWING_WIN32)
g_warning ("Serious window handle mangling error");
#endif
x11_id = 0;
}
g_strfreev (elements);
/* printf ("x11 : %d\n", x11_id);*/
return x11_id;
}
static void
bonobo_control_auto_merge (BonoboControl *control)
{
Bonobo_UIContainer remote_container;
if (control->priv->ui_component == NULL)
return;
/*
* this makes a CORBA call, so re-entrancy can occur here
*/
remote_container = bonobo_control_get_remote_ui_container (control, NULL);
if (remote_container == CORBA_OBJECT_NIL)
return;
/*
* we could have been re-entereted in the previous call, so
* make sure we are still active
*/
if (control->priv->active)
bonobo_ui_component_set_container (
control->priv->ui_component, remote_container, NULL);
bonobo_object_release_unref (remote_container, NULL);
}
static void
bonobo_control_auto_unmerge (BonoboControl *control)
{
if (control->priv->ui_component == NULL)
return;
bonobo_ui_component_unset_container (control->priv->ui_component, NULL);
}
static void
impl_Bonobo_Control_activate (PortableServer_Servant servant,
CORBA_boolean activated,
CORBA_Environment *ev)
{
BonoboControl *control = BONOBO_CONTROL (bonobo_object_from_servant (servant));
gboolean old_activated;
if (activated == control->priv->active)
return;
/*
* store the old activated value as we can be re-entered
* during (un)merge
*/
old_activated = control->priv->active;
control->priv->active = activated;
if (control->priv->automerge) {
if (activated)
bonobo_control_auto_merge (control);
else
bonobo_control_auto_unmerge (control);
}
/*
* if our active state is not what we are changing it to, then
* don't emit the signal
*/
if (control->priv->active == old_activated)
return;
g_signal_emit (control, control_signals [ACTIVATE], 0, control->priv->active);
bonobo_control_activate_notify (control, control->priv->active, ev);
}
static void
bonobo_control_unset_control_frame (BonoboControl *control,
CORBA_Environment *opt_ev)
{
CORBA_Environment *ev, tmp_ev;
if (!opt_ev) {
CORBA_exception_init (&tmp_ev);
ev = &tmp_ev;
} else
ev = opt_ev;
if (control->priv->no_frame_timeout_id != 0) {
g_source_remove (control->priv->no_frame_timeout_id);
control->priv->no_frame_timeout_id = 0;
}
if (control->priv->frame != CORBA_OBJECT_NIL) {
Bonobo_ControlFrame frame = control->priv->frame;
control->priv->frame = CORBA_OBJECT_NIL;
ORBit_small_unlisten_for_broken (
frame, G_CALLBACK (control_frame_connection_died_cb));
if (control->priv->active)
Bonobo_ControlFrame_notifyActivated (
frame, FALSE, ev);
CORBA_Object_release (frame, ev);
}
if (!opt_ev)
CORBA_exception_free (&tmp_ev);
}
static void
create_plug (BonoboControl *control)
{
GtkWidget *plug;
plug = bonobo_plug_new (0);
g_object_ref_sink (plug);
bonobo_control_set_plug (control, BONOBO_PLUG (plug));
if (control->priv->widget)
gtk_container_add (GTK_CONTAINER (plug),
control->priv->widget);
g_signal_emit (control, control_signals [PLUG_CREATED], 0);
g_object_unref (plug);
}
static int
parse_cookie (const CORBA_char *cookie)
{
GString *ident = NULL;
GString *value = NULL;
const char *p;
char *screen = NULL;
int retval = -1;
for (p = cookie; *p && !screen; p++) {
switch (*p) {
case ',':
if (!ident || !value)
goto parse_failed;
if (strcmp (ident->str, "screen")) {
g_string_free (ident, TRUE); ident = NULL;
g_string_free (value, TRUE); value = NULL;
break;
}
screen = value->str;
break;
case '=':
if (!ident || value)
goto parse_failed;
value = g_string_new (NULL);
break;
default:
if (!ident)
ident = g_string_new (NULL);
if (value)
g_string_append_c (value, *p);
else
g_string_append_c (ident, *p);
break;
}
}
if (ident && value && !strcmp (ident->str, "screen"))
screen = value->str;
if (screen)
retval = atoi (screen);
parse_failed:
if (ident) g_string_free (ident, TRUE);
if (value) g_string_free (value, TRUE);
return retval;
}
static CORBA_char *
impl_Bonobo_Control_getWindowId (PortableServer_Servant servant,
const CORBA_char *cookie,
CORBA_Environment *ev)
{
guint32 x11_id;
BonoboControl *control = BONOBO_CONTROL (
bonobo_object_from_servant (servant));
GdkScreen *gdkscreen;
int screen_num;
if (!control->priv->plug)
create_plug (control);
g_assert (control->priv->plug != NULL);
screen_num = parse_cookie (cookie);
if (screen_num != -1)
gdkscreen = gdk_display_get_screen (
gdk_display_get_default (), screen_num);
else
gdkscreen = gdk_screen_get_default ();
gtk_window_set_screen (GTK_WINDOW (control->priv->plug), gdkscreen);
gtk_widget_show (control->priv->plug);
x11_id = gtk_plug_get_id (GTK_PLUG (control->priv->plug));
dbgprintf ("plug id %u\n", x11_id);
return bonobo_control_window_id_from_x11 (x11_id);
}
static Bonobo_UIContainer
impl_Bonobo_Control_getPopupContainer (PortableServer_Servant servant,
CORBA_Environment *ev)
{
BonoboUIContainer *container;
container = bonobo_control_get_popup_ui_container (
BONOBO_CONTROL (bonobo_object_from_servant (servant)));
return bonobo_object_dup_ref (BONOBO_OBJREF (container), ev);
}
static void
impl_Bonobo_Control_setFrame (PortableServer_Servant servant,
Bonobo_ControlFrame frame,
CORBA_Environment *ev)
{
BonoboControl *control = BONOBO_CONTROL (
bonobo_object_from_servant (servant));
g_object_ref (control);
if (control->priv->frame != frame) {
bonobo_control_unset_control_frame (control, ev);
if (frame == CORBA_OBJECT_NIL)
control->priv->frame = CORBA_OBJECT_NIL;
else {
control->priv->frame = CORBA_Object_duplicate (
frame, NULL);
}
control->priv->inproc_frame = (BonoboControlFrame *)
bonobo_object (ORBit_small_get_servant (frame));
if (!control->priv->inproc_frame)
bonobo_control_add_listener (
frame,
G_CALLBACK (control_frame_connection_died_cb),
control, ev);
g_signal_emit (control, control_signals [SET_FRAME], 0);
}
g_object_unref (control);
}
static void
impl_Bonobo_Control_setSize (PortableServer_Servant servant,
const CORBA_short width,
const CORBA_short height,
CORBA_Environment *ev)
{
GtkAllocation size;
BonoboControl *control = BONOBO_CONTROL (
bonobo_object_from_servant (servant));
size.x = size.y = 0;
size.width = width;
size.height = height;
/*
* In the Gnome implementation of Bonobo, all size assignment
* is handled by GtkPlug/GtkSocket for us.
*/
g_warning ("setSize untested");
gtk_widget_size_allocate (GTK_WIDGET (control->priv->plug), &size);
}
static Bonobo_Gtk_Requisition
impl_Bonobo_Control_getDesiredSize (PortableServer_Servant servant,
CORBA_Environment *ev)
{
BonoboControl *control;
GtkRequisition requisition;
Bonobo_Gtk_Requisition req;
control = BONOBO_CONTROL (bonobo_object (servant));
gtk_widget_size_request (control->priv->widget, &requisition);
req.width = requisition.width;
req.height = requisition.height;
return req;
}
static GtkStateType
bonobo_control_gtk_state_from_corba (const Bonobo_Gtk_State state)
{
switch (state) {
case Bonobo_Gtk_StateNormal:
return GTK_STATE_NORMAL;
case Bonobo_Gtk_StateActive:
return GTK_STATE_ACTIVE;
case Bonobo_Gtk_StatePrelight:
return GTK_STATE_PRELIGHT;
case Bonobo_Gtk_StateSelected:
return GTK_STATE_SELECTED;
case Bonobo_Gtk_StateInsensitive:
return GTK_STATE_INSENSITIVE;
default:
g_warning ("bonobo_control_gtk_state_from_corba: Unknown state: %d", (gint) state);
return GTK_STATE_NORMAL;
}
}
static void
impl_Bonobo_Control_setState (PortableServer_Servant servant,
const Bonobo_Gtk_State state,
CORBA_Environment *ev)
{
BonoboControl *control = BONOBO_CONTROL (bonobo_object_from_servant (servant));
GtkStateType gtk_state = bonobo_control_gtk_state_from_corba (state);
g_return_if_fail (control->priv->widget != NULL);
if (gtk_state == GTK_STATE_INSENSITIVE)
gtk_widget_set_sensitive (control->priv->widget, FALSE);
else {
if (! GTK_WIDGET_SENSITIVE (control->priv->widget))
gtk_widget_set_sensitive (control->priv->widget, TRUE);
gtk_widget_set_state (control->priv->widget,
gtk_state);
}
}
static Bonobo_PropertyBag
impl_Bonobo_Control_getProperties (PortableServer_Servant servant,
CORBA_Environment *ev)
{
BonoboControl *control = BONOBO_CONTROL (bonobo_object_from_servant (servant));
if (control->priv->propbag == CORBA_OBJECT_NIL)
return CORBA_OBJECT_NIL;
return bonobo_object_dup_ref (control->priv->propbag, ev);
}
static CORBA_boolean
impl_Bonobo_Control_focus (PortableServer_Servant servant,
Bonobo_Gtk_Direction corba_direction,
CORBA_Environment *ev)
{
BonoboControl *control;
GtkDirectionType direction;
BonoboControlPrivate *priv;
control = BONOBO_CONTROL (bonobo_object (servant));
priv = control->priv;
/* FIXME: this will not work for local controls. */
if (!priv->plug)
return FALSE;
switch (corba_direction) {
case Bonobo_Gtk_DirectionTabForward:
direction = GTK_DIR_TAB_FORWARD;
break;
case Bonobo_Gtk_DirectionTabBackward:
direction = GTK_DIR_TAB_BACKWARD;
break;
case Bonobo_Gtk_DirectionUp:
direction = GTK_DIR_UP;
break;
case Bonobo_Gtk_DirectionDown:
direction = GTK_DIR_DOWN;
break;
case Bonobo_Gtk_DirectionLeft:
direction = GTK_DIR_LEFT;
break;
case Bonobo_Gtk_DirectionRight:
direction = GTK_DIR_RIGHT;
break;
default:
/* Hmmm, we should throw an exception. */
return FALSE;
}
return gtk_widget_child_focus (GTK_WIDGET (priv->plug), direction);
}
#define DEFAULT_CONTROL_PURGE_DELAY_MS 60 * 1000 /* 60 seconds */
static int control_purge_delay = DEFAULT_CONTROL_PURGE_DELAY_MS;
/**
* bonobo_control_life_set_purge:
* @ms: time to wait in milliseconds.
*
* Set time we're prepared to wait without a ControlFrame
* before terminating the Control. This can happen if the
* panel activates us but crashes before the set_frame.
**/
void
bonobo