diff --git a/src/MainView.vala b/src/MainView.vala index f1899271..b84d206a 100644 --- a/src/MainView.vala +++ b/src/MainView.vala @@ -30,7 +30,7 @@ public class Power.MainView : Switchboard.SettingsPage { private const string SETTINGS_DAEMON_NAME = "org.gnome.SettingsDaemon.Power"; private const string SETTINGS_DAEMON_PATH = "/org/gnome/SettingsDaemon/Power"; - private Gtk.DropDown powerbutton_dropdown; + private AccessibleDropDown powerbutton_dropdown; private Gtk.Scale scale; private PowerSettings screen; @@ -201,12 +201,9 @@ public class Power.MainView : Switchboard.SettingsPage { }; // FIXME: Virtual machines can only shutdown or do nothing. Tablets always suspend. - powerbutton_dropdown = new Gtk.DropDown.from_strings ({ - _("Do nothing"), - _("Suspend"), - _("Ask to shutdown") - }) { - hexpand = true + string[] strings = {_("_Do nothing"), _("_Suspend"), _("_Ask to shutdown")}; + powerbutton_dropdown = new AccessibleDropDown (strings, true) { + hexpand = true, }; var powerbutton_label = new Gtk.Label (_("Power Button Behavior")) { @@ -345,7 +342,7 @@ public class Power.MainView : Switchboard.SettingsPage { update_powerbutton_dropdown (); settings.changed["power-button-action"].connect (update_powerbutton_dropdown); - powerbutton_dropdown.notify["selected"].connect (() => { + powerbutton_dropdown.selection_changed.connect (() => { int[] map = {0, 1, 3}; settings.set_enum ( "power-button-action", diff --git a/src/Widgets/AccessibleDropDown.vala b/src/Widgets/AccessibleDropDown.vala new file mode 100644 index 00000000..2a32ebd6 --- /dev/null +++ b/src/Widgets/AccessibleDropDown.vala @@ -0,0 +1,133 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2026 elementary, Inc. (https://elementary.io) + */ + +public class Power.AccessibleDropDown : Gtk.Box { + public signal void selection_changed (); + + public string[] strings { get; construct; } + public uint selected { + get { + return dropdown.selected; + } + + set { + dropdown.selected = value; + } + } + + public Object? selected_item { + get { + return dropdown.selected_item; + } + } + + public Gtk.DropDown dropdown { get; private set; } + public bool show_selected_icon { get; construct; } + private ListStore model; + + // Array parameter can contain strings with a mnenomic character specified by a preceding underscore + public AccessibleDropDown (string[] mnemonic_strings, bool show) { + Object ( + strings: mnemonic_strings, + show_selected_icon: show + ); + } + + construct { + model = new ListStore (typeof (Gtk.StringObject)); + foreach (string s in strings) { + model.append (new Gtk.StringObject (s)); + } + + var f = new Gtk.SignalListItemFactory (); + f.setup.connect ((obj) => { + var li = (Gtk.ListItem)obj; + var widget = new ItemWidget ("", this); + li.set_child (widget); + li.bind_property ("selected", widget, "selected", DEFAULT); + li.bind_property ("position", widget, "pos", DEFAULT); + }); + f.bind.connect ((obj) => { + var li = (Gtk.ListItem)obj; + var widget = (ItemWidget)(li.child); + var text = ((Gtk.StringObject)(li.get_item ())).get_string (); + widget.label.set_text_with_mnemonic (text); + }); + f.unbind.connect ((obj) => { + // No action required + }); + f.teardown.connect ((obj) => { + // Do we need to reverse the property bindings created in setup? + // Probably not because in this usage teardown only occurs when the dropdown closes + }); + + dropdown = new Gtk.DropDown (model, null) { + factory = f + }; + + dropdown.notify["selected"].connect (() => { + selection_changed (); + }); + + dropdown.mnemonic_activate.connect ((group_cycling) => { + warning ("dropdown mnemonic activate - group cycling %s", group_cycling.to_string ()); + warning ("selected is %u", selected); + }); + + append (dropdown); + bind_property ("hexpand", dropdown, "hexpand"); + } + + public void activate () { + dropdown.activate (); + } + + private class ItemWidget : Gtk.Grid { + public Gtk.Label label { get; construct; } + public AccessibleDropDown md { get; construct; } + public bool selected { get; set; } + public uint pos { get; set; } + + private Gtk.Button mnemonic_widget; + private Gtk.Image icon; + + public ItemWidget (string text, AccessibleDropDown md) { + Object ( + label: new Gtk.Label (text) { + use_underline = true, + halign = START, + hexpand = true + }, + md: md + ); + + mnemonic_widget = new Gtk.Button () { + halign = END + }; + + label.set_mnemonic_widget (this.mnemonic_widget); + mnemonic_widget.mnemonic_activate.connect (() => { + md.selected = pos; + md.activate (); + return false; + }); + + attach (label, 0, 0); + if (md.show_selected_icon) { + icon = new Gtk.Image.from_icon_name ("emblem-default") { + visible = selected + }; + + icon.add_css_class ("flat"); + attach (icon, 1, 0); + bind_property ("selected", icon, "visible"); + } + } + + construct { + hexpand = md.hexpand; + } + } +} diff --git a/src/Widgets/LidCloseActionComboBox.vala b/src/Widgets/LidCloseActionComboBox.vala index f0eb1646..782647f9 100644 --- a/src/Widgets/LidCloseActionComboBox.vala +++ b/src/Widgets/LidCloseActionComboBox.vala @@ -25,7 +25,7 @@ class Power.LidCloseActionComboBox : Gtk.Widget { private static Polkit.Permission? permission = null; - private Gtk.DropDown dropdown; + private AccessibleDropDown dropdown; private uint previous_active; public LidCloseActionComboBox (bool dock) { @@ -37,28 +37,31 @@ class Power.LidCloseActionComboBox : Gtk.Widget { } construct { - dropdown = new Gtk.DropDown (null, null) { - hexpand = true - }; - dropdown.set_parent (this); - var helper = LogindHelper.get_logind_helper (); - if (helper != null && helper.present) { - dropdown.model = new Gtk.StringList ({ - _("Suspend"), - _("Shutdown"), - _("Lock"), - _("Halt"), - _("Do nothing") - }); + var helper_present = helper != null && helper.present; + string[] strings; + if (helper_present) { + strings = { + _("_Suspend"), + _("Sh_utdown"), + _("_Lock"), + _("_Halt"), + _("_Do nothing") + }; } else { - dropdown.model = new Gtk.StringList ({_("Not supported")}); - dropdown.sensitive = false; + strings = {_("Not supported")}; } + dropdown = new AccessibleDropDown (strings, true) { + hexpand = true, + sensitive = helper_present + }; + + dropdown.set_parent (this); + update_current_action (); previous_active = dropdown.selected; - dropdown.notify["selected"].connect (on_changed); + dropdown.selection_changed.connect (on_changed); } // Returns true on success diff --git a/src/Widgets/TimeoutComboBox.vala b/src/Widgets/TimeoutComboBox.vala index 7d87c3ef..718d15c3 100644 --- a/src/Widgets/TimeoutComboBox.vala +++ b/src/Widgets/TimeoutComboBox.vala @@ -4,7 +4,9 @@ */ class Power.TimeoutComboBox : Granite.Bin { - private Greeter.AccountsService? greeter_act = null; + public GLib.Settings schema { get; construct; } + public string key { get; construct; } + public int selected_seconds { get; set; } private string? _enum_property = null; public string? enum_property { @@ -45,11 +47,9 @@ class Power.TimeoutComboBox : Granite.Bin { } } - public GLib.Settings schema { get; construct; } - public string key { get; construct; } + private Greeter.AccountsService? greeter_act = null; private VariantType key_type; - private Gtk.DropDown dropdown; - + private AccessibleDropDown dropdown; private const int SECS_IN_MINUTE = 60; private const int[] TIMEOUT = { 0, @@ -71,28 +71,25 @@ class Power.TimeoutComboBox : Granite.Bin { construct { key_type = schema.get_value (key).get_type (); - var liststore = new GLib.ListStore (typeof (Timeout)); - liststore.append (new Timeout (_("Never"), 0, _("(Results in higher energy usage)"))); - liststore.append (new Timeout (_("5 min"), 5 * SECS_IN_MINUTE)); - liststore.append (new Timeout (_("10 min"), 10 * SECS_IN_MINUTE)); - liststore.append (new Timeout (_("15 min"), 15 * SECS_IN_MINUTE)); - liststore.append (new Timeout (_("30 min"), 30 * SECS_IN_MINUTE)); - liststore.append (new Timeout (_("45 min"), 45 * SECS_IN_MINUTE)); - liststore.append (new Timeout (_("1 hour"), 60 * SECS_IN_MINUTE)); - liststore.append (new Timeout (_("2 hours"), 120 * SECS_IN_MINUTE)); - - var factory = new Gtk.SignalListItemFactory (); - factory.bind.connect (bind_factory); - - dropdown = new Gtk.DropDown (liststore, null) { - factory = factory + string[] strings = { + _("Never (uses more energy)"), + _("5 min"), + _("10 min"), + _("30 min"), + _("45 min"), + _("1 hour"), + _("2 hours") + }; + + dropdown = new AccessibleDropDown (strings, true) { + hexpand = true }; child = dropdown; setup_accountsservice.begin (); - dropdown.notify["selected"].connect (update_settings); + dropdown.selection_changed.connect (update_settings); schema.changed[key].connect (update_combo); } @@ -123,10 +120,13 @@ class Power.TimeoutComboBox : Granite.Bin { schema.changed[key].disconnect (update_combo); + int[] map = {0, 5, 10, 15, 30, 45, 60, 120}; + selected_seconds = map[dropdown.selected] * SECS_IN_MINUTE; + if (key_type.equal (VariantType.UINT32)) { - schema.set_uint (key, (uint) ((Timeout) dropdown.selected_item).seconds); + schema.set_uint (key, (uint) selected_seconds); } else if (key_type.equal (VariantType.INT32)) { - schema.set_int (key, ((Timeout) dropdown.selected_item).seconds); + schema.set_int (key, selected_seconds); } else { critical ("Unsupported key type in schema"); } @@ -135,10 +135,10 @@ class Power.TimeoutComboBox : Granite.Bin { if (greeter_act != null) { if (key == "sleep-inactive-ac-timeout") { - greeter_act.sleep_inactive_ac_timeout = ((Timeout) dropdown.selected_item).seconds; + greeter_act.sleep_inactive_ac_timeout = selected_seconds; greeter_act.sleep_inactive_ac_type = schema.get_enum (enum_property); } else if (key == "sleep-inactive-battery-timeout") { - greeter_act.sleep_inactive_battery_timeout = ((Timeout) dropdown.selected_item).seconds; + greeter_act.sleep_inactive_battery_timeout = selected_seconds; greeter_act.sleep_inactive_battery_type = schema.get_enum (enum_property); } } @@ -182,48 +182,4 @@ class Power.TimeoutComboBox : Granite.Bin { dropdown.selected = find_closest (val); dropdown.notify["selected"].connect (update_settings); } - - private void bind_factory (Object object) { - var list_item = (Gtk.ListItem) object; - var timeout = (Timeout) list_item.item; - list_item.child = timeout.get_widget (); - } - - private class Timeout : Object { - public string label { get; construct; } - public string description { get; construct; } - public int seconds { get; construct; } - - public Timeout (string label, int seconds, string description = "") { - Object ( - label: label, - seconds: seconds, - description: description - ); - } - - public Gtk.Widget get_widget () { - var title = new Gtk.Label (label) { - valign = BASELINE, - xalign = 0 - }; - - var box = new Granite.Box (HORIZONTAL, HALF); - box.append (title); - - if (description != "") { - var description = new Gtk.Label (description) { - valign = BASELINE, - xalign = 0, - wrap = true - }; - description.add_css_class (Granite.CssClass.SMALL); - description.add_css_class (Granite.CssClass.WARNING); - - box.append (description); - } - - return box; - } - } } diff --git a/src/meson.build b/src/meson.build index c5781c0f..fe0136ba 100644 --- a/src/meson.build +++ b/src/meson.build @@ -12,6 +12,7 @@ plug_files = files( 'Widgets/TimeoutComboBox.vala', 'Widgets/LidCloseActionComboBox.vala', 'Widgets/PowerModeButton.vala', + 'Widgets/AccessibleDropDown.vala' ) switchboard_dep = dependency('switchboard-3')