+
+/*****************************************************************
+ * Status Window handling
+ *
+ * A status window is a small window attached to the toplevel
+ * that is used to display information to the user about the
+ * current input operation.
+ *
+ * We claim the toplevel's status window for an input context if:
+ *
+ * A) The input context has a toplevel
+ * B) The input context has the focus
+ * C) The input context has an XIC associated with it
+ *
+ * Tracking A) and C) is pretty reliable since we
+ * compute A) and create the XIC for C) ourselves.
+ * For B) we basically have to depend on our callers
+ * calling ::focus-in and ::focus-out at the right time.
+ *
+ * The toplevel is computed by walking up the GdkWindow
+ * hierarchy from context->client_window until we find a
+ * window that is owned by some widget, and then calling
+ * gtk_widget_get_toplevel() on that widget. This should
+ * handle both cases where we might have GdkWindows without widgets,
+ * and cases where GtkWidgets have strange window hierarchies
+ * (like a torn off GtkHandleBox.)
+ *
+ * The status window is visible if and only if there is text
+ * for it; whenever a new GtkIMContextXIM claims the status
+ * window, we blank out any existing text. We actually only
+ * create a GtkWindow for the status window the first time
+ * it is shown; this is an important optimization when we are
+ * using XIM with something like a simple compose-key input
+ * method that never needs a status window.
+ *****************************************************************/
+
+/* Called when we no longer need a status window
+*/
+static void
+disclaim_status_window (GtkIMContextXIM *context_xim)
+{
+ if (context_xim->status_window)
+ {
+ g_assert (context_xim->status_window->context == context_xim);
+
+ status_window_set_text (context_xim->status_window, "");
+
+ context_xim->status_window->context = NULL;
+ context_xim->status_window = NULL;
+ }
+}
+
+/* Called when we need a status window
+ */
+static void
+claim_status_window (GtkIMContextXIM *context_xim)
+{
+ if (!context_xim->status_window && context_xim->client_widget)
+ {
+ GtkWidget *toplevel = gtk_widget_get_toplevel (context_xim->client_widget);
+ if (toplevel && GTK_WIDGET_TOPLEVEL (toplevel))
+ {
+ StatusWindow *status_window = status_window_get (toplevel);
+
+ if (status_window->context)
+ disclaim_status_window (status_window->context);
+
+ status_window->context = context_xim;
+ context_xim->status_window = status_window;
+ }
+ }
+}
+
+/* Basic call made whenever something changed that might cause
+ * us to need, or not to need a status window.
+ */
+static void
+update_status_window (GtkIMContextXIM *context_xim)
+{
+ if (context_xim->ic && context_xim->in_toplevel && context_xim->has_focus)
+ claim_status_window (context_xim);
+ else
+ disclaim_status_window (context_xim);
+}
+
+/* Updates the in_toplevel flag for @context_xim
+ */
+static void
+update_in_toplevel (GtkIMContextXIM *context_xim)
+{
+ if (context_xim->client_widget)
+ {
+ GtkWidget *toplevel = gtk_widget_get_toplevel (context_xim->client_widget);
+
+ context_xim->in_toplevel = (toplevel && GTK_WIDGET_TOPLEVEL (toplevel));
+ }
+ else
+ context_xim->in_toplevel = FALSE;
+
+ /* Some paranoia, in case we don't get a focus out */
+ if (!context_xim->in_toplevel)
+ context_xim->has_focus = FALSE;
+
+ update_status_window (context_xim);
+}
+
+/* Callback when @widget's toplevel changes. It will always
+ * change from NULL to a window, or a window to NULL;
+ * we use that intermediate NULL state to make sure
+ * that we disclaim the toplevel status window for the old
+ * window.
+ */
+static void
+on_client_widget_hierarchy_changed (GtkWidget *widget,
+ GtkWidget *old_toplevel,
+ GtkIMContextXIM *context_xim)
+{
+ update_in_toplevel (context_xim);
+}
+
+/* Finds the GtkWidget that owns the window, or if none, the
+ * widget owning the nearest parent that has a widget.
+ */
+static GtkWidget *
+widget_for_window (GdkWindow *window)
+{
+ while (window)
+ {
+ gpointer user_data;
+ gdk_window_get_user_data (window, &user_data);
+ if (user_data)
+ return user_data;
+
+ window = gdk_window_get_parent (window);
+ }
+
+ return NULL;
+}
+
+/* Called when context_xim->client_window changes; takes care of
+ * removing and/or setting up our watches for the toplevel
+ */
+static void
+update_client_widget (GtkIMContextXIM *context_xim)
+{
+ GtkWidget *new_client_widget = widget_for_window (context_xim->client_window);
+
+ if (new_client_widget != context_xim->client_widget)
+ {
+ if (context_xim->client_widget)
+ {
+ g_signal_handlers_disconnect_by_func (context_xim->client_widget,
+ G_CALLBACK (on_client_widget_hierarchy_changed),
+ context_xim);
+ }
+ context_xim->client_widget = new_client_widget;
+ if (context_xim->client_widget)
+ {
+ g_signal_connect (context_xim->client_widget, "hierarchy-changed",
+ G_CALLBACK (on_client_widget_hierarchy_changed),
+ context_xim);
+ }
+
+ update_in_toplevel (context_xim);
+ }
+}
+
+/* Called when the toplevel is destroyed; frees the status window
+ */
+static void
+on_status_toplevel_destroy (GtkWidget *toplevel,
+ StatusWindow *status_window)
+{
+ status_window_free (status_window);
+}
+
+/* Called when the screen for the toplevel changes; updates the
+ * screen for the status window to match.
+ */
+static void
+on_status_toplevel_notify_screen (GtkWindow *toplevel,
+ GParamSpec *pspec,
+ StatusWindow *status_window)
+{
+ if (status_window->window)
+ gtk_window_set_screen (GTK_WINDOW (status_window->window),
+ gtk_widget_get_screen (GTK_WIDGET (toplevel)));
+}
+
+/* Called when the toplevel window is moved; updates the position of
+ * the status window to follow it.
+ */
+static gboolean
+on_status_toplevel_configure (GtkWidget *toplevel,
+ GdkEventConfigure *event,
+ StatusWindow *status_window)
+{
+ GdkRectangle rect;
+ GtkRequisition requisition;
+ gint y;
+ gint height;
+
+ if (status_window->window)
+ {
+ height = gdk_screen_get_height (gtk_widget_get_screen (toplevel));
+
+ gdk_window_get_frame_extents (toplevel->window, &rect);
+ gtk_widget_size_request (status_window->window, &requisition);
+
+ if (rect.y + rect.height + requisition.height < height)
+ y = rect.y + rect.height;
+ else
+ y = height - requisition.height;
+
+ gtk_window_move (GTK_WINDOW (status_window->window), rect.x, y);
+ }
+
+ return FALSE;
+}
+
+/* Frees a status window and removes its link from the status_windows list
+ */
+static void
+status_window_free (StatusWindow *status_window)
+{
+ status_windows = g_slist_remove (status_windows, status_window);
+
+ if (status_window->context)
+ status_window->context->status_window = NULL;
+
+ g_signal_handlers_disconnect_by_func (status_window->toplevel,
+ G_CALLBACK (on_status_toplevel_destroy),
+ status_window);
+ g_signal_handlers_disconnect_by_func (status_window->toplevel,
+ G_CALLBACK (on_status_toplevel_notify_screen),
+ status_window);
+ g_signal_handlers_disconnect_by_func (status_window->toplevel,
+ G_CALLBACK (on_status_toplevel_configure),
+ status_window);
+
+ if (status_window->window)
+ gtk_widget_destroy (status_window->window);
+
+ g_object_set_data (G_OBJECT (status_window->toplevel), "gtk-im-xim-status-window", NULL);
+
+ g_free (status_window);
+}
+
+/* Finds the status window object for a toplevel, creating it if necessary.
+ */
+static StatusWindow *
+status_window_get (GtkWidget *toplevel)
+{
+ StatusWindow *status_window;
+
+ status_window = g_object_get_data (G_OBJECT (toplevel), "gtk-im-xim-status-window");
+ if (status_window)
+ return status_window;
+
+ status_window = g_new0 (StatusWindow, 1);
+ status_window->toplevel = toplevel;
+
+ status_windows = g_slist_prepend (status_windows, status_window);
+
+ g_signal_connect (toplevel, "destroy",
+ G_CALLBACK (on_status_toplevel_destroy),
+ status_window);
+ g_signal_connect (toplevel, "configure_event",
+ G_CALLBACK (on_status_toplevel_configure),
+ status_window);
+ g_signal_connect (toplevel, "notify::screen",
+ G_CALLBACK (on_status_toplevel_notify_screen),
+ status_window);
+
+ g_object_set_data (G_OBJECT (toplevel), "gtk-im-xim-status-window", status_window);
+
+ return status_window;
+}
+
+/* Draw the background (normally white) and border for the status window
+ */
+static gboolean
+on_status_window_expose_event (GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ gdk_draw_rectangle (widget->window,
+ widget->style->base_gc [GTK_STATE_NORMAL],
+ TRUE,
+ 0, 0,
+ widget->allocation.width, widget->allocation.height);
+ gdk_draw_rectangle (widget->window,
+ widget->style->text_gc [GTK_STATE_NORMAL],
+ FALSE,
+ 0, 0,
+ widget->allocation.width - 1, widget->allocation.height - 1);
+
+ return FALSE;
+}
+
+/* We watch the ::style-set signal for our label widget
+ * and use that to change it's foreground color to match
+ * the 'text' color of the toplevel window. The text/base
+ * pair of colors might be reversed from the fg/bg pair
+ * that are normally used for labels.
+ */
+static void
+on_status_window_style_set (GtkWidget *toplevel,
+ GtkStyle *previous_style,
+ GtkWidget *label)
+{
+ gint i;
+
+ for (i = 0; i < 5; i++)
+ gtk_widget_modify_fg (label, i, &toplevel->style->text[i]);
+}
+
+/* Creates the widgets for the status window; called when we
+ * first need to show text for the status window.
+ */
+static void
+status_window_make_window (StatusWindow *status_window)
+{
+ GtkWidget *window;
+ GtkWidget *status_label;
+
+ status_window->window = gtk_window_new (GTK_WINDOW_POPUP);
+ window = status_window->window;
+
+ gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
+ gtk_widget_set_app_paintable (window, TRUE);
+
+ status_label = gtk_label_new ("");
+ gtk_misc_set_padding (GTK_MISC (status_label), 1, 1);
+ gtk_widget_show (status_label);
+
+ g_signal_connect (window, "style_set",
+ G_CALLBACK (on_status_window_style_set), status_label);
+ gtk_container_add (GTK_CONTAINER (window), status_label);
+
+ g_signal_connect (window, "expose_event",
+ G_CALLBACK (on_status_window_expose_event), NULL);
+
+ gtk_window_set_screen (GTK_WINDOW (status_window->window),
+ gtk_widget_get_screen (status_window->toplevel));
+
+ on_status_toplevel_configure (status_window->toplevel, NULL, status_window);
+}
+
+/* Updates the text in the status window, hiding or
+ * showing the window as necessary.
+ */
+static void
+status_window_set_text (StatusWindow *status_window,
+ const gchar *text)
+{
+ if (text[0])
+ {
+ GtkWidget *label;
+
+ if (!status_window->window)
+ status_window_make_window (status_window);
+
+ label = GTK_BIN (status_window->window)->child;
+ gtk_label_set_text (GTK_LABEL (label), text);
+
+ gtk_widget_show (status_window->window);
+ }
+ else
+ {
+ if (status_window->window)
+ gtk_widget_hide (status_window->window);
+ }
+}
+
+/**
+ * gtk_im_context_xim_shutdown:
+ *
+ * Destroys all the status windows that are kept by the XIM contexts. This
+ * function should only be called by the XIM module exit routine.
+ **/
+void
+gtk_im_context_xim_shutdown (void)
+{
+ while (status_windows)
+ status_window_free (status_windows->data);
+}