Blob Blame History Raw
/*
 * Copyright (C) 2006 Sergey V. Udaltsov <svu@gnome.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include <config.h>

#include <memory.h>

#include <gdk/gdkkeysyms.h>
#include <gdk/gdkx.h>
#include <glib/gi18n-lib.h>

#include <gkbd-indicator.h>
#include <gkbd-indicator-marshal.h>

#include <gkbd-desktop-config.h>
#include <gkbd-indicator-config.h>
#include <gkbd-configuration.h>

typedef struct _gki_globals {
	GkbdConfiguration *config;

	GSList *images;
} gki_globals;

struct _GkbdIndicatorPrivate {
	gboolean set_parent_tooltips;
	gdouble angle;
};

/* one instance for ALL widgets */
static gki_globals globals;

G_DEFINE_TYPE (GkbdIndicator, gkbd_indicator, GTK_TYPE_NOTEBOOK)

static void
gkbd_indicator_global_init (void);
static void
gkbd_indicator_global_term (void);
static GtkWidget *
gkbd_indicator_prepare_drawing (GkbdIndicator * gki, int group);
static void
gkbd_indicator_set_current_page_for_group (GkbdIndicator * gki, int group);
static void
gkbd_indicator_set_current_page (GkbdIndicator * gki);
static void
gkbd_indicator_cleanup (GkbdIndicator * gki);
static void
gkbd_indicator_fill (GkbdIndicator * gki);
static void
gkbd_indicator_set_tooltips (GkbdIndicator * gki, const char *str);

void
gkbd_indicator_set_tooltips (GkbdIndicator * gki, const char *str)
{
	g_assert (str == NULL || g_utf8_validate (str, -1, NULL));

	gtk_widget_set_tooltip_text (GTK_WIDGET (gki), str);

	if (gki->priv->set_parent_tooltips) {
		GtkWidget *parent =
		    gtk_widget_get_parent (GTK_WIDGET (gki));
		if (parent) {
			gtk_widget_set_tooltip_text (parent, str);
		}
	}
}

void
gkbd_indicator_cleanup (GkbdIndicator * gki)
{
	int i;
	GtkNotebook *notebook = GTK_NOTEBOOK (gki);

	/* Do not remove the first page! It is the default page */
	for (i = gtk_notebook_get_n_pages (notebook); --i > 0;) {
		gtk_notebook_remove_page (notebook, i);
	}
}

void
gkbd_indicator_fill (GkbdIndicator * gki)
{
	int grp;
	int total_groups =
	    xkl_engine_get_num_groups (gkbd_configuration_get_xkl_engine
				       (globals.config));
	GtkNotebook *notebook = GTK_NOTEBOOK (gki);
	gchar **full_group_names =
	    gkbd_configuration_get_group_names (globals.config);

	for (grp = 0; grp < total_groups; grp++) {
		GtkWidget *page = NULL;
		gchar *full_group_name =
		    (grp <
		     g_strv_length (full_group_names)) ?
		    full_group_names[grp] : "?";
		page = gkbd_indicator_prepare_drawing (gki, grp);

		if (page == NULL)
			page = gtk_label_new ("");

		gtk_notebook_append_page (notebook, page, NULL);
		gtk_widget_show_all (page);
	}
}

static gboolean
gkbd_indicator_key_pressed (GtkWidget *
			    widget, GdkEventKey * event,
			    GkbdIndicator * gki)
{
	switch (event->keyval) {
	case GDK_KEY_KP_Enter:
	case GDK_KEY_ISO_Enter:
	case GDK_KEY_3270_Enter:
	case GDK_KEY_Return:
	case GDK_KEY_space:
	case GDK_KEY_KP_Space:
		gkbd_configuration_lock_next_group (globals.config);
		return TRUE;
	default:
		break;
	}
	return FALSE;
}

static gboolean
gkbd_indicator_button_pressed (GtkWidget *
			       widget,
			       GdkEventButton * event, GkbdIndicator * gki)
{
	GtkWidget *img = gtk_bin_get_child (GTK_BIN (widget));
	GtkAllocation allocation;
	gtk_widget_get_allocation (img, &allocation);
	xkl_debug (150, "Flag img size %d x %d\n",
		   allocation.width, allocation.height);
	if (event->button == 1 && event->type == GDK_BUTTON_PRESS) {
		xkl_debug (150, "Mouse button pressed on applet\n");
		gkbd_configuration_lock_next_group (globals.config);
		return TRUE;
	}
	return FALSE;
}

