[PATCH 3/3] LayoutIndicator widget

Vratislav Podzimek vpodzime at redhat.com
Mon Feb 11 14:05:19 UTC 2013


Signed-off-by: Vratislav Podzimek <vpodzime at redhat.com>
---
 widgets/configure.ac              |   1 +
 widgets/glade/AnacondaWidgets.xml |   7 +
 widgets/src/LayoutIndicator.c     | 339 ++++++++++++++++++++++++++++++++++++++
 widgets/src/LayoutIndicator.h     |  69 ++++++++
 widgets/src/Makefile.am           |   8 +-
 5 files changed, 421 insertions(+), 3 deletions(-)
 create mode 100644 widgets/src/LayoutIndicator.c
 create mode 100644 widgets/src/LayoutIndicator.h

diff --git a/widgets/configure.ac b/widgets/configure.ac
index 9df0ed7..5b9f066 100644
--- a/widgets/configure.ac
+++ b/widgets/configure.ac
@@ -50,6 +50,7 @@ PKG_PROG_PKG_CONFIG
 PKG_CHECK_MODULES([GLADEUI], [gladeui-2.0 >= 3.10])
 PKG_CHECK_MODULES([GTK], [gtk+-x11-3.0 >= 3.0])
 PKG_CHECK_MODULES([GLIB], [glib-2.0])
+PKG_CHECK_MODULES([LIBXKLAVIER], [libxklavier >= 5.2.1])
 PKG_CHECK_EXISTS([gobject-introspection-1.0 >= 1.30])
 
 AC_CHECK_HEADERS([libintl.h string.h])
diff --git a/widgets/glade/AnacondaWidgets.xml b/widgets/glade/AnacondaWidgets.xml
index 1ede275..298ab3a 100644
--- a/widgets/glade/AnacondaWidgets.xml
+++ b/widgets/glade/AnacondaWidgets.xml
@@ -142,6 +142,12 @@
 	        <property id="spacing" default="6" visible="False" />
 	    </properties>
 	</glade-widget-class>
+
+    <glade-widget-class title="Layout Indicator"
+			            name="AnacondaLayoutIndicator"
+			            icon-name="widget-gtk-label"
+			            generic-name="AnacondaLayoutIndicator">
+	</glade-widget-class>
     </glade-widget-classes>
 
     <glade-widget-group name="anaconda-windows" title="Anaconda Windows">
@@ -157,5 +163,6 @@
     <glade-widget-group name="anaconda-misc-widgets" title="Anaconda Miscellaneous Widgets">
         <glade-widget-class-ref name="AnacondaSpokeSelector" />
         <glade-widget-class-ref name="AnacondaTimezoneMap" />
+        <glade-widget-class-ref name="AnacondaLayoutIndicator" />
     </glade-widget-group>
 </glade-catalog>
