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
This commit is contained in:
parent
08203c9c1e
commit
2b517e352d
@ -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; }
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
<file>dbus-interfaces/org.gnome.Shell.Extensions.xml</file>
|
||||
|
||||
<file>ui/extension-prefs-dialog.ui</file>
|
||||
<file>ui/extension-row.ui</file>
|
||||
<file>ui/extensions-window.ui</file>
|
||||
</gresource>
|
||||
|
197
js/extensionPrefs/data/ui/extension-prefs-dialog.ui
Normal file
197
js/extensionPrefs/data/ui/extension-prefs-dialog.ui
Normal file
@ -0,0 +1,197 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<template class="ExtensionPrefsDialog" parent="GtkWindow">
|
||||
<property name="default_width">600</property>
|
||||
<property name="default_height">400</property>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="headerBar">
|
||||
<property name="visible">True</property>
|
||||
<property name="show_close_button">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="visible">True</property>
|
||||
<property name="hscrollbar_policy">never</property>
|
||||
<property name="propagate_natural_height">True</property>
|
||||
<child>
|
||||
<object class="GtkViewport">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="margin">100</property>
|
||||
<property name="margin_bottom">60</property>
|
||||
<property name="spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Something’s gone wrong</property>
|
||||
<attributes>
|
||||
<attribute name="scale" value="1.44"/> <!-- x-large -->
|
||||
</attributes>
|
||||
<style>
|
||||
<class name="dim-label"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">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.</property>
|
||||
<property name="justify">center</property>
|
||||
<property name="wrap">True</property>
|
||||
<property name="xalign">0.5</property>
|
||||
<property name="yalign">0.5</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="margin_top">12</property>
|
||||
<child>
|
||||
<object class="GtkFrame" id="expander">
|
||||
<property name="visible">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkEventBox">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="margin">12</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="expanderArrow">
|
||||
<property name="visible">True</property>
|
||||
<property name="icon_name">pan-end-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Technical Details</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRevealer" id="revealer">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="GtkFrame">
|
||||
<property name="visible">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<style>
|
||||
<class name="expander-frame"/>
|
||||
</style>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkTextView" id="errorView">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="monospace">True</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="wrap_mode">word</property>
|
||||
<property name="left_margin">12</property>
|
||||
<property name="right_margin">12</property>
|
||||
<property name="top_margin">12</property>
|
||||
<property name="bottom_margin">12</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolbar">
|
||||
<property name="visible">True</property>
|
||||
<style>
|
||||
<class name="expander-toolbar"/>
|
||||
</style>
|
||||
<child>
|
||||
<object class="GtkToolItem">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="action_name">win.copy-error</property>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
<class name="image-button"/>
|
||||
</style>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="icon_name">edit-copy-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorToolItem">
|
||||
<property name="visible">True</property>
|
||||
<property name="draw">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolItem">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="homeButton">
|
||||
<property name="visible"
|
||||
bind-source="homeButton"
|
||||
bind-property="sensitive"
|
||||
bind-flags="sync-create"/>
|
||||
<property name="label" translatable="yes">Homepage</property>
|
||||
<property name="tooltip_text" translatable="yes">Visit extension homepage</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="no_show_all">True</property>
|
||||
<property name="action_name">win.show-url</property>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
@ -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: '<span size="x-large">%s</span>'.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.
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user