diff --git a/po/POTFILES.in b/po/POTFILES.in index e3e9fb5d094c43af52a7ac252ef26e974a3af044..4f116b2111a73526f81906e54c91860fc707600c 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -24,6 +24,7 @@ src/ui/ms-features-panel.ui src/ui/ms-feedback-panel.ui src/ui/ms-lockscreen-panel.ui src/ui/ms-osk-add-layout-dialog.ui +src/ui/ms-osk-add-shortcut-dialog.ui src/ui/ms-osk-layout-prefs.ui src/ui/ms-osk-layout-row.ui src/ui/ms-osk-panel.ui diff --git a/src/meson.build b/src/meson.build index fc25cee096351bd34f9e49aae96516cdca937e9d..100b924321cc230c47319ede1cdefc18ee0ea2cd 100644 --- a/src/meson.build +++ b/src/meson.build @@ -50,6 +50,8 @@ mobile_settings_sources = [ 'ms-main.h', 'ms-osk-add-layout-dialog.c', 'ms-osk-add-layout-dialog.h', + 'ms-osk-add-shortcut-dialog.c', + 'ms-osk-add-shortcut-dialog.h', 'ms-osk-layout.c', 'ms-osk-layout.h', 'ms-osk-layout-prefs.c', diff --git a/src/mobile-settings.gresource.xml b/src/mobile-settings.gresource.xml index 2f64cd8c98e6a2d0c51b4f7d9ee455e21c8fa320..6fad4a9a102eff50da0558abd9d0cabe269805d1 100644 --- a/src/mobile-settings.gresource.xml +++ b/src/mobile-settings.gresource.xml @@ -12,6 +12,7 @@ ui/ms-feedback-row.ui ui/ms-lockscreen-panel.ui ui/ms-osk-add-layout-dialog.ui + ui/ms-osk-add-shortcut-dialog.ui ui/ms-osk-layout-prefs.ui ui/ms-osk-layout-row.ui ui/ms-osk-panel.ui diff --git a/src/ms-osk-add-shortcut-dialog.c b/src/ms-osk-add-shortcut-dialog.c new file mode 100644 index 0000000000000000000000000000000000000000..8d089bbad33ca9cd61d0f3f333df57faf938a7b1 --- /dev/null +++ b/src/ms-osk-add-shortcut-dialog.c @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2025 Tether Operations Limited + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authors: Gotam Gorabh + */ + +#define G_LOG_DOMAIN "ms-osk-add-shortcut-dialog" + +#include "mobile-settings-config.h" + +#include "ms-osk-add-shortcut-dialog.h" + +#define PHOSH_OSK_TERMINAL_SETTINGS "sm.puri.phosh.osk.Terminal" +#define SHORTCUTS_KEY "shortcuts" + +/** + * MsOskAddShortcutDialog: + * + * Dialog to add an OSK shortcut + */ + +typedef enum { + MODIFIER_CTRL, + MODIFIER_ALT, + MODIFIER_SHIFT, + MODIFIER_SUPER, + MODIFIER_LAST +} ShortcutModifiers; + +static const char * const shortcut_modifiers_names[] = { + "ctrl", + "alt", + "shift", + "super", + NULL +}; + +typedef enum { + KEY_UP, + KEY_DOWN, + KEY_LEFT, + KEY_RIGHT, + KEY_F1, + KEY_F2, + KEY_F3, + KEY_F4, + KEY_F5, + KEY_F6, + KEY_F7, + KEY_F8, + KEY_F9, + KEY_F10, + KEY_F11, + KEY_F12, + KEY_TAB, + KEY_DELETE, + KEY_LAST +}ShortcutKeys; + +static const char * const shortcut_keys_names[] = { + "Up", + "Down", + "Left", + "Right", + "F1", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "F10", + "F11", + "F12", + "Tab", + "Delete", + NULL +}; + +struct _MsOskAddShortcutDialog { + AdwDialog parent; + + GSettings *pos_terminal_settings; + + AdwToastOverlay *toast_overlay; + GtkWidget *add_button; + + GtkCheckButton *shortcut_modifiers[MODIFIER_LAST]; + + AdwEntryRow *shortcut_key_entry; + GtkFlowBox *key_flowbox; + GtkFlowBox *preview_flowbox; +}; +G_DEFINE_TYPE (MsOskAddShortcutDialog, ms_osk_add_shortcut_dialog, ADW_TYPE_DIALOG) + + +static void +is_valid_shortcut (MsOskAddShortcutDialog *self) +{ + GtkFlowBoxChild* flow_child = gtk_flow_box_get_child_at_index (self->preview_flowbox, 0); + GtkWidget *shortcut_label = gtk_widget_get_first_child (GTK_WIDGET (flow_child)); + GtkWidget *label_child = gtk_widget_get_first_child (shortcut_label); + + /* If a shortcut is valid then GtkShortcutLabel has one or more GtkLabel */ + if (!label_child) { + GtkWidget *invalid_label = gtk_label_new ("Invalid Shortcut"); + gtk_widget_add_css_class (invalid_label, "error"); + + for (int i = 0; i < MODIFIER_LAST; i++) + gtk_check_button_set_active (self->shortcut_modifiers[i], FALSE); + + gtk_flow_box_remove_all (self->preview_flowbox); + gtk_flow_box_append (self->preview_flowbox, invalid_label); + gtk_editable_set_text (GTK_EDITABLE (self->shortcut_key_entry), ""); + gtk_widget_set_sensitive (GTK_WIDGET (self->add_button), FALSE); + } else { + gtk_widget_set_sensitive (GTK_WIDGET (self->add_button), TRUE); + } +} + + +static GStrv +shortcut_append (const char *const *shortcuts, const char *shortcut) +{ + g_autoptr (GStrvBuilder) builder = g_strv_builder_new (); + + if (g_strv_contains (shortcuts, shortcut)) + return g_strdupv ((GStrv)shortcuts); + + for (int i = 0; shortcuts[i]; i++) + g_strv_builder_add (builder, shortcuts[i]); + + g_strv_builder_add (builder, shortcut); + + return g_strv_builder_end (builder); +} + + +static const char * +get_current_preview_shortcut (MsOskAddShortcutDialog *self) +{ + GtkFlowBoxChild* child = gtk_flow_box_get_child_at_index (self->preview_flowbox, 0); + GtkWidget *shortcut_label; + + if (!child) + return ""; + + shortcut_label = gtk_widget_get_first_child (GTK_WIDGET (child)); + + /* when users continue to choose shortcut without clearing preview */ + if (GTK_IS_LABEL (shortcut_label)) { + gtk_flow_box_remove_all (self->preview_flowbox); + return ""; + } + return gtk_shortcut_label_get_accelerator (GTK_SHORTCUT_LABEL (shortcut_label)); +} + + +static void +on_add_clicked (MsOskAddShortcutDialog *self) +{ + const char *curr_shortcut = get_current_preview_shortcut (self); + g_auto (GStrv) pos_terminal_shortcuts = NULL; + g_auto (GStrv) shortcut = NULL; + + pos_terminal_shortcuts = g_settings_get_strv (self->pos_terminal_settings, SHORTCUTS_KEY); + shortcut = shortcut_append ((const char * const *) pos_terminal_shortcuts, curr_shortcut); + + g_settings_set_strv (self->pos_terminal_settings, SHORTCUTS_KEY, (const char * const *)shortcut); + adw_dialog_close (ADW_DIALOG (self)); +} + + +static void +on_modifiers_toggled (MsOskAddShortcutDialog *self) +{ + g_autofree char *current_modifers = g_strdup (get_current_preview_shortcut (self)); + + for (int i = 0; i < MODIFIER_LAST; i++) { + GtkWidget *check_button_child = gtk_check_button_get_child (self->shortcut_modifiers[i]); + const char *mod_label = gtk_shortcut_label_get_accelerator (GTK_SHORTCUT_LABEL (check_button_child)); + gboolean active = gtk_check_button_get_active (self->shortcut_modifiers[i]); + char *contain = g_strrstr (current_modifers, mod_label); + g_autofree const char *accltr = NULL; + GtkWidget *shortcut_label; + if (active && contain == NULL) { + accltr = g_strconcat (current_modifers, mod_label, NULL); + shortcut_label = gtk_shortcut_label_new (accltr); + gtk_flow_box_remove_all (self->preview_flowbox); + gtk_flow_box_append (self->preview_flowbox, shortcut_label); + is_valid_shortcut (self); + } else if (!active && contain) { + g_auto (GStrv) parts = g_strsplit (current_modifers, mod_label, -1); + accltr = g_strjoinv ("", parts); + shortcut_label = gtk_shortcut_label_new (accltr); + gtk_flow_box_remove_all (self->preview_flowbox); + gtk_flow_box_append (self->preview_flowbox, shortcut_label); + is_valid_shortcut (self); + } + } +} + + +static void +on_shortcut_key_apply (MsOskAddShortcutDialog *self) +{ + const char *modifiers = get_current_preview_shortcut (self); + const char *key = gtk_editable_get_text (GTK_EDITABLE (self->shortcut_key_entry)); + const char *joined = g_strconcat (modifiers, key, NULL); + GtkWidget *shortcut_label = gtk_shortcut_label_new (joined); + gtk_flow_box_remove_all (self->preview_flowbox); + gtk_flow_box_append (self->preview_flowbox, shortcut_label); + is_valid_shortcut (self); +} + + +static void +on_key_selected (GtkFlowBox *box, + GtkFlowBoxChild *child, + gpointer user_data) +{ + MsOskAddShortcutDialog *self = user_data; + GtkWidget *shortcut_label_child = gtk_widget_get_first_child (GTK_WIDGET (child)); + const char *modifiers = get_current_preview_shortcut (self); + const char *box_key = gtk_shortcut_label_get_accelerator (GTK_SHORTCUT_LABEL (shortcut_label_child)); + const char *joined = g_strconcat (modifiers, box_key, NULL); + GtkWidget *shortcut_label = gtk_shortcut_label_new (joined); + gtk_flow_box_remove_all (self->preview_flowbox); + gtk_flow_box_append (self->preview_flowbox, shortcut_label); + is_valid_shortcut (self); +} + + +static void +on_preview_clear_clicked (MsOskAddShortcutDialog *self) +{ + for (int i = 0; i < MODIFIER_LAST; i++) + gtk_check_button_set_active (self->shortcut_modifiers[i], FALSE); + + gtk_flow_box_remove_all (self->preview_flowbox); + gtk_editable_set_text (GTK_EDITABLE (self->shortcut_key_entry), ""); + gtk_widget_set_sensitive (GTK_WIDGET (self->add_button), FALSE); +} + + +static void +ms_osk_add_shortcut_dialog_dispose (GObject *object) +{ + MsOskAddShortcutDialog *self = MS_OSK_ADD_SHORTCUT_DIALOG (object); + + g_clear_object (&self->pos_terminal_settings); + + G_OBJECT_CLASS (ms_osk_add_shortcut_dialog_parent_class)->dispose (object); +} + + +static void +ms_osk_add_shortcut_dialog_class_init (MsOskAddShortcutDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = ms_osk_add_shortcut_dialog_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, + "/mobi/phosh/MobileSettings/" + "ui/ms-osk-add-shortcut-dialog.ui"); + + gtk_widget_class_bind_template_child (widget_class, MsOskAddShortcutDialog, toast_overlay); + gtk_widget_class_bind_template_child (widget_class, MsOskAddShortcutDialog, add_button); + + for (int i = 0; i < MODIFIER_LAST; i++) { + g_autofree char *widget_name = g_strdup_printf ("%s_modifier", shortcut_modifiers_names[i]); + gtk_widget_class_bind_template_child_full (widget_class, + widget_name, + FALSE, + G_STRUCT_OFFSET (MsOskAddShortcutDialog, shortcut_modifiers[i])); + } + + gtk_widget_class_bind_template_child (widget_class, MsOskAddShortcutDialog, shortcut_key_entry); + gtk_widget_class_bind_template_child (widget_class, MsOskAddShortcutDialog, key_flowbox); + gtk_widget_class_bind_template_child (widget_class, MsOskAddShortcutDialog, preview_flowbox); + + gtk_widget_class_bind_template_callback (widget_class, on_add_clicked); + gtk_widget_class_bind_template_callback (widget_class, on_modifiers_toggled); + gtk_widget_class_bind_template_callback (widget_class, on_shortcut_key_apply); + gtk_widget_class_bind_template_callback (widget_class, on_key_selected); + gtk_widget_class_bind_template_callback (widget_class, on_preview_clear_clicked); +} + + +static void +ms_osk_add_shortcut_dialog_init (MsOskAddShortcutDialog *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + self->pos_terminal_settings = g_settings_new (PHOSH_OSK_TERMINAL_SETTINGS); + + for (int i = 0; i < KEY_LAST; i++) { + GtkWidget *key_shortcut_label = gtk_shortcut_label_new (shortcut_keys_names[i]); + gtk_flow_box_append (self->key_flowbox, key_shortcut_label); + } +} + + +GtkWidget * +ms_osk_add_shortcut_dialog_new (void) +{ + return g_object_new (MS_TYPE_OSK_ADD_SHORTCUT_DIALOG, NULL); +} diff --git a/src/ms-osk-add-shortcut-dialog.h b/src/ms-osk-add-shortcut-dialog.h new file mode 100644 index 0000000000000000000000000000000000000000..91288c0cb8354d686f774fe3f3d60626d711fbf0 --- /dev/null +++ b/src/ms-osk-add-shortcut-dialog.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2025 Tether Operations Limited + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define MS_TYPE_OSK_ADD_SHORTCUT_DIALOG (ms_osk_add_shortcut_dialog_get_type ()) + +G_DECLARE_FINAL_TYPE (MsOskAddShortcutDialog, ms_osk_add_shortcut_dialog, + MS, OSK_ADD_SHORTCUT_DIALOG, AdwDialog) + +GtkWidget *ms_osk_add_shortcut_dialog_new (void); + +G_END_DECLS diff --git a/src/ms-osk-panel.c b/src/ms-osk-panel.c index e3bf3a9ebf4ecfec98cc202e6376c2c61cf26f1d..b118a606f368b08de1ccabaf60839e906dcd3e34 100644 --- a/src/ms-osk-panel.c +++ b/src/ms-osk-panel.c @@ -13,6 +13,7 @@ #include "ms-completer-info.h" #include "ms-enum-types.h" #include "ms-osk-layout-prefs.h" +#include "ms-osk-add-shortcut-dialog.h" #include "ms-osk-panel.h" #include "ms-util.h" @@ -191,31 +192,109 @@ on_drop (GtkDropTarget *drop_target, const GValue *value, double x, double y, g } +struct ShortcutData { + MsOskPanel *self; + char *shortcut_string; +}; + + +static void +shortcut_data_free (struct ShortcutData *data) +{ + if (!data) + return; + g_object_unref (data->self); + g_free (data->shortcut_string); + g_free (data); +} + + +static GStrv +shortcut_remove (const char *const *shortcuts, const char *shortcut) +{ + g_autoptr (GStrvBuilder) builder = g_strv_builder_new (); + + if (!shortcuts) + return NULL; + + for (int i = 0; shortcuts[i]; i++) { + if (g_str_equal (shortcut, shortcuts[i])) + continue; + g_strv_builder_add (builder, shortcuts[i]); + } + + return g_strv_builder_end (builder); +} + + +static void +on_remove_button_clicked (gpointer user_data) +{ + struct ShortcutData *data = user_data; + MsOskPanel *self = MS_OSK_PANEL (data->self); + g_auto (GStrv) pos_terminal_shortcuts = NULL; + g_auto (GStrv) shortcuts = NULL; + + pos_terminal_shortcuts = g_settings_get_strv (self->pos_terminal_settings, SHORTCUTS_KEY); + shortcuts = shortcut_remove ((const char * const *) pos_terminal_shortcuts, data->shortcut_string); + + g_settings_set_strv (self->pos_terminal_settings, SHORTCUTS_KEY, (const char * const *)shortcuts); + + shortcut_data_free (data); +} + + static GtkWidget * create_shortcuts_row (gpointer item, gpointer user_data) { MsOskPanel *self = MS_OSK_PANEL (user_data); GtkStringObject *string = GTK_STRING_OBJECT (item); - GtkWidget *label = gtk_shortcut_label_new (gtk_string_object_get_string (string)); - GtkDragSource *drag_source = gtk_drag_source_new (); - g_autoptr (GdkContentProvider) type = NULL; - GtkDropTarget *target = gtk_drop_target_new (G_TYPE_INVALID, GDK_ACTION_COPY); + const char *shortcut_string = gtk_string_object_get_string (string); + GtkWidget *label = gtk_shortcut_label_new (shortcut_string); + + GtkWidget *row_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + GtkWidget *remove_btn = gtk_button_new_from_icon_name ("window-close-symbolic"); + GType targets[] = { GTK_TYPE_STRING_OBJECT }; + g_autoptr (GdkContentProvider) type = NULL; + GtkDragSource *drag_source; + GtkDropTarget *target; + struct ShortcutData *data; + + data = g_new (struct ShortcutData, 1); + data->self = g_object_ref (self); + data->shortcut_string = g_strdup (shortcut_string); + + gtk_widget_add_css_class (row_box, "shortcut-row"); + + gtk_box_append (GTK_BOX (row_box), label); + gtk_widget_add_css_class (remove_btn, "flat"); + gtk_widget_add_css_class (remove_btn, "circular"); + + gtk_widget_set_hexpand (remove_btn, TRUE); + gtk_widget_set_halign (remove_btn, GTK_ALIGN_END); + + gtk_box_append (GTK_BOX (row_box), remove_btn); + + g_signal_connect_swapped (remove_btn, "clicked", G_CALLBACK (on_remove_button_clicked), data); + + drag_source = gtk_drag_source_new (); + target = gtk_drop_target_new (G_TYPE_INVALID, GDK_ACTION_COPY); type = gdk_content_provider_new_typed (GTK_TYPE_STRING_OBJECT, string); /* drag */ gtk_drag_source_set_content (drag_source, type); - g_signal_connect (drag_source, "drag-begin", G_CALLBACK (on_drag_begin), label); - gtk_widget_add_controller (label, GTK_EVENT_CONTROLLER (drag_source)); + g_signal_connect (drag_source, "drag-begin", G_CALLBACK (on_drag_begin), row_box); + gtk_widget_add_controller (row_box, GTK_EVENT_CONTROLLER (drag_source)); /* drop */ gtk_drop_target_set_gtypes (target, targets, G_N_ELEMENTS (targets)); g_signal_connect (target, "drop", G_CALLBACK (on_drop), self); /* No ref as we just use it for string comparison */ g_object_set_data (G_OBJECT (target), "ms-str", string); - gtk_widget_add_controller (label, GTK_EVENT_CONTROLLER (target)); + gtk_widget_add_controller (row_box, GTK_EVENT_CONTROLLER (target)); - return label; + return row_box; } @@ -420,6 +499,16 @@ is_osk_app (void) } +static void +on_new_shortcut_clicked (MsOskPanel *self) +{ + GtkWidget *dialog; + + dialog = ms_osk_add_shortcut_dialog_new (); + adw_dialog_present (ADW_DIALOG (dialog), GTK_WIDGET (self)); +} + + static void ms_osk_panel_finalize (GObject *object) { @@ -466,6 +555,7 @@ ms_osk_panel_class_init (MsOskPanelClass *klass) /* Terminal layout group */ gtk_widget_class_bind_template_child (widget_class, MsOskPanel, terminal_layout_group); gtk_widget_class_bind_template_child (widget_class, MsOskPanel, shortcuts_box); + gtk_widget_class_bind_template_callback (widget_class, on_new_shortcut_clicked); /* Stevia scaling */ gtk_widget_class_bind_template_child (widget_class, MsOskPanel, osk_scaling_group); diff --git a/src/ui/ms-osk-add-shortcut-dialog.ui b/src/ui/ms-osk-add-shortcut-dialog.ui new file mode 100644 index 0000000000000000000000000000000000000000..4359ba88fe353ca69297646075c769e2b18982a2 --- /dev/null +++ b/src/ui/ms-osk-add-shortcut-dialog.ui @@ -0,0 +1,181 @@ + + + + + + diff --git a/src/ui/ms-osk-panel.ui b/src/ui/ms-osk-panel.ui index c921526e80d7918e9eaf47033ba924c6e9900d4f..aa33910edfaf731f091802c9535cb277420815fb 100644 --- a/src/ui/ms-osk-panel.ui +++ b/src/ui/ms-osk-panel.ui @@ -151,6 +151,21 @@ Terminal Layout False + + + + + list-add-symbolic + + Add Shortcut + + + + + + Shortcuts