/* jslint esversion: 6 */ /* * Copyright 2019 Abakkk * * This file is part of DrawOnYourScreen, a drawing extension for GNOME Shell. * https://framagit.org/abakkk/DrawOnYourScreen * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ const Gdk = imports.gi.Gdk; const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const Config = imports.misc.config; const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); const Convenience = ExtensionUtils.getSettings && ExtensionUtils.initTranslations ? ExtensionUtils : Me.imports.convenience; const gettext = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; const _ = function(string) { if (!string) return ""; return gettext(string); }; const _GTK = imports.gettext.domain('gtk30').gettext; const GS_VERSION = Config.PACKAGE_VERSION; const MARGIN = 10; var GLOBAL_KEYBINDINGS = { 'toggle-drawing': "Enter/leave drawing mode", 'toggle-modal': "Grab/ungrab keyboard and pointer", 'erase-drawing': "Erase all drawings" }; var INTERNAL_KEYBINDINGS = { 'undo': "Undo last brushstroke", 'redo': "Redo last brushstroke", 'delete-last-element' : "Erase last brushstroke", 'smooth-last-element': "Smooth last brushstroke", '-separator-1': '', 'select-none-shape': "Free drawing", 'select-line-shape': "Select line", 'select-ellipse-shape': "Select ellipse", 'select-rectangle-shape': "Select rectangle", 'select-polygon-shape': "Select polygon", 'select-polyline-shape': "Select polyline", 'select-text-shape': "Select text", 'select-image-shape': "Select image", 'select-move-tool': "Select move", 'select-resize-tool': "Select resize", 'select-mirror-tool': "Select mirror", '-separator-2': '', 'switch-fill': "Toggle fill/outline", 'switch-fill-rule': "Toggle fill rule", 'switch-color-palette': "Change color palette", 'switch-color-palette-reverse': "Change color palette (reverse)", '-separator-3': '', 'increment-line-width': "Increment line width", 'decrement-line-width': "Decrement line width", 'increment-line-width-more': "Increment line width even more", 'decrement-line-width-more': "Decrement line width even more", 'switch-linejoin': "Change linejoin", 'switch-linecap': "Change linecap", 'switch-dash': "Dashed line", '-separator-4': '', 'switch-font-family': "Change font family", 'switch-font-family-reverse': "Change font family (reverse)", 'switch-font-weight': "Change font weight", 'switch-font-style': "Change font style", 'switch-text-alignment': "Toggle text alignment", 'switch-image-file': "Change image file", '-separator-5': '', 'toggle-panel-and-dock-visibility': "Hide panel and dock", 'toggle-background': "Add a drawing background", 'toggle-grid': "Add a grid overlay", 'toggle-square-area': "Square drawing area", '-separator-6': '', 'open-previous-json': "Open previous drawing", 'open-next-json': "Open next drawing", 'save-as-json': "Save drawing", 'save-as-svg': "Save drawing as a SVG file", 'open-preferences': "Open preferences", 'toggle-help': "Show help" }; if (GS_VERSION < "3.36") delete INTERNAL_KEYBINDINGS['open-preferences']; function getKeyLabel(accel) { let [keyval, mods] = Gtk.accelerator_parse(accel); return Gtk.accelerator_get_label(keyval, mods); } var OTHER_SHORTCUTS = [ { desc: "Draw", get shortcut() { return _("Left click"); } }, { desc: "Menu", get shortcut() { return _("Right click"); } }, { desc: "Toggle fill/outline", get shortcut() { return _("Center click"); } }, { desc: "Increment/decrement line width", get shortcut() { return _("Scroll"); } }, { desc: "Select color", get shortcut() { return _("%s … %s").format(getKeyLabel('1'), getKeyLabel('9')); } }, { desc: "Ignore pointer movement", get shortcut() { return _("%s held").format(getKeyLabel('space')); } }, { desc: "Leave", shortcut: getKeyLabel('Escape') }, { desc: "-separator-1", shortcut: "" }, { desc: "Select eraser (while starting drawing)", shortcut: getKeyLabel('') }, { desc: "Duplicate (while starting handling)", shortcut: getKeyLabel('') }, { desc: "Rotate rectangle, polygon, polyline", shortcut: getKeyLabel('') }, { desc: "Extend circle to ellipse", shortcut: getKeyLabel('') }, { desc: "Curve line", shortcut: getKeyLabel('') }, { desc: "Smooth free drawing outline", shortcut: getKeyLabel('') }, { desc: "Rotate (while moving)", shortcut: getKeyLabel('') }, { desc: "Stretch (while resizing)", shortcut: getKeyLabel('') }, { desc: "Inverse (while mirroring)", shortcut: getKeyLabel('') } ]; function init() { Convenience.initTranslations(); } function buildPrefsWidget() { let topStack = new TopStack(); let switcher = new Gtk.StackSwitcher({halign: Gtk.Align.CENTER, visible: true, stack: topStack}); GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { let window = topStack.get_toplevel(); let headerBar = window.get_titlebar(); headerBar.custom_title = switcher; return false; }); topStack.show_all(); return topStack; } const TopStack = new GObject.Class({ Name: 'DrawOnYourScreenTopStack', GTypeName: 'DrawOnYourScreenTopStack', Extends: Gtk.Stack, _init: function(params) { this.parent({ transition_type: 1, transition_duration: 500, expand: true }); this.prefsPage = new PrefsPage(); this.add_titled(this.prefsPage, 'prefs', _("Preferences")); this.drawingPage = new DrawingPage(); this.add_titled(this.drawingPage, 'drawing', _("Drawing")); this.aboutPage = new AboutPage(); this.add_titled(this.aboutPage, 'about', _("About")); } }); const AboutPage = new GObject.Class({ Name: 'DrawOnYourScreenAboutPage', GTypeName: 'DrawOnYourScreenAboutPage', Extends: Gtk.ScrolledWindow, _init: function(params) { this.parent({ hscrollbar_policy: Gtk.PolicyType.NEVER }); let vbox= new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, margin: MARGIN*3 }); this.add(vbox); let name = " " + _(Me.metadata.name) + ""; let version = _("Version %d").format(Me.metadata.version); let description = _(Me.metadata.description); let link = "" + Me.metadata.url + ""; let licenceName = _GTK("GNU General Public License, version 2 or later"); let licenceLink = "https://www.gnu.org/licenses/old-licenses/gpl-2.0.html"; let licence = "" + _GTK("This program comes with absolutely no warranty.\nSee the %s for details.").format(licenceLink, licenceName) + ""; let aboutLabel = new Gtk.Label({ wrap: true, justify: 2, use_markup: true, label: name + "\n\n" + version + "\n\n" + description + "\n\n" + link + "\n\n" + licence + "\n" }); vbox.add(aboutLabel); let creditBox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL, margin: 2*MARGIN }); let leftBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL }); let rightBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL }); let leftLabel = new Gtk.Label({ wrap: true, valign: 1, halign: 2, justify: 1, use_markup: true, label: "" + _GTK("Created by") + "" }); let rightLabel = new Gtk.Label({ wrap: true, valign: 1, halign: 1, justify: 0, use_markup: true, label: "Abakkk" }); leftBox.pack_start(leftLabel, false, false, 0); rightBox.pack_start(rightLabel, false, false, 0); creditBox.pack_start(leftBox, true, true, 5); creditBox.pack_start(rightBox, true, true, 5); vbox.add(creditBox); if (_("translator-credits") != "translator-credits" && _("translator-credits") != "") { leftBox.pack_start(new Gtk.Label(), false, false, 0); rightBox.pack_start(new Gtk.Label(), false, false, 0); leftLabel = new Gtk.Label({ wrap: true, valign: 1, halign: 2, justify: 1, use_markup: true, label: "" + _GTK("Translated by") + "" }); rightLabel = new Gtk.Label({ wrap: true, valign: 1, halign: 1, justify: 0, use_markup: true, label: "" + _("translator-credits") + "" }); leftBox.pack_start(leftLabel, false, false, 0); rightBox.pack_start(rightLabel, false, false, 0); } } }); const DrawingPage = new GObject.Class({ Name: 'DrawOnYourScreenDrawingPage', GTypeName: 'DrawOnYourScreenDrawingPage', Extends: Gtk.ScrolledWindow, _init: function(params) { this.parent({ hscrollbar_policy: Gtk.PolicyType.NEVER }); this.settings = Convenience.getSettings(Me.metadata['settings-schema'] + '.drawing'); this.schema = this.settings.settings_schema; let box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, margin: MARGIN*3 }); this.add(box); let palettesFrame = new Gtk.Frame({ label_yalign: 1.0 }); palettesFrame.set_label_widget(new Gtk.Label({ margin_bottom: MARGIN/2, use_markup: true, label: "" + _("Palettes") + "" })); box.add(palettesFrame); let palettesScrolledWindow = new Gtk.ScrolledWindow({ vscrollbar_policy: Gtk.PolicyType.NEVER, margin_top: MARGIN/2, margin_bottom: MARGIN/2 }); palettesFrame.add(palettesScrolledWindow); this.palettesListBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true }); this.palettesListBox.get_style_context().add_class('background'); palettesScrolledWindow.add(this.palettesListBox); this.settings.connect('changed::palettes', this._updatePalettes.bind(this)); this._updatePalettes(); this.addBox = new Gtk.Box({ margin_top: MARGIN/2, margin_bottom: MARGIN/2, margin_left: MARGIN, margin_right: MARGIN, tooltip_text: _("Add a new palette") }); let addButton = Gtk.Button.new_from_icon_name('list-add-symbolic', Gtk.IconSize.BUTTON); this.addBox.pack_start(addButton, true, true, 4); addButton.connect('clicked', this._addNewPalette.bind(this)); this.palettesListBox.add(this.addBox); this.addBox.get_parent().set_activatable(false); let areaFrame = new Gtk.Frame({ margin_top: 3*MARGIN, label_yalign: 1.0 }); areaFrame.set_label_widget(new Gtk.Label({ margin_bottom: MARGIN/2, use_markup: true, label: "" + _("Area") + "" })); box.add(areaFrame); let areaListBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true, margin_top: MARGIN/2, margin_bottom: MARGIN/2 }); areaListBox.get_style_context().add_class('background'); areaFrame.add(areaListBox); let squareAreaRow = new PrefRow({ label: _("Square area size") }); let squareAreaAutoButton = new Gtk.CheckButton({ label: _("Auto"), tooltip_text: _(this.schema.get_key('square-area-auto').get_description()) }); let squareAreaSizeButton = new PixelSpinButton({ width_chars: 5, digits: 0, adjustment: Gtk.Adjustment.new(0, 64, 32768, 1, 10, 0), tooltip_text: _(this.schema.get_key('square-area-size').get_description()) }); this.settings.bind('square-area-auto', squareAreaAutoButton, 'active', 0); this.settings.bind('square-area-size', squareAreaSizeButton, 'value', 0); squareAreaAutoButton.bind_property('active', squareAreaSizeButton, 'sensitive', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN); squareAreaRow.addWidget(squareAreaAutoButton); squareAreaRow.addWidget(squareAreaSizeButton); areaListBox.add(squareAreaRow); let backgroundColorRow = new PrefRow({ label: _("Background color") }); let backgroundColorButton = new ColorStringButton({ use_alpha: true, show_editor: true, tooltip_text: _(this.schema.get_key('area-background-color').get_description()) }); this.settings.bind('area-background-color', backgroundColorButton, 'color-string', 0); backgroundColorRow.addWidget(backgroundColorButton); areaListBox.add(backgroundColorRow); let gridLineRow = new PrefRow({ label: _("Grid overlay line") }); let gridLineAutoButton = new Gtk.CheckButton({ label: _("Auto"), tooltip_text: _(this.schema.get_key('grid-line-auto').get_description()) }); let gridLineWidthButton = new PixelSpinButton({ width_chars: 5, digits: 1, adjustment: Gtk.Adjustment.new(0, 0.1, 10, 0.1, 1, 0), tooltip_text: _(this.schema.get_key('grid-line-width').get_description()) }); let gridLineSpacingButton = new PixelSpinButton({ width_chars: 5, digits: 1, adjustment: Gtk.Adjustment.new(0, 1, 16384, 1, 10, 0), tooltip_text: _(this.schema.get_key('grid-line-spacing').get_description()) }); this.settings.bind('grid-line-auto', gridLineAutoButton, 'active', 0); this.settings.bind('grid-line-width', gridLineWidthButton, 'value', 0); this.settings.bind('grid-line-spacing', gridLineSpacingButton, 'value', 0); gridLineAutoButton.bind_property('active', gridLineWidthButton, 'sensitive', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN); gridLineAutoButton.bind_property('active', gridLineSpacingButton, 'sensitive', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN); gridLineRow.addWidget(gridLineAutoButton); gridLineRow.addWidget(gridLineWidthButton); gridLineRow.addWidget(gridLineSpacingButton); areaListBox.add(gridLineRow); let gridColorRow = new PrefRow({ label: _("Grid overlay color") }); let gridColorButton = new ColorStringButton({ use_alpha: true, show_editor: true, tooltip_text: _(this.schema.get_key('grid-color').get_description()) }); this.settings.bind('grid-color', gridColorButton, 'color-string', 0); gridColorRow.addWidget(gridColorButton); areaListBox.add(gridColorRow); let toolsFrame = new Gtk.Frame({ margin_top: 3*MARGIN, label_yalign: 1.0 }); toolsFrame.set_label_widget(new Gtk.Label({ margin_bottom: MARGIN/2, use_markup: true, label: "" + _("Tools") + "" })); box.add(toolsFrame); let toolsListBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true, margin_top: MARGIN/2, margin_bottom: MARGIN/2 }); toolsListBox.get_style_context().add_class('background'); toolsFrame.add(toolsListBox); let dashArrayRow = new PrefRow({ label: _("Dash array") }); let dashArrayAutoButton = new Gtk.CheckButton({ label: _("Auto"), tooltip_text: _(this.schema.get_key('dash-array-auto').get_description()) }); let dashArrayOnButton = new PixelSpinButton({ width_chars: 5, digits: 1, adjustment: Gtk.Adjustment.new(0, 0, 16384, 0.1, 1, 0), tooltip_text: _(this.schema.get_key('dash-array-on').get_description()) }); let dashArrayOffButton = new PixelSpinButton({ width_chars: 5, digits: 1, adjustment: Gtk.Adjustment.new(0, 0, 16384, 0.1, 1, 0), tooltip_text: _(this.schema.get_key('dash-array-off').get_description()) }); this.settings.bind('dash-array-auto', dashArrayAutoButton, 'active', 0); this.settings.bind('dash-array-on', dashArrayOnButton, 'value', 0); this.settings.bind('dash-array-off', dashArrayOffButton, 'value', 0); dashArrayAutoButton.bind_property('active', dashArrayOnButton, 'sensitive', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN); dashArrayAutoButton.bind_property('active', dashArrayOffButton, 'sensitive', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN); dashArrayRow.addWidget(dashArrayAutoButton); dashArrayRow.addWidget(dashArrayOnButton); dashArrayRow.addWidget(dashArrayOffButton); toolsListBox.add(dashArrayRow); let dashOffsetRow = new PrefRow({ label: _("Dash offset") }); let dashOffsetButton = new PixelSpinButton({ width_chars: 5, digits: 1, adjustment: Gtk.Adjustment.new(0, -16384, 16384, 0.1, 1, 0), tooltip_text: _(this.schema.get_key('dash-offset').get_description()) }); this.settings.bind('dash-offset', dashOffsetButton, 'value', 0); dashOffsetRow.addWidget(dashOffsetButton); toolsListBox.add(dashOffsetRow); }, _updatePalettes: function() { this.palettes = this.settings.get_value('palettes').deep_unpack(); this.palettesListBox.get_children().filter(row=> row.get_child() != this.addBox) .slice(this.palettes.length) .forEach(row => this.palettesListBox.remove(row)); let paletteBoxes = this.palettesListBox.get_children().map(row => row.get_child()).filter(child => child != this.addBox); this.palettes.forEach((palette, paletteIndex) => { let [name, colors] = palette; let paletteBox; if (paletteBoxes[paletteIndex]) { paletteBox = paletteBoxes[paletteIndex]; let nameEntry = paletteBox.get_children()[0]; if (nameEntry.get_text() !== _(name)) { GObject.signal_handler_block(nameEntry, nameEntry.paletteNameChangedHandler); nameEntry.set_text(_(name)); GObject.signal_handler_unblock(nameEntry, nameEntry.paletteNameChangedHandler); } } else { let nameEntry = new Gtk.Entry({ text: name, halign: Gtk.Align.START, tooltip_text: _("Rename the palette") }); nameEntry.paletteNameChangedHandler = nameEntry.connect('changed', this._onPaletteNameChanged.bind(this, paletteIndex)); let removeButton = Gtk.Button.new_from_icon_name('list-remove-symbolic', Gtk.IconSize.BUTTON); removeButton.set_tooltip_text(_("Remove the palette")); removeButton.connect('clicked', this._removePalette.bind(this, paletteIndex)); paletteBox = new Gtk.Box({ margin_top: MARGIN/2, margin_bottom: MARGIN/2, margin_left: MARGIN, margin_right: MARGIN }); paletteBox.pack_start(nameEntry, true, true, 4); paletteBox.pack_start(new Gtk.Box({ spacing: 4 }), false, false, 4); paletteBox.pack_start(removeButton, false, false, 4); this.palettesListBox.insert(paletteBox, paletteIndex); paletteBox.get_parent().set_activatable(false); } colors.splice(9); while (colors.length < 9) colors.push('transparent'); let colorsBox = paletteBox.get_children()[1]; let colorButtons = colorsBox.get_children(); colors.forEach((color, colorIndex) => { if (colorButtons[colorIndex]) { colorButtons[colorIndex].color_string = color; } else { let colorButton = new ColorStringButton({ color_string: color, use_alpha: true, show_editor: true, halign: Gtk.Align.START, hexpand: false }); colorButton.connect('notify::color-string', this._onPaletteColorChanged.bind(this, paletteIndex, colorIndex)); colorsBox.add(colorButton); } }); paletteBox.show_all(); }); }, _savePalettes: function() { this.settings.set_value('palettes', new GLib.Variant('a(sas)', this.palettes)); }, _onPaletteNameChanged: function(index, entry) { this.palettes[index][0] = entry.get_text(); this._savePalettes(); }, _onPaletteColorChanged: function(paletteIndex, colorIndex, colorButton) { this.palettes[paletteIndex][1][colorIndex] = colorButton.get_rgba().to_string(); this._savePalettes(); }, _addNewPalette: function() { let colors = Array(9).fill('Black'); this.palettes.push([_("New palette"), colors]); this._savePalettes(); }, _removePalette: function(paletteIndex) { this.palettes.splice(paletteIndex, 1); this._savePalettes(); } }); const PrefsPage = new GObject.Class({ Name: 'DrawOnYourScreenPrefsPage', GTypeName: 'DrawOnYourScreenPrefsPage', Extends: Gtk.ScrolledWindow, _init: function(params) { this.parent({ hscrollbar_policy: Gtk.PolicyType.NEVER }); let settings = Convenience.getSettings(); let internalShortcutSettings = Convenience.getSettings(Me.metadata['settings-schema'] + '.internal-shortcuts'); let box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, margin: MARGIN*3 }); this.add(box); let globalFrame = new Gtk.Frame({ label_yalign: 1.0 }); globalFrame.set_label_widget(new Gtk.Label({ margin_bottom: MARGIN/2, use_markup: true, label: "" + _("Global") + "" })); box.add(globalFrame); let listBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true, margin_top: MARGIN/2, margin_bottom: MARGIN/2 }); globalFrame.add(listBox); let styleContext = listBox.get_style_context(); styleContext.add_class('background'); let globalKeybindingsWidget = new KeybindingsWidget(GLOBAL_KEYBINDINGS, settings); globalKeybindingsWidget.margin = MARGIN; listBox.add(globalKeybindingsWidget); let persistentBox = new Gtk.Box({ margin_top: MARGIN/2, margin_bottom: MARGIN/2, margin_left: MARGIN, margin_right: MARGIN }); let persistentLabelBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL }); let persistentLabel1 = new Gtk.Label({label: _("Persistent")}); let persistentLabel2 = new Gtk.Label({ use_markup: true, halign: 1, wrap: true, xalign: 0, label: "" + _("Persistent drawing through session restart") + "" }); persistentLabel1.set_halign(1); persistentLabel2.get_style_context().add_class('dim-label'); persistentLabelBox.pack_start(persistentLabel1, true, true, 0); persistentLabelBox.pack_start(persistentLabel2, true, true, 0); let persistentSwitch = new Gtk.Switch({valign: 3}); settings.bind('persistent-drawing', persistentSwitch, 'active', 0); persistentBox.pack_start(persistentLabelBox, true, true, 4); persistentBox.pack_start(persistentSwitch, false, false, 4); listBox.add(persistentBox); let desktopBox = new Gtk.Box({ margin_top: MARGIN/2, margin_bottom: MARGIN/2, margin_left: MARGIN, margin_right: MARGIN }); let desktopLabelBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL }); let desktopLabel1 = new Gtk.Label({label: _("Drawing on the desktop")}); let desktopLabel2 = new Gtk.Label({ use_markup: true, halign: 1, wrap: true, xalign: 0, label: "" + _("Draw On Your Screen becomes Draw On Your Desktop") + "" }); desktopLabel1.set_halign(1); desktopLabel2.get_style_context().add_class('dim-label'); desktopLabelBox.pack_start(desktopLabel1, true, true, 0); desktopLabelBox.pack_start(desktopLabel2, true, true, 0); let desktopSwitch = new Gtk.Switch({valign: 3}); settings.bind('drawing-on-desktop', desktopSwitch, 'active', 0); desktopBox.pack_start(desktopLabelBox, true, true, 4); desktopBox.pack_start(desktopSwitch, false, false, 4); listBox.add(desktopBox); let osdBox = new Gtk.Box({ margin_top: MARGIN/2, margin_bottom: MARGIN/2, margin_left: MARGIN, margin_right: MARGIN }); let osdLabelBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL }); let osdLabel1 = new Gtk.Label({label: _("Disable on-screen notifications")}); osdLabel1.set_halign(1); osdLabelBox.pack_start(osdLabel1, true, true, 0); let osdSwitch = new Gtk.Switch({valign: 3}); settings.bind('osd-disabled', osdSwitch, 'active', 0); osdBox.pack_start(osdLabelBox, true, true, 4); osdBox.pack_start(osdSwitch, false, false, 4); listBox.add(osdBox); let indicatorBox = new Gtk.Box({ margin_top: MARGIN/2, margin_bottom: MARGIN/2, margin_left: MARGIN, margin_right: MARGIN }); let indicatorLabelBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL }); let indicatorLabel1 = new Gtk.Label({label: _("Disable panel indicator")}); indicatorLabel1.set_halign(1); indicatorLabelBox.pack_start(indicatorLabel1, true, true, 0); let indicatorSwitch = new Gtk.Switch({valign: 3}); settings.bind('indicator-disabled', indicatorSwitch, 'active', 0); indicatorBox.pack_start(indicatorLabelBox, true, true, 4); indicatorBox.pack_start(indicatorSwitch, false, false, 4); listBox.add(indicatorBox); let children = listBox.get_children(); for (let i = 0; i < children.length; i++) { if (children[i].activatable) children[i].set_activatable(false); } let internalFrame = new Gtk.Frame({ margin_top: 3*MARGIN, label_yalign: 1.0 }); internalFrame.set_label_widget(new Gtk.Label({ margin_bottom: MARGIN/2, use_markup: true, label: "" + _("Internal") + " " + _("(in drawing mode)") })); box.add(internalFrame); listBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true, margin_top: MARGIN }); internalFrame.add(listBox); styleContext = listBox.get_style_context(); styleContext.add_class('background'); for (let i = 0; i < OTHER_SHORTCUTS.length; i++) { if (OTHER_SHORTCUTS[i].desc.indexOf('-separator-') != -1) { listBox.add(new Gtk.Box({ margin_top: MARGIN, margin_left: MARGIN, margin_right: MARGIN })); continue; } let otherBox = new Gtk.Box({ margin_left: MARGIN, margin_right: MARGIN }); let otherLabel = new Gtk.Label({ label: _(OTHER_SHORTCUTS[i].desc), use_markup: true }); otherLabel.set_halign(1); let otherLabel2 = new Gtk.Label({ label: OTHER_SHORTCUTS[i].shortcut }); otherBox.pack_start(otherLabel, true, true, 4); otherBox.pack_start(otherLabel2, false, false, 4); listBox.add(otherBox); } let internalKeybindingsWidget = new KeybindingsWidget(INTERNAL_KEYBINDINGS, internalShortcutSettings); internalKeybindingsWidget.margin = MARGIN; listBox.add(internalKeybindingsWidget); let noteBox = new Gtk.Box({ margin: MARGIN }); let noteLabel = new Gtk.Label({ wrap: true, xalign: 0, use_markup: true, label: _("When you save elements made with eraser in a SVG file, " + "they are colored with background color, transparent if it is disabled.\n" + "See ā€œ%sā€ or edit the SVG file afterwards.").format(_("Add a drawing background")) }); noteLabel.set_halign(1); noteLabel.get_style_context().add_class('dim-label'); noteBox.pack_start(noteLabel, true, true, 4); listBox.add(noteBox); children = listBox.get_children(); for (let i = 0; i < children.length; i++) { if (children[i].activatable) children[i].set_activatable(false); } } }); const PrefRow = new GObject.Class({ Name: 'DrawOnYourScreenPrefRow', GTypeName: 'DrawOnYourScreenPrefRow', Extends: Gtk.ListBoxRow, _init: function(params) { this.parent({ activatable: false }); let hbox = new Gtk.Box({ margin_top: MARGIN/2, margin_bottom: MARGIN/2, margin_left: MARGIN, margin_right: MARGIN }); this.add(hbox); let labelBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL }); hbox.pack_start(labelBox, true, true, 4); this.widgetBox = new Gtk.Box({ spacing: 4 }); hbox.pack_start(this.widgetBox, false, false, 4); labelBox.pack_start(new Gtk.Label({ use_markup: true, label: params.label, halign: Gtk.Align.START }), true, true, 0); if (params.desc) { let desc = new Gtk.Label({ use_markup: true, label: `${params.desc}`, halign: Gtk.Align.START, wrap: true, xalign: 0 }); desc.get_style_context().add_class('dim-label'); labelBox.pack_start(desc, true, true, 0); this.widgetBox.set_valign(Gtk.Align.START); } }, addWidget: function(widget) { this.widgetBox.add(widget); } }); const PixelSpinButton = new GObject.Class({ Name: 'DrawOnYourScreenPixelSpinButton', GTypeName: 'DrawOnYourScreenPixelSpinButton', Extends: Gtk.SpinButton, // Add 'px' unit. vfunc_output: function() { this.text = `${Math.round(this.value * 100) / 100} px`; return true; }, // Prevent accidental scrolling. vfunc_scroll_event: function(event) { return this.has_focus ? this.parent(event) : Gdk.EVENT_PROPAGATE; } }); // A color button that can be easily bound with a color string setting. const ColorStringButton = new GObject.Class({ Name: 'DrawOnYourScreenColorStringButton', GTypeName: 'DrawOnYourScreenColorStringButton', Extends: Gtk.ColorButton, Properties: { 'color-string': GObject.ParamSpec.string('color-string', 'colorString', 'A string that describes the color', GObject.ParamFlags.READWRITE, 'black') }, get color_string() { return this._color_string || 'black'; }, set color_string(colorString) { this._color_string = colorString; let newRgba = new Gdk.RGBA(); newRgba.parse(colorString); this.set_rgba(newRgba); }, // Do nothing if the new color is equivalent to the old color (e.g. "black" and "rgb(0,0,0)"). vfunc_color_set(args) { let oldRgba = new Gdk.RGBA(); oldRgba.parse(this.color_string); if (!this.rgba.equal(oldRgba)) { this._color_string = this.rgba.to_string(); this.notify('color-string'); } } }); // this code comes from Sticky Notes View by Sam Bull, https://extensions.gnome.org/extension/568/notes/ const KeybindingsWidget = new GObject.Class({ Name: 'DrawOnYourScreenKeybindings.Widget', GTypeName: 'DrawOnYourScreenKeybindingsWidget', Extends: Gtk.Box, _init: function(keybindings, settings) { this.parent(); this.set_orientation(Gtk.Orientation.VERTICAL); this._keybindings = keybindings; this._settings = settings; this._columns = { NAME: 0, ACCEL_NAME: 1, MODS: 2, KEY: 3 }; this._store = new Gtk.ListStore(); this._store.set_column_types([ GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_INT, GObject.TYPE_INT ]); this._tree_view = new Gtk.TreeView({ model: this._store, hexpand: false, vexpand: false }); this._tree_view.set_activate_on_single_click(false); this._tree_view.get_selection().set_mode(Gtk.SelectionMode.SINGLE); let action_renderer = new Gtk.CellRendererText(); let action_column = new Gtk.TreeViewColumn({ title: "", expand: true, }); action_column.pack_start(action_renderer, true); action_column.add_attribute(action_renderer, 'text', 1); this._tree_view.append_column(action_column); let keybinding_renderer = new Gtk.CellRendererAccel({ editable: true, accel_mode: Gtk.CellRendererAccelMode.GTK, xalign: 1 }); keybinding_renderer.connect('accel-edited', (renderer, iter, key, mods) => { let value = Gtk.accelerator_name(key, mods); let [success, iterator ] = this._store.get_iter_from_string(iter); if(!success) { printerr("Can't change keybinding"); } let name = this._store.get_value(iterator, 0); this._store.set( iterator, [this._columns.MODS, this._columns.KEY], [mods, key] ); this._settings.set_strv(name, [value]); }); let keybinding_column = new Gtk.TreeViewColumn({ title: "", }); keybinding_column.pack_end(keybinding_renderer, false); keybinding_column.add_attribute( keybinding_renderer, 'accel-mods', this._columns.MODS ); keybinding_column.add_attribute( keybinding_renderer, 'accel-key', this._columns.KEY ); this._tree_view.append_column(keybinding_column); this._tree_view.columns_autosize(); this._tree_view.set_headers_visible(false); this.add(this._tree_view); this.keybinding_column = keybinding_column; this.action_column = action_column; this._refresh(); }, _refresh: function() { this._store.clear(); for(let settings_key in this._keybindings) { if (settings_key.indexOf('-separator-') != -1) continue; let [key, mods] = Gtk.accelerator_parse( this._settings.get_strv(settings_key)[0] ); let iter = this._store.append(); this._store.set(iter, [ this._columns.NAME, this._columns.ACCEL_NAME, this._columns.MODS, this._columns.KEY ], [ settings_key, _(this._keybindings[settings_key]), mods, key ] ); } } });