diff --git a/lib/AbstractSettingsPage.vala b/lib/AbstractSettingsPage.vala new file mode 100644 index 00000000..6fd1b852 --- /dev/null +++ b/lib/AbstractSettingsPage.vala @@ -0,0 +1,86 @@ +/* + * Copyright 2017–2021 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +/** + * AbstractSettingsPage is a {@link Gtk.ScrolledWindow} subclass with properties used + * by other Granite settings widgets. + */ +public abstract class Switchboard.SettingsPage : Gtk.Box { + protected string _icon_name; + protected string _title; + private Gtk.ScrolledWindow scrolled; + + /** + * Used to display a status icon overlayed on the display_widget in a Granite.SettingsSidebar + */ + public enum StatusType { + ERROR, + OFFLINE, + SUCCESS, + WARNING, + NONE + } + + /** + * Selects a colored icon to be displayed in a Granite.SettingsSidebar + */ + public StatusType status_type { get; set; default = StatusType.NONE; } + + /** + * A widget to display in place of an icon in a Granite.SettingsSidebar + */ + public Gtk.Widget? display_widget { get; construct; } + + /** + * A header to be sorted under in a Granite.SettingsSidebar + */ + public string? header { get; construct; } + + /** + * A status string to be displayed underneath the title in a Granite.SettingsSidebar + */ + public string status { get; set construct; } + + /** + * An icon name to be displayed in a Granite.SettingsSidebar + */ + public string? icon_name { + get { + return _icon_name; + } + construct set { + _icon_name = value; + } + } + + /** + * A title to be displayed in a Granite.SettingsSidebar + */ + public string title { + get { + return _title; + } + construct set { + _title = value; + } + } + + public new Gtk.Widget child { + get { + return scrolled.child; + } + set { + scrolled.child = value; + } + } + + construct { + scrolled = new Gtk.ScrolledWindow () { + hscrollbar_policy = Gtk.PolicyType.NEVER + }; + + append (scrolled); + } +} diff --git a/lib/AbstractSimpleSettingsPage.vala b/lib/AbstractSimpleSettingsPage.vala new file mode 100644 index 00000000..053c813b --- /dev/null +++ b/lib/AbstractSimpleSettingsPage.vala @@ -0,0 +1,132 @@ +/* + * Copyright 2017–2022 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/** + * SimpleSettingsPage is a widget divided into three sections: a predefined header, + * a content area, and an action area. + */ + +public abstract class Switchboard.SimpleSettingsPage : Switchboard.SettingsPage { + private Gtk.Label description_label; + private string _description; + + /** + * A {@link Gtk.Box} used as the action area for #this + */ + public Gtk.Box action_area { get; construct; } + + /** + * A {@link Gtk.Grid} used as the content area for #this + */ + public Gtk.Grid content_area { get; construct; } + + /** + * A {@link Gtk.Switch} that appears in the header area when #this.activatable is #true. #status_switch will be #null when #this.activatable is #false + */ + public Gtk.Switch? status_switch { get; construct; } + + /** + * Creates a {@link Gtk.Switch} #status_switch in the header of #this + */ + public bool activatable { get; construct; } + + /** + * Creates a {@link Gtk.Label} with a page description in the header of #this + */ + public string description { + get { + return _description; + } + construct set { + if (description_label != null) { + description_label.label = value; + } + _description = value; + } + } + + /** + * Creates a new SimpleSettingsPage + * Deprecated: Subclass this instead. + */ + protected SimpleSettingsPage () { + + } + + class construct { + set_css_name ("simplesettingspage"); + } + + construct { + var header_icon = new Gtk.Image.from_icon_name (icon_name) { + icon_size = Gtk.IconSize.LARGE, + valign = Gtk.Align.START + }; + + var title_label = new Gtk.Label (title) { + hexpand = true, + selectable = true, + wrap = true, + xalign = 0 + }; + title_label.add_css_class (Granite.STYLE_CLASS_H2_LABEL); + + var header_area = new Gtk.Grid (); + header_area.add_css_class ("header-area"); + header_area.attach (title_label, 1, 0); + + if (description != null) { + description_label = new Gtk.Label (description) { + selectable = true, + use_markup = true, + wrap = true, + xalign = 0 + }; + + header_area.attach (header_icon, 0, 0, 1, 2); + header_area.attach (description_label, 1, 1, 2); + } else { + header_area.attach (header_icon, 0, 0); + } + + if (activatable) { + status_switch = new Gtk.Switch () { + valign = Gtk.Align.START + }; + header_area.attach (status_switch, 2, 0); + } + + content_area = new Gtk.Grid () { + column_spacing = 12, + row_spacing = 12, + vexpand = true + }; + content_area.add_css_class ("content-area"); + + action_area = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0) { + halign = Gtk.Align.END + }; + action_area.add_css_class ("buttonbox"); + + var grid = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); + grid.append (header_area); + grid.append (content_area); + grid.append (action_area); + + child = grid; + + notify["icon-name"].connect (() => { + if (header_icon != null) { + header_icon.icon_name = icon_name; + } + }); + + notify["title"].connect (() => { + if (title_label != null) { + title_label.label = title; + } + }); + } +} diff --git a/lib/SettingsSidebar.vala b/lib/SettingsSidebar.vala new file mode 100644 index 00000000..1e9b4a37 --- /dev/null +++ b/lib/SettingsSidebar.vala @@ -0,0 +1,127 @@ +/* + * Copyright 2017-2022 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +/** + * SettingsSidebar acts as a controller for a Gtk.Stack; it shows a row of buttons + * to switch between the various pages of the associated stack widget. + * + * All the content for the rows comes from the child properties of a Granite.SettingsPage + * inside of the Gtk.Stack + */ +public class Switchboard.SettingsSidebar : Gtk.Widget { + private Gtk.ListBox listbox; + + /** + * The Gtk.Stack to control + */ + public Gtk.Stack stack { get; construct; } + + /** + * The name of the currently visible Granite.SettingsPage + */ + public string? visible_child_name { + get { + var selected_row = listbox.get_selected_row (); + + if (selected_row == null) { + return null; + } else { + return ((SettingsSidebarRow) selected_row).page.title; + } + } + set { + weak Gtk.Widget listbox_child = listbox.get_first_child (); + while (listbox_child != null) { + if (!(listbox_child is SettingsSidebarRow)) { + listbox_child = listbox_child.get_next_sibling (); + continue; + } + + if (((SettingsSidebarRow) listbox_child).page.title == value) { + listbox.select_row ((Gtk.ListBoxRow) listbox_child); + break; + } + + listbox_child = listbox_child.get_next_sibling (); + } + } + } + + /** + * Create a new SettingsSidebar + */ + public SettingsSidebar (Gtk.Stack stack) { + Object (stack: stack); + } + + static construct { + set_layout_manager_type (typeof (Gtk.BinLayout)); + } + + class construct { + set_css_name ("settingssidebar"); + } + + construct { + listbox = new Gtk.ListBox () { + hexpand = true, + activate_on_single_click = true, + selection_mode = Gtk.SelectionMode.SINGLE + }; + + var scrolled = new Gtk.ScrolledWindow () { + hscrollbar_policy = Gtk.PolicyType.NEVER, + child = listbox + }; + scrolled.set_parent (this); + + on_sidebar_changed (); + stack.pages.items_changed.connect (on_sidebar_changed); + + listbox.row_selected.connect ((row) => { + stack.visible_child = ((SettingsSidebarRow) row).page; + }); + + listbox.set_header_func ((row, before) => { + var header = ((SettingsSidebarRow) row).header; + if (header != null) { + var label = new Gtk.Label (header) { + halign = Gtk.Align.START, + xalign = 0 + }; + + label.add_css_class (Granite.STYLE_CLASS_H4_LABEL); + row.set_header (label); + } + }); + + stack.notify["visible-child-name"].connect (() => { + visible_child_name = stack.visible_child_name; + }); + } + + ~SettingsSidebar () { + get_first_child ().unparent (); + } + + private void on_sidebar_changed () { + weak Gtk.Widget listbox_child = listbox.get_first_child (); + while (listbox_child != null) { + weak Gtk.Widget next_child = listbox_child.get_next_sibling (); + listbox.remove (listbox_child); + listbox_child = next_child; + } + + weak Gtk.Widget child = stack.get_first_child (); + while (child != null) { + if (child is SettingsPage) { + var row = new SettingsSidebarRow ((SettingsPage) child); + listbox.append (row); + } + + child = child.get_next_sibling (); + } + } +} diff --git a/lib/SettingsSidebarRow.vala b/lib/SettingsSidebarRow.vala new file mode 100644 index 00000000..560ea9de --- /dev/null +++ b/lib/SettingsSidebarRow.vala @@ -0,0 +1,131 @@ +/* + * Copyright 2017–2021 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +private class Switchboard.SettingsSidebarRow : Gtk.ListBoxRow { + public SettingsPage.StatusType status_type { + set { + switch (value) { + case SettingsPage.StatusType.ERROR: + status_icon.icon_name = "emblem-error"; + break; + case SettingsPage.StatusType.OFFLINE: + status_icon.icon_name = "emblem-disabled"; + break; + case SettingsPage.StatusType.SUCCESS: + status_icon.icon_name = "emblem-enabled"; + break; + case SettingsPage.StatusType.WARNING: + status_icon.icon_name = "emblem-warning"; + break; + case SettingsPage.StatusType.NONE: + status_icon.clear (); + break; + } + } + } + + public Gtk.Widget display_widget { get; construct; } + + public string? header { get; set; } + + public unowned SettingsPage page { get; construct; } + + public string icon_name { + get { + return _icon_name; + } + set { + _icon_name = value; + if (display_widget is Gtk.Image) { + ((Gtk.Image) display_widget).icon_name = value; + ((Gtk.Image) display_widget).icon_size = Gtk.IconSize.LARGE; + } + } + } + + public string status { + set { + status_label.label = value; + status_label.visible = true; + } + } + + public string title { + get { + return _title; + } + set { + _title = value; + title_label.label = value; + } + } + + private Gtk.Image status_icon; + private Gtk.Label status_label; + private Gtk.Label title_label; + private string _icon_name; + private string _title; + + public SettingsSidebarRow (SettingsPage page) { + Object ( + page: page + ); + } + + construct { + title_label = new Gtk.Label (page.title) { + ellipsize = Pango.EllipsizeMode.END, + vexpand = true, + xalign = 0 + }; + title_label.add_css_class (Granite.STYLE_CLASS_H3_LABEL); + + status_icon = new Gtk.Image () { + halign = Gtk.Align.END, + valign = Gtk.Align.END + }; + + status_label = new Gtk.Label (null) { + use_markup = true, + ellipsize = Pango.EllipsizeMode.END, + vexpand = true, + xalign = 0, + visible = false + }; + status_label.add_css_class (Granite.STYLE_CLASS_SMALL_LABEL); + + if (page.icon_name != null) { + display_widget = new Gtk.Image (); + icon_name = page.icon_name; + } else { + display_widget = page.display_widget; + } + + var overlay = new Gtk.Overlay (); + overlay.set_child (display_widget); + overlay.add_overlay (status_icon); + + var grid = new Gtk.Grid (); + grid.attach (overlay, 0, 0, 1, 2); + grid.attach (title_label, 1, 0); + grid.attach (status_label, 1, 1); + + child = grid; + + header = page.header; + page.bind_property ("icon-name", this, "icon-name", BindingFlags.DEFAULT); + page.bind_property ("status", this, "status", BindingFlags.DEFAULT); + page.bind_property ("status-type", this, "status-type", BindingFlags.DEFAULT); + page.bind_property ("title", this, "title", BindingFlags.DEFAULT); + + if (page.status != null) { + status = page.status; + } + + if (page.status_type != SettingsPage.StatusType.NONE) { + status_type = page.status_type; + } + } +} diff --git a/po/POTFILES b/po/POTFILES index 4c3af065..1f8039ce 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -1,5 +1,9 @@ +lib/AbstractSettingsPage.vala +lib/AbstractSimpleSettingsPage.vala lib/PlugsManager.vala lib/Plug.vala +lib/SettingsSidebarRow.vala +lib/SettingsSidebar.vala src/Application.vala src/CategoryView.vala src/PlugsSearch.vala