static void
draw_flag (GtkWidget * flag, cairo_t * cr, GdkPixbuf * image)
{
	/* Image width and height */
	int iw = gdk_pixbuf_get_width (image);
	int ih = gdk_pixbuf_get_height (image);
	GtkAllocation allocation;
	double xwiratio, ywiratio, wiratio;

	gtk_widget_get_allocation (flag, &allocation);

	/* widget-to-image scales, X and Y */
	xwiratio = 1.0 * allocation.width / iw;
	ywiratio = 1.0 * allocation.height / ih;
	wiratio = xwiratio < ywiratio ? xwiratio : ywiratio;

	/* transform cairo context */
	cairo_translate (cr, allocation.width / 2.0,
			 allocation.height / 2.0);
	cairo_scale (cr, wiratio, wiratio);
	cairo_translate (cr, -iw / 2.0, -ih / 2.0);

	gdk_cairo_set_source_pixbuf (cr, image, 0, 0);
	cairo_paint (cr);
}

static GtkWidget *
gkbd_indicator_prepare_drawing (GkbdIndicator * gki, int group)
{
	gpointer pimage;
	GdkPixbuf *image;
	GtkWidget *ebox;

	pimage = g_slist_nth_data (globals.images, group);
	ebox = gtk_event_box_new ();
	gtk_event_box_set_visible_window (GTK_EVENT_BOX (ebox), FALSE);
	if (gkbd_configuration_if_flags_shown (globals.config)) {
		GtkWidget *flag;
		if (pimage == NULL)
			return NULL;
		image = GDK_PIXBUF (pimage);
		flag = gtk_drawing_area_new ();
		gtk_widget_add_events (GTK_WIDGET (flag),
				       GDK_BUTTON_PRESS_MASK);
		g_signal_connect (G_OBJECT (flag), "draw",
				  G_CALLBACK (draw_flag), image);
		gtk_container_add (GTK_CONTAINER (ebox), flag);
	} else {
		char *lbl_title = NULL;
		char *layout_name = NULL;
		GtkWidget *align, *label;
		static GHashTable *ln2cnt_map = NULL;

		layout_name =
		    gkbd_configuration_extract_layout_name (globals.config,
							    group);

		lbl_title =
		    gkbd_configuration_create_label_title (group,
							   &ln2cnt_map,
							   layout_name);

		align = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
		label = gtk_label_new (lbl_title);
		g_free (lbl_title);
		gtk_label_set_angle (GTK_LABEL (label), gki->priv->angle);

		if (group + 1 ==
		    xkl_engine_get_num_groups
		    (gkbd_configuration_get_xkl_engine (globals.config))) {
			g_hash_table_destroy (ln2cnt_map);
			ln2cnt_map = NULL;
		}

		gtk_container_add (GTK_CONTAINER (align), label);
		gtk_container_add (GTK_CONTAINER (ebox), align);

		gtk_container_set_border_width (GTK_CONTAINER (align), 2);
	}

	g_signal_connect (G_OBJECT (ebox),
			  "button_press_event",
			  G_CALLBACK (gkbd_indicator_button_pressed), gki);

	g_signal_connect (G_OBJECT (gki),
			  "key_press_event",
			  G_CALLBACK (gkbd_indicator_key_pressed), gki);

	/* We have everything prepared for that size */

	return ebox;
}

static void
gkbd_indicator_update_tooltips (GkbdIndicator * gki)
{
	gchar *buf =
	    gkbd_configuration_get_current_tooltip (globals.config);
	if (buf != NULL) {
		gkbd_indicator_set_tooltips (gki, buf);
		g_free (buf);
	}
}

static void
gkbd_indicator_parent_set (GtkWidget * gki, GtkWidget * previous_parent)
{
	gkbd_indicator_update_tooltips (GKBD_INDICATOR (gki));
}


void
gkbd_indicator_reinit_ui (GkbdIndicator * gki)
{
	gkbd_indicator_cleanup (gki);
	gkbd_indicator_fill (gki);

	gkbd_indicator_set_current_page (gki);

	g_signal_emit_by_name (gki, "reinit-ui");
}


/* Should be called once for all widgets */
static void
gkbd_indicator_cfg_callback (GkbdConfiguration * configuration)
{
	ForAllObjects (configuration) {
		gkbd_indicator_reinit_ui (GKBD_INDICATOR (gki));
	} NextObject ()
}

