// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- // Start apps on custom workspaces /* exported init buildPrefsWidget */ const { Gio, GObject, Gtk } = imports.gi; const Gettext = imports.gettext.domain('gnome-shell-extensions'); const _ = Gettext.gettext; const N_ = e => e; const ExtensionUtils = imports.misc.extensionUtils; const SETTINGS_KEY = 'application-list'; const WORKSPACE_MAX = 36; // compiled in limit of mutter const Columns = { APPINFO: 0, DISPLAY_NAME: 1, ICON: 2, WORKSPACE: 3, ADJUSTMENT: 4, }; const Widget = GObject.registerClass( class Widget extends Gtk.Grid { _init(params) { super._init(params); this.set_orientation(Gtk.Orientation.VERTICAL); this._settings = ExtensionUtils.getSettings(); this._settings.connect('changed', this._refresh.bind(this)); this._changedPermitted = false; this._store = new Gtk.ListStore(); this._store.set_column_types([ Gio.AppInfo, GObject.TYPE_STRING, Gio.Icon, GObject.TYPE_INT, Gtk.Adjustment, ]); let scrolled = new Gtk.ScrolledWindow({ shadow_type: Gtk.ShadowType.IN }); scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); this.add(scrolled); this._treeView = new Gtk.TreeView({ model: this._store, hexpand: true, vexpand: true, }); this._treeView.get_selection().set_mode(Gtk.SelectionMode.SINGLE); let appColumn = new Gtk.TreeViewColumn({ expand: true, sort_column_id: Columns.DISPLAY_NAME, title: _('Application'), }); let iconRenderer = new Gtk.CellRendererPixbuf(); appColumn.pack_start(iconRenderer, false); appColumn.add_attribute(iconRenderer, 'gicon', Columns.ICON); let nameRenderer = new Gtk.CellRendererText(); appColumn.pack_start(nameRenderer, true); appColumn.add_attribute(nameRenderer, 'text', Columns.DISPLAY_NAME); this._treeView.append_column(appColumn); let workspaceColumn = new Gtk.TreeViewColumn({ title: _('Workspace'), sort_column_id: Columns.WORKSPACE, }); let workspaceRenderer = new Gtk.CellRendererSpin({ editable: true }); workspaceRenderer.connect('edited', this._workspaceEdited.bind(this)); workspaceColumn.pack_start(workspaceRenderer, true); workspaceColumn.add_attribute(workspaceRenderer, 'adjustment', Columns.ADJUSTMENT); workspaceColumn.add_attribute(workspaceRenderer, 'text', Columns.WORKSPACE); this._treeView.append_column(workspaceColumn); scrolled.add(this._treeView); let toolbar = new Gtk.Toolbar({ icon_size: Gtk.IconSize.SMALL_TOOLBAR }); toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR); this.add(toolbar); let newButton = new Gtk.ToolButton({ icon_name: 'bookmark-new-symbolic', label: _('Add Rule'), is_important: true, }); newButton.connect('clicked', this._createNew.bind(this)); toolbar.add(newButton); let delButton = new Gtk.ToolButton({ icon_name: 'edit-delete-symbolic' }); delButton.connect('clicked', this._deleteSelected.bind(this)); toolbar.add(delButton); let selection = this._treeView.get_selection(); selection.connect('changed', () => { delButton.sensitive = selection.count_selected_rows() > 0; }); delButton.sensitive = selection.count_selected_rows() > 0; this._changedPermitted = true; this._refresh(); } _createNew() { let dialog = new Gtk.Dialog({ title: _('Create new matching rule'), transient_for: this.get_toplevel(), use_header_bar: true, modal: true, }); dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL); let addButton = dialog.add_button(_('Add'), Gtk.ResponseType.OK); dialog.set_default_response(Gtk.ResponseType.OK); let grid = new Gtk.Grid({ column_spacing: 10, row_spacing: 15, margin: 10, }); dialog._appChooser = new Gtk.AppChooserWidget({ show_all: true }); dialog._appChooser.connect('application-selected', (w, appInfo) => { addButton.sensitive = appInfo && this._checkId(appInfo.get_id()); }); let appInfo = dialog._appChooser.get_app_info(); addButton.sensitive = appInfo && this._checkId(appInfo.get_id()); grid.attach(dialog._appChooser, 0, 0, 2, 1); grid.attach(new Gtk.Label({ label: _('Workspace'), halign: Gtk.Align.END, }), 0, 1, 1, 1); let adjustment = new Gtk.Adjustment({ lower: 1, upper: WORKSPACE_MAX, step_increment: 1, }); dialog._spin = new Gtk.SpinButton({ adjustment, snap_to_ticks: true, }); dialog._spin.set_value(1); grid.attach(dialog._spin, 1, 1, 1, 1); dialog.get_content_area().add(grid); dialog.connect('response', (dlg, id) => { if (id !== Gtk.ResponseType.OK) { dialog.destroy(); return; } appInfo = dialog._appChooser.get_app_info(); if (!appInfo) return; let index = Math.floor(dialog._spin.value); if (isNaN(index) || index < 0) index = 1; this._changedPermitted = false; this._appendItem(appInfo.get_id(), index); this._changedPermitted = true; this._appendRow(appInfo, index); dialog.destroy(); }); dialog.show_all(); } _deleteSelected() { let [any, model_, iter] = this._treeView.get_selection().get_selected(); if (any) { let appInfo = this._store.get_value(iter, Columns.APPINFO); this._changedPermitted = false; this._removeItem(appInfo.get_id()); this._changedPermitted = true; this._store.remove(iter); } } _workspaceEdited(renderer, pathString, text) { let index = parseInt(text); if (isNaN(index) || index < 0) index = 1; let path = Gtk.TreePath.new_from_string(pathString); let [model_, iter] = this._store.get_iter(path); let appInfo = this._store.get_value(iter, Columns.APPINFO); this._changedPermitted = false; this._changeItem(appInfo.get_id(), index); this._store.set_value(iter, Columns.WORKSPACE, index); this._changedPermitted = true; } _refresh() { if (!this._changedPermitted) // Ignore this notification, model is being modified outside return; this._store.clear(); let currentItems = this._settings.get_strv(SETTINGS_KEY); let validItems = []; for (let i = 0; i < currentItems.length; i++) { let [id, index] = currentItems[i].split(':'); let appInfo = Gio.DesktopAppInfo.new(id); if (!appInfo) continue; validItems.push(currentItems[i]); this._appendRow(appInfo, parseInt(index)); } if (validItems.length !== currentItems.length) // some items were filtered out this._settings.set_strv(SETTINGS_KEY, validItems); } _appendRow(appInfo, workspace) { let iter = this._store.append(); let icon = appInfo.get_icon(); let displayName = appInfo.get_display_name(); let adj = new Gtk.Adjustment({ lower: 1, upper: WORKSPACE_MAX, step_increment: 1, value: workspace, }); let { APPINFO, ICON, DISPLAY_NAME, WORKSPACE, ADJUSTMENT } = Columns; this._store.set(iter, [APPINFO, ICON, DISPLAY_NAME, WORKSPACE, ADJUSTMENT], [appInfo, icon, displayName, workspace, adj]); } _checkId(id) { let items = this._settings.get_strv(SETTINGS_KEY); return !items.some(i => i.startsWith(`${id}:`)); } _appendItem(id, workspace) { let currentItems = this._settings.get_strv(SETTINGS_KEY); currentItems.push(`${id}:${workspace}`); this._settings.set_strv(SETTINGS_KEY, currentItems); } _removeItem(id) { let currentItems = this._settings.get_strv(SETTINGS_KEY); let index = currentItems.map(el => el.split(':')[0]).indexOf(id); if (index < 0) return; currentItems.splice(index, 1); this._settings.set_strv(SETTINGS_KEY, currentItems); } _changeItem(id, workspace) { let currentItems = this._settings.get_strv(SETTINGS_KEY); let index = currentItems.map(el => el.split(':')[0]).indexOf(id); if (index < 0) currentItems.push(`${id}:${workspace}`); else currentItems[index] = `${id}:${workspace}`; this._settings.set_strv(SETTINGS_KEY, currentItems); } }); function init() { ExtensionUtils.initTranslations(); } function buildPrefsWidget() { let widget = new Widget({ margin: 12 }); widget.show_all(); return widget; }