Working tree-view based keybinding editor

Note: ideally we should subscribe to settings changes and update the TreeModel
in the listener instead of updating it directly in the signal handler for the
accel editor.
This commit is contained in:
Ole Jørgen Brønner
2018-07-10 22:17:18 +02:00
committed by Tor Hedin Brønner
parent 55c6b60803
commit c76137fd77
2 changed files with 255 additions and 355 deletions

View File

@@ -214,7 +214,7 @@
<property name="orientation">vertical</property>
<property name="spacing">24</property>
<child>
<object class="GtkFrame" id="dock_display">
<object class="GtkFrame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
@@ -314,359 +314,6 @@
<property name="can_focus">False</property>
<property name="margin_top">24</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkFrame" id="dock_display3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkListBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="selection_mode">none</property>
<child>
<object class="GtkListBoxRow" id="general_row3">
<property name="visible">True</property>
<property name="can_focus">True</property>
<child>
<object class="GtkGrid" id="workspace_grid6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="column_spacing">32</property>
<child>
<object class="GtkLabel" id="workspace_label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">Switch next</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">+</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="always_show_image">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="has_entry">True</property>
<child internal-child="entry">
<object class="GtkEntry">
<property name="can_focus">False</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label">-</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="always_show_image">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkListBoxRow" id="general_row4">
<property name="visible">True</property>
<property name="can_focus">True</property>
<child>
<object class="GtkGrid" id="workspace_grid7">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="column_spacing">32</property>
<child>
<object class="GtkLabel" id="workspace_label4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">Switch previous</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">+</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="always_show_image">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="has_entry">True</property>
<child internal-child="entry">
<object class="GtkEntry">
<property name="can_focus">True</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label">-</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="always_show_image">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkListBoxRow" id="general_row5">
<property name="visible">True</property>
<property name="can_focus">True</property>
<child>
<object class="GtkGrid" id="workspace_grid8">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="column_spacing">32</property>
<child>
<object class="GtkLabel" id="workspace_label5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">Switch left</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">+</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="always_show_image">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="has_entry">True</property>
<child internal-child="entry">
<object class="GtkEntry">
<property name="can_focus">True</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label">-</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="always_show_image">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkListBoxRow" id="general_row6">
<property name="visible">True</property>
<property name="can_focus">True</property>
<child>
<object class="GtkGrid" id="workspace_grid9">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="column_spacing">32</property>
<child>
<object class="GtkLabel" id="workspace_label6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="label" translatable="yes">Switch right</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">+</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="always_show_image">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBoxText">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="has_entry">True</property>
<child internal-child="entry">
<object class="GtkEntry">
<property name="can_focus">True</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label">-</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="always_show_image">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child type="label_item">
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>

255
prefs.js
View File