/* Should be called once for all applets */
static void
gkbd_indicator_state_callback (GkbdConfiguration * configuration,
			       gint group)
{
	ForAllObjects (configuration) {
		xkl_debug (200, "do repaint\n");
		gkbd_indicator_set_current_page_for_group (GKBD_INDICATOR
							   (gki), group);
	}
	NextObject ()
}


void
gkbd_indicator_set_current_page (GkbdIndicator * gki)
{
	XklEngine *engine =
	    gkbd_configuration_get_xkl_engine (globals.config);
	XklState *cur_state = xkl_engine_get_current_state (engine);
	if (cur_state->group >= 0)
		gkbd_indicator_set_current_page_for_group (gki,
							   cur_state->
							   group);
}

void
gkbd_indicator_set_current_page_for_group (GkbdIndicator * gki, int group)
{
	xkl_debug (200, "Revalidating for group %d\n", group);

	gtk_notebook_set_current_page (GTK_NOTEBOOK (gki), group + 1);

	gkbd_indicator_update_tooltips (gki);
}

/* Should be called once for all widgets */
static GdkFilterReturn
gkbd_indicator_filter_x_evt (GdkXEvent * xev, GdkEvent * event)
{
	XEvent *xevent = (XEvent *) xev;
	XklEngine *engine =
	    gkbd_configuration_get_xkl_engine (globals.config);

	xkl_engine_filter_events (engine, xevent);
	switch (xevent->type) {
	case ReparentNotify:
		{
			XReparentEvent *rne = (XReparentEvent *) xev;

			ForAllObjects (globals.config) {
				GdkWindow *w =
				    gtk_widget_get_parent_window
				    (GTK_WIDGET (gki));

				/* compare the indicator's parent window with the even window */
				if (w != NULL
				    && GDK_WINDOW_XID (w) == rne->window) {
					/* if so - make it transparent... */
					xkl_engine_set_window_transparent
					    (engine, rne->window, TRUE);
				}
			}
			NextObject ()
		}
		break;
	}
	return GDK_FILTER_CONTINUE;
}


/* Should be called once for all widgets */
static void
gkbd_indicator_start_listen (void)
{
	gdk_window_add_filter (NULL, (GdkFilterFunc)
			       gkbd_indicator_filter_x_evt, NULL);
	gdk_window_add_filter (gdk_get_default_root_window (),
			       (GdkFilterFunc)
			       gkbd_indicator_filter_x_evt, NULL);
}

/* Should be called once for all widgets */
static void
gkbd_indicator_stop_listen (void)
{
	gdk_window_remove_filter (NULL, (GdkFilterFunc)
				  gkbd_indicator_filter_x_evt, NULL);
	gdk_window_remove_filter
	    (gdk_get_default_root_window (),
	     (GdkFilterFunc) gkbd_indicator_filter_x_evt, NULL);
}

static gboolean
gkbd_indicator_scroll (GtkWidget * gki, GdkEventScroll * event)
{
	/* mouse wheel events should be ignored, otherwise funny effects appear */
	return TRUE;
}

static void
gkbd_indicator_init (GkbdIndicator * gki)
{
	GtkWidget *def_drawing;
	GtkNotebook *notebook;

	if (!gkbd_configuration_if_any_object_exists (globals.config))
		gkbd_indicator_global_init ();

	gki->priv = g_new0 (GkbdIndicatorPrivate, 1);

	notebook = GTK_NOTEBOOK (gki);

	xkl_debug (100, "Initiating the widget startup process for %p\n",
		   gki);

	gtk_notebook_set_show_tabs (notebook, FALSE);
	gtk_notebook_set_show_border (notebook, FALSE);

	def_drawing =
	    gtk_image_new_from_stock (GTK_STOCK_STOP,
				      GTK_ICON_SIZE_BUTTON);

	gtk_notebook_append_page (notebook, def_drawing,
				  gtk_label_new (""));

	if (gkbd_configuration_get_xkl_engine (globals.config) == NULL) {
		gkbd_indicator_set_tooltips (gki,
					     _
					     ("XKB initialization error"));
		return;
	}

	gkbd_indicator_set_tooltips (gki, NULL);

	gkbd_indicator_fill (gki);
	gkbd_indicator_set_current_page (gki);

	gtk_widget_add_events (GTK_WIDGET (gki), GDK_BUTTON_PRESS_MASK);

	/* append AFTER all initialization work is finished */
	gkbd_configuration_append_object (globals.config, G_OBJECT (gki));
}

