/* * Copyright (C) 2006 Sergey V. Udaltsov * * 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 #include #include #include #include #include #include #include #include #include typedef struct _gki_globals { GkbdConfiguration *config; GSList *images; } gki_globals; typedef struct { gboolean set_parent_tooltips; gdouble angle; } GkbdIndicatorPrivate; /* one instance for ALL widgets */ static gki_globals globals; G_DEFINE_TYPE_WITH_PRIVATE (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) { GkbdIndicatorPrivate *priv = gkbd_indicator_get_instance_private (gki); g_return_if_fail (GKBD_IS_INDICATOR (gki)); g_return_if_fail (str == NULL || g_utf8_validate (str, -1, NULL)); gtk_widget_set_tooltip_text (GTK_WIDGET (gki), str); if (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); for (grp = 0; grp < total_groups; grp++) { GtkWidget *page = NULL; 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) { GkbdIndicatorPrivate *priv = gkbd_indicator_get_instance_private (gki); 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 *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); label = gtk_label_new (lbl_title); gtk_widget_set_halign (label, GTK_ALIGN_CENTER); gtk_widget_set_valign (label, GTK_ALIGN_CENTER); gtk_widget_set_hexpand (label, TRUE); gtk_widget_set_vexpand (label, TRUE); g_free (lbl_title); gtk_label_set_angle (GTK_LABEL (label), 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 (ebox), label); gtk_container_set_border_width (GTK_CONTAINER (ebox), 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 (); 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_icon_name ("process-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_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) { GkbdIndicatorPrivate *priv = gkbd_indicator_get_instance_private (gki); g_return_if_fail (GKBD_IS_INDICATOR (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) { GkbdIndicatorPrivate *priv = gkbd_indicator_get_instance_private (gki); g_return_if_fail (GKBD_IS_INDICATOR (gki)); priv->angle = angle; }