From 2b517e352d717b8ee630fef17739ed522897fd44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Fri, 6 Mar 2020 01:03:45 +0100 Subject: [PATCH] extensionPrefs: Use template for preference dialog The dialog that contains the extension's preference widget has become fairly complex over time, mostly due to the error handling. It therefore makes sense to move it to a template, just like we did for the main application window and extension rows. https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1106 --- js/extensionPrefs/data/css/application.css | 3 + .../org.gnome.Extensions.data.gresource.xml | 1 + .../data/ui/extension-prefs-dialog.ui | 197 ++++++++++ js/extensionPrefs/js/main.js | 339 ++++++------------ po/POTFILES.in | 1 + 5 files changed, 303 insertions(+), 238 deletions(-) create mode 100644 js/extensionPrefs/data/ui/extension-prefs-dialog.ui diff --git a/js/extensionPrefs/data/css/application.css b/js/extensionPrefs/data/css/application.css index 37105d081..5641c5d23 100644 --- a/js/extensionPrefs/data/css/application.css +++ b/js/extensionPrefs/data/css/application.css @@ -9,3 +9,6 @@ } image.warning { color: @warning_color; } + +.expander-frame > * { border-top-width: 0; } +.expander-toolbar { border: 0 solid @borders; border-top-width: 1px; } diff --git a/js/extensionPrefs/data/org.gnome.Extensions.data.gresource.xml b/js/extensionPrefs/data/org.gnome.Extensions.data.gresource.xml index 659c03ffc..de65e0823 100644 --- a/js/extensionPrefs/data/org.gnome.Extensions.data.gresource.xml +++ b/js/extensionPrefs/data/org.gnome.Extensions.data.gresource.xml @@ -5,6 +5,7 @@ dbus-interfaces/org.gnome.Shell.Extensions.xml + ui/extension-prefs-dialog.ui ui/extension-row.ui ui/extensions-window.ui diff --git a/js/extensionPrefs/data/ui/extension-prefs-dialog.ui b/js/extensionPrefs/data/ui/extension-prefs-dialog.ui new file mode 100644 index 000000000..fc08eae3c --- /dev/null +++ b/js/extensionPrefs/data/ui/extension-prefs-dialog.ui @@ -0,0 +1,197 @@ + + + + + + diff --git a/js/extensionPrefs/js/main.js b/js/extensionPrefs/js/main.js index d1da73a02..fd8c00959 100644 --- a/js/extensionPrefs/js/main.js +++ b/js/extensionPrefs/js/main.js @@ -191,31 +191,10 @@ var ExtensionsWindow = GObject.registerClass({ if (!row || !row.hasPrefs) return false; - let widget; - - try { - widget = row.prefsModule.buildPrefsWidget(); - } catch (e) { - widget = this._buildErrorUI(row, e); - } - - this._prefsDialog = new Gtk.Window({ - application: this.application, - default_width: 600, - default_height: 400, - modal: this.visible, - type_hint: Gdk.WindowTypeHint.DIALOG, - window_position: Gtk.WindowPosition.CENTER, - }); - - this._prefsDialog.set_titlebar(new Gtk.HeaderBar({ - show_close_button: true, - title: row.name, - visible: true, - })); + this._prefsDialog = new ExtensionPrefsDialog(row); if (this.visible) - this._prefsDialog.transient_for = this; + this._prefsDialog.set({ transient_for: this, modal: true }); this._prefsDialog.connect('destroy', () => { this._prefsDialog = null; @@ -224,7 +203,6 @@ var ExtensionsWindow = GObject.registerClass({ this.destroy(); }); - this._prefsDialog.add(widget); this._prefsDialog.show(); return true; @@ -266,121 +244,6 @@ var ExtensionsWindow = GObject.registerClass({ }); } - _buildErrorUI(row, exc) { - let scroll = new Gtk.ScrolledWindow({ - hscrollbar_policy: Gtk.PolicyType.NEVER, - propagate_natural_height: true, - }); - - let box = new Gtk.Box({ - orientation: Gtk.Orientation.VERTICAL, - spacing: 12, - margin: 100, - margin_bottom: 60, - }); - scroll.add(box); - - let label = new Gtk.Label({ - label: '%s'.format(_("Something’s gone wrong")), - use_markup: true, - }); - label.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL); - box.add(label); - - label = new Gtk.Label({ - label: _("We’re very sorry, but there’s been a problem: the settings for this extension can’t be displayed. We recommend that you report the issue to the extension authors."), - justify: Gtk.Justification.CENTER, - wrap: true, - }); - box.add(label); - - let expander = new Expander({ - label: _("Technical Details"), - margin_top: 12, - }); - box.add(expander); - - let errortext = '%s\n\nStack trace:\n'.format(exc); - // Indent stack trace. - errortext += - exc.stack.split('\n').map(line => ' %s'.format(line)).join('\n'); - - let buffer = new Gtk.TextBuffer({ text: errortext }); - let textview = new Gtk.TextView({ - buffer, - wrap_mode: Gtk.WrapMode.WORD, - monospace: true, - editable: false, - top_margin: 12, - bottom_margin: 12, - left_margin: 12, - right_margin: 12, - }); - - let toolbar = new Gtk.Toolbar(); - let provider = new Gtk.CssProvider(); - provider.load_from_data(`* { - border: 0 solid @borders; - border-top-width: 1px; - }`); - toolbar.get_style_context().add_provider( - provider, - Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION - ); - - let copyButton = new Gtk.ToolButton({ - icon_name: 'edit-copy-symbolic', - tooltip_text: _("Copy Error"), - }); - toolbar.add(copyButton); - - copyButton.connect('clicked', w => { - let clipboard = Gtk.Clipboard.get_default(w.get_display()); - // markdown for pasting in gitlab issues - let lines = [ - 'The settings of extension %s had an error:'.format(row.uuid), - '```', // '`' (xgettext throws up on odd number of backticks) - exc.toString(), - '```', // '`' - '', - 'Stack trace:', - '```', // '`' - exc.stack.replace(/\n$/, ''), // stack without trailing newline - '```', // '`' - '', - ]; - clipboard.set_text(lines.join('\n'), -1); - }); - - let spacing = new Gtk.SeparatorToolItem({ draw: false }); - toolbar.add(spacing); - toolbar.child_set_property(spacing, "expand", true); - - let urlButton = new Gtk.ToolButton({ - label: _("Homepage"), - tooltip_text: _("Visit extension homepage"), - no_show_all: true, - visible: row.url !== '', - }); - toolbar.add(urlButton); - - urlButton.connect('clicked', w => { - let context = w.get_display().get_app_launch_context(); - Gio.AppInfo.launch_default_for_uri(row.url, context); - }); - - let expandedBox = new Gtk.Box({ - orientation: Gtk.Orientation.VERTICAL, - }); - expandedBox.add(textview); - expandedBox.add(toolbar); - - expander.add(expandedBox); - - scroll.show_all(); - return scroll; - } - _sortList(row1, row2) { return row1.name.localeCompare(row2.name); } @@ -504,105 +367,6 @@ var ExtensionsWindow = GObject.registerClass({ } }); -var Expander = GObject.registerClass({ - Properties: { - 'label': GObject.ParamSpec.string( - 'label', 'label', 'label', - GObject.ParamFlags.READWRITE, - null - ), - }, -}, class Expander extends Gtk.Box { - _init(params = {}) { - this._labelText = null; - - super._init(Object.assign(params, { - orientation: Gtk.Orientation.VERTICAL, - spacing: 0, - })); - - this._frame = new Gtk.Frame({ - shadow_type: Gtk.ShadowType.IN, - hexpand: true, - }); - - let eventBox = new Gtk.EventBox(); - this._frame.add(eventBox); - - let hbox = new Gtk.Box({ - spacing: 6, - margin: 12, - }); - eventBox.add(hbox); - - this._arrow = new Gtk.Image({ - icon_name: 'pan-end-symbolic', - }); - hbox.add(this._arrow); - - this._label = new Gtk.Label({ label: this._labelText }); - hbox.add(this._label); - - this._revealer = new Gtk.Revealer(); - - this._childBin = new Gtk.Frame({ - shadow_type: Gtk.ShadowType.IN, - }); - this._revealer.add(this._childBin); - - // Directly chain up to parent for internal children - super.add(this._frame); - super.add(this._revealer); - - let provider = new Gtk.CssProvider(); - provider.load_from_data('* { border-top-width: 0; }'); - this._childBin.get_style_context().add_provider( - provider, - Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION - ); - - this._gesture = new Gtk.GestureMultiPress({ - widget: this._frame, - button: 0, - exclusive: true, - }); - this._gesture.connect('released', (gesture, nPress) => { - if (nPress == 1) - this._revealer.reveal_child = !this._revealer.reveal_child; - }); - this._revealer.connect('notify::reveal-child', () => { - if (this._revealer.reveal_child) - this._arrow.icon_name = 'pan-down-symbolic'; - else - this._arrow.icon_name = 'pan-end-symbolic'; - }); - } - - get label() { - return this._labelText; - } - - set label(text) { - if (this._labelText == text) - return; - - if (this._label) - this._label.label = text; - this._labelText = text; - this.notify('label'); - } - - add(child) { - // set expanded child - this._childBin.get_children().forEach(c => { - this._childBin.remove(c); - }); - - if (child) - this._childBin.add(child); - } -}); - var ExtensionRow = GObject.registerClass({ GTypeName: 'ExtensionRow', Template: 'resource:///org/gnome/Extensions/ui/extension-row.ui', @@ -768,6 +532,105 @@ var ExtensionRow = GObject.registerClass({ } }); +var ExtensionPrefsDialog = GObject.registerClass({ + GTypeName: 'ExtensionPrefsDialog', + Template: 'resource:///org/gnome/Extensions/ui/extension-prefs-dialog.ui', + InternalChildren: [ + 'headerBar', + 'stack', + 'expander', + 'expanderArrow', + 'revealer', + 'errorView', + ], +}, class ExtensionPrefsDialog extends Gtk.Window { + _init(extension) { + super._init(); + + this._uuid = extension.uuid; + this._url = extension.url; + + this._headerBar.title = extension.name; + + this._actionGroup = new Gio.SimpleActionGroup(); + this.insert_action_group('win', this._actionGroup); + + this._initActions(); + + this._gesture = new Gtk.GestureMultiPress({ + widget: this._expander, + button: 0, + exclusive: true, + }); + + this._gesture.connect('released', (gesture, nPress) => { + if (nPress === 1) + this._revealer.reveal_child = !this._revealer.reveal_child; + }); + + this._revealer.connect('notify::reveal-child', () => { + this._expanderArrow.icon_name = this._revealer.reveal_child + ? 'pan-down-symbolic' + : 'pan-end-symbolic'; + }); + + try { + const widget = extension.prefsModule.buildPrefsWidget(); + this._stack.add(widget); + this._stack.visible_child = widget; + } catch (e) { + this._setError(e); + } + } + + _setError(exc) { + this._errorView.buffer.text = '%s\n\nStack trace:\n'.format(exc); + // Indent stack trace. + this._errorView.buffer.text += + exc.stack.split('\n').map(line => ' %s'.format(line)).join('\n'); + + // markdown for pasting in gitlab issues + let lines = [ + 'The settings of extension %s had an error:'.format(this._uuid), + '```', // '`' (xgettext throws up on odd number of backticks) + exc.toString(), + '```', // '`' + '', + 'Stack trace:', + '```', // '`' + exc.stack.replace(/\n$/, ''), // stack without trailing newline + '```', // '`' + '', + ]; + this._errorMarkdown = lines.join('\n'); + this._actionGroup.lookup('copy-error').enabled = true; + } + + _initActions() { + let action; + + action = new Gio.SimpleAction({ + name: 'copy-error', + enabled: false, + }); + action.connect('activate', () => { + const clipboard = Gtk.Clipboard.get_default(this.get_display()); + clipboard.set_text(this._errorMarkdown, -1); + }); + this._actionGroup.add_action(action); + + action = new Gio.SimpleAction({ + name: 'show-url', + enabled: this._url !== '', + }); + action.connect('activate', () => { + Gio.AppInfo.launch_default_for_uri(this._url, + this.get_display().get_app_launch_context()); + }); + this._actionGroup.add_action(action); + } +}); + function initEnvironment() { // Monkey-patch in a "global" object that fakes some Shell utilities // that ExtensionUtils depends on. diff --git a/po/POTFILES.in b/po/POTFILES.in index 3a0c0a151..ba8a5b13b 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -7,6 +7,7 @@ data/org.gnome.Shell.PortalHelper.desktop.in.in js/extensionPrefs/data/metainfo/org.gnome.Extensions.metainfo.xml.in js/extensionPrefs/data/org.gnome.Extensions.desktop.in.in js/extensionPrefs/js/main.js +js/extensionPrefs/data/ui/extension-prefs-dialog.ui js/extensionPrefs/data/ui/extension-row.ui js/extensionPrefs/data/ui/extensions-window.ui js/gdm/authPrompt.js