diff --git a/widgets/src/LayoutIndicator.c b/widgets/src/LayoutIndicator.c
new file mode 100644
index 0000000..4b6527a
--- /dev/null
+++ b/widgets/src/LayoutIndicator.c
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2013  Red Hat, Inc.
+ *
+ * Some parts of this code were inspired by the xfce4-xkb-plugin's sources.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Vratislav Podzimek <vpodzime at redhat.com>
+ */
+
+#include <glib.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <gtk/gtk.h>
+#include <libxklavier/xklavier.h>
+
+#include "LayoutIndicator.h"
+#include "intl.h"
+
+#define TOOLTIP_FORMAT_STR _("Current layout: '%s'. Click to switch to the next layout")
+#define DEFAULT_LAYOUT "us"
+#define LAYOUT_LABEL_MAX_CHAR_WIDTH 8
+#define MARKUP_FORMAT_STR "<span fgcolor='black' weight='bold'>%s</span>"
+
+/**
+ * SECTION: LayoutIndicator
+ * @title: AnacondaLayoutIndicator
+ * @short_description: An indicator of currently activated X layout
+ *
+ * An #AnacondaLayoutIndicator is a widget that can be used in any place where
+ * indication of currently activated X layout should be shown.
+ *
+ * An #AnacondaLayoutIndicator is a subclass of a #GtkEventBox.
+ */
+
+enum {
+    PROP_LAYOUT = 1
+};
+
+struct _AnacondaLayoutIndicatorPrivate {
+    gchar *layout;
+    GtkLabel *layout_label;
+    GdkCursor *cursor;
+    XklEngine *engine;
+    XklConfigRec *config_rec;
+};
+
+G_DEFINE_TYPE(AnacondaLayoutIndicator, anaconda_layout_indicator, GTK_TYPE_EVENT_BOX)
+
+static void anaconda_layout_indicator_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
+
+static void anaconda_layout_indicator_dispose(AnacondaLayoutIndicator *indicator);
+static void anaconda_layout_indicator_realize(GtkWidget *widget, gpointer user_data);
+
+static void anaconda_layout_indicator_clicked(GtkWidget *widget, GdkEvent *event, gpointer user_data);
+static void anaconda_layout_indicator_refresh_ui_elements(AnacondaLayoutIndicator *indicator);
+static void anaconda_layout_indicator_refresh_layout(AnacondaLayoutIndicator *indicator);
+
+/* helper functions */
+static gchar* get_current_layout(XklEngine *engine, XklConfigRec *conf_rec);
+static void x_state_changed(XklEngine *engine, XklEngineStateChange type,
+                            gint arg2, gboolean arg3, gpointer indicator);
+static void x_config_changed(XklEngine *engine, gpointer indicator);
+static GdkFilterReturn handle_xevent(GdkXEvent *xev, GdkEvent *event, gpointer engine);
+
+static void anaconda_layout_indicator_class_init(AnacondaLayoutIndicatorClass *klass) {
+    GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+    object_class->get_property = anaconda_layout_indicator_get_property;
+    object_class->dispose = (GObjectFinalizeFunc) anaconda_layout_indicator_dispose;
+
+    /**
+     * AnacondaLayoutIndicator:layout:
+     *
+     * The :layout is the currently activated X layout.
+     *
+     * Since: 1.0
+     */
+    g_object_class_install_property(object_class,
+                                    PROP_LAYOUT,
+                                    g_param_spec_string("layout",
+                                                        P_("layout"),
+                                                        P_("Current layout"),
+                                                        DEFAULT_LAYOUT,
+                                                        G_PARAM_READABLE));
+
+    g_type_class_add_private(object_class, sizeof(AnacondaLayoutIndicatorPrivate));
+}
+
+/**
+ * anaconda_layout_indicator_new:
+ *
+ * Creates a new #AnacondaLayoutIndicator, which is an indicator of the
+ * currently activated X layout. When the indicator is clicked, it activates
+ * the next layout in the list of configured layouts.
+ *
+ * Returns: A new #AnacondaLayoutIndicator.
+ */
+GtkWidget *anaconda_layout_indicator_new() {
+    return g_object_new(ANACONDA_TYPE_LAYOUT_INDICATOR, NULL);
+}
+
+static void anaconda_layout_indicator_init(AnacondaLayoutIndicator *self) {
+    GdkDisplay *display;
+
+    self->priv = G_TYPE_INSTANCE_GET_PRIVATE(self,
+                                             ANACONDA_TYPE_LAYOUT_INDICATOR,
+                                             AnacondaLayoutIndicatorPrivate);
+
+    /* layout indicator should not change focus when it is clicked */
+    gtk_widget_set_can_focus(GTK_WIDGET(self), FALSE);
+
+    /* layout indicator should have a tooltip saying what is the current layout
+       and what clicking it does
+    */
+    gtk_widget_set_has_tooltip(GTK_WIDGET(self), TRUE);
+
+    /* layout indicator activates next layout when it is clicked */
+    gtk_widget_add_events(GTK_WIDGET(self), GDK_BUTTON_RELEASE_MASK);
+    g_signal_connect(self, "button-release-event",
+                     G_CALLBACK(anaconda_layout_indicator_clicked),
+                     NULL);
+
+    /* layout indicator should have a hand cursor so that looks like clickable widget */
+    self->priv->cursor = gdk_cursor_new(GDK_HAND2);
+    g_signal_connect(self, "realize",
+                     G_CALLBACK(anaconda_layout_indicator_realize),
+                     NULL);
+
+    /* initialize XklEngine and XklConfigRec instances providing signals and data */
+    display = gdk_display_get_default();
+    self->priv->engine = xkl_engine_get_instance(GDK_DISPLAY_XDISPLAY(display));
+    self->priv->config_rec = xkl_config_rec_new();
+    xkl_config_rec_get_from_server(self->priv->config_rec, self->priv->engine);
+
+    /* make XklEngine listening and hook up handler for its "X-state-changed" and
+       "X-config-changed" signals */
+    xkl_engine_start_listen(self->priv->engine, XKLL_TRACK_KEYBOARD_STATE);
+    g_signal_connect(self->priv->engine, "X-state-changed",
+                     G_CALLBACK(x_state_changed),
+                     g_object_ref(self));
+    g_signal_connect(self->priv->engine, "X-config-changed",
+                     G_CALLBACK(x_config_changed),
+                     g_object_ref(self));
+
+    /*
+     * hook up X events with XklEngine
+     * (passing NULL as the first argument means we want X events from all windows)
+     */
+    gdk_window_add_filter(NULL, (GdkFilterFunc) handle_xevent, self->priv->engine);
+
+    /* init layout item with the current layout */
+    self->priv->layout = get_current_layout(self->priv->engine, self->priv->config_rec);
+
+    /* create layout label and set desired properties */
+    self->priv->layout_label = GTK_LABEL(gtk_label_new(NULL));
+    gtk_widget_set_hexpand(GTK_WIDGET(self->priv->layout_label), FALSE);
+    gtk_label_set_max_width_chars(self->priv->layout_label, LAYOUT_LABEL_MAX_CHAR_WIDTH);
+    gtk_label_set_width_chars(self->priv->layout_label, LAYOUT_LABEL_MAX_CHAR_WIDTH);
+    gtk_label_set_ellipsize(self->priv->layout_label, PANGO_ELLIPSIZE_END);
+    g_object_set(G_OBJECT(self->priv->layout_label), "xalign", 0.5, NULL);
+
+    /* initialize the label with the current layout name */
+    anaconda_layout_indicator_refresh_ui_elements(self);
+
+    gtk_container_add(GTK_CONTAINER(self), GTK_WIDGET(self->priv->layout_label));
+}
+
+static void anaconda_layout_indicator_dispose(AnacondaLayoutIndicator *self) {
+    /* finalize X events listening and unref all objects we reference
+       (may be called multiple times) */
+    if (self->priv->layout_label) {
+        gtk_widget_destroy(GTK_WIDGET(self->priv->layout_label));
+        self->priv->layout_label = NULL;
+    }
+    if (self->priv->cursor) {
+        g_object_unref(self->priv->cursor);
+        self->priv->cursor = NULL;
+    }
+    if (self->priv->engine) {
+        xkl_engine_stop_listen(self->priv->engine, XKLL_TRACK_KEYBOARD_STATE);
+        gdk_window_remove_filter(NULL, (GdkFilterFunc) handle_xevent, self->priv->engine);
+        g_object_unref(self->priv->engine);
+        self->priv->engine = NULL;
+    }
+    if (self->priv->config_rec) {
+        g_object_unref(self->priv->config_rec);
+        self->priv->config_rec = NULL;
+    }
+    if (self->priv->layout) {
+        g_free(self->priv->layout);
+        self->priv->layout = NULL;
+    }
+}
+
+static void anaconda_layout_indicator_realize(GtkWidget *widget, gpointer data) {
+    /* set cursor for the widget's GdkWindow */
+    AnacondaLayoutIndicator *self = ANACONDA_LAYOUT_INDICATOR(widget);
+
+    gdk_window_set_cursor(gtk_widget_get_window(widget), self->priv->cursor);
+}
+
+static void anaconda_layout_indicator_get_property(GObject *object, guint prop_id,
+                                                   GValue *value, GParamSpec *pspec) {
+    AnacondaLayoutIndicator *self = ANACONDA_LAYOUT_INDICATOR(object);
+
+    switch (prop_id) {
+        case PROP_LAYOUT:
+            g_value_set_string(value, self->priv->layout);
+            break;
+    }
+}
+
+static void anaconda_layout_indicator_clicked(GtkWidget *widget, GdkEvent *event, gpointer data) {
+    AnacondaLayoutIndicator *self = ANACONDA_LAYOUT_INDICATOR(widget);
+
+    if (event->type != GDK_BUTTON_RELEASE)
+        return;
+
+    XklState *state = xkl_engine_get_current_state(self->priv->engine);
+    guint n_groups = xkl_engine_get_num_groups(self->priv->engine);
+
+    /* cycle over groups */
+    guint next_group = (state->group + 1) % n_groups;
+
+    /* activate next group */
+    xkl_engine_lock_group(self->priv->engine, next_group);
+}
+
+static void anaconda_layout_indicator_refresh_ui_elements(AnacondaLayoutIndicator *self) {
+    gchar *markup;
+    gchar *tooltip;
+
+    markup = g_markup_printf_escaped(MARKUP_FORMAT_STR, self->priv->layout);
+    gtk_label_set_markup(self->priv->layout_label, markup);
+    g_free(markup);
+
+    tooltip = g_strdup_printf(TOOLTIP_FORMAT_STR, self->priv->layout);
+    gtk_widget_set_tooltip_text(GTK_WIDGET(self), tooltip);
+    g_free(tooltip);
+}
+
+static void anaconda_layout_indicator_refresh_layout(AnacondaLayoutIndicator *self) {
+    g_free(self->priv->layout);
+    self->priv->layout = get_current_layout(self->priv->engine, self->priv->config_rec);
+
+    anaconda_layout_indicator_refresh_ui_elements(self);
+}
+
+/**
+ * get_current_layout:
+ *
+ * Returns: newly allocated string with the currently activated layout as
+ *          'layout (variant)'
+ */
+static gchar* get_current_layout(XklEngine *engine, XklConfigRec *conf_rec) {
+    /* engine has to be listening with XKLL_TRACK_KEYBOARD_STATE mask */
+    gchar *layout = NULL;
+    gchar *variant = NULL;
+    gint32 cur_group;
+    gint i;
+
+    /* returns statically allocated buffer, shouldn't be freed */
+    XklState *state = xkl_engine_get_current_state(engine);
+    cur_group = state->group;
+
+    guint n_groups = xkl_engine_get_num_groups(engine);
+
+    /* BUG?: if the last layout in the list is activated and removed,
+             state->group may be equal to n_groups that would result in
+             layout being NULL
+    */
+    if (cur_group >= n_groups)
+        cur_group = n_groups - 1;
+
+    layout = conf_rec->layouts[cur_group];
+
+    /* variant defined for the current layout */
+    variant = conf_rec->variants[cur_group];
+
+    /* variant may be NULL or "" if not defined */
+    if (variant && g_strcmp0("", variant))
+        return g_strdup_printf("%s (%s)", layout, variant);
+    else
+        return g_strdup(layout);
+}
+
+static GdkFilterReturn handle_xevent(GdkXEvent *xev, GdkEvent *event, gpointer data) {
+    XklEngine *engine = XKL_ENGINE(data);
+    XEvent *xevent = (XEvent *) xev;
+
+    xkl_engine_filter_events(engine, xevent);
+
+    return GDK_FILTER_CONTINUE;
+}
+
+static void x_state_changed(XklEngine *engine, XklEngineStateChange type,
+                            gint arg2, gboolean arg3, gpointer data) {
+    g_return_if_fail(data);
+    AnacondaLayoutIndicator *indicator = ANACONDA_LAYOUT_INDICATOR(data);
+
+    anaconda_layout_indicator_refresh_layout(indicator);
+}
+
+static void x_config_changed(XklEngine *engine, gpointer data) {
+    g_return_if_fail(data);
+    AnacondaLayoutIndicator *indicator = ANACONDA_LAYOUT_INDICATOR(data);
+
+    /* load current configuration from the X server */
+    xkl_config_rec_get_from_server(indicator->priv->config_rec, indicator->priv->engine);
+
+    anaconda_layout_indicator_refresh_layout(indicator);
+}
+
+/**
+ * anaconda_layout_indicator_get_current_layout:
+ * @indicator: a #AnacondaLayoutIndicator
+ *
+ * Returns: (transfer full): the currently activated X layout.
+ *
+ * Since: 1.0
+ */
+gchar* anaconda_layout_indicator_get_current_layout(AnacondaLayoutIndicator *indicator) {
+    g_return_val_if_fail(indicator->priv->layout, NULL);
+
+    /* TODO: return description instead of raw layout name? */
+    return g_strdup(indicator->priv->layout);
+}
diff --git a/widgets/src/LayoutIndicator.h b/widgets/src/LayoutIndicator.h
new file mode 100644
index 0000000..88a7666
--- /dev/null
+++ b/widgets/src/LayoutIndicator.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2013  Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Vratislav Podzimek <vpodzime at redhat.com>
+ */
+
+#ifndef _LAYOUT_INDICATOR_H
+#define _LAYOUT_INDICATOR_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define ANACONDA_TYPE_LAYOUT_INDICATOR            (anaconda_layout_indicator_get_type())
+#define ANACONDA_LAYOUT_INDICATOR(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), ANACONDA_TYPE_LAYOUT_INDICATOR, AnacondaLayoutIndicator))
+#define ANACONDA_IS_LAYOUT_INDICATOR(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj)), ANACONDA_TYPE_LAYOUT_INDICATOR)
+#define ANACONDA_LAYOUT_INDICATOR_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), ANACONDA_TYPE_LAYOUT_INDICATOR, AnacondaLayoutIndicatorClass))
+#define ANACONDA_IS_LAYOUT_INDICATOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), ANACONDA_TYPE_LAYOUT_INDICATOR))
+#define ANACONDA_LAYOUT_INDICATOR_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), ANACONDA_TYPE_LAYOUT_INDICATOR, AnacondaLayoutIndicatorClass))
+
+typedef struct _AnacondaLayoutIndicator        AnacondaLayoutIndicator;
+typedef struct _AnacondaLayoutIndicatorClass   AnacondaLayoutIndicatorClass;
+typedef struct _AnacondaLayoutIndicatorPrivate AnacondaLayoutIndicatorPrivate;
+
+/**
+ * AnacondaLayoutIndicator:
+ *
+ * The AnacondaLayoutIndicator struct contains only private fields and should
+ * not be directly accessed.
+ */
+struct _AnacondaLayoutIndicator {
+    /*< private >*/
+    GtkEventBox                   parent;
+    AnacondaLayoutIndicatorPrivate *priv;
+};
+
+/**
+ * AnacondaLayoutIndicatorClass:
+ * @parent_class: The object class structure needs to be the first element in
+ *                the widget class structure in order for the class mechanism
+ *                to work correctly.  This allows an AnacondaLayoutIndicatorClass
+ *                pointer to be cast to a #GtkEventBox pointer.
+ */
+struct _AnacondaLayoutIndicatorClass {
+    GtkEventBoxClass parent_class;
+};
+
+GType       anaconda_layout_indicator_get_type (void);
+GtkWidget  *anaconda_layout_indicator_new      ();
+
+gchar      *anaconda_layout_indicator_get_current_layout (AnacondaLayoutIndicator *indicator);
+
+
+G_END_DECLS
+
+#endif
diff --git a/widgets/src/Makefile.am b/widgets/src/Makefile.am
index 79afc82..c2f142b 100644
--- a/widgets/src/Makefile.am
+++ b/widgets/src/Makefile.am
@@ -33,6 +33,7 @@ GISOURCES = BaseWindow.c \
 	 SpokeWindow.c \
 	 StandaloneWindow.c \
 	 TimezoneMap.c \
