[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