@@ -12,11 +12,21 @@ const Me = ExtensionUtils.getCurrentExtension();
const Convenience = Me.imports.convenience;
const WORKSPACE_KEY = 'org.gnome.Shell.Extensions.PaperWM.Workspace';
const KEYBINDINGS_KEY = 'org.gnome.Shell.Extensions.PaperWM.Keybindings';
const WORKSPACE_LIST_KEY = 'org.gnome.Shell.Extensions.PaperWM.WorkspaceList';
const wmSettings = new Gio.Settings({ schema_id:
'org.gnome.desktop.wm.preferences'});
let _ = s => s;
// TreeStore model
const COLUMN_ID = 0;
const COLUMN_INDEX = 1;
const COLUMN_DESCRIPTION = 2;
const COLUMN_KEY = 3;
const COLUMN_MODS = 4;
const schemaSource = Gio.SettingsSchemaSource.new_from_directory(
GLib.build_filenamev([Me.path, "schemas"]),
Gio.SettingsSchemaSource.get_default(),
@@ -62,6 +72,14 @@ function getNewWorkspaceSettings(index) {
return [uuid, settings];
}
function ok(okValue) {
if(okValue[0]) {
return okValue[1]
} else {
return null
}
}
class SettingsWidget {
constructor() {
this._settings = Convenience.getSettings();
@@ -125,8 +143,51 @@ class SettingsWidget {
workspaceCombo.set_active(0);
createRow('foo', new Gtk.ColorButton());
// Keybindings
let settings = Convenience.getSettings(KEYBINDINGS_KEY);
let box = this.builder.get_object('keybindings');
box.spacing = 12;
let windowFrame = new Gtk.Frame({label: _('Windows'),
label_xalign: 0.5});
let windows = createKeybindingWidget(settings);
box.add(windowFrame);
windowFrame.add(windows);
['new-window', 'close-window', 'switch-next', 'switch-previous',
'switch-left', 'switch-right', 'switch-up', 'switch-down',
'switch-first', 'switch-last', 'live-alt-tab', 'live-alt-tab-backward',
'move-left', 'move-right', 'move-up', 'move-down',
'slurp-in', 'barf-out', 'center-horizontally',
'paper-toggle-fullscreen', 'toggle-maximize-width', 'cycle-width']
.forEach(k => {
addKeybinding(windows.model, settings, k);
});
let workspaceFrame = new Gtk.Frame({label: _('Workspaces'),
label_xalign: 0.5});
let workspaces = createKeybindingWidget(settings);
box.add(workspaceFrame);
workspaceFrame.add(workspaces);
['previous-workspace', 'previous-workspace-backward',
'move-previous-workspace', 'move-previous-workspace-backward' ]
.forEach(k => {
addKeybinding(workspaces.model, settings, k);
});
let scratchFrame = new Gtk.Frame({label: _('Scratch layer'),
label_xalign: 0.5});
let scratch = createKeybindingWidget(settings);
box.add(scratchFrame);
scratchFrame.add(scratch);
['toggle-scratch-layer', 'toggle-scratch']
.forEach(k => {
addKeybinding(scratch.model, settings, k);
});
}
createWorkspacePage(settings, index) {
@@ -219,7 +280,199 @@ function createRow(text, widget, signal, handler) {
row.add(box);
return row;
}
function createKeybindingWidget(settings) {
let model = new Gtk.TreeStore();
model.set_column_types(
[GObject.TYPE_STRING, // COLUMN_ID
GObject.TYPE_INT, // COLUMN_INDEX
GObject.TYPE_STRING, // COLUMN_DESCRIPTION
GObject.TYPE_INT, // COLUMN_KEY
GObject.TYPE_INT]); // COLUMN_MODS
let treeView = new Gtk.TreeView();
treeView.model = model;
treeView.headers_visible = false;
treeView.margin_start = 12;
treeView.margin_end = 12;
treeView.search_column = COLUMN_DESCRIPTION;
treeView.enable_search = true;
let descriptionRenderer = new Gtk.CellRendererText();
let descriptionColumn = new Gtk.TreeViewColumn();
descriptionColumn.expand = true;
descriptionColumn.pack_start(descriptionRenderer, true);
descriptionColumn.add_attribute(descriptionRenderer, "text", COLUMN_DESCRIPTION);
treeView.append_column(descriptionColumn);
let accelRenderer = new Gtk.CellRendererAccel();
accelRenderer.accel_mode = Gtk.CellRendererAccelMode.GTK;
accelRenderer.editable = true;
accelRenderer.connect("accel-edited",
(accelRenderer, path, key, mods, hwCode) => {
let iter = ok(model.get_iter_from_string(path));
if(!iter)
return;
// Update the UI.
model.set(iter, [COLUMN_KEY, COLUMN_MODS], [key, mods]);
// Update the stored setting.
let id = model.get_value(iter, COLUMN_ID);
let index = model.get_value(iter, COLUMN_INDEX);
let accelString = Gtk.accelerator_name(key, mods);
let accels = settings.get_strv(id);
if (index === -1) {
accels.push(accelString);
} else {
accels[index] = accelString
}
settings.set_strv(id, accels);
let newEmptyRow = null, parent;
if (index === -1) {
model.set_value(iter, COLUMN_INDEX, accels.length-1);
model.set_value(iter, COLUMN_DESCRIPTION, "");
let parent = ok(model.iter_parent(iter));
newEmptyRow = model.insert_after(parent, iter);
} else if (index === 0 && accels.length === 1) {
newEmptyRow = model.insert(iter, -1);
}
if (newEmptyRow) {
model.set(newEmptyRow, ...transpose([
[COLUMN_ID, id],
[COLUMN_INDEX, -1],
[COLUMN_DESCRIPTION, "New binding"],
[COLUMN_KEY, 0],
[COLUMN_MODS, 0],
]));
}
});
accelRenderer.connect("accel-cleared",
(accelRenderer, path) => {
let iter = ok(model.get_iter_from_string(path));
if(!iter)
return;
let index = model.get_value(iter, COLUMN_INDEX);
// Update the UI.
model.set(iter, [COLUMN_KEY, COLUMN_MODS], [0, 0]);
if (index === -1) {
// Clearing the empty row
return;
}
let id = model.get_value(iter, COLUMN_ID);
let accels = settings.get_strv(id);
accels.splice(index, 1);
let parent, nextSibling;
// Simply rebuild the model for this action
if (index === 0) {
parent = iter.copy();
} else {
parent = ok(model.iter_parent(iter));
}
nextSibling = parent.copy();
if(!model.iter_next(nextSibling))
nextSibling = null;
model.remove(parent);
// Update the stored setting.
settings.set_strv(id, accels);
let recreated = addKeybinding(model, settings, id, nextSibling);
let selection = treeView.get_selection();
selection.select_iter(recreated);
});
let accelColumn = new Gtk.TreeViewColumn();
accelColumn.pack_end(accelRenderer, false);
accelColumn.add_attribute(accelRenderer, "accel-key", COLUMN_KEY);
accelColumn.add_attribute(accelRenderer, "accel-mods", COLUMN_MODS);
treeView.append_column(accelColumn);
return treeView;
}
function parseAccelerator(accelerator) {
let key, mods;
if (accelerator == null)
[key, mods] = [0, 0];
else
[key, mods] = Gtk.accelerator_parse(accelerator);
return [key, mods];
}
function transpose(colValPairs) {
let colKeys = [], values = [];
colValPairs.forEach(([k, v]) => {
colKeys.push(k); values.push(v);
})
return [colKeys, values];
}
function addKeybinding(model, settings, id, position=null) {
let accels = settings.get_strv(id);
let accelerator = accels[0];
let schema = settings.settings_schema;
let schemaKey = schema.get_key(id);
let description = _(schemaKey.get_summary());
// Add a row for the keybinding.
let [key, mods] = parseAccelerator(accelerator);
let row = model.insert_before(null, position);
model.set(row, ...transpose([
[COLUMN_ID, id],
[COLUMN_INDEX, 0],
[COLUMN_DESCRIPTION, description],
[COLUMN_KEY, key],
[COLUMN_MODS, mods],
]));
// Add one subrow for each additional keybinding
accels.slice(1).forEach((accelerator, i) => {
let [key, mods] = parseAccelerator(accelerator);
let subrow = model.insert(row, 0);
model.set(subrow, ...transpose([
[COLUMN_ID, id],
[COLUMN_INDEX, i+1],
[COLUMN_DESCRIPTION, ""],
[COLUMN_KEY, key],
[COLUMN_MODS, mods],
]));
})
if (accelerator) {
// Add an empty row used for adding new bindings
let emptyRow = model.append(row);
model.set(emptyRow, ...transpose([
[COLUMN_ID, id],
[COLUMN_INDEX, -1],
[COLUMN_DESCRIPTION, "New binding"],
[COLUMN_KEY, 0],
[COLUMN_MODS, 0],
]));
}
return row;
}
function init() {