These are the results of joint work of me and Máirín to implement layout indication in Anaconda's GUI.
PATCH 1/3 adds a new Anaconda widget called LayoutIndicator. It is based on the libxklavier library (no way to use the GNOME's replacement yet (if ever)) and uses the XklEngine singleton instance to get information about layout switching, configuration changes etc. I hope the code is vastly commented, but in case of any doubts I'm glad to explain anything.
PATCH 2/3 adds the LayoutIndicator to our BaseWindow class, so that it appears in the header of every spoke and hub. The patch also includes some changes that create more space for the indicator and rearrange the heading a little bit. Among the other things the red "beta warning" label became smaller. But for some reason on the WelcomeSpoke it has the same size. Any ideas?
Anyway, that is the "global indicator". Since for dialogs we use lightboxing that hides the underlying spoke/hub, we would need to place some more indicators to places where they are needed. The only one so far seems to be the LUKS passphrase dialog which is the case covered with PATCH 3/3.
The static preview can be seen at: http://vpodzime.fedorapeople.org/making_room_for_LI_new.png (the smaller indicator just shows the one that is used in the LUKS dialog)
Tomorrow I will also post a screencast I cannot record it now via SSH.
Vratislav Podzimek (3): LayoutIndicator widget Add layout indicator to the BaseWindow Add layout indicator to the LUKS passphrase dialog
data/pixmaps/anaconda_spoke_header.png | Bin 185 -> 183 bytes pyanaconda/ui/gui/spokes/lib/passphrase.glade | 19 +- pyanaconda/ui/gui/spokes/lib/passphrase.py | 20 +- widgets/configure.ac | 1 + widgets/glade/AnacondaWidgets.xml | 10 + widgets/src/BaseWindow.c | 15 +- widgets/src/LayoutIndicator.c | 446 ++++++++++++++++++++++++++ widgets/src/LayoutIndicator.h | 78 +++++ widgets/src/Makefile.am | 8 +- widgets/src/SpokeWindow.c | 4 +- 10 files changed, 578 insertions(+), 23 deletions(-) create mode 100644 widgets/src/LayoutIndicator.c create mode 100644 widgets/src/LayoutIndicator.h
Signed-off-by: Vratislav Podzimek vpodzime@redhat.com --- widgets/configure.ac | 1 + widgets/glade/AnacondaWidgets.xml | 10 + widgets/src/LayoutIndicator.c | 430 ++++++++++++++++++++++++++++++++++++++ widgets/src/LayoutIndicator.h | 71 +++++++ widgets/src/Makefile.am | 8 +- 5 files changed, 517 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..56da94c 100644 --- a/widgets/glade/AnacondaWidgets.xml +++ b/widgets/glade/AnacondaWidgets.xml @@ -142,6 +142,15 @@ <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"> + <properties> + <property id="label-width" default="12" /> + </properties> + </glade-widget-class> </glade-widget-classes>
<glade-widget-group name="anaconda-windows" title="Anaconda Windows"> @@ -157,5 +166,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..1ae7432 --- /dev/null +++ b/widgets/src/LayoutIndicator.c @@ -0,0 +1,430 @@ +/* + * 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@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 DEFAULT_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, + PROP_LABEL_WIDTH +}; + +struct _AnacondaLayoutIndicatorPrivate { + gchar *layout; + guint label_width; + GtkBox *main_box; + GtkWidget *icon; + 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_set_property(GObject *object, guint prop_id, const 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->set_property = anaconda_layout_indicator_set_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)); + + /** + * AnacondaLayoutIndicator:label-width: + * + * Width of the label showing the current layout in number of characters. + * + * Since: 1.0 + */ + g_object_class_install_property(object_class, + PROP_LABEL_WIDTH, + g_param_spec_uint("label-width", + P_("Label width"), + P_("Width of the label showing the current layout"), + 0, 20, DEFAULT_LABEL_MAX_CHAR_WIDTH, + G_PARAM_READWRITE)); + + 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; + GdkRGBA background_color = { 0.0, 0.0, 0.0, 0.0 }; + + g_return_if_fail(gdk_rgba_parse(&background_color, "#fdfdfd")); + + 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); + + /* layout indicator should have a different background color + TODO: should be "exported" to allow changes in glade from code? */ + gtk_widget_override_background_color(GTK_WIDGET(self), + GTK_STATE_FLAG_NORMAL, &background_color); + + /* 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, DEFAULT_LABEL_MAX_CHAR_WIDTH); + gtk_label_set_width_chars(self->priv->layout_label, DEFAULT_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, NULL); + + /* initialize the label with the current layout name */ + anaconda_layout_indicator_refresh_ui_elements(self); + + /* create the little keyboard icon and set its left margin */ + self->priv->icon = gtk_image_new_from_icon_name("input-keyboard-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR); + + /* create and populate the main box */ + self->priv->main_box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4)); + gtk_box_pack_start(self->priv->main_box, self->priv->icon, FALSE, FALSE, 0); + gtk_box_pack_end(self->priv->main_box, GTK_WIDGET(self->priv->layout_label), FALSE, FALSE, 0); + gtk_widget_set_margin_left(GTK_WIDGET(self->priv->main_box), 4); + gtk_widget_set_margin_right(GTK_WIDGET(self->priv->main_box), 4); + gtk_widget_set_margin_top(GTK_WIDGET(self->priv->main_box), 3); + gtk_widget_set_margin_bottom(GTK_WIDGET(self->priv->main_box), 3); + + /* add box to the main container (self) */ + gtk_container_add(GTK_CONTAINER(self), GTK_WIDGET(self->priv->main_box)); +} + +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; + case PROP_LABEL_WIDTH: + g_value_set_uint(value, self->priv->label_width); + break; + } +} + +static void anaconda_layout_indicator_set_property(GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) { + AnacondaLayoutIndicator *self = ANACONDA_LAYOUT_INDICATOR(object); + + switch (prop_id) { + case PROP_LABEL_WIDTH: + self->priv->label_width = g_value_get_uint(value); + gtk_label_set_max_width_chars(self->priv->layout_label, self->priv->label_width); + gtk_label_set_width_chars(self->priv->layout_label, self->priv->label_width); + 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; + + /* 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); +} + +/** + * anaconda_layout_indicator_get_label_width: + * @indicator: a #AnacondaLayoutIndicator + * + * Returns: (transfer none): the current width of the layout label in number of chars + * + * Since: 1.0 + */ +guint anaconda_layout_indicator_get_label_width(AnacondaLayoutIndicator *indicator) { + g_return_val_if_fail(indicator->priv->layout, 0); + + return indicator->priv->label_width; +} + +/** + * anaconda_layout_indicator_set_label_width: + * @indicator: a #AnacondaLayoutIndicator + * @new_width: a new requested width of the layout label in number of chars + * + * + * Since: 1.0 + */ +void anaconda_layout_indicator_set_label_width(AnacondaLayoutIndicator *indicator, + guint new_width) { + g_return_if_fail(indicator->priv->layout); + + GValue width = G_VALUE_INIT; + g_value_init(&width, G_TYPE_UINT); + g_value_set_uint(&width, new_width); + + anaconda_layout_indicator_set_property(G_OBJECT(indicator), PROP_LABEL_WIDTH, + &width, NULL); +} diff --git a/widgets/src/LayoutIndicator.h b/widgets/src/LayoutIndicator.h new file mode 100644 index 0000000..c679186 --- /dev/null +++ b/widgets/src/LayoutIndicator.h @@ -0,0 +1,71 @@ +/* + * 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@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); +guint anaconda_layout_indicator_get_label_width (AnacondaLayoutIndicator *indicator); +void anaconda_layout_indicator_set_label_width (AnacondaLayoutIndicator *indicator, + guint new_width); + +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
widgets/configure.ac | 1 + widgets/glade/AnacondaWidgets.xml | 10 + widgets/src/LayoutIndicator.c | 430 ++++++++++++++++++++++++++++++++++++++ widgets/src/LayoutIndicator.h | 71 +++++++ widgets/src/Makefile.am | 8 +-
Make sure to add your new C file to po/POTFILES.in.
+static void anaconda_layout_indicator_init(AnacondaLayoutIndicator *self) {
- GdkDisplay *display;
- GdkRGBA background_color = { 0.0, 0.0, 0.0, 0.0 };
- g_return_if_fail(gdk_rgba_parse(&background_color, "#fdfdfd"));
- 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);
Is this widget usable with just the keyboard?
- /* layout indicator should have a different background color
TODO: should be "exported" to allow changes in glade from code? */- gtk_widget_override_background_color(GTK_WIDGET(self),
GTK_STATE_FLAG_NORMAL, &background_color);
I really only have two major complaints with this new widget, and this is one. I think the white background makes it look weird and unfinished. I would much rather find some other way to make it look activatable while still fitting in better with how everything else looks.
- /* create and populate the main box */
- self->priv->main_box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4));
- gtk_box_pack_start(self->priv->main_box, self->priv->icon, FALSE, FALSE, 0);
- gtk_box_pack_end(self->priv->main_box, GTK_WIDGET(self->priv->layout_label), FALSE, FALSE, 0);
- gtk_widget_set_margin_left(GTK_WIDGET(self->priv->main_box), 4);
- gtk_widget_set_margin_right(GTK_WIDGET(self->priv->main_box), 4);
- gtk_widget_set_margin_top(GTK_WIDGET(self->priv->main_box), 3);
- gtk_widget_set_margin_bottom(GTK_WIDGET(self->priv->main_box), 3);
We've tried to use multiples of 6 for margins and box spacing wherever possible. Using 3 is probably fine if you need things to look closer, but I'd rather the 4 be either a 3 or a 6, your choice.
- Chris
On Tue, 2013-04-30 at 11:37 -0400, Chris Lumens wrote:
widgets/configure.ac | 1 + widgets/glade/AnacondaWidgets.xml | 10 + widgets/src/LayoutIndicator.c | 430 ++++++++++++++++++++++++++++++++++++++ widgets/src/LayoutIndicator.h | 71 +++++++ widgets/src/Makefile.am | 8 +-
Make sure to add your new C file to po/POTFILES.in.
Done.
+static void anaconda_layout_indicator_init(AnacondaLayoutIndicator *self) {
- GdkDisplay *display;
- GdkRGBA background_color = { 0.0, 0.0, 0.0, 0.0 };
- g_return_if_fail(gdk_rgba_parse(&background_color, "#fdfdfd"));
- 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);Is this widget usable with just the keyboard?
It's not, but I'm not sure we want something like that. The primary purpose of the widget is to indicate which layout is currently activated. Keyboard switching should be done by the shortcut (like Alt+Shift) and the widget intentionally doesn't grab focus (which would be needed to make it usable just with the keyboard), so that it can be clicked with the focus kept in the text field that is being edited.
- /* layout indicator should have a different background color
TODO: should be "exported" to allow changes in glade from code? */- gtk_widget_override_background_color(GTK_WIDGET(self),
GTK_STATE_FLAG_NORMAL, &background_color);I really only have two major complaints with this new widget, and this is one. I think the white background makes it look weird and unfinished. I would much rather find some other way to make it look activatable while still fitting in better with how everything else looks.
Well, this was actually Máirín's idea and I must say I like it. The other way to make the widget compact and looking activatable could be a frame around it [1], but that looks to me really old school with the grey background. Any other ideas?
[1] http://vpodzime.fedorapeople.org/layout_indicator_old_school.png
- /* create and populate the main box */
- self->priv->main_box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4));
- gtk_box_pack_start(self->priv->main_box, self->priv->icon, FALSE, FALSE, 0);
- gtk_box_pack_end(self->priv->main_box, GTK_WIDGET(self->priv->layout_label), FALSE, FALSE, 0);
- gtk_widget_set_margin_left(GTK_WIDGET(self->priv->main_box), 4);
- gtk_widget_set_margin_right(GTK_WIDGET(self->priv->main_box), 4);
- gtk_widget_set_margin_top(GTK_WIDGET(self->priv->main_box), 3);
- gtk_widget_set_margin_bottom(GTK_WIDGET(self->priv->main_box), 3);
We've tried to use multiples of 6 for margins and box spacing wherever possible. Using 3 is probably fine if you need things to look closer, but I'd rather the 4 be either a 3 or a 6, your choice.
Changed to 3 and 6.
Is this widget usable with just the keyboard?
It's not, but I'm not sure we want something like that. The primary purpose of the widget is to indicate which layout is currently activated. Keyboard switching should be done by the shortcut (like Alt+Shift) and the widget intentionally doesn't grab focus (which would be needed to make it usable just with the keyboard), so that it can be clicked with the focus kept in the text field that is being edited.
Okay, thanks for the explanation. That makes sense to me.
I really only have two major complaints with this new widget, and this is one. I think the white background makes it look weird and unfinished. I would much rather find some other way to make it look activatable while still fitting in better with how everything else looks.
Well, this was actually Máirín's idea and I must say I like it. The other way to make the widget compact and looking activatable could be a frame around it [1], but that looks to me really old school with the grey background. Any other ideas?
[1] http://vpodzime.fedorapeople.org/layout_indicator_old_school.png
Hah, I wonder what it says about me that I think the one with the frame looks nicer. I don't really have any good ideas right now, but I didn't have any to begin with. I'm willing to go with the white background version for now (as long as it's got the left alignment from the other link you sent) and we can always work on making it look different later.
- Chris
On Thu, 2013-05-02 at 10:56 -0400, Chris Lumens wrote:
Is this widget usable with just the keyboard?
It's not, but I'm not sure we want something like that. The primary purpose of the widget is to indicate which layout is currently activated. Keyboard switching should be done by the shortcut (like Alt+Shift) and the widget intentionally doesn't grab focus (which would be needed to make it usable just with the keyboard), so that it can be clicked with the focus kept in the text field that is being edited.
Okay, thanks for the explanation. That makes sense to me.
I really only have two major complaints with this new widget, and this is one. I think the white background makes it look weird and unfinished. I would much rather find some other way to make it look activatable while still fitting in better with how everything else looks.
Well, this was actually Máirín's idea and I must say I like it. The other way to make the widget compact and looking activatable could be a frame around it [1], but that looks to me really old school with the grey background. Any other ideas?
[1] http://vpodzime.fedorapeople.org/layout_indicator_old_school.png
Hah, I wonder what it says about me that I think the one with the frame looks nicer. I don't really have any good ideas right now, but I didn't have any to begin with. I'm willing to go with the white background version for now (as long as it's got the left alignment from the other link you sent) and we can always work on making it look different later.
I was quite sure somebody would come with such response. :)
Okay, I'm definitely for going with the background and doing any additional tweaks later. I'm pushing the corrected patches (with the modified alignment) so that they appear in the Beta TCs and can be tested.
Thanks,
Also make Done button use two cells in the grid because BaseWindow's navigation area has now three rows instead of two (layout indicator added) and the "Done" button should be centered between the second and the third rows.
Also use taller PNG for the spoke header.
Signed-off-by: Vratislav Podzimek vpodzime@redhat.com --- data/pixmaps/anaconda_spoke_header.png | Bin 185 -> 183 bytes widgets/src/BaseWindow.c | 15 +++++- widgets/src/LayoutIndicator.c | 96 +++++++++++++++++++-------------- widgets/src/LayoutIndicator.h | 7 +++ widgets/src/SpokeWindow.c | 4 +- 5 files changed, 80 insertions(+), 42 deletions(-)
diff --git a/data/pixmaps/anaconda_spoke_header.png b/data/pixmaps/anaconda_spoke_header.png index f1feefc..492be50 100644 Binary files a/data/pixmaps/anaconda_spoke_header.png and b/data/pixmaps/anaconda_spoke_header.png differ diff --git a/widgets/src/BaseWindow.c b/widgets/src/BaseWindow.c index 7493ca4..8c285b7 100644 --- a/widgets/src/BaseWindow.c +++ b/widgets/src/BaseWindow.c @@ -21,6 +21,7 @@ #include <stdlib.h> #include <string.h>
+#include "LayoutIndicator.h" #include "BaseWindow.h" #include "intl.h"
@@ -85,6 +86,7 @@ enum { #define DEFAULT_DISTRIBUTION N_("DISTRIBUTION INSTALLATION") #define DEFAULT_WINDOW_NAME N_("SPOKE NAME") #define DEFAULT_BETA N_("PRE-RELEASE / TESTING") +#define LAYOUT_INDICATOR_LABEL_WIDTH 10
struct _AnacondaBaseWindowPrivate { gboolean is_beta, info_shown; @@ -92,6 +94,7 @@ struct _AnacondaBaseWindowPrivate { GtkWidget *alignment; GtkWidget *nav_box, *nav_area, *action_area; GtkWidget *name_label, *distro_label, *beta_label; + GtkWidget *layout_indicator;
/* Untranslated versions of various things. */ gchar *orig_name, *orig_distro, *orig_beta; @@ -267,7 +270,7 @@ static void anaconda_base_window_init(AnacondaBaseWindow *win) {
/* Create the beta label. */ win->priv->beta_label = gtk_label_new(NULL); - markup = g_markup_printf_escaped("<span foreground='red' weight='bold' size='large'>%s</span>", _(DEFAULT_BETA)); + markup = g_markup_printf_escaped("<span foreground='red' weight='bold' size='medium'>%s</span>", _(DEFAULT_BETA)); gtk_label_set_markup(GTK_LABEL(win->priv->beta_label), markup); g_free(markup); gtk_misc_set_alignment(GTK_MISC(win->priv->beta_label), 0, 0); @@ -275,10 +278,20 @@ static void anaconda_base_window_init(AnacondaBaseWindow *win) {
win->priv->orig_beta = g_strdup(DEFAULT_BETA);
+ /* Create the layout indicator */ + win->priv->layout_indicator = anaconda_layout_indicator_new(); + anaconda_layout_indicator_set_label_width(ANACONDA_LAYOUT_INDICATOR(win->priv->layout_indicator), + LAYOUT_INDICATOR_LABEL_WIDTH); + gtk_widget_set_halign(win->priv->layout_indicator, GTK_ALIGN_CENTER); + gtk_widget_set_hexpand(win->priv->layout_indicator, FALSE); + gtk_widget_set_margin_top(win->priv->layout_indicator, 6); + gtk_widget_set_margin_bottom(win->priv->layout_indicator, 4); + /* Add everything to the nav area. */ gtk_grid_attach(GTK_GRID(win->priv->nav_area), win->priv->name_label, 0, 0, 1, 1); gtk_grid_attach(GTK_GRID(win->priv->nav_area), win->priv->distro_label, 1, 0, 1, 1); gtk_grid_attach(GTK_GRID(win->priv->nav_area), win->priv->beta_label, 1, 1, 1, 1); + gtk_grid_attach(GTK_GRID(win->priv->nav_area), win->priv->layout_indicator, 1, 2, 1, 1); }
static void anaconda_base_window_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { diff --git a/widgets/src/LayoutIndicator.c b/widgets/src/LayoutIndicator.c index 1ae7432..5ac34c5 100644 --- a/widgets/src/LayoutIndicator.c +++ b/widgets/src/LayoutIndicator.c @@ -56,8 +56,9 @@ struct _AnacondaLayoutIndicatorPrivate { GtkWidget *icon; GtkLabel *layout_label; GdkCursor *cursor; - XklEngine *engine; XklConfigRec *config_rec; + gulong state_changed_handler_id; + gulong config_changed_handler_id; };
G_DEFINE_TYPE(AnacondaLayoutIndicator, anaconda_layout_indicator, GTK_TYPE_EVENT_BOX) @@ -65,7 +66,7 @@ G_DEFINE_TYPE(AnacondaLayoutIndicator, anaconda_layout_indicator, GTK_TYPE_EVENT static void anaconda_layout_indicator_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void anaconda_layout_indicator_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
-static void anaconda_layout_indicator_dispose(AnacondaLayoutIndicator *indicator); +static void anaconda_layout_indicator_dispose(GObject *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); @@ -84,7 +85,7 @@ static void anaconda_layout_indicator_class_init(AnacondaLayoutIndicatorClass *k
object_class->get_property = anaconda_layout_indicator_get_property; object_class->set_property = anaconda_layout_indicator_set_property; - object_class->dispose = (GObjectFinalizeFunc) anaconda_layout_indicator_dispose; + object_class->dispose = anaconda_layout_indicator_dispose;
/** * AnacondaLayoutIndicator:layout: @@ -135,6 +136,26 @@ GtkWidget *anaconda_layout_indicator_new() { static void anaconda_layout_indicator_init(AnacondaLayoutIndicator *self) { GdkDisplay *display; GdkRGBA background_color = { 0.0, 0.0, 0.0, 0.0 }; + AnacondaLayoutIndicatorClass *klass = ANACONDA_LAYOUT_INDICATOR_GET_CLASS(self); + + if (!klass->engine) { + /* This code cannot go to class_init because that way it would be called + when GObject type system is initialized and Gdk won't give us the + display. Thus the first instance being created has to populate these + class-wide stuff */ + + /* initialize XklEngine instance that will be used by all LayoutIndicator instances */ + display = gdk_display_get_default(); + klass->engine = xkl_engine_get_instance(GDK_DISPLAY_XDISPLAY(display)); + + /* make XklEngine listening */ + xkl_engine_start_listen(klass->engine, XKLL_TRACK_KEYBOARD_STATE); + + /* 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, klass->engine); + }
g_return_if_fail(gdk_rgba_parse(&background_color, "#fdfdfd"));
@@ -167,30 +188,20 @@ static void anaconda_layout_indicator_init(AnacondaLayoutIndicator *self) { gtk_widget_override_background_color(GTK_WIDGET(self), GTK_STATE_FLAG_NORMAL, &background_color);
- /* 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)); + /* initialize XklConfigRec instance providing data */ 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); + xkl_config_rec_get_from_server(self->priv->config_rec, klass->engine);
- /* init layout item with the current layout */ - self->priv->layout = get_current_layout(self->priv->engine, self->priv->config_rec); + /* hook up handler for "X-state-changed" and "X-config-changed" signals */ + self->priv->state_changed_handler_id = g_signal_connect(klass->engine, "X-state-changed", + G_CALLBACK(x_state_changed), + g_object_ref(self)); + self->priv->config_changed_handler_id = g_signal_connect(klass->engine, "X-config-changed", + G_CALLBACK(x_config_changed), + g_object_ref(self)); + + /* init layout attribute with the current layout */ + self->priv->layout = get_current_layout(klass->engine, self->priv->config_rec);
/* create layout label and set desired properties */ self->priv->layout_label = GTK_LABEL(gtk_label_new(NULL)); @@ -204,7 +215,8 @@ static void anaconda_layout_indicator_init(AnacondaLayoutIndicator *self) { anaconda_layout_indicator_refresh_ui_elements(self);
/* create the little keyboard icon and set its left margin */ - self->priv->icon = gtk_image_new_from_icon_name("input-keyboard-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR); + self->priv->icon = gtk_image_new_from_icon_name("input-keyboard-symbolic", + GTK_ICON_SIZE_SMALL_TOOLBAR);
/* create and populate the main box */ self->priv->main_box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4)); @@ -219,9 +231,15 @@ static void anaconda_layout_indicator_init(AnacondaLayoutIndicator *self) { gtk_container_add(GTK_CONTAINER(self), GTK_WIDGET(self->priv->main_box)); }
-static void anaconda_layout_indicator_dispose(AnacondaLayoutIndicator *self) { - /* finalize X events listening and unref all objects we reference - (may be called multiple times) */ +static void anaconda_layout_indicator_dispose(GObject *object) { + AnacondaLayoutIndicator *self = ANACONDA_LAYOUT_INDICATOR(object); + AnacondaLayoutIndicatorClass *klass = ANACONDA_LAYOUT_INDICATOR_GET_CLASS(self); + + /* disconnect signals (XklEngine will outlive us) */ + g_signal_handler_disconnect(klass->engine, self->priv->state_changed_handler_id); + g_signal_handler_disconnect(klass->engine, self->priv->config_changed_handler_id); + + /* 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; @@ -230,12 +248,6 @@ static void anaconda_layout_indicator_dispose(AnacondaLayoutIndicator *self) { 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; @@ -282,18 +294,19 @@ static void anaconda_layout_indicator_set_property(GObject *object, guint prop_i
static void anaconda_layout_indicator_clicked(GtkWidget *widget, GdkEvent *event, gpointer data) { AnacondaLayoutIndicator *self = ANACONDA_LAYOUT_INDICATOR(widget); + AnacondaLayoutIndicatorClass *klass = ANACONDA_LAYOUT_INDICATOR_GET_CLASS(self);
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); + XklState *state = xkl_engine_get_current_state(klass->engine); + guint n_groups = xkl_engine_get_num_groups(klass->engine);
/* cycle over groups */ guint next_group = (state->group + 1) % n_groups;
/* activate next group */ - xkl_engine_lock_group(self->priv->engine, next_group); + xkl_engine_lock_group(klass->engine, next_group); }
static void anaconda_layout_indicator_refresh_ui_elements(AnacondaLayoutIndicator *self) { @@ -310,8 +323,10 @@ static void anaconda_layout_indicator_refresh_ui_elements(AnacondaLayoutIndicato }
static void anaconda_layout_indicator_refresh_layout(AnacondaLayoutIndicator *self) { + AnacondaLayoutIndicatorClass *klass = ANACONDA_LAYOUT_INDICATOR_GET_CLASS(self); + g_free(self->priv->layout); - self->priv->layout = get_current_layout(self->priv->engine, self->priv->config_rec); + self->priv->layout = get_current_layout(klass->engine, self->priv->config_rec);
anaconda_layout_indicator_refresh_ui_elements(self); } @@ -373,9 +388,10 @@ static void x_state_changed(XklEngine *engine, XklEngineStateChange type, static void x_config_changed(XklEngine *engine, gpointer data) { g_return_if_fail(data); AnacondaLayoutIndicator *indicator = ANACONDA_LAYOUT_INDICATOR(data); + AnacondaLayoutIndicatorClass *klass = ANACONDA_LAYOUT_INDICATOR_GET_CLASS(indicator);
/* load current configuration from the X server */ - xkl_config_rec_get_from_server(indicator->priv->config_rec, indicator->priv->engine); + xkl_config_rec_get_from_server(indicator->priv->config_rec, klass->engine);
anaconda_layout_indicator_refresh_layout(indicator); } diff --git a/widgets/src/LayoutIndicator.h b/widgets/src/LayoutIndicator.h index c679186..82694e6 100644 --- a/widgets/src/LayoutIndicator.h +++ b/widgets/src/LayoutIndicator.h @@ -21,6 +21,7 @@ #define _LAYOUT_INDICATOR_H
#include <gtk/gtk.h> +#include <libxklavier/xklavier.h>
G_BEGIN_DECLS
@@ -53,9 +54,15 @@ struct _AnacondaLayoutIndicator { * 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. + * @engine: A singleton XklEngine instance that is used by all instances of + * LayoutIndicator. */ struct _AnacondaLayoutIndicatorClass { GtkEventBoxClass parent_class; + + /* this has to be a class attribute, because XklEngine is a singleton that + should be used by all instances */ + XklEngine *engine; };
GType anaconda_layout_indicator_get_type (void); diff --git a/widgets/src/SpokeWindow.c b/widgets/src/SpokeWindow.c index 9c1ae47..5cdec4a 100644 --- a/widgets/src/SpokeWindow.c +++ b/widgets/src/SpokeWindow.c @@ -144,6 +144,8 @@ static void anaconda_spoke_window_init(AnacondaSpokeWindow *win) { /* Create the button. */ win->priv->button = gtk_button_new_with_mnemonic(DEFAULT_BUTTON_LABEL); gtk_widget_set_halign(win->priv->button, GTK_ALIGN_START); + gtk_widget_set_vexpand(win->priv->button, FALSE); + gtk_widget_set_valign(win->priv->button, GTK_ALIGN_CENTER);
/* Hook up some signals for that button. The signal handlers here will * just raise our own custom signals for the whole window. @@ -153,7 +155,7 @@ static void anaconda_spoke_window_init(AnacondaSpokeWindow *win) {
/* And then put the button into the navigation area. */ nav_area = anaconda_base_window_get_nav_area(ANACONDA_BASE_WINDOW(win)); - gtk_grid_attach(GTK_GRID(nav_area), win->priv->button, 0, 1, 1, 1); + gtk_grid_attach(GTK_GRID(nav_area), win->priv->button, 0, 1, 1, 2); }
static void anaconda_spoke_window_realize(GtkWidget *widget, gpointer user_data) {
Also make Done button use two cells in the grid because BaseWindow's navigation area has now three rows instead of two (layout indicator added) and the "Done" button should be centered between the second and the third rows.
That's fine, but can you move the Done button to appear at either the top of one cell or the bottom of another? Its new layout has kind of a strange amount of whitespace around it.
@@ -267,7 +270,7 @@ static void anaconda_base_window_init(AnacondaBaseWindow *win) {
/* Create the beta label. */ win->priv->beta_label = gtk_label_new(NULL);
- markup = g_markup_printf_escaped("<span foreground='red' weight='bold' size='large'>%s</span>", _(DEFAULT_BETA));
- markup = g_markup_printf_escaped("<span foreground='red' weight='bold' size='medium'>%s</span>", _(DEFAULT_BETA)); gtk_label_set_markup(GTK_LABEL(win->priv->beta_label), markup); g_free(markup); gtk_misc_set_alignment(GTK_MISC(win->priv->beta_label), 0, 0);
Remember to make this change in anaconda_base_window_retranslate as well. If you would prefer, you could move to using a format_ function like I am doing in MountpointSelector.c and SpokeSelector.c Just grep for that.
@@ -275,10 +278,20 @@ static void anaconda_base_window_init(AnacondaBaseWindow *win) {
win->priv->orig_beta = g_strdup(DEFAULT_BETA);
- /* Create the layout indicator */
- win->priv->layout_indicator = anaconda_layout_indicator_new();
- anaconda_layout_indicator_set_label_width(ANACONDA_LAYOUT_INDICATOR(win->priv->layout_indicator),
LAYOUT_INDICATOR_LABEL_WIDTH);- gtk_widget_set_halign(win->priv->layout_indicator, GTK_ALIGN_CENTER);
- gtk_widget_set_hexpand(win->priv->layout_indicator, FALSE);
- gtk_widget_set_margin_top(win->priv->layout_indicator, 6);
- gtk_widget_set_margin_bottom(win->priv->layout_indicator, 4);
- /* Add everything to the nav area. */ gtk_grid_attach(GTK_GRID(win->priv->nav_area), win->priv->name_label, 0, 0, 1, 1); gtk_grid_attach(GTK_GRID(win->priv->nav_area), win->priv->distro_label, 1, 0, 1, 1); gtk_grid_attach(GTK_GRID(win->priv->nav_area), win->priv->beta_label, 1, 1, 1, 1);
- gtk_grid_attach(GTK_GRID(win->priv->nav_area), win->priv->layout_indicator, 1, 2, 1, 1);
}
This is the other big problem I have. I don't like the position here in the header. It might be a simple matter of changing the center alignment to something else. It just kind of feels out of place to me.
- Chris
On Tue, 2013-04-30 at 11:42 -0400, Chris Lumens wrote:
Also make Done button use two cells in the grid because BaseWindow's navigation area has now three rows instead of two (layout indicator added) and the "Done" button should be centered between the second and the third rows.
That's fine, but can you move the Done button to appear at either the top of one cell or the bottom of another? Its new layout has kind of a strange amount of whitespace around it.
You mean something like that -- http://vpodzime.fedorapeople.org/new_spoke_header_layout.png ? Also please note the change of the indicator's position.
@@ -267,7 +270,7 @@ static void anaconda_base_window_init(AnacondaBaseWindow *win) {
/* Create the beta label. */ win->priv->beta_label = gtk_label_new(NULL);
- markup = g_markup_printf_escaped("<span foreground='red' weight='bold' size='large'>%s</span>", _(DEFAULT_BETA));
- markup = g_markup_printf_escaped("<span foreground='red' weight='bold' size='medium'>%s</span>", _(DEFAULT_BETA)); gtk_label_set_markup(GTK_LABEL(win->priv->beta_label), markup); g_free(markup); gtk_misc_set_alignment(GTK_MISC(win->priv->beta_label), 0, 0);
Remember to make this change in anaconda_base_window_retranslate as well. If you would prefer, you could move to using a format_ function like I am doing in MountpointSelector.c and SpokeSelector.c Just grep for that.
Done, thanks for pointing that out, I had no idea why the label had large font on the welcome screen.
This means we can change the warning not to include the static value for current layout.
Signed-off-by: Vratislav Podzimek vpodzime@redhat.com --- pyanaconda/ui/gui/spokes/lib/passphrase.glade | 19 +++++++++++++++++-- pyanaconda/ui/gui/spokes/lib/passphrase.py | 20 ++++---------------- 2 files changed, 21 insertions(+), 18 deletions(-)
diff --git a/pyanaconda/ui/gui/spokes/lib/passphrase.glade b/pyanaconda/ui/gui/spokes/lib/passphrase.glade index cb08899..4f5dc76 100644 --- a/pyanaconda/ui/gui/spokes/lib/passphrase.glade +++ b/pyanaconda/ui/gui/spokes/lib/passphrase.glade @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <interface> + <!-- interface-requires AnacondaWidgets 1.0 --> <!-- interface-requires gtk+ 3.0 --> <object class="GtkDialog" id="passphrase_dialog"> <property name="can_focus">False</property> @@ -186,7 +187,21 @@ </packing> </child> <child> - <placeholder/> + <object class="AnacondaLayoutIndicator" id="AnacondaLayoutIndicator1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="has_tooltip">True</property> + <property name="tooltip_markup" translatable="yes">Current layout: &apos;us&apos;. Click to switch to the next layout</property> + <property name="tooltip_text" translatable="yes">Current layout: 'us'. Click to switch to the next layout</property> + <property name="halign">center</property> + <property name="label_width">4</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">1</property> + <property name="height">1</property> + </packing> </child> </object> <packing> @@ -216,7 +231,7 @@ <object class="GtkLabel" id="passphrase_warning_label"> <property name="visible">True</property> <property name="can_focus">False</property> - <property name="label" translatable="yes">keymap warning label</property> + <property name="label" translatable="yes">Warning: You want be able to change your keyboard layout (from the default one) when you decrypt your disks after install.</property> <property name="use_markup">True</property> <property name="wrap">True</property> </object> diff --git a/pyanaconda/ui/gui/spokes/lib/passphrase.py b/pyanaconda/ui/gui/spokes/lib/passphrase.py index ecf2484..3cb7ae5 100644 --- a/pyanaconda/ui/gui/spokes/lib/passphrase.py +++ b/pyanaconda/ui/gui/spokes/lib/passphrase.py @@ -19,28 +19,22 @@ # Red Hat Author(s): David Lehman dlehman@redhat.com #
-from gi.repository import Gtk -from gi.repository import Gdk +# pylint: disable-msg=E0611 +from gi.repository import Gtk, Gdk
import gettext import pwquality
from pyanaconda.ui.gui import GUIObject
-from pyanaconda import keyboard - _ = lambda x: gettext.ldgettext("anaconda", x) N_ = lambda x: x P_ = lambda x, y, z: gettext.ldngettext("anaconda", x, y, z)
__all__ = ["PassphraseDialog"]
-warning_label_template = N_("Warning: Your current keyboard layout is <b>%s</b>." - "If you change your keyboard layout, you may not be " - "able to decrypt your disks after install.") - ERROR_WEAK = N_("You have provided a weak passphrase: %s") -ERROR_NOT_MATCHING = N_("Passphrases do not match.") +ERROR_NOT_MATCHING = N_("Passphrases do not match.")
class PassphraseDialog(GUIObject): builderObjects = ["passphrase_dialog"] @@ -49,7 +43,6 @@ class PassphraseDialog(GUIObject):
def refresh(self): super(GUIObject, self).refresh() - self._warning_label = self.builder.get_object("passphrase_warning_label")
# disable input methods for the passphrase Entry widgets and make sure # the focus change mask is enabled @@ -78,12 +71,6 @@ class PassphraseDialog(GUIObject): self._pwq = pwquality.PWQSettings() self._pwq.read_config()
- # update the warning label with the currently selected keymap - xkl_wrapper = keyboard.XklWrapper.get_instance() - keymap_name = xkl_wrapper.get_current_layout_name() - warning_label_text = _(warning_label_template) % keymap_name - self._warning_label.set_markup(warning_label_text) - # initialize with the previously set passphrase self.passphrase = self.data.autopart.passphrase
@@ -98,6 +85,7 @@ class PassphraseDialog(GUIObject):
def run(self): self.refresh() + self.window.show_all() rc = self.window.run() self.window.destroy() return rc
On Wed, 2013-04-10 at 19:16 +0200, Vratislav Podzimek wrote:
These are the results of joint work of me and Máirín to implement layout indication in Anaconda's GUI.
PATCH 1/3 adds a new Anaconda widget called LayoutIndicator. It is based on the libxklavier library (no way to use the GNOME's replacement yet (if ever)) and uses the XklEngine singleton instance to get information about layout switching, configuration changes etc. I hope the code is vastly commented, but in case of any doubts I'm glad to explain anything.
PATCH 2/3 adds the LayoutIndicator to our BaseWindow class, so that it appears in the header of every spoke and hub. The patch also includes some changes that create more space for the indicator and rearrange the heading a little bit. Among the other things the red "beta warning" label became smaller. But for some reason on the WelcomeSpoke it has the same size. Any ideas?
Anyway, that is the "global indicator". Since for dialogs we use lightboxing that hides the underlying spoke/hub, we would need to place some more indicators to places where they are needed. The only one so far seems to be the LUKS passphrase dialog which is the case covered with PATCH 3/3.
The static preview can be seen at: http://vpodzime.fedorapeople.org/making_room_for_LI_new.png (the smaller indicator just shows the one that is used in the LUKS dialog)
Tomorrow I will also post a screencast I cannot record it now via SSH.
The video preview can be seen at: http://vpodzime.fedorapeople.org/layout_indicator_preview.webm
It also shows a bug in one of the patches that I've already fixed -- Glade for some reason saves the tooltip for the indicator so it was incorrect when it appeared for the first time in the LUKS passphrase dialog.
anaconda-patches@lists.fedorahosted.org