forked from AuroraMiddleware/gtk
a546ae32d7
Those property features don't seem to be in use anywhere. They are redundant since the docs cover the same information and more. They also created unnecessary translation work. Closes #4904
604 lines
16 KiB
C
604 lines
16 KiB
C
/* gtkshortcutlabel.c
|
||
*
|
||
* Copyright (C) 2015 Christian Hergert <christian@hergert.me>
|
||
*
|
||
* This library is free software; you can redistribute it and/or
|
||
* modify it under the terms of the GNU Library General Public License as
|
||
* published by the Free Software Foundation; either version 2 of the
|
||
* License, or (at your option) any later version.
|
||
*
|
||
* This library is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
* Library General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU Library General Public
|
||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#include "config.h"
|
||
|
||
#include "gtkshortcutlabel.h"
|
||
#include "gtkboxlayout.h"
|
||
#include "gtklabel.h"
|
||
#include "gtkframe.h"
|
||
#include "gtkwidgetprivate.h"
|
||
#include "gtkintl.h"
|
||
|
||
/**
|
||
* GtkShortcutLabel:
|
||
*
|
||
* `GtkShortcutLabel` displays a single keyboard shortcut or gesture.
|
||
*
|
||
* The main use case for `GtkShortcutLabel` is inside a [class@Gtk.ShortcutsWindow].
|
||
*/
|
||
|
||
struct _GtkShortcutLabel
|
||
{
|
||
GtkWidget parent_instance;
|
||
char *accelerator;
|
||
char *disabled_text;
|
||
};
|
||
|
||
struct _GtkShortcutLabelClass
|
||
{
|
||
GtkWidgetClass parent_class;
|
||
};
|
||
|
||
G_DEFINE_TYPE (GtkShortcutLabel, gtk_shortcut_label, GTK_TYPE_WIDGET)
|
||
|
||
enum {
|
||
PROP_0,
|
||
PROP_ACCELERATOR,
|
||
PROP_DISABLED_TEXT,
|
||
LAST_PROP
|
||
};
|
||
|
||
static GParamSpec *properties[LAST_PROP];
|
||
|
||
static char *
|
||
get_modifier_label (guint key)
|
||
{
|
||
const char *subscript;
|
||
const char *label;
|
||
|
||
switch (key)
|
||
{
|
||
case GDK_KEY_Shift_L:
|
||
case GDK_KEY_Control_L:
|
||
case GDK_KEY_Alt_L:
|
||
case GDK_KEY_Meta_L:
|
||
case GDK_KEY_Super_L:
|
||
case GDK_KEY_Hyper_L:
|
||
/* Translators: This string is used to mark left/right variants of modifier
|
||
* keys in the shortcut window (e.g. Control_L vs Control_R). Please keep
|
||
* this string very short, ideally just a single character, since it will
|
||
* be rendered as part of the key.
|
||
*/
|
||
subscript = C_("keyboard side marker", "L");
|
||
break;
|
||
case GDK_KEY_Shift_R:
|
||
case GDK_KEY_Control_R:
|
||
case GDK_KEY_Alt_R:
|
||
case GDK_KEY_Meta_R:
|
||
case GDK_KEY_Super_R:
|
||
case GDK_KEY_Hyper_R:
|
||
/* Translators: This string is used to mark left/right variants of modifier
|
||
* keys in the shortcut window (e.g. Control_L vs Control_R). Please keep
|
||
* this string very short, ideally just a single character, since it will
|
||
* be rendered as part of the key.
|
||
*/
|
||
subscript = C_("keyboard side marker", "R");
|
||
break;
|
||
default:
|
||
g_assert_not_reached ();
|
||
}
|
||
|
||
switch (key)
|
||
{
|
||
case GDK_KEY_Shift_L: case GDK_KEY_Shift_R:
|
||
label = C_("keyboard label", "Shift");
|
||
break;
|
||
case GDK_KEY_Control_L: case GDK_KEY_Control_R:
|
||
label = C_("keyboard label", "Ctrl");
|
||
break;
|
||
case GDK_KEY_Alt_L: case GDK_KEY_Alt_R:
|
||
label = C_("keyboard label", "Alt");
|
||
break;
|
||
case GDK_KEY_Meta_L: case GDK_KEY_Meta_R:
|
||
label = C_("keyboard label", "Meta");
|
||
break;
|
||
case GDK_KEY_Super_L: case GDK_KEY_Super_R:
|
||
label = C_("keyboard label", "Super");
|
||
break;
|
||
case GDK_KEY_Hyper_L: case GDK_KEY_Hyper_R:
|
||
label = C_("keyboard label", "Hyper");
|
||
break;
|
||
default:
|
||
g_assert_not_reached ();
|
||
}
|
||
|
||
return g_strdup_printf ("%s <small><b>%s</b></small>", label, subscript);
|
||
}
|
||
|
||
static char **
|
||
get_labels (guint key, GdkModifierType modifier, guint *n_mods)
|
||
{
|
||
const char *labels[16];
|
||
GList *freeme = NULL;
|
||
char key_label[6];
|
||
const char *tmp;
|
||
gunichar ch;
|
||
int i = 0;
|
||
char **retval;
|
||
|
||
if (modifier & GDK_SHIFT_MASK)
|
||
labels[i++] = C_("keyboard label", "Shift");
|
||
if (modifier & GDK_CONTROL_MASK)
|
||
labels[i++] = C_("keyboard label", "Ctrl");
|
||
if (modifier & GDK_ALT_MASK)
|
||
labels[i++] = C_("keyboard label", "Alt");
|
||
if (modifier & GDK_SUPER_MASK)
|
||
labels[i++] = C_("keyboard label", "Super");
|
||
if (modifier & GDK_HYPER_MASK)
|
||
labels[i++] = C_("keyboard label", "Hyper");
|
||
if (modifier & GDK_META_MASK)
|
||
labels[i++] = C_("keyboard label", "Meta");
|
||
|
||
*n_mods = i;
|
||
|
||
ch = gdk_keyval_to_unicode (key);
|
||
if (ch && ch < 0x80 && g_unichar_isgraph (ch))
|
||
{
|
||
switch (ch)
|
||
{
|
||
case '<':
|
||
labels[i++] = "<";
|
||
break;
|
||
case '>':
|
||
labels[i++] = ">";
|
||
break;
|
||
case '&':
|
||
labels[i++] = "&";
|
||
break;
|
||
case '"':
|
||
labels[i++] = """;
|
||
break;
|
||
case '\'':
|
||
labels[i++] = "'";
|
||
break;
|
||
case '\\':
|
||
labels[i++] = C_("keyboard label", "Backslash");
|
||
break;
|
||
default:
|
||
memset (key_label, 0, 6);
|
||
g_unichar_to_utf8 (g_unichar_toupper (ch), key_label);
|
||
labels[i++] = key_label;
|
||
break;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
switch (key)
|
||
{
|
||
case GDK_KEY_Shift_L: case GDK_KEY_Shift_R:
|
||
case GDK_KEY_Control_L: case GDK_KEY_Control_R:
|
||
case GDK_KEY_Alt_L: case GDK_KEY_Alt_R:
|
||
case GDK_KEY_Meta_L: case GDK_KEY_Meta_R:
|
||
case GDK_KEY_Super_L: case GDK_KEY_Super_R:
|
||
case GDK_KEY_Hyper_L: case GDK_KEY_Hyper_R:
|
||
freeme = g_list_prepend (freeme, get_modifier_label (key));
|
||
labels[i++] = (const char *)freeme->data;
|
||
break;
|
||
case GDK_KEY_Left:
|
||
labels[i++] = "\xe2\x86\x90";
|
||
break;
|
||
case GDK_KEY_Up:
|
||
labels[i++] = "\xe2\x86\x91";
|
||
break;
|
||
case GDK_KEY_Right:
|
||
labels[i++] = "\xe2\x86\x92";
|
||
break;
|
||
case GDK_KEY_Down:
|
||
labels[i++] = "\xe2\x86\x93";
|
||
break;
|
||
case GDK_KEY_space:
|
||
labels[i++] = "\xe2\x90\xa3";
|
||
break;
|
||
case GDK_KEY_Return:
|
||
labels[i++] = "\xe2\x8f\x8e";
|
||
break;
|
||
case GDK_KEY_Page_Up:
|
||
labels[i++] = C_("keyboard label", "Page_Up");
|
||
break;
|
||
case GDK_KEY_Page_Down:
|
||
labels[i++] = C_("keyboard label", "Page_Down");
|
||
break;
|
||
default:
|
||
tmp = gdk_keyval_name (gdk_keyval_to_lower (key));
|
||
if (tmp != NULL)
|
||
{
|
||
if (tmp[0] != 0 && tmp[1] == 0)
|
||
{
|
||
key_label[0] = g_ascii_toupper (tmp[0]);
|
||
key_label[1] = '\0';
|
||
labels[i++] = key_label;
|
||
}
|
||
else
|
||
{
|
||
labels[i++] = g_dpgettext2 (GETTEXT_PACKAGE, "keyboard label", tmp);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
labels[i] = NULL;
|
||
|
||
retval = g_strdupv ((char **)labels);
|
||
|
||
g_list_free_full (freeme, g_free);
|
||
|
||
return retval;
|
||
}
|
||
|
||
static GtkWidget *
|
||
dim_label (const char *text)
|
||
{
|
||
GtkWidget *label;
|
||
|
||
label = gtk_label_new (text);
|
||
gtk_widget_add_css_class (label, "dim-label");
|
||
|
||
return label;
|
||
}
|
||
|
||
static void
|
||
display_shortcut (GtkWidget *self,
|
||
guint key,
|
||
GdkModifierType modifier)
|
||
{
|
||
char **keys = NULL;
|
||
int i;
|
||
guint n_mods;
|
||
|
||
keys = get_labels (key, modifier, &n_mods);
|
||
for (i = 0; keys[i]; i++)
|
||
{
|
||
GtkWidget *disp;
|
||
|
||
if (i > 0)
|
||
gtk_widget_set_parent (dim_label ("+"), self);
|
||
|
||
disp = gtk_label_new (keys[i]);
|
||
if (i < n_mods)
|
||
gtk_widget_set_size_request (disp, 50, -1);
|
||
|
||
gtk_widget_add_css_class (disp, "keycap");
|
||
gtk_label_set_use_markup (GTK_LABEL (disp), TRUE);
|
||
|
||
gtk_widget_set_parent (disp, self);
|
||
}
|
||
g_strfreev (keys);
|
||
}
|
||
|
||
static gboolean
|
||
parse_combination (GtkShortcutLabel *self,
|
||
const char *str)
|
||
{
|
||
char **accels;
|
||
int k;
|
||
GdkModifierType modifier = 0;
|
||
guint key = 0;
|
||
gboolean retval = TRUE;
|
||
|
||
accels = g_strsplit (str, "&", 0);
|
||
for (k = 0; accels[k]; k++)
|
||
{
|
||
if (!gtk_accelerator_parse (accels[k], &key, &modifier))
|
||
{
|
||
retval = FALSE;
|
||
break;
|
||
}
|
||
if (k > 0)
|
||
gtk_widget_set_parent (dim_label ("+"), GTK_WIDGET (self));
|
||
|
||
display_shortcut (GTK_WIDGET (self), key, modifier);
|
||
}
|
||
g_strfreev (accels);
|
||
|
||
return retval;
|
||
}
|
||
|
||
static gboolean
|
||
parse_sequence (GtkShortcutLabel *self,
|
||
const char *str)
|
||
{
|
||
char **accels;
|
||
int k;
|
||
gboolean retval = TRUE;
|
||
|
||
accels = g_strsplit (str, "+", 0);
|
||
for (k = 0; accels[k]; k++)
|
||
{
|
||
if (!parse_combination (self, accels[k]))
|
||
{
|
||
retval = FALSE;
|
||
break;
|
||
}
|
||
}
|
||
|
||
g_strfreev (accels);
|
||
|
||
return retval;
|
||
}
|
||
|
||
static gboolean
|
||
parse_range (GtkShortcutLabel *self,
|
||
const char *str)
|
||
{
|
||
char *dots;
|
||
|
||
dots = strstr (str, "...");
|
||
if (!dots)
|
||
return parse_sequence (self, str);
|
||
|
||
dots[0] = '\0';
|
||
if (!parse_sequence (self, str))
|
||
return FALSE;
|
||
|
||
gtk_widget_set_parent (dim_label ("⋯"), GTK_WIDGET (self));
|
||
|
||
if (!parse_sequence (self, dots + 3))
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static void
|
||
clear_children (GtkShortcutLabel *self)
|
||
{
|
||
GtkWidget *child;
|
||
|
||
child = gtk_widget_get_first_child (GTK_WIDGET (self));
|
||
|
||
while (child)
|
||
{
|
||
GtkWidget *next = gtk_widget_get_next_sibling (child);
|
||
|
||
gtk_widget_unparent (child);
|
||
|
||
child = next;
|
||
}
|
||
}
|
||
|
||
static void
|
||
gtk_shortcut_label_rebuild (GtkShortcutLabel *self)
|
||
{
|
||
char **accels;
|
||
int k;
|
||
|
||
clear_children (self);
|
||
|
||
if (self->accelerator == NULL || self->accelerator[0] == '\0')
|
||
{
|
||
GtkWidget *label;
|
||
|
||
label = dim_label (self->disabled_text);
|
||
|
||
gtk_widget_set_parent (label, GTK_WIDGET (self));
|
||
return;
|
||
}
|
||
|
||
accels = g_strsplit (self->accelerator, " ", 0);
|
||
for (k = 0; accels[k]; k++)
|
||
{
|
||
if (k > 0)
|
||
gtk_widget_set_parent (dim_label ("/"), GTK_WIDGET (self));
|
||
|
||
if (!parse_range (self, accels[k]))
|
||
{
|
||
g_warning ("Failed to parse %s, part of accelerator '%s'", accels[k], self->accelerator);
|
||
break;
|
||
}
|
||
}
|
||
g_strfreev (accels);
|
||
}
|
||
|
||
static void
|
||
gtk_shortcut_label_finalize (GObject *object)
|
||
{
|
||
GtkShortcutLabel *self = (GtkShortcutLabel *)object;
|
||
|
||
g_free (self->accelerator);
|
||
g_free (self->disabled_text);
|
||
|
||
clear_children (self);
|
||
|
||
G_OBJECT_CLASS (gtk_shortcut_label_parent_class)->finalize (object);
|
||
}
|
||
|
||
static void
|
||
gtk_shortcut_label_get_property (GObject *object,
|
||
guint prop_id,
|
||
GValue *value,
|
||
GParamSpec *pspec)
|
||
{
|
||
GtkShortcutLabel *self = GTK_SHORTCUT_LABEL (object);
|
||
|
||
switch (prop_id)
|
||
{
|
||
case PROP_ACCELERATOR:
|
||
g_value_set_string (value, gtk_shortcut_label_get_accelerator (self));
|
||
break;
|
||
|
||
case PROP_DISABLED_TEXT:
|
||
g_value_set_string (value, gtk_shortcut_label_get_disabled_text (self));
|
||
break;
|
||
|
||
default:
|
||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
}
|
||
}
|
||
|
||
static void
|
||
gtk_shortcut_label_set_property (GObject *object,
|
||
guint prop_id,
|
||
const GValue *value,
|
||
GParamSpec *pspec)
|
||
{
|
||
GtkShortcutLabel *self = GTK_SHORTCUT_LABEL (object);
|
||
|
||
switch (prop_id)
|
||
{
|
||
case PROP_ACCELERATOR:
|
||
gtk_shortcut_label_set_accelerator (self, g_value_get_string (value));
|
||
break;
|
||
|
||
case PROP_DISABLED_TEXT:
|
||
gtk_shortcut_label_set_disabled_text (self, g_value_get_string (value));
|
||
break;
|
||
|
||
default:
|
||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
}
|
||
}
|
||
|
||
static void
|
||
gtk_shortcut_label_class_init (GtkShortcutLabelClass *klass)
|
||
{
|
||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||
|
||
object_class->finalize = gtk_shortcut_label_finalize;
|
||
object_class->get_property = gtk_shortcut_label_get_property;
|
||
object_class->set_property = gtk_shortcut_label_set_property;
|
||
|
||
/**
|
||
* GtkShortcutLabel:accelerator: (attributes org.gtk.Property.get=gtk_shortcut_label_get_accelerator org.gtk.Property.set=gtk_shortcut_label_set_accelerator)
|
||
*
|
||
* The accelerator that @self displays.
|
||
*
|
||
* See [property@Gtk.ShortcutsShortcut:accelerator]
|
||
* for the accepted syntax.
|
||
*/
|
||
properties[PROP_ACCELERATOR] =
|
||
g_param_spec_string ("accelerator", NULL, NULL,
|
||
NULL,
|
||
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||
|
||
/**
|
||
* GtkShortcutLabel:disabled-text: (attributes org.gtk.Property.get=gtk_shortcut_label_get_disabled_text org.gtk.Property.set=gtk_shortcut_label_set_disabled_text)
|
||
*
|
||
* The text that is displayed when no accelerator is set.
|
||
*/
|
||
properties[PROP_DISABLED_TEXT] =
|
||
g_param_spec_string ("disabled-text", NULL, NULL,
|
||
NULL,
|
||
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||
|
||
g_object_class_install_properties (object_class, LAST_PROP, properties);
|
||
|
||
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT);
|
||
gtk_widget_class_set_css_name (widget_class, I_("shortcut"));
|
||
}
|
||
|
||
static void
|
||
gtk_shortcut_label_init (GtkShortcutLabel *self)
|
||
{
|
||
/* Always use LTR so that modifiers are always left to the keyval */
|
||
gtk_widget_set_direction (GTK_WIDGET (self), GTK_TEXT_DIR_LTR);
|
||
}
|
||
|
||
/**
|
||
* gtk_shortcut_label_new:
|
||
* @accelerator: the initial accelerator
|
||
*
|
||
* Creates a new `GtkShortcutLabel` with @accelerator set.
|
||
*
|
||
* Returns: a newly-allocated `GtkShortcutLabel`
|
||
*/
|
||
GtkWidget *
|
||
gtk_shortcut_label_new (const char *accelerator)
|
||
{
|
||
return g_object_new (GTK_TYPE_SHORTCUT_LABEL,
|
||
"accelerator", accelerator,
|
||
NULL);
|
||
}
|
||
|
||
/**
|
||
* gtk_shortcut_label_get_accelerator: (attributes org.gtk.Method.get_property=accelerator)
|
||
* @self: a `GtkShortcutLabel`
|
||
*
|
||
* Retrieves the current accelerator of @self.
|
||
*
|
||
* Returns: (transfer none)(nullable): the current accelerator.
|
||
*/
|
||
const char *
|
||
gtk_shortcut_label_get_accelerator (GtkShortcutLabel *self)
|
||
{
|
||
g_return_val_if_fail (GTK_IS_SHORTCUT_LABEL (self), NULL);
|
||
|
||
return self->accelerator;
|
||
}
|
||
|
||
/**
|
||
* gtk_shortcut_label_set_accelerator: (attributes org.gtk.Method.set_property=accelerator)
|
||
* @self: a `GtkShortcutLabel`
|
||
* @accelerator: the new accelerator
|
||
*
|
||
* Sets the accelerator to be displayed by @self.
|
||
*/
|
||
void
|
||
gtk_shortcut_label_set_accelerator (GtkShortcutLabel *self,
|
||
const char *accelerator)
|
||
{
|
||
g_return_if_fail (GTK_IS_SHORTCUT_LABEL (self));
|
||
|
||
if (g_strcmp0 (accelerator, self->accelerator) != 0)
|
||
{
|
||
g_free (self->accelerator);
|
||
self->accelerator = g_strdup (accelerator);
|
||
gtk_shortcut_label_rebuild (self);
|
||
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ACCELERATOR]);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* gtk_shortcut_label_get_disabled_text: (attributes org.gtk.Method.get_property=disabled-text)
|
||
* @self: a `GtkShortcutLabel`
|
||
*
|
||
* Retrieves the text that is displayed when no accelerator is set.
|
||
*
|
||
* Returns: (transfer none)(nullable): the current text displayed when no
|
||
* accelerator is set.
|
||
*/
|
||
const char *
|
||
gtk_shortcut_label_get_disabled_text (GtkShortcutLabel *self)
|
||
{
|
||
g_return_val_if_fail (GTK_IS_SHORTCUT_LABEL (self), NULL);
|
||
|
||
return self->disabled_text;
|
||
}
|
||
|
||
/**
|
||
* gtk_shortcut_label_set_disabled_text: (attributes org.gtk.Method.set_property=disabled-text)
|
||
* @self: a `GtkShortcutLabel`
|
||
* @disabled_text: the text to be displayed when no accelerator is set
|
||
*
|
||
* Sets the text to be displayed by @self when no accelerator is set.
|
||
*/
|
||
void
|
||
gtk_shortcut_label_set_disabled_text (GtkShortcutLabel *self,
|
||
const char *disabled_text)
|
||
{
|
||
g_return_if_fail (GTK_IS_SHORTCUT_LABEL (self));
|
||
|
||
if (g_strcmp0 (disabled_text, self->disabled_text) != 0)
|
||
{
|
||
g_free (self->disabled_text);
|
||
self->disabled_text = g_strdup (disabled_text);
|
||
gtk_shortcut_label_rebuild (self);
|
||
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DISABLED_TEXT]);
|
||
}
|
||
}
|