static void
gkbd_indicator_finalize (GObject * obj)
{
	GkbdIndicator *gki = GKBD_INDICATOR (obj);
	xkl_debug (100,
		   "Starting the gnome-kbd-indicator widget shutdown process for %p\n",
		   gki);

	/* remove BEFORE all termination work is finished */
	gkbd_configuration_remove_object (globals.config, G_OBJECT (gki));

	gkbd_indicator_cleanup (gki);

	xkl_debug (100,
		   "The instance of gnome-kbd-indicator successfully finalized\n");

	g_free (gki->priv);

	G_OBJECT_CLASS (gkbd_indicator_parent_class)->finalize (obj);

	if (!gkbd_configuration_if_any_object_exists (globals.config))
		gkbd_indicator_global_term ();
}

static void
gkbd_indicator_global_term (void)
{
	xkl_debug (100, "*** Last  GkbdIndicator instance *** \n");

	gkbd_configuration_free_images (globals.config, globals.images);
	globals.images = NULL;

	gkbd_indicator_stop_listen ();
	g_object_unref (globals.config);
	globals.config = NULL;

	xkl_debug (100, "*** Terminated globals *** \n");
}

static void
gkbd_indicator_class_init (GkbdIndicatorClass * klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

	xkl_debug (100, "*** First GkbdIndicator instance *** \n");

	memset (&globals, 0, sizeof (globals));

	/* Initing vtable */
	object_class->finalize = gkbd_indicator_finalize;

	widget_class->scroll_event = gkbd_indicator_scroll;
	widget_class->parent_set = gkbd_indicator_parent_set;

	/* Signals */
	g_signal_new ("reinit-ui", GKBD_TYPE_INDICATOR,
		      G_SIGNAL_RUN_LAST,
		      G_STRUCT_OFFSET (GkbdIndicatorClass, reinit_ui),
		      NULL, NULL, gkbd_indicator_VOID__VOID,
		      G_TYPE_NONE, 0);
}

static void
gkbd_indicator_global_init (void)
{
	globals.config = gkbd_configuration_get ();

	g_signal_connect (globals.config, "group-changed",
			  G_CALLBACK (gkbd_indicator_state_callback),
			  NULL);
	g_signal_connect (globals.config, "changed",
			  G_CALLBACK (gkbd_indicator_cfg_callback), NULL);

	globals.images = gkbd_configuration_load_images (globals.config);

	gkbd_indicator_start_listen ();

	xkl_debug (100, "*** Inited globals *** \n");
}

GtkWidget *
gkbd_indicator_new (void)
{
	return
	    GTK_WIDGET (g_object_new (gkbd_indicator_get_type (), NULL));
}

void
gkbd_indicator_set_parent_tooltips (GkbdIndicator * gki, gboolean spt)
{
	gki->priv->set_parent_tooltips = spt;
	gkbd_indicator_update_tooltips (gki);
}

/**
 * gkbd_indicator_get_xkl_engine:
 *
 * Returns: (transfer none): The engine shared by all GkbdIndicator objects
 */
XklEngine *
gkbd_indicator_get_xkl_engine ()
{
	return gkbd_configuration_get_xkl_engine (globals.config);
}

/**
 * gkbd_indicator_get_group_names:
 *
 * Returns: (transfer none) (array zero-terminated=1): List of group names
 */
gchar **
gkbd_indicator_get_group_names ()
{
	return (gchar **)
	    gkbd_configuration_get_group_names (globals.config);
}

gchar *
gkbd_indicator_get_image_filename (guint group)
{
	return gkbd_configuration_get_image_filename (globals.config,
						      group);
}

gdouble
gkbd_indicator_get_max_width_height_ratio (void)
{
	gdouble rv = 0.0;
	GSList *ip = globals.images;
	if (!gkbd_configuration_if_flags_shown (globals.config))
		return 0;
	while (ip != NULL) {
		GdkPixbuf *img = GDK_PIXBUF (ip->data);
		gdouble r =
		    1.0 * gdk_pixbuf_get_width (img) /
		    gdk_pixbuf_get_height (img);
		if (r > rv)
			rv = r;
		ip = ip->next;
	}
	return rv;
}

void
gkbd_indicator_set_angle (GkbdIndicator * gki, gdouble angle)
{
	gki->priv->angle = angle;
}