gtk2/gtk/gtkshortcutlabel.c
2023-01-06 12:08:55 +01:00

609 lines
16 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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 <glib/gi18n-lib.h>
#include "gtkprivate.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)
#ifndef GDK_WINDOWING_MACOS
labels[i++] = C_("keyboard label", "Meta");
#else
labels[i++] = "";
#endif
*n_mods = i;
ch = gdk_keyval_to_unicode (key);
if (ch && ch < 0x80 && g_unichar_isgraph (ch))
{
switch (ch)
{
case '<':
labels[i++] = "&lt;";
break;
case '>':
labels[i++] = "&gt;";
break;
case '&':
labels[i++] = "&amp;";
break;
case '"':
labels[i++] = "&quot;";
break;
case '\'':
labels[i++] = "&apos;";
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]);
}
}