+	 LayoutIndicator.c \
 	 lightbox.c
 
 GIHDRS = BaseWindow.h \
@@ -43,6 +44,7 @@ GIHDRS = BaseWindow.h \
 	 SpokeWindow.h \
 	 StandaloneWindow.h \
 	 TimezoneMap.h \
+	 LayoutIndicator.h \
 	 lightbox.h
 
 NONGISOURCES = tz.c
@@ -59,10 +61,10 @@ TZMAPDATA = '"tzmapdata"'
 noinst_HEADERS = gettext.h intl.h
 
 lib_LTLIBRARIES = libAnacondaWidgets.la
-libAnacondaWidgets_la_CFLAGS = $(GTK_CFLAGS) $(GLADEUI_CFLAGS) -Wall -g\
+libAnacondaWidgets_la_CFLAGS = $(GTK_CFLAGS) $(GLADEUI_CFLAGS) $(LIBXKLAVIER_CFLAGS) -Wall -g\
 			       -DWIDGETS_DATADIR=$(WIDGETSDATA)\
 			       -DTZMAP_DATADIR=$(TZMAPDATA)
-libAnacondaWidgets_la_LIBADD = $(GTK_LIBS) $(GLADEUI_LIBS)
+libAnacondaWidgets_la_LIBADD = $(GTK_LIBS) $(GLADEUI_LIBS) $(LIBXKLAVIER_LIBS)
 libAnacondaWidgets_la_LDFLAGS = $(LTLIBINTL)
 libAnacondaWidgets_la_SOURCES = $(SOURCES) $(HDRS) \
 	  glade-adaptor.c
@@ -76,7 +78,7 @@ AnacondaWidgets-1.0.gir: libAnacondaWidgets.la
 AnacondaWidgets_1_0_gir_FILES = $(GISOURCES) $(GIHDRS)
 AnacondaWidgets_1_0_gir_LIBS = libAnacondaWidgets.la
 AnacondaWidgets_1_0_gir_SCANNERFLAGS = --warn-all --identifier-prefix=Anaconda --symbol-prefix=anaconda
-AnacondaWidgets_1_0_gir_INCLUDES = Gtk-3.0
+AnacondaWidgets_1_0_gir_INCLUDES = Gtk-3.0 Xkl-1.0
 
 INTROSPECTION_GIRS = AnacondaWidgets-1.0.gir
 
-- 
1.7.11.7



More information about the anaconda-patches mailing list