gnome-shell/js/extensionPrefs/main.js
Florian Müllner 75f8279a19 extensionPrefs: Tweak UI
Update according to the latest mockups:
 - don't use a border around the list as specified
   by the latest HIG version
 - use more generous spacing between controls
 - allow extension descriptions to take up two lines
 - drop GNOME branding from the window title
 - make the app name consistent with the window title

https://bugzilla.gnome.org/show_bug.cgi?id=778672
2017-02-15 23:20:14 +01:00

401 lines
13 KiB
JavaScript

const Lang = imports.lang;
const Gettext = imports.gettext;
const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const Gio = imports.gi.Gio;
const Gtk = imports.gi.Gtk;
const Pango = imports.gi.Pango;
const Format = imports.format;
const _ = Gettext.gettext;
const Config = imports.misc.config;
const ExtensionUtils = imports.misc.extensionUtils;
const GnomeShellIface = '<node> \
<interface name="org.gnome.Shell.Extensions"> \
<signal name="ExtensionStatusChanged"> \
<arg type="s" name="uuid"/> \
<arg type="i" name="state"/> \
<arg type="s" name="error"/> \
</signal> \
</interface> \
</node>';
const GnomeShellProxy = Gio.DBusProxy.makeProxyWrapper(GnomeShellIface);
function stripPrefix(string, prefix) {
if (string.slice(0, prefix.length) == prefix)
return string.slice(prefix.length);
return string;
}
const Application = new Lang.Class({
Name: 'Application',
_init: function() {
GLib.set_prgname('gnome-shell-extension-prefs');
this.application = new Gtk.Application({
application_id: 'org.gnome.shell.ExtensionPrefs',
flags: Gio.ApplicationFlags.HANDLES_COMMAND_LINE
});
this.application.connect('activate', Lang.bind(this, this._onActivate));
this.application.connect('command-line', Lang.bind(this, this._onCommandLine));
this.application.connect('startup', Lang.bind(this, this._onStartup));
this._extensionPrefsModules = {};
this._startupUuid = null;
this._loaded = false;
this._skipMainWindow = false;
},
_extensionAvailable: function(uuid) {
let extension = ExtensionUtils.extensions[uuid];
if (!extension)
return false;
if (!extension.dir.get_child('prefs.js').query_exists(null))
return false;
return true;
},
_getExtensionPrefsModule: function(extension) {
let uuid = extension.metadata.uuid;
if (this._extensionPrefsModules.hasOwnProperty(uuid))
return this._extensionPrefsModules[uuid];
ExtensionUtils.installImporter(extension);
let prefsModule = extension.imports.prefs;
prefsModule.init(extension.metadata);
this._extensionPrefsModules[uuid] = prefsModule;
return prefsModule;
},
_selectExtension: function(uuid) {
if (!this._extensionAvailable(uuid))
return;
let extension = ExtensionUtils.extensions[uuid];
let widget;
try {
let prefsModule = this._getExtensionPrefsModule(extension);
widget = prefsModule.buildPrefsWidget();
} catch (e) {
widget = this._buildErrorUI(extension, e);
}
let dialog = new Gtk.Dialog({ use_header_bar: true,
modal: true,
title: extension.metadata.name });
if (this._skipMainWindow) {
this.application.add_window(dialog);
if (this._window)
this._window.destroy();
this._window = dialog;
this._window.window_position = Gtk.WindowPosition.CENTER;
} else {
dialog.transient_for = this._window;
}
dialog.set_default_size(600, 400);
dialog.get_content_area().add(widget);
dialog.show();
},
_buildErrorUI: function(extension, exc) {
let box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL });
let label = new Gtk.Label({
label: _("There was an error loading the preferences dialog for %s:").format(extension.metadata.name)
});
box.add(label);
let errortext = '';
errortext += exc;
errortext += '\n\n';
errortext += 'Stack trace:\n';
// Indent stack trace.
errortext += exc.stack.split('\n').map(function(line) {
return ' ' + line;
}).join('\n');
let scroll = new Gtk.ScrolledWindow({ vexpand: true });
let buffer = new Gtk.TextBuffer({ text: errortext });
let textview = new Gtk.TextView({ buffer: buffer });
textview.override_font(Pango.font_description_from_string('monospace'));
scroll.add(textview);
box.add(scroll);
box.show_all();
return box;
},
_buildUI: function(app) {
this._window = new Gtk.ApplicationWindow({ application: app,
window_position: Gtk.WindowPosition.CENTER });
this._window.set_default_size(800, 500);
this._titlebar = new Gtk.HeaderBar({ show_close_button: true,
title: _("Shell Extensions") });
this._window.set_titlebar(this._titlebar);
let killSwitch = new Gtk.Switch({ valign: Gtk.Align.CENTER });
this._titlebar.pack_end(killSwitch);
this._settings = new Gio.Settings({ schema_id: 'org.gnome.shell' });
this._settings.bind('disable-user-extensions', killSwitch, 'active',
Gio.SettingsBindFlags.BIND_DEFAULT |
Gio.SettingsBindFlags.INVERT_BOOLEAN);
let scroll = new Gtk.ScrolledWindow({ hscrollbar_policy: Gtk.PolicyType.NEVER });
this._window.add(scroll);
this._extensionSelector = new Gtk.ListBox({ selection_mode: Gtk.SelectionMode.NONE });
this._extensionSelector.set_sort_func(Lang.bind(this, this._sortList));
this._extensionSelector.set_header_func(Lang.bind(this, this._updateHeader));
scroll.add(this._extensionSelector);
this._shellProxy = new GnomeShellProxy(Gio.DBus.session, 'org.gnome.Shell', '/org/gnome/Shell');
this._shellProxy.connectSignal('ExtensionStatusChanged', Lang.bind(this, function(proxy, senderName, [uuid, state, error]) {
if (ExtensionUtils.extensions[uuid] !== undefined)
this._scanExtensions();
}));
this._window.show_all();
},
_sortList: function(row1, row2) {
let name1 = ExtensionUtils.extensions[row1.uuid].metadata.name;
let name2 = ExtensionUtils.extensions[row2.uuid].metadata.name;
return name1.localeCompare(name2);
},
_updateHeader: function(row, before) {
if (!before || row.get_header())
return;
let sep = new Gtk.Separator({ orientation: Gtk.Orientation.HORIZONTAL });
row.set_header(sep);
},
_scanExtensions: function() {
let finder = new ExtensionUtils.ExtensionFinder();
finder.connect('extension-found', Lang.bind(this, this._extensionFound));
finder.scanExtensions();
this._extensionsLoaded();
},
_extensionFound: function(finder, extension) {
let row = new ExtensionRow(extension.uuid);
row.prefsButton.visible = this._extensionAvailable(row.uuid);
row.prefsButton.connect('clicked', Lang.bind(this,
function() {
this._selectExtension(row.uuid);
}));
row.show_all();
this._extensionSelector.add(row);
},
_extensionsLoaded: function() {
if (this._startupUuid && this._extensionAvailable(this._startupUuid))
this._selectExtension(this._startupUuid);
this._startupUuid = null;
this._skipMainWindow = false;
this._loaded = true;
},
_onActivate: function() {
this._window.present();
},
_onStartup: function(app) {
this._buildUI(app);
this._scanExtensions();
},
_onCommandLine: function(app, commandLine) {
app.activate();
let args = commandLine.get_arguments();
if (args.length) {
let uuid = args[0];
this._skipMainWindow = true;
// Strip off "extension:///" prefix which fakes a URI, if it exists
uuid = stripPrefix(uuid, "extension:///");
if (this._extensionAvailable(uuid))
this._selectExtension(uuid);
else if (!this._loaded)
this._startupUuid = uuid;
else
this._skipMainWindow = false;
}
return 0;
}
});
const DescriptionLabel = new Lang.Class({
Name: 'DescriptionLabel',
Extends: Gtk.Label,
vfunc_get_preferred_height_for_width: function(width) {
// Hack: Request the maximum height allowed by the line limit
if (this.lines > 0)
return this.parent(0);
return this.parent(width);
}
});
const ExtensionRow = new Lang.Class({
Name: 'ExtensionRow',
Extends: Gtk.ListBoxRow,
_init: function(uuid) {
this.parent();
this.uuid = uuid;
this._settings = new Gio.Settings({ schema_id: 'org.gnome.shell' });
this._settings.connect('changed::enabled-extensions', Lang.bind(this,
function() {
this._switch.state = this._isEnabled();
}));
this._settings.connect('changed::disable-extension-version-validation',
Lang.bind(this, function() {
this._switch.sensitive = this._canEnable();
}));
this._settings.connect('changed::disable-user-extensions',
Lang.bind(this, function() {
this._switch.sensitive = this._canEnable();
}));
this._buildUI();
},
_buildUI: function() {
let extension = ExtensionUtils.extensions[this.uuid];
let hbox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL,
hexpand: true, margin_end: 24, spacing: 24,
margin: 12 });
this.add(hbox);
let vbox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL,
spacing: 6, hexpand: true });
hbox.add(vbox);
let name = GLib.markup_escape_text(extension.metadata.name, -1);
let label = new Gtk.Label({ label: '<b>' + name + '</b>',
use_markup: true,
halign: Gtk.Align.START });
vbox.add(label);
let desc = extension.metadata.description.split('\n')[0];
label = new DescriptionLabel({ label: desc, wrap: true, lines: 2,
ellipsize: Pango.EllipsizeMode.END,
xalign: 0, yalign: 0 });
vbox.add(label);
let button = new Gtk.Button({ valign: Gtk.Align.CENTER,
no_show_all: true });
button.add(new Gtk.Image({ icon_name: 'emblem-system-symbolic',
icon_size: Gtk.IconSize.BUTTON,
visible: true }));
button.get_style_context().add_class('circular');
hbox.add(button);
this.prefsButton = button;
this._switch = new Gtk.Switch({ valign: Gtk.Align.CENTER,
sensitive: this._canEnable(),
state: this._isEnabled() });
this._switch.connect('notify::active', Lang.bind(this,
function() {
if (this._switch.active)
this._enable();
else
this._disable();
}));
this._switch.connect('state-set', function() { return true; });
hbox.add(this._switch);
},
_canEnable: function() {
let extension = ExtensionUtils.extensions[this.uuid];
let checkVersion = !this._settings.get_boolean('disable-extension-version-validation');
return !this._settings.get_boolean('disable-user-extensions') &&
!(checkVersion && ExtensionUtils.isOutOfDate(extension));
},
_isEnabled: function() {
let extensions = this._settings.get_strv('enabled-extensions');
return extensions.indexOf(this.uuid) != -1;
},
_enable: function() {
let extensions = this._settings.get_strv('enabled-extensions');
if (extensions.indexOf(this.uuid) != -1)
return;
extensions.push(this.uuid);
this._settings.set_strv('enabled-extensions', extensions);
},
_disable: function() {
let extensions = this._settings.get_strv('enabled-extensions');
let pos = extensions.indexOf(this.uuid);
if (pos == -1)
return;
do {
extensions.splice(pos, 1);
pos = extensions.indexOf(this.uuid);
} while (pos != -1);
this._settings.set_strv('enabled-extensions', extensions);
}
});
function initEnvironment() {
// Monkey-patch in a "global" object that fakes some Shell utilities
// that ExtensionUtils depends on.
window.global = {
log: function() {
print([].join.call(arguments, ', '));
},
logError: function(s) {
log('ERROR: ' + s);
},
userdatadir: GLib.build_filenamev([GLib.get_user_data_dir(), 'gnome-shell'])
};
String.prototype.format = Format.format;
}
function main(argv) {
initEnvironment();
Gettext.bindtextdomain(Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
Gettext.textdomain(Config.GETTEXT_PACKAGE);
let app = new Application();
app.application.run(argv);
}