190 lines
5.4 KiB
JavaScript
190 lines
5.4 KiB
JavaScript
|
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
|
||
|
/* exported init buildPrefsWidget */
|
||
|
|
||
|
// we use async/await here to not block the mainloop, not to parallelize
|
||
|
/* eslint-disable no-await-in-loop */
|
||
|
|
||
|
const { Gio, GLib, GObject, Gtk } = imports.gi;
|
||
|
|
||
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||
|
|
||
|
const Me = ExtensionUtils.getCurrentExtension();
|
||
|
const Util = Me.imports.util;
|
||
|
|
||
|
Gio._promisify(Gio._LocalFilePrototype,
|
||
|
'enumerate_children_async', 'enumerate_children_finish');
|
||
|
Gio._promisify(Gio._LocalFilePrototype,
|
||
|
'query_info_async', 'query_info_finish');
|
||
|
Gio._promisify(Gio.FileEnumerator.prototype,
|
||
|
'next_files_async', 'next_files_finish');
|
||
|
|
||
|
const UserThemePrefsWidget = GObject.registerClass(
|
||
|
class UserThemePrefsWidget extends Gtk.ScrolledWindow {
|
||
|
_init() {
|
||
|
super._init({
|
||
|
hscrollbar_policy: Gtk.PolicyType.NEVER,
|
||
|
});
|
||
|
|
||
|
const box = new Gtk.Box();
|
||
|
this.add(box);
|
||
|
|
||
|
this._list = new Gtk.ListBox({
|
||
|
selection_mode: Gtk.SelectionMode.NONE,
|
||
|
halign: Gtk.Align.CENTER,
|
||
|
valign: Gtk.Align.START,
|
||
|
hexpand: true,
|
||
|
margin: 60,
|
||
|
});
|
||
|
this._list.get_style_context().add_class('frame');
|
||
|
this._list.set_header_func(this._updateHeader.bind(this));
|
||
|
box.add(this._list);
|
||
|
|
||
|
this._actionGroup = new Gio.SimpleActionGroup();
|
||
|
this._list.insert_action_group('theme', this._actionGroup);
|
||
|
|
||
|
this._settings = ExtensionUtils.getSettings();
|
||
|
this._actionGroup.add_action(
|
||
|
this._settings.create_action('name'));
|
||
|
|
||
|
this.connect('destroy', () => this._settings.run_dispose());
|
||
|
|
||
|
this._rows = new Map();
|
||
|
this._addTheme(''); // default
|
||
|
|
||
|
this._collectThemes();
|
||
|
}
|
||
|
|
||
|
async _collectThemes() {
|
||
|
for (const dirName of Util.getThemeDirs()) {
|
||
|
const dir = Gio.File.new_for_path(dirName);
|
||
|
for (const name of await this._enumerateDir(dir)) {
|
||
|
if (this._rows.has(name))
|
||
|
continue;
|
||
|
|
||
|
const file = dir.resolve_relative_path(
|
||
|
`${name}/gnome-shell/gnome-shell.css`);
|
||
|
try {
|
||
|
await file.query_info_async(
|
||
|
Gio.FILE_ATTRIBUTE_STANDARD_NAME,
|
||
|
Gio.FileQueryInfoFlags.NONE,
|
||
|
GLib.PRIORITY_DEFAULT, null);
|
||
|
this._addTheme(name);
|
||
|
} catch (e) {
|
||
|
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
|
||
|
logError(e);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (const dirName of Util.getModeThemeDirs()) {
|
||
|
const dir = Gio.File.new_for_path(dirName);
|
||
|
for (const filename of await this._enumerateDir(dir)) {
|
||
|
if (!filename.endsWith('.css'))
|
||
|
continue;
|
||
|
|
||
|
const name = filename.slice(0, -4);
|
||
|
if (!this._rows.has(name))
|
||
|
this._addTheme(name);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_addTheme(name) {
|
||
|
const row = new ThemeRow(name);
|
||
|
this._rows.set(name, row);
|
||
|
|
||
|
this._list.add(row);
|
||
|
row.show_all();
|
||
|
}
|
||
|
|
||
|
async _enumerateDir(dir) {
|
||
|
const fileInfos = [];
|
||
|
let fileEnum;
|
||
|
|
||
|
try {
|
||
|
fileEnum = await dir.enumerate_children_async(
|
||
|
Gio.FILE_ATTRIBUTE_STANDARD_NAME,
|
||
|
Gio.FileQueryInfoFlags.NONE,
|
||
|
GLib.PRIORITY_DEFAULT, null);
|
||
|
} catch (e) {
|
||
|
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
|
||
|
logError(e);
|
||
|
return [];
|
||
|
}
|
||
|
|
||
|
let infos;
|
||
|
do {
|
||
|
infos = await fileEnum.next_files_async(100,
|
||
|
GLib.PRIORITY_DEFAULT, null);
|
||
|
fileInfos.push(...infos);
|
||
|
} while (infos.length > 0);
|
||
|
|
||
|
return fileInfos.map(info => info.get_name());
|
||
|
}
|
||
|
|
||
|
_updateHeader(row, before) {
|
||
|
if (!before || row.get_header())
|
||
|
return;
|
||
|
row.set_header(new Gtk.Separator());
|
||
|
}
|
||
|
});
|
||
|
|
||
|
const ThemeRow = GObject.registerClass(
|
||
|
class ThemeRow extends Gtk.ListBoxRow {
|
||
|
_init(name) {
|
||
|
this._name = new GLib.Variant('s', name);
|
||
|
|
||
|
super._init({
|
||
|
action_name: 'theme.name',
|
||
|
action_target: this._name,
|
||
|
});
|
||
|
|
||
|
const box = new Gtk.Box({
|
||
|
spacing: 12,
|
||
|
margin: 12,
|
||
|
});
|
||
|
this.add(box);
|
||
|
|
||
|
box.add(new Gtk.Label({
|
||
|
label: name || 'Default',
|
||
|
hexpand: true,
|
||
|
xalign: 0,
|
||
|
max_width_chars: 25,
|
||
|
width_chars: 25,
|
||
|
}));
|
||
|
|
||
|
this._checkmark = new Gtk.Image({
|
||
|
icon_name: 'emblem-ok-symbolic',
|
||
|
pixel_size: 16,
|
||
|
});
|
||
|
box.add(this._checkmark);
|
||
|
|
||
|
box.show_all();
|
||
|
|
||
|
const id = this.connect('parent-set', () => {
|
||
|
this.disconnect(id);
|
||
|
|
||
|
const actionGroup = this.get_action_group('theme');
|
||
|
actionGroup.connect('action-state-changed::name',
|
||
|
this._syncCheckmark.bind(this));
|
||
|
this._syncCheckmark();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
_syncCheckmark() {
|
||
|
const actionGroup = this.get_action_group('theme');
|
||
|
const state = actionGroup.get_action_state('name');
|
||
|
this._checkmark.opacity = this._name.equal(state);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
function init() {
|
||
|
}
|
||
|
|
||
|
function buildPrefsWidget() {
|
||
|
let widget = new UserThemePrefsWidget();
|
||
|
widget.show_all();
|
||
|
|
||
|
return widget;
|
||
|
}
|