%PDF- %PDF-
Direktori : /usr/share/gnome-shell/extensions/tiling-assistant@ubuntu.com/src/prefs/ |
Current File : //usr/share/gnome-shell/extensions/tiling-assistant@ubuntu.com/src/prefs/layoutRow.js |
import { Gdk, Gtk, GObject } from '../dependencies/prefs/gi.js'; import { _ } from '../dependencies/prefs.js'; import { Layout } from '../common.js'; import { LayoutRowEntry } from './layoutRowEntry.js'; /** * 1 LayoutRow represents 1 Layout in the preference window. It's just instanced * by layoutsPrefs.js (see that file for more details and general information * about layouts). 1 LayoutRow has a bunch of LayoutRowEntries, which each * represent a LayoutItem. A LayoutItem is a simple JS Object and has a * { rect, appId, loopType }. The rect is mandatory, the rest not. */ export const LayoutRow = GObject.registerClass({ GTypeName: 'TilingLayoutRow', Template: import.meta.url.replace(/prefs\/(.*)\.js$/, 'ui/$1.ui'), InternalChildren: [ 'addRowEntryButton', 'deleteButton', 'drawingArea', 'entryBox', 'errorLabel', 'expanderButton', 'nameEntry', 'rectCountLabel', 'shortcut', 'revealer' ], Signals: { 'changed': { param_types: [GObject.TYPE_BOOLEAN] } } }, class TilingLayoutRow extends Gtk.ListBoxRow { // Use a static variable to make sure the indices are unique since just using // something like the child index isn't enough because the user may add *and* // delete rows at random... so 1 child index may appear multiple times static instanceCount = 0; /** * @returns {number} the number of created LayoutRows since the last time * the layouts were loaded into the preference window. */ static getInstanceCount() { return TilingLayoutRow.instanceCount; } static resetInstanceCount() { TilingLayoutRow.instanceCount = 0; } /** * @param {{_name: string, _items: {rect: object, appId: ?string, loopType: ?string}[] * }|null} layout a parsed JS object representing a layout from the * layouts.json file. */ _init(layout, settings) { super._init(); this._settings = settings; this._layout = new Layout(layout); this._idx = TilingLayoutRow.instanceCount++; this._shortcutKey = `activate-layout${this._idx}`; // Initialize shortcut and its clear-button this._shortcut.initialize(this._shortcutKey, this._settings); // Set name. Don't use a placeholder, if there is one because of a bug // when reloading the layouts const name = this._layout.getName(); this._nameEntry.get_buffer().set_text(name, -1); this._nameEntry.set_placeholder_text(name ? '' : 'Nameless Layout...'); // Load the entries with values from the layout const items = this._layout.getItems(); items.forEach((item, idx) => { const rowEntry = new LayoutRowEntry(idx, item); rowEntry.connect('changed', this._onRowEntryChanged.bind(this)); this._entryBox.append(rowEntry); }); // Show the nr of rects for a quicker overview. this._rectCountLabel.set_label(items.length ? `(${items.length})` : ''); // Add one empty entry row this._onAddRowEntryButtonClicked(); // Update the preview / show the errorLabel this._updatePreview(); } destroy() { this.get_parent().remove(this); } activate() { this._nameEntry.grab_focus(); } /** * toggles whether the layout's rects are visible. */ toggleReveal() { this._revealer.reveal_child = !this._revealer.reveal_child; } /** * @returns {number} the index of this layout. */ getIdx() { return this._idx; } /** * @returns {{_name: string, _items: {rect: object, appId: ?string, loopType: ?string}[] * }|null} the layout object represented by this row. */ getLayout() { // First, filter out empty rows (i. e. rows without valid rects) this._layout.setItems(this._layout.getItems()); // Then, remove problematic items, if the rects have problems. E. g., // they may overlap each other, extend outside of the screen etc... // This is irreversible but fine since this function is only called // when the user presses the save button. Before that there will be // error messages shown in the preview area. let [ok, , idx] = this._layout.validate(); while (this._layout.getItemCount() && !ok) { this._layout.removeItem(idx); [ok, , idx] = this._layout.validate(); } return this._layout.getItemCount() ? this._layout : null; } /** * @returns {[boolean, string]} whether the preview was successful and a * potential error message. */ _updatePreview() { const [ok, errMsg] = this._layout.validate(); if (!ok) { // Print error in the preview area this._errorLabel.set_label(errMsg); this._drawingArea.set_draw_func(() => {}); } else { // Draw the actual preview for the rects this._errorLabel.set_label(''); this._drawingArea.set_draw_func((drawingArea, cr) => { const color = new Gdk.RGBA(); const width = drawingArea.get_allocated_width(); const height = drawingArea.get_allocated_height(); cr.setLineWidth(1.0); this._layout.getItems().forEach(item => { // Rects are in a slightly transparent white with a 1px outline // and a 5px gap between the different rects const rect = item.rect; color.parse('rgba(255, 255, 255, .2)'); Gdk.cairo_set_source_rgba(cr, color); cr.moveTo(rect.x * width + 5, rect.y * height + 5); cr.lineTo((rect.x + rect.width) * width - 5, rect.y * height + 5); cr.lineTo((rect.x + rect.width) * width - 5, (rect.y + rect.height) * height - 5); cr.lineTo(rect.x * width + 5, (rect.y + rect.height) * height - 5); cr.lineTo(rect.x * width + 5, rect.y * height + 5); cr.strokePreserve(); // Fill the rects in transparent black. // If the rect is a 'loop', lower the transparency. color.parse(`rgba(0, 0, 0, ${item.loopType ? .1 : .3})`); Gdk.cairo_set_source_rgba(cr, color); cr.fill(); }); cr.$dispose(); }); } this._drawingArea.queue_draw(); return [ok, errMsg]; } _onNameEntryChanged() { const name = this._nameEntry.get_buffer().get_text(); this._nameEntry.set_tooltip_text(name); this._layout.setName(name); const [ok] = this._layout.validate(); this.emit('changed', ok); } _onDeleteButtonClicked() { this._settings.set_strv(this._shortcutKey, []); this.emit('changed', true); this.destroy(); } _onExpanderButtonClicked() { this.toggleReveal(); } _onClearShortcutButtonClicked() { this._settings.set_strv(`activate-layout${this._idx}`, []); } _onAddRowEntryButtonClicked() { const rowEntry = new LayoutRowEntry(this._layout.getItemCount(), this._layout.addItem()); rowEntry.connect('changed', this._onRowEntryChanged.bind(this)); this._entryBox.append(rowEntry); } _onRowEntryChanged(entry, ok) { // ok only is about the change being ok for the *individual* entry // i. e. whether their format is correct if (!ok) { this.emit('changed', ok); return; } // allOk is about whether the guiEntries are also valid as a whole const [allOk] = this._updatePreview(); this.emit('changed', allOk); } });