From 5dad0ea609c14dd87d502693a61221a0895bf487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abram=C3=A9?= Date: Thu, 30 Nov 2023 17:33:54 +0100 Subject: [PATCH 1/4] #47 gnome 45 support added --- area.js | 752 ++++++++++++++++++++------------------- elements.js | 28 +- extension.js | 116 +++--- files.js | 802 ++++++++++++++++++++++-------------------- gimpPaletteParser.js | 7 +- helper.js | 48 +-- menu.js | 213 +++++------ metadata.json | 3 +- prefs.js | 41 +-- shortcuts.js | 84 +---- ui/about.js | 63 ++-- ui/areamanager.js | 204 +++++------ ui/drawingpage.js | 30 +- ui/preferencespage.js | 259 +++++++------- utils.js | 2 + 15 files changed, 1334 insertions(+), 1318 deletions(-) create mode 100644 utils.js diff --git a/area.js b/area.js index f49fe7a..540628f 100644 --- a/area.js +++ b/area.js @@ -21,92 +21,79 @@ /* jslint esversion: 6 */ /* exported Tool, DrawingArea */ -const Cairo = imports.cairo; -const Clutter = imports.gi.Clutter; -const Gio = imports.gi.Gio; -const GLib = imports.gi.GLib; -const GObject = imports.gi.GObject; -const Gtk = imports.gi.Gtk; -const Pango = imports.gi.Pango; -const Shell = imports.gi.Shell; -const St = imports.gi.St; -const System = imports.system; - -const ExtensionUtils = imports.misc.extensionUtils; -const Main = imports.ui.main; -const Screenshot = imports.ui.screenshot; - -const Me = ExtensionUtils.getCurrentExtension(); -const Elements = Me.imports.elements; -const Files = Me.imports.files; -const Menu = Me.imports.menu; -const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; -const pgettext = imports.gettext.domain(Me.metadata['gettext-domain']).pgettext; +import Cairo from 'cairo'; +import System from 'system'; + +import Clutter from 'gi://Clutter'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Gtk from 'gi://Gtk'; +import Pango from 'gi://Pango'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; + +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; +import * as Screenshot from 'resource:///org/gnome/shell/ui/screenshot.js'; + +import { gettext as _, pgettext } from 'resource:///org/gnome/shell/extensions/extension.js'; + +import { CURATED_UUID as UUID } from './utils.js'; +import * as Elements from './elements.js' +import { Image } from './files.js' +import * as Menu from './menu.js' + const MOTION_TIME = 1; // ms, time accuracy for free drawing, max is about 33 ms. The lower it is, the smoother the drawing is. const TEXT_CURSOR_TIME = 600; // ms const ELEMENT_GRABBER_TIME = 80; // ms, default is about 16 ms const TOGGLE_ANIMATION_DURATION = 300; // ms const GRID_TILES_HORIZONTAL_NUMBER = 30; -const COLOR_PICKER_EXTENSION_UUID = 'color-picker@tuberry'; -const UUID = Me.uuid.replace(/@/gi, '_at_').replace(/[^a-z0-9+_-]/gi, '_'); +const COLOR_PICKER_EXTENSION_UUID = 'color-picker@tuberry'; const { Shape, TextAlignment, Transformation } = Elements; const { DisplayStrings } = Menu; const FontGenericFamilies = ['Sans-Serif', 'Serif', 'Monospace', 'Cursive', 'Fantasy']; const Manipulation = { MOVE: 100, RESIZE: 101, MIRROR: 102 }; -var Tool = Object.assign({ - getNameOf: function(value) { +export const Tool = { + getNameOf: function (value) { return Object.keys(this).find(key => this[key] == value); - } -}, Shape, Manipulation); + }, + ...Shape, + ...Manipulation +}; Object.defineProperty(Tool, 'getNameOf', { enumerable: false }); -// toJSON provides a string suitable for SVG color attribute whereas -// toString provides a string suitable for displaying the color name to the user. -const getColorFromString = function(string, fallback) { - let [colorString, displayName] = string.split(':'); - let [success, color] = Clutter.Color.from_string(colorString); - color.toJSON = () => colorString; - color.toString = () => displayName || colorString; - if (success) - return color; - - log(`${Me.metadata.uuid}: "${string}" color cannot be parsed.`); - color = Clutter.Color.get_static(Clutter.StaticColor[fallback.toUpperCase()]); - color.toJSON = () => fallback; - color.toString = () => fallback; - return color; -}; + // Drawing layers are the proper drawing area widgets (painted thanks to Cairo). const DrawingLayer = GObject.registerClass({ GTypeName: `${UUID}-DrawingLayer`, -}, class DrawingArea extends St.DrawingArea{ +}, class DrawingArea extends St.DrawingArea { _init(repaintFunction, getHasImageFunction) { this._repaint = repaintFunction; this._getHasImage = getHasImageFunction || (() => false); super._init(); } - + // Bind the size of layers and layer container. vfunc_parent_set() { this.clear_constraints(); - + if (this.get_parent()) this.add_constraint(new Clutter.BindConstraint({ coordinate: Clutter.BindCoordinate.SIZE, source: this.get_parent() })); } - + vfunc_repaint() { let cr = this.get_context(); - + try { this._repaint(cr); - } catch(e) { + } catch (e) { logError(e, "An error occured while painting"); } - + cr.$dispose(); if (this._getHasImage()) System.gc(); @@ -117,24 +104,27 @@ const DrawingLayer = GObject.registerClass({ // There is a drawing element for each "brushstroke". // There is a separated layer for the current element so only the current element is redisplayed when drawing. // It handles pointer/mouse/(touch?) events and some keyboard events. -var DrawingArea = GObject.registerClass({ +export const DrawingArea = GObject.registerClass({ GTypeName: `${UUID}-DrawingArea`, - Signals: { 'show-osd': { param_types: [Gio.Icon.$gtype, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_DOUBLE, GObject.TYPE_BOOLEAN] }, - 'pointer-cursor-changed': { param_types: [GObject.TYPE_STRING] }, - 'update-action-mode': {}, - 'leave-drawing-mode': {} }, -}, class DrawingArea extends St.Widget{ - - _init(params, monitor, helper, areaManagerUtils, loadPersistent, toolConf) { - super._init({ style_class: 'draw-on-your-screen', name: params.name}); + Signals: { + 'show-osd': { param_types: [Gio.Icon.$gtype, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_DOUBLE, GObject.TYPE_BOOLEAN] }, + 'pointer-cursor-changed': { param_types: [GObject.TYPE_STRING] }, + 'update-action-mode': {}, + 'leave-drawing-mode': {} + }, +}, class DrawingArea extends St.Widget { + + _init(extension, params, monitor, helper, areaManagerUtils, loadPersistent, toolConf) { + super._init({ style_class: 'draw-on-your-screen', name: params.name }); + this._extension = extension; this.monitor = monitor; this.helper = helper; this.areaManagerUtils = areaManagerUtils; - + this.layerContainer = new St.Widget({ width: monitor.width, height: monitor.height }); this.add_child(this.layerContainer); this.add_child(this.helper); - + this.backLayer = new DrawingLayer(this._repaintBack.bind(this), this._getHasImageBack.bind(this)); this.layerContainer.add_child(this.backLayer); this.foreLayer = new DrawingLayer(this._repaintFore.bind(this), this._getHasImageFore.bind(this)); @@ -143,7 +133,7 @@ var DrawingArea = GObject.registerClass({ this.gridLayer.hide(); this.gridLayer.opacity = 0; this.layerContainer.add_child(this.gridLayer); - + this.elements = []; this.undoneElements = []; this.currentElement = null; @@ -152,11 +142,11 @@ var DrawingArea = GObject.registerClass({ this.currentPalette = toolConf["toolPalette"] } if (toolConf["toolColor"] != "") { - this.currentColor = getColorFromString(toolConf["toolColor"], "White"); + this.currentColor = this.getColorFromString(toolConf["toolColor"], "White"); } this.currentImage = null; this.currentTextAlignment = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL ? TextAlignment.RIGHT : TextAlignment.LEFT; - let fontName = St.Settings && St.Settings.get().font_name || ExtensionUtils.getSettings('org.gnome.desktop.interface').get_string('font-name'); + let fontName = St.Settings && St.Settings.get().font_name || this._extension.getSettings('org.gnome.desktop.interface').get_string('font-name'); this.currentFont = Pango.FontDescription.from_string(fontName); this.currentFont.unset_fields(Pango.FontMask.SIZE); this.defaultFontFamily = this.currentFont.get_family(); @@ -169,35 +159,35 @@ var DrawingArea = GObject.registerClass({ this.textHasCursor = false; this.dashedLine = false; this.fill = false; - + this.connect('destroy', this._onDestroy.bind(this)); this.connect('notify::reactive', this._onReactiveChanged.bind(this)); - this.drawingSettingsChangedHandler = Me.drawingSettings.connect('changed', this._onDrawingSettingsChanged.bind(this)); + this.drawingSettingsChangedHandler = this._extension.drawingSettings.connect('changed', this._onDrawingSettingsChanged.bind(this)); this._onDrawingSettingsChanged(); - + if (loadPersistent) this._loadPersistent(); } - + get menu() { if (!this._menu) - this._menu = new Menu.DrawingMenu(this, this.monitor, Tool, this.areaManagerUtils); + this._menu = new Menu.DrawingMenu(this._extension, this, this.monitor, Tool, this.areaManagerUtils); return this._menu; } - - closeMenu(){ + + closeMenu() { if (this._menu) this._menu.close(); } - + get isWriting() { - return this.textEntry ? true : false; + return this.textEntry; } - + get currentTool() { return this._currentTool; } - + set currentTool(tool) { this._currentTool = tool; if (this.hasManipulationTool) @@ -205,68 +195,68 @@ var DrawingArea = GObject.registerClass({ else this._stopElementGrabber(); } - + get currentPalette() { return this._currentPalette; } - + set currentPalette(palette) { this._currentPalette = palette; - this.colors = palette[1].map(colorString => getColorFromString(colorString, 'White')); + this.colors = palette[1].map(colorString => this.getColorFromString(colorString, 'White')); if (!this.colors[0]) this.colors.push(Clutter.Color.get_static(Clutter.StaticColor.WHITE)); - Me.drawingSettings.set_value("tool-palette", new GLib.Variant('(sas)', palette)) + this._extension.drawingSettings.set_value("tool-palette", new GLib.Variant('(sas)', palette)) } - + get currentImage() { if (!this._currentImage) - this._currentImage = Files.Images.getNext(this._currentImage); - + this._currentImage = this._extension.FILES.IMAGES.getNext(this._currentImage); + return this._currentImage; } - + set currentImage(image) { this._currentImage = image; } - + get currentFontFamily() { return this.currentFont.get_family(); } - + set currentFontFamily(family) { this.currentFont.set_family(family); } - + get currentFontStyle() { return this.currentFont.get_style(); } - + set currentFontStyle(style) { this.currentFont.set_style(style); } - + get currentFontWeight() { return this.currentFont.get_weight(); } - + set currentFontWeight(weight) { this.currentFont.set_weight(weight); } - + get hasManipulationTool() { // No Object.values method in GS 3.24. return Object.keys(Manipulation).map(key => Manipulation[key]).indexOf(this.currentTool) != -1; } - + // Boolean wrapper for switch menu item. get currentEvenodd() { return this.currentFillRule == Cairo.FillRule.EVEN_ODD; } - + set currentEvenodd(evenodd) { this.currentFillRule = evenodd ? Cairo.FillRule.EVEN_ODD : Cairo.FillRule.WINDING; } - + get fontFamilies() { if (!this._fontFamilies) { let otherFontFamilies = Elements.getAllFontFamilies().filter(family => { @@ -276,9 +266,9 @@ var DrawingArea = GObject.registerClass({ } return this._fontFamilies; } - + _onDrawingSettingsChanged() { - this.palettes = Me.drawingSettings.get_value('palettes').deep_unpack(); + this.palettes = this._extension.drawingSettings.get_value('palettes').deep_unpack(); if (!this.colors) { if (this.palettes[0]) this.currentPalette = this.palettes[0]; @@ -287,81 +277,87 @@ var DrawingArea = GObject.registerClass({ } if (!this.currentColor) this.currentColor = this.colors[0]; - - if (Me.drawingSettings.get_boolean('square-area-auto')) { + + if (this._extension.drawingSettings.get_boolean('square-area-auto')) { this.squareAreaSize = Math.pow(2, 6); while (this.squareAreaSize * 2 < Math.min(this.monitor.width, this.monitor.height)) this.squareAreaSize *= 2; } else { - this.squareAreaSize = Me.drawingSettings.get_uint('square-area-size'); + this.squareAreaSize = this._extension.drawingSettings.get_uint('square-area-size'); } - - this.areaBackgroundColor = getColorFromString(Me.drawingSettings.get_string('background-color'), 'Black'); - - this.gridColor = getColorFromString(Me.drawingSettings.get_string('grid-color'), 'Gray'); - if (Me.drawingSettings.get_boolean('grid-line-auto')) { + + this.areaBackgroundColor = this.getColorFromString(this._extension.drawingSettings.get_string('background-color'), 'Black'); + + this.gridColor = this.getColorFromString(this._extension.drawingSettings.get_string('grid-color'), 'Gray'); + if (this._extension.drawingSettings.get_boolean('grid-line-auto')) { this.gridLineSpacing = Math.round(this.monitor.width / (5 * GRID_TILES_HORIZONTAL_NUMBER)); this.gridLineWidth = this.gridLineSpacing / 20; } else { - this.gridLineSpacing = Me.drawingSettings.get_uint('grid-line-spacing'); - this.gridLineWidth = Math.round(Me.drawingSettings.get_double('grid-line-width') * 100) / 100; + this.gridLineSpacing = this._extension.drawingSettings.get_uint('grid-line-spacing'); + this.gridLineWidth = Math.round(this._extension.drawingSettings.get_double('grid-line-width') * 100) / 100; } - - this.dashOffset = Math.round(Me.drawingSettings.get_double('dash-offset') * 100) / 100; - if (Me.drawingSettings.get_boolean('dash-array-auto')) { + + this.dashOffset = Math.round(this._extension.drawingSettings.get_double('dash-offset') * 100) / 100; + if (this._extension.drawingSettings.get_boolean('dash-array-auto')) { this.dashArray = [0, 0]; } else { - let on = Math.round(Me.drawingSettings.get_double('dash-array-on') * 100) / 100; - let off = Math.round(Me.drawingSettings.get_double('dash-array-off') * 100) / 100; + let on = Math.round(this._extension.drawingSettings.get_double('dash-array-on') * 100) / 100; + let off = Math.round(this._extension.drawingSettings.get_double('dash-array-off') * 100) / 100; this.dashArray = [on, off]; } } - + _repaintBack(cr) { - for (let i = 0; i < this.elements.length; i++) { + this.elements.forEach(element => { cr.save(); - - this.elements[i].buildCairo(cr, { showElementBounds: this.grabbedElement && this.grabbedElement == this.elements[i], - drawElementBounds: this.grabPoint ? true : false }); - + element.buildCairo(cr, { + showElementBounds: this.grabbedElement && this.grabbedElement == element, + drawElementBounds: this.grabPoint ? true : false + }); + if (this.grabPoint) - this._searchElementToGrab(cr, this.elements[i]); - - if (this.elements[i].fill && !this.elements[i].isStraightLine) { + this._searchElementToGrab(cr, element); + + if (element.fill && !element.isStraightLine) { cr.fillPreserve(); - if (this.elements[i].shape == Shape.NONE || this.elements[i].shape == Shape.LINE) + if (element.shape == Shape.NONE || element.shape == Shape.LINE) cr.closePath(); - } - + } + cr.stroke(); - this.elements[i]._addMarks(cr); + element._addMarks(cr); cr.restore(); - } - - if (this.currentElement && this.currentElement.eraser) { - this.currentElement.buildCairo(cr, { showTextCursor: this.textHasCursor, - showElementBounds: this.currentElement.shape != Shape.TEXT || !this.isWriting, - dummyStroke: this.currentElement.fill && this.currentElement.line.lineWidth == 0 }); + + }); + + if (this.currentElement?.eraser) { + this.currentElement.buildCairo(cr, { + showTextCursor: this.textHasCursor, + showElementBounds: this.currentElement.shape != Shape.TEXT || !this.isWriting, + dummyStroke: this.currentElement.fill && this.currentElement.line.lineWidth == 0 + }); cr.stroke(); } } - + _repaintFore(cr) { if (!this.currentElement || this.currentElement.eraser) return; - - this.currentElement.buildCairo(cr, { showTextCursor: this.textHasCursor, - showElementBounds: this.currentElement.shape != Shape.TEXT || !this.isWriting, - dummyStroke: this.currentElement.fill && this.currentElement.line.lineWidth == 0 }); + + this.currentElement.buildCairo(cr, { + showTextCursor: this.textHasCursor, + showElementBounds: this.currentElement.shape != Shape.TEXT || !this.isWriting, + dummyStroke: this.currentElement.fill && this.currentElement.line.lineWidth == 0 + }); cr.stroke(); } - + _repaintGrid(cr) { if (!this.reactive) return; - + Clutter.cairo_set_source_color(cr, this.gridColor); - + let [gridX, gridY] = [0, 0]; while (gridX < this.monitor.width / 2) { cr.setLineWidth((gridX / this.gridLineSpacing) % 5 ? this.gridLineWidth / 2 : this.gridLineWidth); @@ -382,15 +378,15 @@ var DrawingArea = GObject.registerClass({ cr.stroke(); } } - + _getHasImageBack() { return this.elements.some(element => element.shape == Shape.IMAGE); } - + _getHasImageFore() { return this.currentElement && this.currentElement.shape == Shape.IMAGE || false; } - + _redisplay() { // force area to emit 'repaint' this.backLayer.queue_repaint(); @@ -398,34 +394,34 @@ var DrawingArea = GObject.registerClass({ if (this.hasGrid) this.gridLayer.queue_repaint(); } - + _transformStagePoint(stageX, stageY) { let [s, x, y] = this.transform_stage_point(stageX, stageY); if (!s || !this.layerContainer.get_allocation_box().contains(x, y)) return [false, 0, 0]; - + return this.layerContainer.transform_stage_point(stageX, stageY); } - + _onButtonPressed(actor, event) { if (this.spaceKeyPressed) return Clutter.EVENT_PROPAGATE; - + let button = event.get_button(); let [x, y] = event.get_coords(); let controlPressed = event.has_control_modifier(); let shiftPressed = event.has_shift_modifier(); - + if (this.currentElement && this.currentElement.shape == Shape.TEXT && this.isWriting) // finish writing this._stopWriting(); - + if (this.helper.visible) { // hide helper this.toggleHelp(); return Clutter.EVENT_STOP; } - + if (button == 1) { if (this.hasManipulationTool) { if (this.grabbedElement) @@ -438,23 +434,23 @@ var DrawingArea = GObject.registerClass({ this.switchFill(); } else if (button == 3) { this._stopAll(); - + this.menu.open(x, y); return Clutter.EVENT_STOP; } return Clutter.EVENT_PROPAGATE; } - + _onKeyboardPopupMenu() { this._stopAll(); - + if (this.helper.visible) this.toggleHelp(); this.menu.popup(); return Clutter.EVENT_STOP; } - + _onStageKeyPressed(actor, event) { if (event.get_key_symbol() == Clutter.KEY_Escape) { if (this.helper.visible) @@ -465,17 +461,17 @@ var DrawingArea = GObject.registerClass({ } else if (event.get_key_symbol() == Clutter.KEY_space) { this.spaceKeyPressed = true; } - + return Clutter.EVENT_PROPAGATE; } - + _onStageKeyReleased(actor, event) { if (event.get_key_symbol() == Clutter.KEY_space) this.spaceKeyPressed = false; - + return Clutter.EVENT_PROPAGATE; } - + _onKeyPressed(actor, event) { if (event.get_key_symbol() == Clutter.KEY_Escape) { if (this.helper.visible) { @@ -487,79 +483,79 @@ var DrawingArea = GObject.registerClass({ if (this.currentElement && this.currentElement.shape == Shape.LINE && (event.get_key_symbol() == Clutter.KEY_Return || - event.get_key_symbol() == Clutter.KEY_KP_Enter || - event.get_key_symbol() == Clutter.KEY_Control_L)) { - + event.get_key_symbol() == Clutter.KEY_KP_Enter || + event.get_key_symbol() == Clutter.KEY_Control_L)) { + if (this.currentElement.points.length == 2) // Translators: %s is a key label - this.emit('show-osd', Files.Icons.ARC, _("Press %s to get\na fourth control point") - .format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1, true); + this.emit('show-osd', this._extension.FILES.ICONS.ARC, _("Press %s to get\na fourth control point") + .format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1, true); this.currentElement.addPoint(); this.updatePointerCursor(true); this._redisplay(); return Clutter.EVENT_STOP; } else if (this.currentElement && - (this.currentElement.shape == Shape.POLYGON || this.currentElement.shape == Shape.POLYLINE) && - (event.get_key_symbol() == Clutter.KEY_Return || event.get_key_symbol() == Clutter.KEY_KP_Enter)) { - + (this.currentElement.shape == Shape.POLYGON || this.currentElement.shape == Shape.POLYLINE) && + (event.get_key_symbol() == Clutter.KEY_Return || event.get_key_symbol() == Clutter.KEY_KP_Enter)) { + this.currentElement.addPoint(); return Clutter.EVENT_STOP; } - + return Clutter.EVENT_PROPAGATE; } - + _onScroll(actor, event) { if (this.helper.visible) return Clutter.EVENT_PROPAGATE; let direction = event.get_scroll_direction(); if (direction == Clutter.ScrollDirection.UP) - this.incrementLineWidth(1); + this.incrementLineWidth(1); else if (direction == Clutter.ScrollDirection.DOWN) this.incrementLineWidth(-1); else return Clutter.EVENT_PROPAGATE; return Clutter.EVENT_STOP; } - + _searchElementToGrab(cr, element) { if (element.getContainsPoint(cr, this.grabPoint[0], this.grabPoint[1])) this.grabbedElement = element; else if (this.grabbedElement == element) this.grabbedElement = null; - + if (element == this.elements[this.elements.length - 1]) // All elements have been tested, the winner is the last. this.updatePointerCursor(); } - + _startElementGrabber() { if (this.elementGrabberHandler) return; - + this.elementGrabberHandler = this.connect('motion-event', (actor, event) => { if (this.motionHandler || this.grabbedElementLocked) { this.grabPoint = null; return; } - + // Reduce computing without notable effect. if (event.get_time() - (this.elementGrabberTimestamp || 0) < ELEMENT_GRABBER_TIME) return; this.elementGrabberTimestamp = event.get_time(); - + let coords = event.get_coords(); let [s, x, y] = this._transformStagePoint(coords[0], coords[1]); if (!s) return; - + this.grabPoint = [x, y]; this.grabbedElement = null; // this._redisplay calls this._searchElementToGrab. this._redisplay(); }); } - + _stopElementGrabber() { if (this.elementGrabberHandler) { this.disconnect(this.elementGrabberHandler); @@ -567,29 +563,29 @@ var DrawingArea = GObject.registerClass({ this.elementGrabberHandler = null; } } - + _startTransforming(stageX, stageY, controlPressed, duplicate) { let [success, startX, startY] = this._transformStagePoint(stageX, stageY); - + if (!success) return; - + if (this.currentTool == Manipulation.MIRROR) { this.grabbedElementLocked = !this.grabbedElementLocked; if (this.grabbedElementLocked) { this.updatePointerCursor(); let label = controlPressed ? _("Mark a point of symmetry") : _("Draw a line of symmetry"); - this.emit('show-osd', Files.Icons.TOOL_MIRROR, label, "", -1, true); + this.emit('show-osd', this._extension.FILES.ICONS.TOOL_MIRROR, label, "", -1, true); return; } } - + this.grabPoint = null; - + this.buttonReleasedHandler = this.connect('button-release-event', (actor, event) => { this._stopTransforming(); }); - + if (duplicate) { // deep cloning let copy = new this.grabbedElement.constructor(JSON.parse(JSON.stringify(this.grabbedElement))); @@ -602,22 +598,22 @@ var DrawingArea = GObject.registerClass({ this.elements.push(copy); this.grabbedElement = copy; } - + let undoable = !duplicate; - + if (this.currentTool == Manipulation.MOVE) this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformation.ROTATION : Transformation.TRANSLATION, undoable); else if (this.currentTool == Manipulation.RESIZE) this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformation.STRETCH : Transformation.SCALE_PRESERVE, undoable); - else if (this.currentTool == Manipulation.MIRROR) { + else if (this.currentTool == Manipulation.MIRROR) { this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformation.INVERSION : Transformation.REFLECTION, undoable); this._redisplay(); } - + this.motionHandler = this.connect('motion-event', (actor, event) => { if (this.spaceKeyPressed) return; - + let coords = event.get_coords(); let [s, x, y] = this._transformStagePoint(coords[0], coords[1]); if (!s) @@ -626,10 +622,10 @@ var DrawingArea = GObject.registerClass({ this._updateTransforming(x, y, controlPressed); }); } - + _updateTransforming(x, y, controlPressed) { let undoable = this.grabbedElement.lastTransformation.undoable || false; - + if (controlPressed && this.grabbedElement.lastTransformation.type == Transformation.TRANSLATION) { this.grabbedElement.stopTransformation(); this.grabbedElement.startTransformation(x, y, Transformation.ROTATION, undoable); @@ -637,7 +633,7 @@ var DrawingArea = GObject.registerClass({ this.grabbedElement.stopTransformation(); this.grabbedElement.startTransformation(x, y, Transformation.TRANSLATION, undoable); } - + if (controlPressed && this.grabbedElement.lastTransformation.type == Transformation.SCALE_PRESERVE) { this.grabbedElement.stopTransformation(); this.grabbedElement.startTransformation(x, y, Transformation.STRETCH, undoable); @@ -645,7 +641,7 @@ var DrawingArea = GObject.registerClass({ this.grabbedElement.stopTransformation(); this.grabbedElement.startTransformation(x, y, Transformation.SCALE_PRESERVE, undoable); } - + if (controlPressed && this.grabbedElement.lastTransformation.type == Transformation.REFLECTION) { this.grabbedElement.transformations.pop(); this.grabbedElement.startTransformation(x, y, Transformation.INVERSION, undoable); @@ -653,11 +649,11 @@ var DrawingArea = GObject.registerClass({ this.grabbedElement.transformations.pop(); this.grabbedElement.startTransformation(x, y, Transformation.REFLECTION, undoable); } - + this.grabbedElement.updateTransformation(x, y); this._redisplay(); } - + _stopTransforming() { if (this.motionHandler) { this.disconnect(this.motionHandler); @@ -667,7 +663,7 @@ var DrawingArea = GObject.registerClass({ this.disconnect(this.buttonReleasedHandler); this.buttonReleasedHandler = null; } - + this.grabbedElement.stopTransformation(); this.grabbedElement = null; this.grabbedElementLocked = false; @@ -679,11 +675,11 @@ var DrawingArea = GObject.registerClass({ if (!success) return; - + this.buttonReleasedHandler = this.connect('button-release-event', (actor, event) => { this._stopDrawing(); }); - + if (this.currentTool == Shape.TEXT) { this.currentElement = new Elements.DrawingElement({ shape: this.currentTool, @@ -714,18 +710,17 @@ var DrawingArea = GObject.registerClass({ points: [] }); } - + this.currentElement.startDrawing(startX, startY); - + if (this.currentTool == Shape.POLYGON || this.currentTool == Shape.POLYLINE) { - let icon = Files.Icons[this.currentTool == Shape.POLYGON ? 'TOOL_POLYGON' : 'TOOL_POLYLINE']; + let icon = this._extension.FILES.ICONS[this.currentTool == Shape.POLYGON ? 'TOOL_POLYGON' : 'TOOL_POLYLINE']; // Translators: %s is a key label this.emit('show-osd', icon, _("Press %s to mark vertices") - .format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1, true); + .format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1, true); } // Wayland supports two cursors so its important to disconnect motionhandler to avoid broken two cursors drawing - if (this.motionHandler) - { + if (this.motionHandler) { this.disconnect(this.motionHandler); this.motionHandler = null; } @@ -734,34 +729,34 @@ var DrawingArea = GObject.registerClass({ // To avoid painting due to the wrong device (2 cursors wayland support) if (clickedDevice != event.get_device()) return; - + if (this.spaceKeyPressed) return; - + let coords = event.get_coords(); let [s, x, y] = this._transformStagePoint(coords[0], coords[1]); if (!s) return; - + let controlPressed = event.has_control_modifier(); this._updateDrawing(x, y, controlPressed); }); } - + _updateDrawing(x, y, controlPressed) { if (!this.currentElement) return; - + this.currentElement.updateDrawing(x, y, controlPressed); - + if (this.currentElement.eraser) this._redisplay(); else this.foreLayer.queue_repaint(); this.updatePointerCursor(controlPressed); } - + _stopDrawing() { if (this.motionHandler) { this.disconnect(this.motionHandler); @@ -771,40 +766,40 @@ var DrawingArea = GObject.registerClass({ this.disconnect(this.buttonReleasedHandler); this.buttonReleasedHandler = null; } - + // skip when a polygon has not at least 3 points if (this.currentElement && this.currentElement.shape == Shape.POLYGON && this.currentElement.points.length < 3) this.currentElement = null; - + if (this.currentElement) this.currentElement.stopDrawing(); - + if (this.currentElement && this.currentElement.points.length >= 2) { if (this.currentElement.shape == Shape.TEXT && !this.isWriting) { this._startWriting(); return; } - + this.elements.push(this.currentElement); } - + this.currentElement = null; this._redisplay(); this.updatePointerCursor(); } - + _startWriting() { let [stageX, stageY] = this.get_transformed_position(); let [x, y] = [this.currentElement.x, this.currentElement.y]; this.currentElement.text = ''; this.currentElement.cursorPosition = 0; // Translators: %s is a key label - this.emit('show-osd', Files.Icons.TOOL_TEXT, _("Press %s\nto start a new line") - .format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1, true); + this.emit('show-osd', this._extension.FILES.ICONS.TOOL_TEXT, _("Press %s\nto start a new line") + .format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1, true); this._updateTextCursorTimeout(); this.textHasCursor = true; this._redisplay(); - + // Do not hide and do not set opacity to 0 because: // 1. ibusCandidatePopup need a mapped text entry to init correctly its position. // 2. 'cursor-changed' signal is no emitted if the text entry is not visible. @@ -813,7 +808,7 @@ var DrawingArea = GObject.registerClass({ this.textEntry.grab_key_focus(); this.updateActionMode(); this.updatePointerCursor(); - + let ibusCandidatePopup = Main.layoutManager.uiGroup.get_children().find(child => child.has_style_class_name && child.has_style_class_name('candidate-popup-boxpointer')); if (ibusCandidatePopup) { @@ -825,10 +820,10 @@ var DrawingArea = GObject.registerClass({ }); this.textEntry.connect('destroy', () => ibusCandidatePopup.disconnect(this.ibusHandler)); } - + this.textEntry.clutterText.set_single_line_mode(false); this.textEntry.clutterText.set_activatable(false); - + let showCursorOnPositionChanged = true; this.textEntry.clutterText.connect('text-changed', clutterText => { this.textEntry.y = stageY + y + (this.textEntry.clutterText.get_layout().get_line_count() - 1) * this.currentElement.height; @@ -836,7 +831,7 @@ var DrawingArea = GObject.registerClass({ showCursorOnPositionChanged = false; this._redisplay(); }); - + this.textEntry.clutterText.connect('cursor-changed', clutterText => { this.currentElement.cursorPosition = clutterText.cursorPosition; this._updateTextCursorTimeout(); @@ -845,21 +840,21 @@ var DrawingArea = GObject.registerClass({ showCursorOnPositionChanged = true; this._redisplay(); }); - + this.textEntry.clutterText.connect('key-press-event', (clutterText, event) => { if (event.get_key_symbol() == Clutter.KEY_Escape) { this._stopWriting(); return Clutter.EVENT_STOP; } - + return Clutter.EVENT_PROPAGATE; }); } - + _stopWriting() { if (this.currentElement.text.length > 0) this.elements.push(this.currentElement); - + this.currentElement = null; this._stopTextCursorTimeout(); this.textEntry.destroy(); @@ -867,17 +862,17 @@ var DrawingArea = GObject.registerClass({ this.grab_key_focus(); this.updateActionMode(); this.updatePointerCursor(); - + this._redisplay(); } - + setPointerCursor(pointerCursorName) { if (!this.currentPointerCursorName || this.currentPointerCursorName != pointerCursorName) { this.currentPointerCursorName = pointerCursorName; this.emit('pointer-cursor-changed', pointerCursorName); } } - + updatePointerCursor(controlPressed) { if (this.currentTool == Manipulation.MIRROR && this.grabbedElementLocked) this.setPointerCursor('CROSSHAIR'); @@ -890,12 +885,12 @@ var DrawingArea = GObject.registerClass({ else if (this.currentElement.shape != Shape.NONE && controlPressed) this.setPointerCursor('MOVE_OR_RESIZE_WINDOW'); } - + initPointerCursor() { this.currentPointerCursorName = null; this.updatePointerCursor(); } - + _stopTextCursorTimeout() { if (this.textCursorTimeoutId) { GLib.source_remove(this.textCursorTimeoutId); @@ -903,7 +898,7 @@ var DrawingArea = GObject.registerClass({ } this.textHasCursor = false; } - + _updateTextCursorTimeout() { this._stopTextCursorTimeout(); this.textCursorTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, TEXT_CURSOR_TIME, () => { @@ -912,7 +907,7 @@ var DrawingArea = GObject.registerClass({ return GLib.SOURCE_CONTINUE; }); } - + // A priori there is nothing to stop, except transformations, if there is no current element. // 'force' argument is passed when leaving drawing mode to ensure all is clean, as a workaround for possible bugs. _stopAll(force) { @@ -922,189 +917,195 @@ var DrawingArea = GObject.registerClass({ this.grabbedElementLocked = null; this.updatePointerCursor(); } - + if (!this.currentElement && !force) return; - + if (this.isWriting) this._stopWriting(); - + this._stopDrawing(); } - + erase() { this.deleteLastElement(); this.elements = []; this.undoneElements = []; this._redisplay(); } - + deleteLastElement() { this._stopAll(); this.elements.pop(); - + if (this.elements.length) this.elements[this.elements.length - 1].resetUndoneTransformations(); - + this._redisplay(); } - + undo() { if (!this.elements.length) return; - + let success = this.elements[this.elements.length - 1].undoTransformation(); if (!success) { this.undoneElements.push(this.elements.pop()); if (this.elements.length) this.elements[this.elements.length - 1].resetUndoneTransformations(); } - + this._redisplay(); } - + redo() { let success = false; - + if (this.elements.length) success = this.elements[this.elements.length - 1].redoTransformation(); - + if (!success && this.undoneElements.length > 0) this.elements.push(this.undoneElements.pop()); - + this._redisplay(); } - + smoothLastElement() { if (this.elements.length > 0 && this.elements[this.elements.length - 1].shape == Shape.NONE) { this.elements[this.elements.length - 1].smoothAll(); this._redisplay(); } } - + toggleBackground() { this.hasBackground = !this.hasBackground; let backgroundColor = this.hasBackground ? this.areaBackgroundColor : Clutter.Color.get_static(Clutter.StaticColor.TRANSPARENT); - + if (this.ease) { this.remove_all_transitions(); - this.ease({ backgroundColor, - duration: TOGGLE_ANIMATION_DURATION, - transition: Clutter.AnimationMode.EASE_IN_OUT_QUAD }); + this.ease({ + backgroundColor, + duration: TOGGLE_ANIMATION_DURATION, + transition: Clutter.AnimationMode.EASE_IN_OUT_QUAD + }); } else { this.set_background_color(backgroundColor); } } - + get hasGrid() { return this.gridLayer.visible; } - + toggleGrid() { // The grid layer is repainted when the visibility changes. if (this.gridLayer.ease) { this.gridLayer.remove_all_transitions(); let visible = !this.gridLayer.visible; this.gridLayer.visible = true; - this.gridLayer.ease({ opacity: visible ? 255 : 0, - duration: TOGGLE_ANIMATION_DURATION, - transition: Clutter.AnimationMode.EASE_IN_OUT_QUAD, - onStopped: () => this.gridLayer.visible = visible }); + this.gridLayer.ease({ + opacity: visible ? 255 : 0, + duration: TOGGLE_ANIMATION_DURATION, + transition: Clutter.AnimationMode.EASE_IN_OUT_QUAD, + onStopped: () => this.gridLayer.visible = visible + }); } else { this.gridLayer.visible = !this.gridLayer.visible; } } - + toggleSquareArea() { this.isSquareArea = !this.isSquareArea; let x, y, width, height, onComplete; - + if (this.isSquareArea) { this.layerContainer.add_style_class_name('draw-on-your-screen-square-area'); [x, y] = [(this.monitor.width - this.squareAreaSize) / 2, (this.monitor.height - this.squareAreaSize) / 2]; width = height = this.squareAreaSize; - onComplete = () => {}; + onComplete = () => { }; } else { x = y = 0; [width, height] = [this.monitor.width, this.monitor.height]; onComplete = () => this.layerContainer.remove_style_class_name('draw-on-your-screen-square-area'); } - + if (this.layerContainer.ease) { this.layerContainer.remove_all_transitions(); - this.layerContainer.ease({ x, y, width, height, onComplete, - duration: TOGGLE_ANIMATION_DURATION, - transition: Clutter.AnimationMode.EASE_OUT_QUAD }); + this.layerContainer.ease({ + x, y, width, height, onComplete, + duration: TOGGLE_ANIMATION_DURATION, + transition: Clutter.AnimationMode.EASE_OUT_QUAD + }); } else { this.layerContainer.set_position(x, y); this.layerContainer.set_size(width, height); onComplete(); } } - + selectColor(index) { if (!this.colors[index]) return; - + this.currentColor = this.colors[index]; - Me.drawingSettings.set_string("tool-color", this.colors[index].to_string()); + this._extension.drawingSettings.set_string("tool-color", this.colors[index].to_string()); if (this.currentElement) { this.currentElement.color = this.currentColor; this._redisplay(); } // Foreground color markup is not displayed since 3.36, use style instead but the transparency is lost. - this.emit('show-osd', Files.Icons.COLOR, String(this.currentColor), this.currentColor.to_string().slice(0, 7), -1, false); + this.emit('show-osd', this._extension.FILES.ICONS.COLOR, String(this.currentColor), this.currentColor.to_string().slice(0, 7), -1, false); } - + selectTool(tool) { this.currentTool = tool; - this.emit('show-osd', Files.Icons[`TOOL_${Tool.getNameOf(tool)}`] || null, DisplayStrings.Tool[tool], "", -1, false); + this.emit('show-osd', this._extension.FILES.ICONS[`TOOL_${Tool.getNameOf(tool)}`] || null, DisplayStrings.Tool[tool], "", -1, false); this.updatePointerCursor(); } - + switchFill() { this.fill = !this.fill; - let icon = Files.Icons[this.fill ? 'FILL' : 'STROKE']; + let icon = this._extension.FILES.ICONS[this.fill ? 'FILL' : 'STROKE']; this.emit('show-osd', icon, DisplayStrings.getFill(this.fill), "", -1, false); } - + switchFillRule() { this.currentFillRule = this.currentFillRule == 1 ? 0 : this.currentFillRule + 1; - let icon = Files.Icons[this.currentEvenodd ? 'FILLRULE_EVENODD' : 'FILLRULE_NONZERO']; + let icon = this._extension.FILES.ICONS[this.currentEvenodd ? 'FILLRULE_EVENODD' : 'FILLRULE_NONZERO']; this.emit('show-osd', icon, DisplayStrings.FillRule[this.currentFillRule], "", -1, false); } - + switchColorPalette(reverse) { let index = this.palettes.indexOf(this.currentPalette); if (reverse) this.currentPalette = index <= 0 ? this.palettes[this.palettes.length - 1] : this.palettes[index - 1]; else this.currentPalette = index == this.palettes.length - 1 ? this.palettes[0] : this.palettes[index + 1]; - this.emit('show-osd', Files.Icons.PALETTE, this.currentPalette[0], "", -1, false); + this.emit('show-osd', this._extension.FILES.ICONS.PALETTE, this.currentPalette[0], "", -1, false); } - + switchDash() { this.dashedLine = !this.dashedLine; - let icon = Files.Icons[this.dashedLine ? 'DASHED_LINE' : 'FULL_LINE']; + let icon = this._extension.FILES.ICONS[this.dashedLine ? 'DASHED_LINE' : 'FULL_LINE']; this.emit('show-osd', icon, DisplayStrings.getDashedLine(this.dashedLine), "", -1, false); } - + incrementLineWidth(increment) { this.currentLineWidth = Math.max(this.currentLineWidth + increment, 0); this.emit('show-osd', null, DisplayStrings.getPixels(this.currentLineWidth), "", 2 * this.currentLineWidth, false); - Me.drawingSettings.set_int("tool-size", this.currentLineWidth) + this._extension.drawingSettings.set_int("tool-size", this.currentLineWidth) } - + switchLineJoin() { this.currentLineJoin = this.currentLineJoin == 2 ? 0 : this.currentLineJoin + 1; - this.emit('show-osd', Files.Icons.LINEJOIN, DisplayStrings.LineJoin[this.currentLineJoin], "", -1, false); + this.emit('show-osd', this._extension.FILES.ICONS.LINEJOIN, DisplayStrings.LineJoin[this.currentLineJoin], "", -1, false); } - + switchLineCap() { this.currentLineCap = this.currentLineCap == 2 ? 0 : this.currentLineCap + 1; - this.emit('show-osd', Files.Icons.LINECAP, DisplayStrings.LineCap[this.currentLineCap], "", -1, false); + this.emit('show-osd', this._extension.FILES.ICONS.LINECAP, DisplayStrings.LineCap[this.currentLineCap], "", -1, false); } - + switchFontWeight() { let fontWeights = Object.keys(DisplayStrings.FontWeight).map(key => Number(key)); let index = fontWeights.indexOf(this.currentFontWeight); @@ -1113,20 +1114,20 @@ var DrawingArea = GObject.registerClass({ this.currentElement.font.set_weight(this.currentFontWeight); this._redisplay(); } - this.emit('show-osd', Files.Icons.FONT_WEIGHT, `` + - `${DisplayStrings.FontWeight[this.currentFontWeight]}`, "", -1, false); + this.emit('show-osd', this._extension.FILES.ICONS.FONT_WEIGHT, `` + + `${DisplayStrings.FontWeight[this.currentFontWeight]}`, "", -1, false); } - + switchFontStyle() { this.currentFontStyle = this.currentFontStyle == 2 ? 0 : this.currentFontStyle + 1; if (this.currentElement && this.currentElement.font) { this.currentElement.font.set_style(this.currentFontStyle); this._redisplay(); } - this.emit('show-osd', Files.Icons.FONT_STYLE, `` + - `${DisplayStrings.FontStyle[this.currentFontStyle]}`, "", -1, false); + this.emit('show-osd', this._extension.FILES.ICONS.FONT_STYLE, `` + + `${DisplayStrings.FontStyle[this.currentFontStyle]}`, "", -1, false); } - + switchFontFamily(reverse) { let index = Math.max(0, this.fontFamilies.indexOf(this.currentFontFamily)); if (reverse) @@ -1137,60 +1138,60 @@ var DrawingArea = GObject.registerClass({ this.currentElement.font.set_family(this.currentFontFamily); this._redisplay(); } - this.emit('show-osd', Files.Icons.FONT_FAMILY, `${DisplayStrings.getFontFamily(this.currentFontFamily)}`, "", -1, false); + this.emit('show-osd', this._extension.FILES.ICONS.FONT_FAMILY, `${DisplayStrings.getFontFamily(this.currentFontFamily)}`, "", -1, false); } - + switchTextAlignment() { this.currentTextAlignment = this.currentTextAlignment == 2 ? 0 : this.currentTextAlignment + 1; if (this.currentElement && this.currentElement.textAlignment != this.currentTextAlignment) { this.currentElement.textAlignment = this.currentTextAlignment; this._redisplay(); } - let icon = Files.Icons[this.currentTextAlignment == TextAlignment.RIGHT ? 'RIGHT_ALIGNED' : this.currentTextAlignment == TextAlignment.CENTER ? 'CENTERED' : 'LEFT_ALIGNED']; + let icon = this._extension.FILES.ICONS[this.currentTextAlignment == TextAlignment.RIGHT ? 'RIGHT_ALIGNED' : this.currentTextAlignment == TextAlignment.CENTER ? 'CENTERED' : 'LEFT_ALIGNED']; this.emit('show-osd', icon, DisplayStrings.TextAlignment[this.currentTextAlignment], "", -1, false); } - + switchImageFile(reverse) { - this.currentImage = Files.Images[reverse ? 'getPrevious' : 'getNext'](this.currentImage); + this.currentImage = this._extension.FILES.IMAGES[reverse ? 'getPrevious' : 'getNext'](this.currentImage); if (this.currentImage) this.emit('show-osd', this.currentImage.gicon, this.currentImage.toString(), "", -1, false); } - + pasteImageFiles() { - Files.Images.addImagesFromClipboard(lastImage => { + this._extension.FILES.IMAGES.addImagesFromClipboard(lastImage => { this.currentImage = lastImage; this.currentTool = Shape.IMAGE; this.updatePointerCursor(); this.emit('show-osd', this.currentImage.gicon, this.currentImage.toString(), "", -1, false); }); } - + _onColorPicked(color) { if (color instanceof Clutter.Color) color = color.to_string().slice(0, -2); - - this.currentColor = getColorFromString(color); - Me.drawingSettings.set_string("tool-color", color); + + this.currentColor = this.getColorFromString(color); + this._extension.drawingSettings.set_string("tool-color", color); if (this.currentElement) { this.currentElement.color = this.currentColor; this._redisplay(); } - this.emit('show-osd', Files.Icons.COLOR, String(this.currentColor), this.currentColor.to_string().slice(0, 7), -1, false); + this.emit('show-osd', this._extension.FILES.ICONS.COLOR, String(this.currentColor), this.currentColor.to_string().slice(0, 7), -1, false); this.initPointerCursor(); - - if (Me.settings.get_boolean("copy-picked-hex")) { + + if (this._extension.settings.get_boolean("copy-picked-hex")) { St.Clipboard.get_default().set_text(St.ClipboardType.CLIPBOARD, color); } } - + pickColor() { if (!Screenshot.PickPixel) // GS 3.28- return; - + // Translators: It is displayed in an OSD notification to ask the user to start picking, so it should use the imperative mood. - this.emit('show-osd', Files.Icons.COLOR_PICKER, pgettext("osd-notification", "Pick a color"), "", -1, false); - + this.emit('show-osd', this._extension.FILES.ICONS.COLOR_PICKER, pgettext("osd-notification", "Pick a color"), "", -1, false); + let extension = Main.extensionManager && Main.extensionManager.lookup(COLOR_PICKER_EXTENSION_UUID); if (extension && extension.state == ExtensionUtils.ExtensionState.ENABLED && extension.stateObj && extension.stateObj.pickAsync) { extension.stateObj.pickAsync().then(result => { @@ -1201,14 +1202,14 @@ var DrawingArea = GObject.registerClass({ }).catch(e => { this.initPointerCursor(); }); - + return; } - + try { let screenshot = new Shell.Screenshot(); let pickPixel = new Screenshot.PickPixel(screenshot); - + if (pickPixel.pickAsync) { pickPixel.pickAsync().then(result => { if (result instanceof Clutter.Color) { @@ -1236,12 +1237,12 @@ var DrawingArea = GObject.registerClass({ this.initPointerCursor(); }); } - } catch(e) { - log(`${Me.metadata.uuid}: color picker failed: ${e.message}`); + } catch (e) { + log(`${this._extension.metadata.uuid}: color picker failed: ${e.message}`); this.initPointerCursor(); } } - + toggleHelp() { if (this.helper.visible) { this.helper.hideHelp(); @@ -1252,7 +1253,7 @@ var DrawingArea = GObject.registerClass({ this.grab_key_focus(); } } - + // The area is reactive when it is modal. _onReactiveChanged() { if (this.hasGrid) @@ -1261,7 +1262,7 @@ var DrawingArea = GObject.registerClass({ this.toggleHelp(); if (this.textEntry && this.reactive) this.textEntry.grab_key_focus(); - + if (this.reactive) { this.stageKeyPressedHandler = global.stage.connect('key-press-event', this._onStageKeyPressed.bind(this)); this.stageKeyReleasedHandler = global.stage.connect('key-release-event', this._onStageKeyReleased.bind(this)); @@ -1277,19 +1278,19 @@ var DrawingArea = GObject.registerClass({ this.spaceKeyPressed = false; } } - + _onDestroy() { - Me.drawingSettings.disconnect(this.drawingSettingsChangedHandler); + this._extension.drawingSettings.disconnect(this.drawingSettingsChangedHandler); this.erase(); if (this._menu) this._menu.disable(); delete this.areaManagerUtils; } - + updateActionMode() { this.emit('update-action-mode'); } - + enterDrawingMode() { this.keyPressedHandler = this.connect('key-press-event', this._onKeyPressed.bind(this)); this.buttonPressedHandler = this.connect('button-press-event', this._onButtonPressed.bind(this)); @@ -1297,7 +1298,7 @@ var DrawingArea = GObject.registerClass({ this.scrollHandler = this.connect('scroll-event', this._onScroll.bind(this)); this.set_background_color(this.reactive && this.hasBackground ? this.areaBackgroundColor : null); } - + leaveDrawingMode(save, erase) { if (this.keyPressedHandler) { this.disconnect(this.keyPressedHandler); @@ -1315,53 +1316,53 @@ var DrawingArea = GObject.registerClass({ this.disconnect(this.scrollHandler); this.scrollHandler = null; } - + this._stopAll(true); - + if (erase) this.erase(); - + this.closeMenu(); this.set_background_color(null); - Files.Images.reset(); + this._extension.FILES.IMAGES.reset(); if (save) this.savePersistent(); } - + // Used by the menu. getSvgContentsForJson(json) { let elements = []; let elementsContent = ''; - + elements.push(...JSON.parse(json.contents).map(object => { if (object.color) - object.color = getColorFromString(object.color, 'White'); + object.color = this.getColorFromString(object.color, 'White'); if (object.font && typeof object.font == 'string') object.font = Pango.FontDescription.from_string(object.font); if (object.image) - object.image = new Files.Image(object.image); + object.image = new Image(object.image); return new Elements.DrawingElement(object); })); elements.forEach(element => elementsContent += element.buildSVG('transparent')); - + let prefixes = 'xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"'; - + let getGiconSvgContent = () => { let size = Math.min(this.monitor.width, this.monitor.height); let [x, y] = [(this.monitor.width - size) / 2, (this.monitor.height - size) / 2]; return `${elementsContent}\n`; }; - + let getImageSvgContent = () => { return `${elementsContent}\n`; }; - + return [getGiconSvgContent, getImageSvgContent]; } - + exportToSvg() { this._stopAll(); - + let prefixes = 'xmlns="http://www.w3.org/2000/svg"'; if (this.elements.some(element => element.shape == Shape.IMAGE)) prefixes += ' xmlns:xlink="http://www.w3.org/1999/xlink"'; @@ -1371,8 +1372,8 @@ var DrawingArea = GObject.registerClass({ content += `\n `; this.elements.forEach(element => content += element.buildSVG(backgroundColorString)); content += "\n"; - - if (Files.saveSvg(content)) { + + if (this._extension.FILES.saveSvg(content)) { let flashspot = new Screenshot.Flashspot(this); flashspot.fire(); if (global.play_theme_sound) { @@ -1383,95 +1384,112 @@ var DrawingArea = GObject.registerClass({ } } } - + _saveAsJson(json, notify, callback) { this._stopAll(); - + // do not use "content = JSON.stringify(this.elements, null, 2);", neither "content = JSON.stringify(this.elements);" // do compromise between disk usage and human readability let contents = this.elements.length ? `[\n ` + new Array(...this.elements.map(element => JSON.stringify(element))).join(`,\n\n `) + `\n]` : '[]'; - + GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { json.contents = contents; if (notify) - this.emit('show-osd', Files.Icons.SAVE, json.name, "", -1, false); + this.emit('show-osd', this._extension.FILES.ICONS.SAVE, json.name, "", -1, false); if (!json.isPersistent) this.currentJson = json; if (callback) callback(); }); } - + saveAsJsonWithName(name, callback) { - this._saveAsJson(Files.Jsons.getNamed(name), false, callback); + this._saveAsJson(this._extension.FILES.JSONS.getNamed(name), false, callback); } - + saveAsJson(notify, callback) { - this._saveAsJson(Files.Jsons.getDated(), notify, callback); + this._saveAsJson(this._extension.FILES.JSONS.getDated(), notify, callback); } - + savePersistent() { - this._saveAsJson(Files.Jsons.getPersistent()); + this._saveAsJson(this._extension.FILES.JSONS.getPersistent()); } - + syncPersistent() { // do not override peristent.json with an empty drawing when changing persistency setting if (!this.elements.length) this._loadPersistent(); else this.savePersistent(); - + } - + _loadJson(json, notify) { this._stopAll(); - + this.elements = []; this.currentElement = null; - + if (!json.contents) return; - + this.elements.push(...JSON.parse(json.contents).map(object => { if (object.color) - object.color = getColorFromString(object.color, 'White'); + object.color = this.getColorFromString(object.color, 'White'); if (object.font && typeof object.font == 'string') object.font = Pango.FontDescription.from_string(object.font); if (object.image) - object.image = new Files.Image(object.image); + object.image = new Image(object.image); return new Elements.DrawingElement(object); })); - + if (notify) - this.emit('show-osd', Files.Icons.OPEN, json.name, "", -1, false); + this.emit('show-osd', this._extension.FILES.ICONS.OPEN, json.name, "", -1, false); if (!json.isPersistent) this.currentJson = json; } - + _loadPersistent() { - this._loadJson(Files.Jsons.getPersistent()); + this._loadJson(this._extension.FILES.JSONS.getPersistent()); } - + loadJson(json, notify) { this._loadJson(json, notify); this._redisplay(); } - + loadPreviousJson() { - let json = Files.Jsons.getPrevious(this.currentJson || null); + let json = this._extension.FILES.JSONS.getPrevious(this.currentJson || null); if (json) this.loadJson(json, true); } - + loadNextJson() { - let json = Files.Jsons.getNext(this.currentJson || null); + let json = this._extension.FILES.JSONS.getNext(this.currentJson || null); if (json) this.loadJson(json, true); } - + get drawingContentsHasChanged() { let contents = `[\n ` + new Array(...this.elements.map(element => JSON.stringify(element))).join(`,\n\n `) + `\n]`; return contents != (this.currentJson && this.currentJson.contents); } + + // toJSON provides a string suitable for SVG color attribute whereas + // toString provides a string suitable for displaying the color name to the user. + getColorFromString(string, fallback) { + let [colorString, displayName] = string.split(':'); + let [success, color] = Clutter.Color.from_string(colorString); + color.toJSON = () => colorString; + color.toString = () => displayName || colorString; + if (success) + return color; + + log(`${this._extension.metadata.uuid}: "${string}" color cannot be parsed.`); + color = Clutter.Color.get_static(Clutter.StaticColor[fallback.toUpperCase()]); + color.toJSON = () => fallback; + color.toString = () => fallback; + return color; + }; }); diff --git a/elements.js b/elements.js index 1f14c43..5be49e2 100644 --- a/elements.js +++ b/elements.js @@ -21,21 +21,21 @@ /* jslint esversion: 6 */ /* exported Shape, TextAlignment, Transformation, getAllFontFamilies, DrawingElement */ -const Cairo = imports.cairo; -const Clutter = imports.gi.Clutter; -const GObject = imports.gi.GObject; +import Cairo from 'cairo'; -const Pango = imports.gi.Pango; -const PangoCairo = imports.gi.PangoCairo; +import Clutter from 'gi://Clutter'; +import GObject from 'gi://GObject'; +import Pango from 'gi://Pango'; +import PangoCairo from 'gi://PangoCairo'; -const Me = imports.misc.extensionUtils.getCurrentExtension(); -const UUID = Me.uuid.replace(/@/gi, '_at_').replace(/[^a-z0-9+_-]/gi, '_'); +import { CURATED_UUID as UUID } from './utils.js'; -var Shape = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6, IMAGE: 7 }; -var TextAlignment = { LEFT: 0, CENTER: 1, RIGHT: 2 }; -var Transformation = { TRANSLATION: 0, ROTATION: 1, SCALE_PRESERVE: 2, STRETCH: 3, REFLECTION: 4, INVERSION: 5, SMOOTH: 100 }; -var getAllFontFamilies = function() { +export const Shape = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6, IMAGE: 7 }; +export const TextAlignment = { LEFT: 0, CENTER: 1, RIGHT: 2 }; +export const Transformation = { TRANSLATION: 0, ROTATION: 1, SCALE_PRESERVE: 2, STRETCH: 3, REFLECTION: 4, INVERSION: 5, SMOOTH: 100 }; + +export const getAllFontFamilies = function() { return PangoCairo.font_map_get_default().list_families().map(fontFamily => fontFamily.get_name()).sort((a,b) => a.localeCompare(b)); }; @@ -67,7 +67,7 @@ const MIN_DRAWING_SIZE = 3; // px const MIN_INTERMEDIATE_POINT_DISTANCE = 1; // px, the higher it is, the fewer points there will be const MARK_COLOR = Clutter.Color.get_static(Clutter.StaticColor.BLUE); -var DrawingElement = function(params) { +export const DrawingElement = function(params) { return params.shape == Shape.TEXT ? new TextElement(params) : params.shape == Shape.IMAGE ? new ImageElement(params) : new _DrawingElement(params); @@ -103,7 +103,7 @@ const _DrawingElement = GObject.registerClass({ }); } - if (params.transform && params.transform.center) { + if (params.transform?.center) { let angle = (params.transform.angle || 0) + (params.transform.startAngle || 0); if (angle) this.transformations.push({ type: Transformation.ROTATION, angle: angle }); @@ -150,7 +150,7 @@ const _DrawingElement = GObject.registerClass({ if (this.fillRule) cr.setFillRule(this.fillRule); - if (this.dash && this.dash.active && this.dash.array && this.dash.array[0] && this.dash.array[1]) + if (this.dash?.active && this.dash.array && this.dash.array[0] && this.dash.array[1]) cr.setDash(this.dash.array, this.dash.offset); if (this.eraser) diff --git a/extension.js b/extension.js index 1198983..9504eee 100644 --- a/extension.js +++ b/extension.js @@ -19,25 +19,24 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -/* jslint esversion: 6 */ -/* exported init */ - -const {Gio, GObject} = imports.gi; -const {QuickToggle, SystemIndicator} = imports.ui.quickSettings; -const QuickSettingsMenu = imports.ui.main.panel.statusArea.quickSettings; -const Panel = imports.ui.main.panel; -const ExtensionUtils = imports.misc.extensionUtils; -const Me = ExtensionUtils.getCurrentExtension(); -const AreaManager = Me.imports.ui.areamanager; -const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; -const UUID = Me.uuid.replace(/@/gi, '_at_').replace(/[^a-z0-9+_-]/gi, '_'); - -const Config = imports.misc.config; + +import GObject from 'gi://GObject'; + +import { QuickToggle, SystemIndicator, QuickSettingsMenu } from 'resource:///org/gnome/shell/ui/quickSettings.js'; +import * as Panel from 'resource:///org/gnome/shell/ui/panel.js'; + +import * as Config from 'resource:///org/gnome/shell/misc/config.js'; + +import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js'; + +import { Files } from './files.js'; +import * as AreaManager from './ui/areamanager.js'; + + + const GS_VERSION = Config.PACKAGE_VERSION; -function init() { - return new Extension(); -} + const FeatureToggle = GObject.registerClass( class FeatureToggle extends QuickToggle { @@ -47,39 +46,26 @@ class FeatureToggle extends QuickToggle { iconName: 'applications-graphics-symbolic', toggleMode: true, }); - - - // NOTE: In GNOME 44, the `label` property must be set after - // construction. The newer `title` property can be set at construction. - // this.label = 'Feature Name'; - - // Binding the toggle to a GSettings key - //this._settings = new Gio.Settings({ - // schema_id: 'org.gnome.shell.extensions.example', - //}); - - //this._settings.bind('feature-enabled', - // this, 'checked', - // Gio.SettingsBindFlags.DEFAULT); } }); -var Indicator = GObject.registerClass( + + +const Indicator = GObject.registerClass( class Indicator extends SystemIndicator { _init() { super._init(); this.toggle = new FeatureToggle(); this.quickSettingsItems.push(this.toggle); - QuickSettingsMenu._indicators.add_child(this); - QuickSettingsMenu._addItems(this.quickSettingsItems); + this._addIndicator(); //Place the toggles above the background apps entry - if (GS_VERSION >= 44) { - this.quickSettingsItems.forEach((item) => { - QuickSettingsMenu.menu._grid.set_child_below_sibling(item, - QuickSettingsMenu._backgroundApps.quickSettingsItems[0]); - }); - } + // if (GS_VERSION >= 44) { + // this.quickSettingsItems.forEach((item) => { + // QuickSettingsMenu.menu._grid.set_child_below_sibling(item, + // QuickSettingsMenu._backgroundApps.quickSettingsItems[0]); + // }); + // } this.connect('destroy', () => { this.quickSettingsItems.forEach(item => item.destroy()); @@ -91,20 +77,22 @@ class Indicator extends SystemIndicator { } }); -const Extension = GObject.registerClass({ - GTypeName: `${UUID}-Extension`, -}, class Extension extends GObject.Object{ - _init() { - ExtensionUtils.initTranslations(); + +export default class DrawOnYourScreenExtension extends Extension { + + constructor(metadata) { + super(metadata); + this.initTranslations(); + this.FILES = new Files(this); } create_toggle() { if (GS_VERSION >= '44.0') { - if (!Me.settings.get_boolean("quicktoggle-disabled") && !this.toggle) { + if (!this.getSettings().get_boolean("quicktoggle-disabled") && !this.toggle) { this.toggle = new Indicator(); this.drawingtoggle = this.toggle.get_toggle(); this.drawingtoggle.connect('clicked',this.toggle_drawing.bind(this)); - } else if (Me.settings.get_boolean("quicktoggle-disabled") && this.toggle) { + } else if (this.getSettings().get_boolean("quicktoggle-disabled") && this.toggle) { this.toggle.destroy(); this.toggle = null; } @@ -112,39 +100,39 @@ const Extension = GObject.registerClass({ } enable() { - if (ExtensionUtils.isOutOfDate(Me)) - log(`${Me.metadata.uuid}: GNOME Shell ${Number.parseFloat(GS_VERSION)} is not supported.`); - - Me.settings = ExtensionUtils.getSettings(); - Me.internalShortcutSettings = ExtensionUtils.getSettings(Me.metadata['settings-schema'] + '.internal-shortcuts'); - Me.drawingSettings = ExtensionUtils.getSettings(Me.metadata['settings-schema'] + '.drawing'); - this.areaManager = new AreaManager.AreaManager(); + this.settings = this.getSettings(); + this.internalShortcutSettings = this.getSettings(this.metadata['settings-schema'] + '.internal-shortcuts'); + this.drawingSettings = this.getSettings(this.metadata['settings-schema'] + '.drawing'); + this.areaManager = new AreaManager.AreaManager(this); + this.areaManager.enable(); this.toggle = null; this.create_toggle(); - Me.settings.connect('changed', this._onSettingsChanged.bind(this)); - } - toggle_drawing() - { - Panel.closeQuickSettings(); - this.drawingtoggle.set_checked(false); - this.areaManager.toggleDrawing(); + this.getSettings().connect('changed', this._onSettingsChanged.bind(this)); } + disable() { if (this.toggle) this.toggle.destroy(); this.areaManager.disable(); delete this.areaManager; - delete Me.settings; - delete Me.internalShortcutSettings; + delete this.settings; + delete this.internalShortcutSettings; + } + + toggle_drawing() + { + Panel.closeQuickSettings(); + this.drawingtoggle.set_checked(false); + this.areaManager.toggleDrawing(); } _onSettingsChanged() { this.create_toggle() } -}); +} diff --git a/files.js b/files.js index 81bb2d3..552c1fc 100644 --- a/files.js +++ b/files.js @@ -21,95 +21,357 @@ /* jslint esversion: 6 */ /* exported Icons, Image, Images, Json, Jsons, getDateString, saveSvg */ -const ByteArray = imports.byteArray; -const Gdk = imports.gi.Gdk; -const GdkPixbuf = imports.gi.GdkPixbuf; -const Gio = imports.gi.Gio; -const GLib = imports.gi.GLib; -const GObject = imports.gi.GObject; -const St = imports.gi.St; - -const ExtensionUtils = imports.misc.extensionUtils; -const Me = ExtensionUtils.getCurrentExtension(); -const UUID = Me.uuid.replace(/@/gi, '_at_').replace(/[^a-z0-9+_-]/gi, '_'); -const EXAMPLE_IMAGE_DIRECTORY = Me.dir.get_child('data').get_child('images'); -const DEFAULT_USER_IMAGE_LOCATION = GLib.build_filenamev([GLib.get_user_data_dir(), Me.metadata['data-dir'], 'images']); -const Clipboard = St.Clipboard.get_default(); -const CLIPBOARD_TYPE = St.ClipboardType.CLIPBOARD; -const ICON_DIR = Me.dir.get_child('data').get_child('icons'); -const ICON_NAMES = [ - 'arc', 'color', 'dashed-line', 'document-export', 'fillrule-evenodd', 'fillrule-nonzero', 'fill', 'full-line', 'linecap', 'linejoin', 'palette', 'smooth', 'stroke', - 'tool-ellipse', 'tool-line', 'tool-mirror', 'tool-move', 'tool-none', 'tool-polygon', 'tool-polyline', 'tool-rectangle', 'tool-resize', -]; -const ThemedIconName = { - COLOR_PICKER: 'color-select-symbolic', - ENTER: 'applications-graphics', LEAVE: 'application-exit', - GRAB: 'input-touchpad', UNGRAB: 'touchpad-disabled', - OPEN: 'document-open', SAVE: 'document-save', - FONT_FAMILY: 'font-x-generic', FONT_STYLE: 'format-text-italic', FONT_WEIGHT:'format-text-bold', - LEFT_ALIGNED: 'format-justify-left', CENTERED: 'format-justify-center',RIGHT_ALIGNED: 'format-justify-right', - TOOL_IMAGE: 'insert-image', TOOL_TEXT: 'insert-text', -}; +import Gdk from 'gi://Gdk'; +import GdkPixbuf from 'gi://GdkPixbuf'; +import Gio from 'gi://Gio'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import St from 'gi://St'; + +import { CURATED_UUID as UUID } from './utils.js'; + + + +class Icons { + + constructor(extension) { + const ICON_NAMES = [ + 'arc', 'color', 'dashed-line', 'document-export', 'fillrule-evenodd', 'fillrule-nonzero', 'fill', 'full-line', 'linecap', 'linejoin', 'palette', 'smooth', 'stroke', + 'tool-ellipse', 'tool-line', 'tool-mirror', 'tool-move', 'tool-none', 'tool-polygon', 'tool-polyline', 'tool-rectangle', 'tool-resize', + ]; + const ICON_DIR = extension.dir.get_child('data').get_child('icons'); + const THEMED_ICON_NAMES = { + COLOR_PICKER: 'color-select-symbolic', + ENTER: 'applications-graphics', LEAVE: 'application-exit', + GRAB: 'input-touchpad', UNGRAB: 'touchpad-disabled', + OPEN: 'document-open', SAVE: 'document-save', + FONT_FAMILY: 'font-x-generic', FONT_STYLE: 'format-text-italic', FONT_WEIGHT: 'format-text-bold', + LEFT_ALIGNED: 'format-justify-left', CENTERED: 'format-justify-center', RIGHT_ALIGNED: 'format-justify-right', + TOOL_IMAGE: 'insert-image', TOOL_TEXT: 'insert-text', + }; + ICON_NAMES.forEach(name => { + Object.defineProperty(this, name.toUpperCase().replace(/-/gi, '_'), { + get: function () { + if (!this[`_${name}`]) { + let file = Gio.File.new_for_path(ICON_DIR.get_child(`${name}-symbolic.svg`).get_path()); + this[`_${name}`] = file.query_exists(null) ? new Gio.FileIcon({ file }) : new Gio.ThemedIcon({ name: 'action-unavailable-symbolic' }); + } + return this[`_${name}`]; + } + }); + }); + + Object.keys(THEMED_ICON_NAMES).forEach(key => { + Object.defineProperty(this, key, { + get: function () { + if (!this[`_${key}`]) + this[`_${key}`] = new Gio.ThemedIcon({ name: `${THEMED_ICON_NAMES[key]}-symbolic` }); + return this[`_${key}`]; + } + }); + }); + } -var Icons = { get FAKE() { if (!this._fake) { let bytes = new GLib.Bytes(''); this._fake = Gio.BytesIcon.new(bytes); } - return this._fake; } -}; -ICON_NAMES.forEach(name => { - Object.defineProperty(Icons, name.toUpperCase().replace(/-/gi, '_'), { - get: function() { - if (!this[`_${name}`]) { - let file = Gio.File.new_for_path(ICON_DIR.get_child(`${name}-symbolic.svg`).get_path()); - this[`_${name}`] = file.query_exists(null) ? new Gio.FileIcon({ file }) : new Gio.ThemedIcon({ name: 'action-unavailable-symbolic' }); +} + + + + + + + +// Access images with getPrevious, getNext, getSorted or by iterating over it. +class Images { + _images = []; + _clipboardImages = []; + _upToDate = false; + + constructor(extension) { + this._extension = extension; + this._EXAMPLE_IMAGE_DIRECTORY = extension.dir.get_child('data').get_child('images'); + this._DEFAULT_USER_IMAGE_LOCATION = GLib.build_filenamev([GLib.get_user_data_dir(), extension.metadata['data-dir'], 'images']); + } + + disable() { + this._images = []; + this._clipboardImages = []; + this._upToDate = false; + } + + _clipboardImagesContains(file) { + return this._clipboardImages.some(image => image.file.equal(file)); + } + + // Firstly iterate over the extension directory that contains Example.svg, + // secondly iterate over the directory that was configured by the user in prefs, + // finally iterate over the images pasted from the clipboard. + [Symbol.iterator]() { + if (this._upToDate) + return this._images.concat(this._clipboardImages)[Symbol.iterator](); + + this._upToDate = true; + let oldImages = this._images; + let newImages = this._images = []; + let clipboardImagesContains = this._clipboardImagesContains.bind(this); + let clipboardIterator = this._clipboardImages[Symbol.iterator](); + + return { + getExampleEnumerator: function () { + try { + return this._EXAMPLE_IMAGE_DIRECTORY.enumerate_children('standard::,thumbnail::', Gio.FileQueryInfoFlags.NONE, null); + } catch (e) { + return this.getUserEnumerator(); + } + }, + + getUserEnumerator: function () { + try { + let userLocation = this.extension.drawingSettings.get_string('image-location') || this._DEFAULT_USER_IMAGE_LOCATION; + let userDirectory = Gio.File.new_for_commandline_arg(userLocation); + return userDirectory.enumerate_children('standard::,thumbnail::', Gio.FileQueryInfoFlags.NONE, null); + } catch (e) { + return null; + } + }, + + get enumerator() { + if (this._enumerator === undefined) + this._enumerator = this.getExampleEnumerator(); + else if (this._enumerator?.get_container().equal(this._EXAMPLE_IMAGE_DIRECTORY) && this._enumerator.is_closed()) + this._enumerator = this.getUserEnumerator(); + else if (this._enumerator?.is_closed()) + this._enumerator = null; + + return this._enumerator; + }, + + next: function () { + if (!this.enumerator) + return clipboardIterator.next(); + + let info = this.enumerator.next_file(null); + if (!info) { + this.enumerator.close(null); + return this.next(); + } + + let file = this.enumerator.get_child(info); + + if (info.get_content_type().indexOf('image') == 0 && !clipboardImagesContains(file)) { + let image = oldImages.find(oldImage => oldImage.file.equal(file)) || new ImageWithGicon({ file, info }); + newImages.push(image); + return { value: image, done: false }; + } else { + return this.next(); + } } - return this[`_${name}`]; - } - }); -}); + }; + } + + getSorted() { + return [...this].sort((a, b) => a.toString().localeCompare(b.toString())); + } -Object.keys(ThemedIconName).forEach(key => { - Object.defineProperty(Icons, key, { - get: function() { - if (!this[`_${key}`]) - this[`_${key}`] = new Gio.ThemedIcon({ name: `${ThemedIconName[key]}-symbolic` }); - return this[`_${key}`]; + getNext(currentImage) { + let images = this.getSorted(); + let index = currentImage?.file ? images.findIndex(image => image.file.equal(currentImage.file)) : -1; + return images[index == images.length - 1 ? 0 : index + 1] || null; + } + + getPrevious(currentImage) { + let images = this.getSorted(); + let index = currentImage?.file ? images.findIndex(image => image.file.equal(currentImage.file)) : -1; + return images[index <= 0 ? images.length - 1 : index - 1] || null; + } + + reset() { + this._upToDate = false; + } + + addImagesFromClipboard(callback) { + Clipboard.get_text(St.ClipboardType.CLIPBOARD, (clipboard, text) => { + if (!text) + return; + + // Since 3.38 there is a line terminator character, that has to be removed with .trim(). + let lines = text.split('\n').map(line => line.trim()); + if (lines[0] == 'x-special/nautilus-clipboard') + lines = lines.slice(2); + + let images = lines.filter(line => !!line) + .map(line => Gio.File.new_for_commandline_arg(line)) + .filter(file => file.query_exists(null)) + .map(file => [file, file.query_info('standard::,thumbnail::', Gio.FileQueryInfoFlags.NONE, null)]) + .filter(pair => pair[1].get_content_type().indexOf('image') == 0) + .map(pair => new ImageWithGicon({ file: pair[0], info: pair[1] })); + + // Prevent duplicated + images.filter(image => !this._clipboardImagesContains(image.file)) + .forEach(image => this._clipboardImages.push(image)); + + if (images.length) { + this.reset(); + let lastFile = images[images.length - 1].file; + callback(this._clipboardImages.find(image => image.file.equal(lastFile))); + } + }); + } +} + + + + + +// Access jsons with getPersistent, getDated, getNamed, getPrevious, getNext, getSorted or by iterating over it. +class Jsons { + _jsons = []; + _upToDate = false; + + constructor(extension) { + this._extension = extension; + } + + disable() { + if (this._monitor) { + this._monitor.disconnect(this._monitorHandler); + this._monitor.cancel(); } - }); -}); -const replaceColor = function (contents, color) { - if (contents instanceof Uint8Array) - contents = ByteArray.toString(contents); - else - contents = contents.toString(); - - return contents.replace(/fill(?=="transparent"|="none"|:transparent|:none)/gi, 'filll') - .replace(/fill="[^"]+"/gi, `fill="${color}"`) - .replace(/fill:[^";]+/gi, `fill:${color};`) - .replace(/filll/gi, 'fill'); + delete this._monitor; + delete this._persistent; + + this._jsons = []; + this._upToDate = false; + } + + _updateMonitor() { + if (this._monitor) + return; + let directory = Gio.File.new_for_path(GLib.build_filenamev([GLib.get_user_data_dir(), this._extension.metadata['data-dir']])); + // It is important to specify that the file to monitor is a directory because maybe the directory does not exist yet + // and remove events would not be monitored. + this._monitor = directory.monitor_directory(Gio.FileMonitorFlags.NONE, null); + this._monitorHandler = this._monitor.connect('changed', (monitor, file) => { + if (file.get_basename() != `${this._extension.metadata['persistent-file-name']}.json` && file.get_basename().indexOf('.goutputstream')) + this.reset(); + }); + } + + [Symbol.iterator]() { + if (this._upToDate) + return this._jsons[Symbol.iterator](); + + this._updateMonitor(); + this._upToDate = true; + let newJsons = this._jsons = []; + const extension = this._extension; + + return { + get enumerator() { + if (this._enumerator === undefined) { + try { + let directory = Gio.File.new_for_path(GLib.build_filenamev([GLib.get_user_data_dir(), extension.metadata['data-dir']])); + this._enumerator = directory.enumerate_children('standard::name,standard::display-name,standard::content-type,time::modified', Gio.FileQueryInfoFlags.NONE, null); + } catch (e) { + this._enumerator = null; + } + } + return this._enumerator; + }, + + next: function () { + if (!this.enumerator || this.enumerator.is_closed()) + return { done: true }; + + let info = this.enumerator.next_file(null); + if (!info) { + this.enumerator.close(null); + return this.next(); + } + + let file = this.enumerator.get_child(info); + + if (info.get_content_type().indexOf('json') != -1 && info.get_name() != `${extension.metadata['persistent-file-name']}.json`) { + let json = new Json(this._extension, { + file, name: info.get_name().slice(0, -5), + displayName: info.get_display_name().slice(0, -5), + // info.get_modification_date_time: Gio 2.62+ + modificationUnixTime: info.get_attribute_uint64('time::modified') + }); + + newJsons.push(json); + return { value: json, done: false }; + } else { + return this.next(); + } + } + }; + } + + getSorted() { + return [...this].sort((a, b) => b.modificationUnixTime - a.modificationUnixTime); + } + + getNext(currentJson) { + let jsons = this.getSorted(); + let index = currentJson ? jsons.findIndex(json => json.name == currentJson.name) : -1; + return jsons[index == jsons.length - 1 ? 0 : index + 1] || null; + } + + getPrevious(currentJson) { + let jsons = this.getSorted(); + let index = currentJson ? jsons.findIndex(json => json.name == currentJson.name) : -1; + return jsons[index <= 0 ? jsons.length - 1 : index - 1] || null; + } + + getPersistent() { + if (!this._persistent) + this._persistent = new Json(this._extension, { name: this._extension.metadata['persistent-file-name'] }); + + return this._persistent; + } + + getDated() { + return new Json(this._extension, { name: getDateString() }); + } + + getNamed(name) { + return [...this].find(json => json.name == name) || new Json(this._extension, { name }); + } + + reset() { + this._upToDate = false; + } }; + + + + + + // Wrapper around image data. If not subclassed, it is used when loading in the area an image element for a drawing file (.json) // and it takes { displayName, contentType, base64, hash } as params. -var Image = GObject.registerClass({ +export const Image = GObject.registerClass({ GTypeName: `${UUID}-Image`, -}, class Image extends GObject.Object { +}, class Image extends GObject.Object { _init(params) { for (let key in params) this[key] = params[key]; } - + toString() { return this.displayName; } - + toJSON() { return { displayName: this.displayName, @@ -118,50 +380,50 @@ var Image = GObject.registerClass({ hash: this.hash }; } - + get bytes() { if (!this._bytes) this._bytes = new GLib.Bytes(GLib.base64_decode(this.base64)); return this._bytes; } - + get base64() { if (!this._base64) this._base64 = GLib.base64_encode(this.bytes.get_data()); return this._base64; } - + set base64(base64) { this._base64 = base64; } - + getBase64ForColor(color) { if (!color || this.contentType != 'image/svg+xml') return this.base64; - + let contents = GLib.base64_decode(this.base64); - return GLib.base64_encode(replaceColor(contents, color)); + return GLib.base64_encode(this._replaceColor(contents, color)); } - + // hash is not used get hash() { if (!this._hash) this._hash = this.bytes.hash(); return this._hash; } - + set hash(hash) { this._hash = hash; } - + getBytesForColor(color) { if (!color || this.contentType != 'image/svg+xml') return this.bytes; - + let contents = this.bytes.get_data(); - return new GLib.Bytes(replaceColor(contents, color)); + return new GLib.Bytes(this._replaceColor(contents, color)); } - + getPixbuf(color) { if (color) { let stream = Gio.MemoryInputStream.new_from_bytes(this.getBytesForColor(color)); @@ -169,7 +431,7 @@ var Image = GObject.registerClass({ stream.close(null); return pixbuf; } - + if (!this._pixbuf) { let stream = Gio.MemoryInputStream.new_from_bytes(this.bytes); this._pixbuf = GdkPixbuf.Pixbuf.new_from_stream(stream, null); @@ -177,34 +439,48 @@ var Image = GObject.registerClass({ } return this._pixbuf; } - + getPixbufAtScale(width, height, color) { let stream = Gio.MemoryInputStream.new_from_bytes(this.getBytesForColor(color)); let pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale(stream, width, height, true, null); stream.close(null); return pixbuf; } - + setCairoSource(cr, x, y, width, height, preserveAspectRatio, color) { let pixbuf = preserveAspectRatio ? this.getPixbufAtScale(width, height, color) - : this.getPixbuf(color).scale_simple(width, height, GdkPixbuf.InterpType.BILINEAR); + : this.getPixbuf(color).scale_simple(width, height, GdkPixbuf.InterpType.BILINEAR); Gdk.cairo_set_source_pixbuf(cr, pixbuf, x, y); } + + _replaceColor(contents, color) { + if (contents instanceof Uint8Array) + contents = new TextDecoder('utf-8').decode(contents); + else + contents = contents.toString(); + + return contents.replace(/fill(?=="transparent"|="none"|:transparent|:none)/gi, 'filll') + .replace(/fill="[^"]+"/gi, `fill="${color}"`) + .replace(/fill:[^";]+/gi, `fill:${color};`) + .replace(/filll/gi, 'fill'); + } }); + + // Add a gicon generator to Image. It is used with image files and it takes { file, info } as params. const ImageWithGicon = GObject.registerClass({ GTypeName: `${UUID}-ImageWithGicon`, -}, class ImageWithGicon extends Image{ - +}, class ImageWithGicon extends Image { + get displayName() { return this.info.get_display_name(); } - + get contentType() { return this.info.get_content_type(); } - + get thumbnailFile() { if (!this._thumbnailFile) { if (this.info.has_attribute('thumbnail::path') && this.info.get_attribute_boolean('thumbnail::is-valid')) { @@ -214,30 +490,30 @@ const ImageWithGicon = GObject.registerClass({ } return this._thumbnailFile || null; } - + get gicon() { if (!this._gicon) this._gicon = new Gio.FileIcon({ file: this.thumbnailFile || this.file }); return this._gicon; } - + // use only thumbnails in menu (memory) get thumbnailGicon() { if (this.contentType != 'image/svg+xml' && !this.thumbnailFile) return null; - + return this.gicon; } - + get bytes() { if (!this._bytes) { try { // load_bytes available in GLib 2.56+ this._bytes = this.file.load_bytes(null)[0]; - } catch(e) { + } catch (e) { let [, contents] = this.file.load_contents(null); if (contents instanceof Uint8Array) - this._bytes = ByteArray.toGBytes(contents); + this._bytes = new GLib.Bytes(contents); else this._bytes = contents.toGBytes(); } @@ -246,362 +522,130 @@ const ImageWithGicon = GObject.registerClass({ } }); + + + // It is directly generated from a Json object, without an image file. It takes { bytes, displayName, gicon } as params. const ImageFromJson = GObject.registerClass({ GTypeName: `${UUID}-ImageFromJson`, contentType: 'image/svg+xml', -}, class ImageFromJson extends Image{ +}, class ImageFromJson extends Image { get bytes() { return this._bytes; } - + set bytes(bytes) { this._bytes = bytes; } }); -// Access images with getPrevious, getNext, getSorted or by iterating over it. -var Images = { - _images: [], - _clipboardImages: [], - _upToDate: false, - - disable: function() { - this._images = []; - this._clipboardImages = []; - this._upToDate = false; - }, - - _clipboardImagesContains: function(file) { - return this._clipboardImages.some(image => image.file.equal(file)); - }, - - // Firstly iterate over the extension directory that contains Example.svg, - // secondly iterate over the directory that was configured by the user in prefs, - // finally iterate over the images pasted from the clipboard. - [Symbol.iterator]: function() { - if (this._upToDate) - return this._images.concat(this._clipboardImages)[Symbol.iterator](); - - this._upToDate = true; - let oldImages = this._images; - let newImages = this._images = []; - let clipboardImagesContains = this._clipboardImagesContains.bind(this); - let clipboardIterator = this._clipboardImages[Symbol.iterator](); - - return { - getExampleEnumerator: function() { - try { - return EXAMPLE_IMAGE_DIRECTORY.enumerate_children('standard::,thumbnail::', Gio.FileQueryInfoFlags.NONE, null); - } catch(e) { - return this.getUserEnumerator(); - } - }, - - getUserEnumerator: function() { - try { - let userLocation = Me.drawingSettings.get_string('image-location') || DEFAULT_USER_IMAGE_LOCATION; - let userDirectory = Gio.File.new_for_commandline_arg(userLocation); - return userDirectory.enumerate_children('standard::,thumbnail::', Gio.FileQueryInfoFlags.NONE, null); - } catch(e) { - return null; - } - }, - - get enumerator() { - if (this._enumerator === undefined) - this._enumerator = this.getExampleEnumerator(); - else if (this._enumerator && this._enumerator.get_container().equal(EXAMPLE_IMAGE_DIRECTORY) && this._enumerator.is_closed()) - this._enumerator = this.getUserEnumerator(); - else if (this._enumerator && this._enumerator.is_closed()) - this._enumerator = null; - - return this._enumerator; - }, - - next: function() { - if (!this.enumerator) - return clipboardIterator.next(); - - let info = this.enumerator.next_file(null); - if (!info) { - this.enumerator.close(null); - return this.next(); - } - - let file = this.enumerator.get_child(info); - - if (info.get_content_type().indexOf('image') == 0 && !clipboardImagesContains(file)) { - let image = oldImages.find(oldImage => oldImage.file.equal(file)) || new ImageWithGicon({ file, info }); - newImages.push(image); - return { value: image, done: false }; - } else { - return this.next(); - } - } - }; - }, - - getSorted: function() { - return [...this].sort((a, b) => a.toString().localeCompare(b.toString())); - }, - - getNext: function(currentImage) { - let images = this.getSorted(); - let index = currentImage && currentImage.file ? images.findIndex(image => image.file.equal(currentImage.file)) : -1; - return images[index == images.length - 1 ? 0 : index + 1] || null; - }, - - getPrevious: function(currentImage) { - let images = this.getSorted(); - let index = currentImage && currentImage.file ? images.findIndex(image => image.file.equal(currentImage.file)) : -1; - return images[index <= 0 ? images.length - 1 : index - 1] || null; - }, - - reset: function() { - this._upToDate = false; - }, - - addImagesFromClipboard: function(callback) { - Clipboard.get_text(CLIPBOARD_TYPE, (clipboard, text) => { - if (!text) - return; - - // Since 3.38 there is a line terminator character, that has to be removed with .trim(). - let lines = text.split('\n').map(line => line.trim()); - if (lines[0] == 'x-special/nautilus-clipboard') - lines = lines.slice(2); - - let images = lines.filter(line => !!line) - .map(line => Gio.File.new_for_commandline_arg(line)) - .filter(file => file.query_exists(null)) - .map(file => [file, file.query_info('standard::,thumbnail::', Gio.FileQueryInfoFlags.NONE, null)]) - .filter(pair => pair[1].get_content_type().indexOf('image') == 0) - .map(pair => new ImageWithGicon({ file: pair[0], info: pair[1] })); - - // Prevent duplicated - images.filter(image => !this._clipboardImagesContains(image.file)) - .forEach(image => this._clipboardImages.push(image)); - - if (images.length) { - this.reset(); - let lastFile = images[images.length - 1].file; - callback(this._clipboardImages.find(image => image.file.equal(lastFile))); - } - }); - } -}; + // Wrapper around a json file (drawing saves). -var Json = new GObject.registerClass({ +export const Json = new GObject.registerClass({ GTypeName: `${UUID}-Json`, -}, class Json extends GObject.Object{ - _init(params) { +}, class Json extends GObject.Object { + _init(extension, params) { + this._extension = extension; for (let key in params) this[key] = params[key]; } - + get isPersistent() { - return this.name == Me.metadata['persistent-file-name']; + return this.name == this._extension.metadata['persistent-file-name']; } - + toString() { return this.displayName || this.name; } - + delete() { this.file.delete(null); } - + get file() { - if (!this._file) - this._file = Gio.File.new_for_path(GLib.build_filenamev([GLib.get_user_data_dir(), Me.metadata['data-dir'], `${this.name}.json`])); - + if (!this._file) + this._file = Gio.File.new_for_path(GLib.build_filenamev([GLib.get_user_data_dir(), this._extension.metadata['data-dir'], `${this.name}.json`])); return this._file; } - + set file(file) { this._file = file; } - + get contents() { if (this._contents === undefined) { try { [, this._contents] = this.file.load_contents(null); if (this._contents instanceof Uint8Array) - this._contents = ByteArray.toString(this._contents); - } catch(e) { + this._contents = new TextDecoder('utf-8').decode(this._contents); + } catch (e) { this._contents = null; } } - + return this._contents; } - + set contents(contents) { if (this.isPersistent && (this.contents == contents || !this.contents && contents == '[]')) return; - + try { this.file.replace_contents(contents, null, false, Gio.FileCreateFlags.NONE, null); - } catch(e) { + } catch (e) { this.file.get_parent().make_directory_with_parents(null); this.file.replace_contents(contents, null, false, Gio.FileCreateFlags.NONE, null); } - + this._contents = contents; } - + addSvgContents(getGiconSvgContent, getImageSvgContent) { let giconSvgBytes = new GLib.Bytes(getGiconSvgContent()); this.gicon = Gio.BytesIcon.new(giconSvgBytes); this.getImageSvgBytes = () => new GLib.Bytes(getImageSvgContent()); } - + get image() { if (!this._image) this._image = new ImageFromJson({ bytes: this.getImageSvgBytes(), gicon: this.gicon, displayName: this.displayName }); - + return this._image; } }); -// Access jsons with getPersistent, getDated, getNamed, getPrevious, getNext, getSorted or by iterating over it. -var Jsons = { - _jsons: [], - _upToDate: false, - - disable: function() { - if (this._monitor) { - this._monitor.disconnect(this._monitorHandler); - this._monitor.cancel(); - } - - delete this._monitor; - delete this._persistent; - - this._jsons = []; - this._upToDate = false; - }, - - _updateMonitor: function() { - if (this._monitor) - return; - - let directory = Gio.File.new_for_path(GLib.build_filenamev([GLib.get_user_data_dir(), Me.metadata['data-dir']])); - // It is important to specify that the file to monitor is a directory because maybe the directory does not exist yet - // and remove events would not be monitored. - this._monitor = directory.monitor_directory(Gio.FileMonitorFlags.NONE, null); - this._monitorHandler = this._monitor.connect('changed', (monitor, file) => { - if (file.get_basename() != `${Me.metadata['persistent-file-name']}.json` && file.get_basename().indexOf('.goutputstream')) - this.reset(); - }); - }, - - [Symbol.iterator]: function() { - if (this._upToDate) - return this._jsons[Symbol.iterator](); - - this._updateMonitor(); - this._upToDate = true; - let newJsons = this._jsons = []; - - return { - get enumerator() { - if (this._enumerator === undefined) { - try { - let directory = Gio.File.new_for_path(GLib.build_filenamev([GLib.get_user_data_dir(), Me.metadata['data-dir']])); - this._enumerator = directory.enumerate_children('standard::name,standard::display-name,standard::content-type,time::modified', Gio.FileQueryInfoFlags.NONE, null); - } catch(e) { - this._enumerator = null; - } - } - - return this._enumerator; - }, - - next: function() { - if (!this.enumerator || this.enumerator.is_closed()) - return { done: true }; - - let info = this.enumerator.next_file(null); - if (!info) { - this.enumerator.close(null); - return this.next(); - } - - let file = this.enumerator.get_child(info); - - if (info.get_content_type().indexOf('json') != -1 && info.get_name() != `${Me.metadata['persistent-file-name']}.json`) { - let json = new Json({ - file, name: info.get_name().slice(0, -5), - displayName: info.get_display_name().slice(0, -5), - // info.get_modification_date_time: Gio 2.62+ - modificationUnixTime: info.get_attribute_uint64('time::modified') - }); - - newJsons.push(json); - return { value: json, done: false }; - } else { - return this.next(); - } - } - }; - }, - - getSorted: function() { - return [...this].sort((a, b) => b.modificationUnixTime - a.modificationUnixTime); - }, - - getNext: function(currentJson) { - let jsons = this.getSorted(); - let index = currentJson ? jsons.findIndex(json => json.name == currentJson.name) : -1; - return jsons[index == jsons.length - 1 ? 0 : index + 1] || null; - }, - - getPrevious: function(currentJson) { - let jsons = this.getSorted(); - let index = currentJson ? jsons.findIndex(json => json.name == currentJson.name) : -1; - return jsons[index <= 0 ? jsons.length - 1 : index - 1] || null; - }, - - getPersistent: function() { - if (!this._persistent) - this._persistent = new Json({ name: Me.metadata['persistent-file-name'] }); - - return this._persistent; - }, - - getDated: function() { - return new Json({ name: getDateString() }); - }, - - getNamed: function(name) { - return [...this].find(json => json.name == name) || new Json({ name }); - }, - - reset: function() { - this._upToDate = false; - } -}; -var getDateString = function() { + + +export function getDateString() { let date = GLib.DateTime.new_now_local(); return `${date.format("%F")} ${date.format("%T")}`; -}; +} + + -var saveSvg = function(content) { - let filename = `${Me.metadata['svg-file-name']} ${getDateString()}.svg`; - let dir = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_PICTURES); - let path = GLib.build_filenamev([dir, filename]); - let file = Gio.File.new_for_path(path); - if (file.query_exists(null)) - return false; - - try { - return file.replace_contents(content, null, false, Gio.FileCreateFlags.NONE, null)[0]; - } catch(e) { - return false; +export class Files { + + constructor(extension) { + this._extension = extension; + this.ICONS = new Icons(extension); + this.IMAGES = new Images(extension); + this.JSONS = new Jsons(extension); } -}; + saveSvg(content) { + let filename = `${this._extension.metadata['svg-file-name']} ${getDateString()}.svg`; + let dir = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_PICTURES); + let path = GLib.build_filenamev([dir, filename]); + let file = Gio.File.new_for_path(path); + if (file.query_exists(null)) + return false; + + try { + return file.replace_contents(content, null, false, Gio.FileCreateFlags.NONE, null)[0]; + } catch (e) { + return false; + } + } +} diff --git a/gimpPaletteParser.js b/gimpPaletteParser.js index 1361fb9..628c6a9 100644 --- a/gimpPaletteParser.js +++ b/gimpPaletteParser.js @@ -21,7 +21,8 @@ /* jslint esversion: 6 */ /* exported parseFile */ -const ByteArray = imports.byteArray; + +const decoder = new TextDecoder('utf-8'); /* * [ @@ -83,13 +84,13 @@ function parse(contents) { return columns.map((column, index) => [columnNumber > 1 ? `${name} ${index + 1}` : name, column]); } -function parseFile(file) { +export function parseFile(file) { if (!file.query_exists(null)) return []; let [, contents] = file.load_contents(null); if (contents instanceof Uint8Array) - contents = ByteArray.toString(contents); + contents = decoder.decode(contents); return parse(contents); } diff --git a/helper.js b/helper.js index bffd9a8..6325ea4 100644 --- a/helper.js +++ b/helper.js @@ -21,16 +21,16 @@ /* jslint esversion: 6 */ /* exported DrawingHelper */ -const Clutter = imports.gi.Clutter; -const Gtk = imports.gi.Gtk; -const St = imports.gi.St; -const GObject = imports.gi.GObject; -const Config = imports.misc.config; -const ExtensionUtils = imports.misc.extensionUtils; +import Clutter from 'gi://Clutter'; +import Gtk from 'gi://Gtk'; +import St from 'gi://St'; +import GObject from 'gi://GObject'; +import * as Config from 'resource:///org/gnome/shell/misc/config.js' +import * as Shortcuts from './shortcuts.js'; +import { gettext as _ } from 'resource:///org/gnome/shell/extensions/extension.js'; + +import { CURATED_UUID as UUID } from './utils.js'; -const Me = ExtensionUtils.getCurrentExtension(); -const Shortcuts = Me.imports.shortcuts; -const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; const GS_VERSION = Config.PACKAGE_VERSION; const Tweener = GS_VERSION < '3.33.0' ? imports.ui.tweener : null; @@ -38,26 +38,26 @@ const Tweener = GS_VERSION < '3.33.0' ? imports.ui.tweener : null; const HELPER_ANIMATION_TIME = 0.25; const MEDIA_KEYS_SCHEMA = 'org.gnome.settings-daemon.plugins.media-keys'; const MEDIA_KEYS_KEYS = ['screenshot', 'screenshot-clip', 'area-screenshot', 'area-screenshot-clip']; -const UUID = Me.uuid.replace(/@/gi, '_at_').replace(/[^a-z0-9+_-]/gi, '_'); // DrawingHelper provides the "help osd" (Ctrl + F1) // It uses the same texts as in prefs //TODO: Review this class later -var DrawingHelper = GObject.registerClass({ +export const DrawingHelper = GObject.registerClass({ GTypeName: `${UUID}-DrawingHelper`, }, class DrawingHelper extends St.ScrollView { - _init(params, monitor) { + _init(extension, params, monitor) { params.style_class = 'osd-window draw-on-your-screen-helper'; super._init(params); + this._extension = extension; this.monitor = monitor; this.hide(); - this.settingsHandler = Me.settings.connect('changed', this._onSettingsChanged.bind(this)); - this.internalShortcutsettingsHandler = Me.internalShortcutSettings.connect('changed', this._onSettingsChanged.bind(this)); + this.settingsHandler = this._extension.settings.connect('changed', this._onSettingsChanged.bind(this)); + this.internalShortcutsettingsHandler = this._extension.internalShortcutSettings.connect('changed', this._onSettingsChanged.bind(this)); this.connect('destroy', () => { - Me.settings.disconnect(this.settingsHandler); - Me.internalShortcutSettings.disconnect(this.internalShortcutsettingsHandler); + this._extension.settings.disconnect(this.settingsHandler); + this._extension.internalShortcutSettings.disconnect(this.internalShortcutsettingsHandler); }); } @@ -73,7 +73,7 @@ var DrawingHelper = GObject.registerClass({ _updateHelpKeyLabel() { try { - let [keyval, mods] = Gtk.accelerator_parse(Me.internalShortcutSettings.get_strv('toggle-help')[0] || ''); + let [keyval, mods] = Gtk.accelerator_parse(this._extension.internalShortcutSettings.get_strv('toggle-help')[0] || ''); this._helpKeyLabel = Gtk.accelerator_get_label(keyval, mods); } catch(e) { logError(e); @@ -98,12 +98,12 @@ var DrawingHelper = GObject.registerClass({ this.vbox.add_child(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-helper-separator' })); //settingKeys.forEach(settingKey => { - if (!Me.settings.get_strv(settingKeys)[0]) + if (!this._extension.settings.get_strv(settingKeys)[0]) return; let hbox = new St.BoxLayout({ vertical: false }); - let [keyval, mods] = Gtk.accelerator_parse(Me.settings.get_strv(settingKeys)[0] || ''); - hbox.add_child(new St.Label({ text: Me.settings.settings_schema.get_key(settingKeys).get_summary() })); + let [keyval, mods] = Gtk.accelerator_parse(this._extension.settings.get_strv(settingKeys)[0] || ''); + hbox.add_child(new St.Label({ text: this._extension.settings.settings_schema.get_key(settingKeys).get_summary() })); hbox.add_child(new St.Label({ text: Gtk.accelerator_get_label(keyval, mods), x_expand: true })); this.vbox.add_child(hbox); // }); @@ -133,19 +133,19 @@ var DrawingHelper = GObject.registerClass({ this.vbox.add_child(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-helper-separator' })); //settingKeys.forEach(settingKey => { - if (!Me.internalShortcutSettings.get_strv(settingKeys)[0]) + if (!this._extension.internalShortcutSettings.get_strv(settingKeys)[0]) return; let hbox = new St.BoxLayout({ vertical: false }); - let [keyval, mods] = Gtk.accelerator_parse(Me.internalShortcutSettings.get_strv(settingKeys)[0] || ''); - hbox.add_child(new St.Label({ text: Me.internalShortcutSettings.settings_schema.get_key(settingKeys).get_summary() })); + let [keyval, mods] = Gtk.accelerator_parse(this._extension.internalShortcutSettings.get_strv(settingKeys)[0] || ''); + hbox.add_child(new St.Label({ text: this._extension.internalShortcutSettings.settings_schema.get_key(settingKeys).get_summary() })); hbox.add_child(new St.Label({ text: Gtk.accelerator_get_label(keyval, mods), x_expand: true })); this.vbox.add_child(hbox); //}); }); let mediaKeysSettings; - try { mediaKeysSettings = ExtensionUtils.getSettings(MEDIA_KEYS_SCHEMA); } catch(e) { return; } + try { mediaKeysSettings = this._extension.getSettings(MEDIA_KEYS_SCHEMA); } catch(e) { return; } this.vbox.add_child(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-helper-separator' })); this.vbox.add_child(new St.Label({ text: _("System") })); diff --git a/menu.js b/menu.js index dca0f68..ffb40da 100644 --- a/menu.js +++ b/menu.js @@ -19,45 +19,38 @@ */ /* jslint esversion: 6 */ -/* exported DisplayStrings, DrawingMenu */ -const Clutter = imports.gi.Clutter; -const GLib = imports.gi.GLib; -const GObject = imports.gi.GObject; -const Gtk = imports.gi.Gtk; -const St = imports.gi.St; +import Clutter from 'gi://Clutter'; +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Gtk from 'gi://Gtk'; +import St from 'gi://St'; + +import * as BoxPointer from 'resource:///org/gnome/shell/ui/boxpointer.js'; +import * as Dash from 'resource:///org/gnome/shell/ui/dash.js'; +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; +import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; +import * as Slider from 'resource:///org/gnome/shell/ui/slider.js'; + +import * as Config from 'resource:///org/gnome/shell/misc/config.js' + +import {gettext as _, pgettext} from 'resource:///org/gnome/shell/extensions/extension.js'; + +import { CURATED_UUID as UUID } from './utils.js'; -const BoxPointer = imports.ui.boxpointer; -const Config = imports.misc.config; -const Dash = imports.ui.dash; -const Main = imports.ui.main; -const PopupMenu = imports.ui.popupMenu; -const Slider = imports.ui.slider; -const ExtensionUtils = imports.misc.extensionUtils; -const Me = ExtensionUtils.getCurrentExtension(); -const Files = Me.imports.files; -const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; -const pgettext = imports.gettext.domain(Me.metadata['gettext-domain']).pgettext; const GS_VERSION = Config.PACKAGE_VERSION; // 150 labels with font-family style take ~15Mo const FONT_FAMILY_STYLE = true; // use 'login-dialog-message-warning' class in order to get GS theme warning color (default: #f57900) const WARNING_COLOR_STYLE_CLASS_NAME = 'login-dialog-message-warning'; -const UUID = Me.uuid.replace(/@/gi, '_at_').replace(/[^a-z0-9+_-]/gi, '_'); -const TextAlignmentIcon = { 0: Files.Icons.LEFT_ALIGNED, 1: Files.Icons.CENTERED, 2: Files.Icons.RIGHT_ALIGNED }; -const getActor = function(object) { - return GS_VERSION < '3.33.0' ? object.actor : object; -}; -const getSummary = function(settingKey) { - return Me.internalShortcutSettings.settings_schema.get_key(settingKey).get_summary(); -}; + // Used by both menu and osd notifications. -var DisplayStrings = { +export const DisplayStrings = { getDashedLine: function(dashed) { return dashed ? _("Dashed line") : // Translators: as the alternative to "Dashed line" @@ -142,11 +135,13 @@ var DisplayStrings = { } }; -var DrawingMenu = GObject.registerClass({ + +export const DrawingMenu = GObject.registerClass({ GTypeName: `${UUID}-DrawingMenu`, }, class DrawingMenu extends GObject.Object { - _init(area, monitor, DrawingTool, areaManagerUtils) { + _init(extension, area, monitor, DrawingTool, areaManagerUtils) { super._init({}); + this._extension = extension; this.area = area; this.monitor = monitor; this.DrawingTool = DrawingTool; @@ -236,42 +231,42 @@ var DrawingMenu = GObject.registerClass({ this.menu.removeAll(); let groupItem = new PopupMenu.PopupBaseMenuItem({ reactive: false, can_focus: false, style_class: 'draw-on-your-screen-menu-group-item' }); - this.undoButton = new ActionButton(getSummary('undo'), 'edit-undo-symbolic', this.area.undo.bind(this.area), this._updateActionSensitivity.bind(this)); - this.redoButton = new ActionButton(getSummary('redo'), 'edit-redo-symbolic', this.area.redo.bind(this.area), this._updateActionSensitivity.bind(this)); + this.undoButton = new ActionButton(this._getSummary('undo'), 'edit-undo-symbolic', this.area.undo.bind(this.area), this._updateActionSensitivity.bind(this)); + this.redoButton = new ActionButton(this._getSummary('redo'), 'edit-redo-symbolic', this.area.redo.bind(this.area), this._updateActionSensitivity.bind(this)); this.eraseButton = new ActionButton(_("Erase"), 'edit-clear-all-symbolic', this.area.deleteLastElement.bind(this.area), this._updateActionSensitivity.bind(this)); - this.smoothButton = new ActionButton(_("Smooth"), Files.Icons.SMOOTH, this.area.smoothLastElement.bind(this.area), this._updateActionSensitivity.bind(this)); + this.smoothButton = new ActionButton(_("Smooth"), this._extension.FILES.ICONS.SMOOTH, this.area.smoothLastElement.bind(this.area), this._updateActionSensitivity.bind(this)); this.eraseButton.child.add_style_class_name('draw-on-your-screen-menu-destructive-button'); - getActor(groupItem).add_child(this.undoButton); - getActor(groupItem).add_child(this.redoButton); - getActor(groupItem).add_child(this.eraseButton); - getActor(groupItem).add_child(this.smoothButton); + this._getActor(groupItem).add_child(this.undoButton); + this._getActor(groupItem).add_child(this.redoButton); + this._getActor(groupItem).add_child(this.eraseButton); + this._getActor(groupItem).add_child(this.smoothButton); this.menu.addMenuItem(groupItem); this._addSeparator(this.menu, true); this.toolItem = this._addToolSubMenuItem(this.menu, this._updateSectionVisibility.bind(this)); - this.paletteItem = this._addPaletteSubMenuItem(this.menu, Files.Icons.PALETTE); - this.colorItem = this._addColorSubMenuItem(this.menu, Files.Icons.COLOR); - this.fillItem = this._addSwitchItem(this.menu, DisplayStrings.getFill(true), Files.Icons.STROKE, Files.Icons.FILL, this.area, 'fill', this._updateSectionVisibility.bind(this)); + this.paletteItem = this._addPaletteSubMenuItem(this.menu, this._extension.FILES.ICONS.PALETTE); + this.colorItem = this._addColorSubMenuItem(this.menu, this._extension.FILES.ICONS.COLOR); + this.fillItem = this._addSwitchItem(this.menu, DisplayStrings.getFill(true), this._extension.FILES.ICONS.STROKE, this._extension.FILES.ICONS.FILL, this.area, 'fill', this._updateSectionVisibility.bind(this)); this.fillSection = new PopupMenu.PopupMenuSection(); this.fillSection.itemActivated = () => {}; - this.fillRuleItem = this._addSwitchItem(this.fillSection, DisplayStrings.FillRule[1], Files.Icons.FILLRULE_NONZERO, Files.Icons.FILLRULE_EVENODD, this.area, 'currentEvenodd'); + this.fillRuleItem = this._addSwitchItem(this.fillSection, DisplayStrings.FillRule[1], this._extension.FILES.ICONS.FILLRULE_NONZERO, this._extension.FILES.ICONS.FILLRULE_EVENODD, this.area, 'currentEvenodd'); this.menu.addMenuItem(this.fillSection); this._addSeparator(this.menu); let lineSection = new PopupMenu.PopupMenuSection(); this._addSliderItem(lineSection, this.area, 'currentLineWidth'); - this._addSubMenuItem(lineSection, Files.Icons.LINEJOIN, DisplayStrings.LineJoin, this.area, 'currentLineJoin'); - this._addSubMenuItem(lineSection, Files.Icons.LINECAP, DisplayStrings.LineCap, this.area, 'currentLineCap'); - this._addSwitchItem(lineSection, DisplayStrings.getDashedLine(true), Files.Icons.FULL_LINE, Files.Icons.DASHED_LINE, this.area, 'dashedLine'); + this._addSubMenuItem(lineSection, this._extension.FILES.ICONS.LINEJOIN, DisplayStrings.LineJoin, this.area, 'currentLineJoin'); + this._addSubMenuItem(lineSection, this._extension.FILES.ICONS.LINECAP, DisplayStrings.LineCap, this.area, 'currentLineCap'); + this._addSwitchItem(lineSection, DisplayStrings.getDashedLine(true), this._extension.FILES.ICONS.FULL_LINE, this._extension.FILES.ICONS.DASHED_LINE, this.area, 'dashedLine'); this._addSeparator(lineSection); this.menu.addMenuItem(lineSection); lineSection.itemActivated = () => {}; this.lineSection = lineSection; let fontSection = new PopupMenu.PopupMenuSection(); - this._addFontFamilySubMenuItem(fontSection, Files.Icons.FONT_FAMILY); - this._addSubMenuItem(fontSection, Files.Icons.FONT_WEIGHT, DisplayStrings.FontWeight, this.area, 'currentFontWeight'); - this._addSubMenuItem(fontSection, Files.Icons.FONT_STYLE, DisplayStrings.FontStyle, this.area, 'currentFontStyle'); + this._addFontFamilySubMenuItem(fontSection, this._extension.FILES.ICONS.FONT_FAMILY); + this._addSubMenuItem(fontSection, this._extension.FILES.ICONS.FONT_WEIGHT, DisplayStrings.FontWeight, this.area, 'currentFontWeight'); + this._addSubMenuItem(fontSection, this._extension.FILES.ICONS.FONT_STYLE, DisplayStrings.FontStyle, this.area, 'currentFontStyle'); this._addTextAlignmentSubMenuItem(fontSection); this._addSeparator(fontSection); this.menu.addMenuItem(fontSection); @@ -285,10 +280,10 @@ var DrawingMenu = GObject.registerClass({ imageSection.itemActivated = () => {}; this.imageSection = imageSection; - this._addSimpleSwitchItem(this.menu, getSummary('toggle-panel-and-dock-visibility'), !!this.areaManagerUtils.getHiddenList(), this.areaManagerUtils.togglePanelAndDockOpacity); - this._addSimpleSwitchItem(this.menu, getSummary('toggle-background'), this.area.hasBackground, this.area.toggleBackground.bind(this.area)); - this._addSimpleSwitchItem(this.menu, getSummary('toggle-grid'), this.area.hasGrid, this.area.toggleGrid.bind(this.area)); - this._addSimpleSwitchItem(this.menu, getSummary('toggle-square-area'), this.area.isSquareArea, this.area.toggleSquareArea.bind(this.area)); + this._addSimpleSwitchItem(this.menu, this._getSummary('toggle-panel-and-dock-visibility'), !!this.areaManagerUtils.getHiddenList(), this.areaManagerUtils.togglePanelAndDockOpacity); + this._addSimpleSwitchItem(this.menu, this._getSummary('toggle-background'), this.area.hasBackground, this.area.toggleBackground.bind(this.area)); + this._addSimpleSwitchItem(this.menu, this._getSummary('toggle-grid'), this.area.hasGrid, this.area.toggleGrid.bind(this.area)); + this._addSimpleSwitchItem(this.menu, this._getSummary('toggle-square-area'), this.area.isSquareArea, this.area.toggleSquareArea.bind(this.area)); this._addSeparator(this.menu); this._addDrawingNameItem(this.menu); @@ -297,14 +292,14 @@ var DrawingMenu = GObject.registerClass({ this._addSeparator(this.menu); groupItem = new PopupMenu.PopupBaseMenuItem({ reactive: false, can_focus: false, style_class: 'draw-on-your-screen-menu-group-item' }); - this.saveButton = new ActionButton(getSummary('save-as-json'), 'document-save-symbolic', this.area.saveAsJson.bind(this.area, false, this._onDrawingSaved.bind(this)), null); - this.svgButton = new ActionButton(getSummary('export-to-svg'), Files.Icons.DOCUMENT_EXPORT, this.area.exportToSvg.bind(this.area), null); - this.prefsButton = new ActionButton(getSummary('open-preferences'), 'document-page-setup-symbolic', this.areaManagerUtils.openPreferences, null); - this.helpButton = new ActionButton(getSummary('toggle-help'), 'preferences-desktop-keyboard-shortcuts-symbolic', () => { this.close(); this.area.toggleHelp(); }, null); - getActor(groupItem).add_child(this.saveButton); - getActor(groupItem).add_child(this.svgButton); - getActor(groupItem).add_child(this.prefsButton); - getActor(groupItem).add_child(this.helpButton); + this.saveButton = new ActionButton(this._getSummary('save-as-json'), 'document-save-symbolic', this.area.saveAsJson.bind(this.area, false, this._onDrawingSaved.bind(this)), null); + this.svgButton = new ActionButton(this._getSummary('export-to-svg'), this._extension.FILES.ICONS.DOCUMENT_EXPORT, this.area.exportToSvg.bind(this.area), null); + this.prefsButton = new ActionButton(this._getSummary('open-preferences'), 'document-page-setup-symbolic', this.areaManagerUtils.openPreferences, null); + this.helpButton = new ActionButton(this._getSummary('toggle-help'), 'preferences-desktop-keyboard-shortcuts-symbolic', () => { this.close(); this.area.toggleHelp(); }, null); + this._getActor(groupItem).add_child(this.saveButton); + this._getActor(groupItem).add_child(this.svgButton); + this._getActor(groupItem).add_child(this.prefsButton); + this._getActor(groupItem).add_child(this.helpButton); this.menu.addMenuItem(groupItem); this._updateActionSensitivity(); @@ -339,7 +334,7 @@ var DrawingMenu = GObject.registerClass({ let item = new PopupMenu.PopupSwitchMenuItem(label, target[targetProperty]); item.icon = new St.Icon({ style_class: 'popup-menu-icon' }); - getActor(item).insert_child_at_index(item.icon, 1); + this._getActor(item).insert_child_at_index(item.icon, 1); let icon = target[targetProperty] ? iconTrue : iconFalse; if (icon) item.icon.set_gicon(icon); @@ -371,7 +366,7 @@ var DrawingMenu = GObject.registerClass({ slider.connect('value-changed', (slider, value, property) => { target[targetProperty] = Math.max(Math.round(value * 50), 0); label.set_text(DisplayStrings.getPixels(target[targetProperty])); - Me.drawingSettings.set_int("tool-size", target[targetProperty]); + this._extension.drawingSettings.set_int("tool-size", target[targetProperty]); if (target[targetProperty] === 0) label.add_style_class_name(WARNING_COLOR_STYLE_CLASS_NAME); else @@ -381,7 +376,7 @@ var DrawingMenu = GObject.registerClass({ slider.connect('notify::value', () => { target[targetProperty] = Math.max(Math.round(slider.value * 50), 0); label.set_text(DisplayStrings.getPixels(target[targetProperty])); - Me.drawingSettings.set_int("tool-size", target[targetProperty]); + this._extension.drawingSettings.set_int("tool-size", target[targetProperty]); if (target[targetProperty] === 0) label.add_style_class_name(WARNING_COLOR_STYLE_CLASS_NAME); else @@ -389,11 +384,11 @@ var DrawingMenu = GObject.registerClass({ }); } - getActor(slider).x_expand = true; - getActor(item).add_child(getActor(slider)); - getActor(item).add_child(label); + this._getActor(slider).x_expand = true; + this._getActor(item).add_child(this._getActor(slider)); + this._getActor(item).add_child(label); if (slider.onKeyPressEvent) - getActor(item).connect('key-press-event', slider.onKeyPressEvent.bind(slider)); + this._getActor(item).connect('key-press-event', slider.onKeyPressEvent.bind(slider)); menu.addMenuItem(item); } @@ -415,7 +410,7 @@ var DrawingMenu = GObject.registerClass({ }); subItem.label.get_clutter_text().set_use_markup(true); - getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment); + this._getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment); }); return GLib.SOURCE_REMOVE; }); @@ -428,7 +423,7 @@ var DrawingMenu = GObject.registerClass({ item.update = () => { item.label.set_text(DisplayStrings.Tool[this.area.currentTool]); let toolName = this.DrawingTool.getNameOf(this.area.currentTool); - item.icon.set_gicon(Files.Icons[`TOOL_${toolName}`]); + item.icon.set_gicon(this._extension.FILES.ICONS[`TOOL_${toolName}`]); }; item.update(); @@ -438,7 +433,7 @@ var DrawingMenu = GObject.registerClass({ Object.keys(DisplayStrings.Tool).forEach(key => { let text = DisplayStrings.Tool[key]; let toolName = this.DrawingTool.getNameOf(key); - let subItemIcon = Files.Icons[`TOOL_${toolName}`]; + let subItemIcon = this._extension.FILES.ICONS[`TOOL_${toolName}`]; let subItem = item.menu.addAction(text, () => { this.area.currentTool = Number(key); item.update(); @@ -446,7 +441,7 @@ var DrawingMenu = GObject.registerClass({ }, subItemIcon); subItem.label.get_clutter_text().set_use_markup(true); - getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment); + this._getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment); // change the display order of tools if (key == this.DrawingTool.POLYGON) @@ -479,7 +474,7 @@ var DrawingMenu = GObject.registerClass({ this.area.currentPalette = palette; this._populateColorSubMenu(); }); - getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment); + this._getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment); }); return GLib.SOURCE_REMOVE; }); @@ -500,9 +495,9 @@ var DrawingMenu = GObject.registerClass({ this.area.pickColor(); }; // Translators: It is displayed in a menu button tooltip or as a shortcut action description, so it should NOT use the imperative mood. - let colorPickerButton = new ActionButton(_("Pick a color"), Files.Icons.COLOR_PICKER, colorPickerCallback, null, true); - let index = getActor(item).get_children().length - 1; - getActor(item).insert_child_at_index(colorPickerButton, index); + let colorPickerButton = new ActionButton(_("Pick a color"), this._extension.FILES.ICONS.COLOR_PICKER, colorPickerCallback, null, true); + let index = this._getActor(item).get_children().length - 1; + this._getActor(item).insert_child_at_index(colorPickerButton, index); } item.menu.itemActivated = item.menu.close; @@ -519,12 +514,12 @@ var DrawingMenu = GObject.registerClass({ let text = String(color); let subItem = this.colorSubMenu.addAction(text, () => { this.area.currentColor = color; - Me.drawingSettings.set_string("tool-color", color.to_string()); + this._extension.drawingSettings.set_string("tool-color", color.to_string()); this.colorItem.icon.set_style(`color:${color.to_string().slice(0, 7)};`); }); // Foreground color markup is not displayed since 3.36, use style instead but the transparency is lost. subItem.label.set_style(`color:${color.to_string().slice(0, 7)};`); - getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment); + this._getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment); }); return GLib.SOURCE_REMOVE; }); @@ -551,7 +546,7 @@ var DrawingMenu = GObject.registerClass({ subItem.label.set_style(`font-family:${family}`); }); - getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment); + this._getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment); }); } item.menu.openOld(); @@ -562,6 +557,7 @@ var DrawingMenu = GObject.registerClass({ _addTextAlignmentSubMenuItem(menu) { let item = new PopupMenu.PopupSubMenuMenuItem(DisplayStrings.TextAlignment[this.area.currentTextAlignment], true); + const TextAlignmentIcon = { 0: this._extension.FILES.ICONS.LEFT_ALIGNED, 1: this._extension.FILES.ICONS.CENTERED, 2: this._extension.FILES.ICONS.RIGHT_ALIGNED }; item.icon.set_gicon(TextAlignmentIcon[this.area.currentTextAlignment]); item.menu.itemActivated = item.menu.close; @@ -574,7 +570,7 @@ var DrawingMenu = GObject.registerClass({ item.icon.set_gicon(TextAlignmentIcon[key]); }); - getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment); + this._getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment); }); return GLib.SOURCE_REMOVE; }); @@ -585,8 +581,10 @@ var DrawingMenu = GObject.registerClass({ _addImageSubMenuItem(menu, images) { let item = new PopupMenu.PopupSubMenuMenuItem('', true); item.update = () => { - item.label.set_text(this.area.currentImage.toString()); - item.icon.set_gicon(this.area.currentImage.gicon); + if (this.area.currentImage) { // null value. what consequences ? + item.label.set_text(this.area.currentImage.toString()); + item.icon.set_gicon(this.area.currentImage.gicon); + } }; item.update(); @@ -596,18 +594,18 @@ var DrawingMenu = GObject.registerClass({ item.menu.openOld = item.menu.open; item.menu.open = (animate) => { if (!item.menu.isOpen && item.menu.isEmpty()) { - Files.Images.getSorted().forEach(image => { + this._extension.FILES.IMAGES.getSorted().forEach(image => { let subItem = item.menu.addAction(image.toString(), () => { this.area.currentImage = image; item.update(); - }, Files.Icons.FAKE); + }, this._extension.FILES.ICONS.FAKE); GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { if (subItem.setIcon && image.thumbnailGicon) subItem.setIcon(image.thumbnailGicon); }); - getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment); + this._getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment); }); } item.menu.openOld(); @@ -620,13 +618,13 @@ var DrawingMenu = GObject.registerClass({ _addDrawingNameItem(menu) { this.drawingNameMenuItem = new PopupMenu.PopupMenuItem('', { reactive: false, activate: false }); this.drawingNameMenuItem.setSensitive(false); - getActor(this.drawingNameMenuItem).add_style_class_name('draw-on-your-screen-menu-ellipsized'); + this._getActor(this.drawingNameMenuItem).add_style_class_name('draw-on-your-screen-menu-ellipsized'); menu.addMenuItem(this.drawingNameMenuItem); this._updateDrawingNameMenuItem(); } _updateDrawingNameMenuItem() { - getActor(this.drawingNameMenuItem).visible = this.area.currentJson ? true : false; + this._getActor(this.drawingNameMenuItem).visible = this.area.currentJson ? true : false; if (this.area.currentJson) { let prefix = this.area.drawingContentsHasChanged ? "* " : ""; this.drawingNameMenuItem.label.set_text(`${prefix}${this.area.currentJson.name}`); @@ -638,7 +636,7 @@ var DrawingMenu = GObject.registerClass({ let item = new PopupMenu.PopupSubMenuMenuItem(label, true); this.openDrawingSubMenuItem = item; this.openDrawingSubMenu = item.menu; - item.setSensitive(Boolean(Files.Jsons.getSorted().length)); + item.setSensitive(Boolean(this._extension.FILES.JSONS.getSorted().length)); item.icon.set_icon_name(icon); item.menu.itemActivated = item.menu.close; @@ -656,7 +654,7 @@ var DrawingMenu = GObject.registerClass({ _populateOpenDrawingSubMenu() { this.openDrawingSubMenu.removeAll(); - Files.Jsons.getSorted().forEach(json => { + this._extension.FILES.JSONS.getSorted().forEach(json => { if (!json.gicon) json.addSvgContents(...this.area.getSvgContentsForJson(json)); @@ -664,7 +662,7 @@ var DrawingMenu = GObject.registerClass({ this.area.loadJson(json); this._updateDrawingNameMenuItem(); this._updateActionSensitivity(); - }, Files.Icons.FAKE); + }, this._extension.FILES.ICONS.FAKE); GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { if (subItem.setIcon) @@ -672,13 +670,13 @@ var DrawingMenu = GObject.registerClass({ }); subItem.label.get_clutter_text().set_use_markup(true); - getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment); + this._getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment); let expander = new St.Bin({ style_class: 'popup-menu-item-expander', x_expand: true, }); - getActor(subItem).add_child(expander); + this._getActor(subItem).add_child(expander); let insertCallback = () => { this.area.currentImage = json.image; @@ -688,7 +686,7 @@ var DrawingMenu = GObject.registerClass({ this._updateSectionVisibility(); }; let insertButton = new ActionButton(_("Add to images"), 'insert-image-symbolic', insertCallback, null, true); - getActor(subItem).add_child(insertButton); + this._getActor(subItem).add_child(insertButton); let deleteCallback = () => { json.delete(); @@ -697,7 +695,7 @@ var DrawingMenu = GObject.registerClass({ }; let deleteButton = new ActionButton(_("Delete"), 'edit-delete-symbolic', deleteCallback, null, true); deleteButton.child.add_style_class_name('draw-on-your-screen-menu-destructive-button'); - getActor(subItem).add_child(deleteButton); + this._getActor(subItem).add_child(deleteButton); }); this.openDrawingSubMenuItem.setSensitive(!this.openDrawingSubMenu.isEmpty()); @@ -737,7 +735,7 @@ var DrawingMenu = GObject.registerClass({ this.area.saveAsJsonWithName(text, this._onDrawingSaved.bind(this)); this.saveDrawingSubMenu.toggle(); }, - invalidStrings: [Me.metadata['persistent-file-name'], '/'], + invalidStrings: [this._extension.metadata['persistent-file-name'], '/'], primaryIconName: 'insert-text' }); this.saveDrawingSubMenu.addMenuItem(saveEntry.item); } @@ -745,14 +743,25 @@ var DrawingMenu = GObject.registerClass({ _addSeparator(menu, thin) { if (this.hasSeparators) { let separatorItem = new PopupMenu.PopupSeparatorMenuItem(' '); - getActor(separatorItem).add_style_class_name('draw-on-your-screen-menu-separator-item'); + this._getActor(separatorItem).add_style_class_name('draw-on-your-screen-menu-separator-item'); if (thin) - getActor(separatorItem).add_style_class_name('draw-on-your-screen-menu-thin-separator-item'); + this._getActor(separatorItem).add_style_class_name('draw-on-your-screen-menu-thin-separator-item'); menu.addMenuItem(separatorItem); } } + + _getSummary(settingKey) { + return this._extension.internalShortcutSettings.settings_schema.get_key(settingKey).get_summary(); + }; + + _getActor(object) { + return GS_VERSION < '3.33.0' ? object.actor : object; + }; }); + + + // based on ApplicationsButton.scrollToButton , https://gitlab.gnome.org/GNOME/gnome-shell-extensions/blob/master/extensions/apps-menu/extension.js const updateSubMenuAdjustment = function(itemActor) { let scrollView = itemActor.get_parent().get_parent(); @@ -770,6 +779,9 @@ const updateSubMenuAdjustment = function(itemActor) { adjustment.set_value(newScrollValue); }; + + + // An action button that uses upstream dash item tooltips. const ActionButton = GObject.registerClass ({ GTypeName: `${UUID}-DrawingMenuActionButton`, @@ -849,8 +861,8 @@ const Entry = GObject.registerClass({ }); this.entry.connect('secondary-icon-clicked', this._reset.bind(this)); - getActor(this.item).add_child(this.entry); - getActor(this.item).connect('notify::mapped', (actor) => { + this._getActor(this.item).add_child(this.entry); + this._getActor(this.item).connect('notify::mapped', (actor) => { if (actor.mapped) { this.entry.set_text(this.params.initialTextGetter()); this.entry.clutter_text.grab_key_focus(); @@ -891,13 +903,12 @@ const Entry = GObject.registerClass({ } _getIsInvalid() { - for (let i = 0; i < this.params.invalidStrings.length; i++) { - if (this.entry.text.indexOf(this.params.invalidStrings[i]) != -1) - return true; - } - - return false; + return this.params.invalidStrings.some(invalidString => this.entry.text.indexOf(invalidString) != -1); } + + _getActor(object) { + return GS_VERSION < '3.33.0' ? object.actor : object; + }; }); diff --git a/metadata.json b/metadata.json index e36fe36..3a41f25 100644 --- a/metadata.json +++ b/metadata.json @@ -10,7 +10,8 @@ "svg-file-name": "DrawOnYourScreen", "shell-version": [ "43", - "44" + "44", + "45" ], "version": 12.7 } diff --git a/prefs.js b/prefs.js index d7be343..d6f3dac 100644 --- a/prefs.js +++ b/prefs.js @@ -17,29 +17,30 @@ * */ -const ExtensionUtils = imports.misc.extensionUtils; -const Me = ExtensionUtils.getCurrentExtension(); -const Prefs = Me.imports.ui.preferencespage; -const Drawpage = Me.imports.ui.drawingpage; -const AboutPage = Me.imports.ui.about; - -function init() -{ - ExtensionUtils.initTranslations(Me.metadata.uuid); -} +import { ExtensionPreferences } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'; +import PreferencesPage from './ui/preferencespage.js'; +import DrawingPage from './ui/drawingpage.js'; +import AboutPage from './ui/about.js'; -function fillPreferencesWindow(window) -{ - window.search_enabled = true; - let page1 = new Prefs.Preferences(); - let page2 = new Drawpage.DrawingPage(window); - let page3 = new AboutPage.AboutPage(); +export default class DrawOnYourScreenExtensionPreferences extends ExtensionPreferences { - window.add(page1); - window.add(page2); - window.add(page3); -} + constructor(metadata) { + super(metadata); + this.initTranslations(); + } + + fillPreferencesWindow(window) { + window._settings = this.getSettings(); + window.search_enabled = true; + let page1 = new PreferencesPage(this); + let page2 = new DrawingPage(this, window); + let page3 = new AboutPage(this); + window.add(page1); + window.add(page2); + window.add(page3); + } +} diff --git a/shortcuts.js b/shortcuts.js index 1d33cda..712963d 100644 --- a/shortcuts.js +++ b/shortcuts.js @@ -18,37 +18,16 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -/* jslint esversion: 6 */ -/* exported GLOBAL_KEYBINDINGS, INTERNAL_KEYBINDINGS, OTHERS */ - -const Gtk = imports.gi.Gtk; -const IS_GTK3 = Gtk.get_major_version() == 3; - -const GS_VERSION = imports.misc.config.PACKAGE_VERSION; -const ExtensionUtils = imports.misc.extensionUtils; -const Me = ExtensionUtils.getCurrentExtension(); -const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; - -const internalShortcutsSchema = ExtensionUtils.getSettings(Me.metadata['settings-schema'] + '.internal-shortcuts').settings_schema; - -const getKeyLabel = function(accel) { - let success_, keyval, mods; - if (IS_GTK3) - [keyval, mods] = Gtk.accelerator_parse(accel); - else - [success_, keyval, mods] = Gtk.accelerator_parse(accel); - return Gtk.accelerator_get_label(keyval, mods); -}; // The setting keys of the "org.gnome.shell.extensions.draw-on-your-screen" schema. -var GLOBAL_KEYBINDINGS = [ +export const GLOBAL_KEYBINDINGS = [ 'toggle-drawing', 'toggle-modal', 'erase-drawings', ]; -var GLOBAL_KEYBINDINGS_SWITCHES = [ +export const GLOBAL_KEYBINDINGS_SWITCHES = [ 'persistent-over-toggles', 'persistent-over-restarts', 'drawing-on-desktop', 'osd-disabled', 'indicator-disabled', 'quicktoggle-disabled', 'copy-picked-hex', ]; // The setting keys of the "org.gnome.shell.extensions.draw-on-your-screen.internal-shortcuts" schema. -var INTERNAL_KEYBINDINGS = [ +export const INTERNAL_KEYBINDINGS = [ 'undo', 'redo', 'delete-last-element', 'smooth-last-element', 'select-none-shape', 'select-line-shape', 'select-ellipse-shape', 'select-rectangle-shape', 'select-polygon-shape', 'select-polyline-shape', 'select-text-shape', 'select-image-shape', 'select-move-tool', 'select-resize-tool', 'select-mirror-tool', @@ -60,60 +39,3 @@ var INTERNAL_KEYBINDINGS = [ 'toggle-panel-and-dock-visibility', 'toggle-background', 'toggle-grid', 'toggle-square-area', 'open-next-json', 'open-previous-json', 'save-as-json', 'export-to-svg', 'open-preferences', 'toggle-help', ]; - -if (GS_VERSION < '3.30') { - // Remove 'pick-color' keybinding. - INTERNAL_KEYBINDINGS.forEach(settingKeys => { - let index = settingKeys.indexOf('pick-color'); - if (index != -1) - settingKeys.splice(index, 1); - }); -} - -if (GS_VERSION < '3.36') { - // Remove 'open-preferences' keybinding. - INTERNAL_KEYBINDINGS.forEach(settingKeys => { - let index = settingKeys.indexOf('open-preferences'); - if (index != -1) - settingKeys.splice(index, 1); - }); -} - -const getOthers = function() { - return [ - [ - [_("Draw"), _("Left click")], - [_("Menu"), _("Right click")], - [internalShortcutsSchema.get_key('switch-fill').get_summary(), _("Center click")], - [_("Increment/decrement line width"), _("Scroll")], - // Translators: %s are key labels (Ctrl+F1 and Ctrl+F9) - [_("Select color"), _("%s … %s").format(getKeyLabel('1'), getKeyLabel('9'))], - // Translators: %s is a key label - [_("Ignore pointer movement"), _("%s held").format(getKeyLabel('space'))], - [_("Leave"), getKeyLabel('Escape')], - ], [ - [_("Select eraser (while starting drawing)"), getKeyLabel('')], - [_("Duplicate (while starting handling)"), getKeyLabel('')], - [_("Rotate rectangle, polygon, polyline"), getKeyLabel('')], - [_("Extend circle to ellipse"), getKeyLabel('')], - [_("Curve line"), getKeyLabel('')], - [_("Smooth free drawing outline"), getKeyLabel('')], - [_("Do not preserve image ratio"), getKeyLabel('')], - [_("Rotate (while moving)"), getKeyLabel('')], - [_("Stretch (while resizing)"), getKeyLabel('')], - [_("Inverse (while mirroring)"), getKeyLabel('')], - ], - ]; -}; - -let _OTHERS; -// Equivalent to "var OTHERS = [[ ... ]]", but as a getter so the translations are got after the initTranslations call. -// 'this' is the module. -Object.defineProperty(this, 'OTHERS', { - get: function() { - if (!_OTHERS) - _OTHERS = getOthers(); - return _OTHERS; - } -}); - diff --git a/ui/about.js b/ui/about.js index 0ad4333..78ccb03 100644 --- a/ui/about.js +++ b/ui/about.js @@ -15,22 +15,24 @@ * along with this program. If not, see . * */ -const { Adw, Gtk, GObject }= imports.gi; -const ExtensionUtils = imports.misc.extensionUtils; -const Me = ExtensionUtils.getCurrentExtension(); -const gettext = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; -const _ = gettext; + +import Adw from 'gi://Adw'; +import Gtk from 'gi://Gtk'; +import GObject from 'gi://GObject'; + +import { gettext as _ } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'; + +import { CURATED_UUID as UUID } from '../utils.js'; const MARGIN = 10; -const ROWBOX_MARGIN_PARAMS = { margin_top: MARGIN / 2, margin_bottom: MARGIN / 2, margin_start: MARGIN, margin_end: MARGIN, spacing: 4 }; //TODO: Follow GNOME HIG for About Pages -var AboutPage = GObject.registerClass({ - GTypeName: 'About' +const AboutPage = GObject.registerClass({ + GTypeName: `${UUID}-AboutPage` }, class AboutPage extends Adw.PreferencesPage { - constructor() { + constructor(extensionPreferences) { super({}); - this.set_title( _("About")); + this.set_title(_("About")); this.set_name('about'); this.set_icon_name("dialog-question-symbolic"); @@ -38,7 +40,7 @@ var AboutPage = GObject.registerClass({ let scrolledWindow = Gtk.ScrolledWindow.new(); scrolledWindow.set_vexpand(true); - scrolledWindow.hscrollbar_policy= Gtk.PolicyType.NEVER; + scrolledWindow.hscrollbar_policy = Gtk.PolicyType.NEVER; aboutGroup.add(scrolledWindow); @@ -48,31 +50,41 @@ var AboutPage = GObject.registerClass({ // Translators: you are free to translate the extension name, that is displayed in About page, or not let name = " " + _("Draw On You Screen 2") + ""; // Translators: version number in "About" page - let version = _("Version %f").format(Me.metadata.version); + let version = _("Version %f").format(extensionPreferences.metadata.version); // Translators: you are free to translate the extension description, that is displayed in About page, or not let description = _("This is a forked from Abakkk original Draw On Your Screen Extension.\nStart drawing with Super+Alt+D and save your beautiful work by taking a screenshot"); - let link = "" + Me.metadata.url + ""; + let link = "" + extensionPreferences.metadata.url + ""; let licenseName = _("GNU General Public License, version 3 or later"); let licenseLink = "https://www.gnu.org/licenses/gpl-3.0.html"; let license = "" + _("This program comes with absolutely no warranty.\nSee the %s for details.").format(licenseLink, licenseName) + ""; - let aboutLabel = new Gtk.Label({ wrap: true, justify: Gtk.Justification.CENTER, use_markup: true, label: - name + "\n\n" + version + "\n\n" + description + "\n\n" + link + "\n\n" + license + "\n" }); + let aboutLabel = new Gtk.Label({ + wrap: true, justify: Gtk.Justification.CENTER, use_markup: true, label: + name + "\n\n" + version + "\n\n" + description + "\n\n" + link + "\n\n" + license + "\n" + }); vbox.append(aboutLabel); let creditBox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL, margin_top: 2 * MARGIN, margin_bottom: 2 * MARGIN, margin_start: 2 * MARGIN, margin_end: 2 * MARGIN, spacing: 5 }); let leftBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, hexpand: true }); let rightBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, hexpand: true }); - leftBox.append(new Gtk.Label({ wrap: true, valign: Gtk.Align.START, halign: Gtk.Align.END, justify: Gtk.Justification.RIGHT, - use_markup: true, label: "" + _("Created by") + "" })); - rightBox.append(new Gtk.Label({ wrap: true, valign: Gtk.Align.START, halign: Gtk.Align.START, justify: Gtk.Justification.LEFT, - use_markup: true, label: "Abakkk" })); + leftBox.append(new Gtk.Label({ + wrap: true, valign: Gtk.Align.START, halign: Gtk.Align.END, justify: Gtk.Justification.RIGHT, + use_markup: true, label: "" + _("Created by") + "" + })); + rightBox.append(new Gtk.Label({ + wrap: true, valign: Gtk.Align.START, halign: Gtk.Align.START, justify: Gtk.Justification.LEFT, + use_markup: true, label: "Abakkk" + })); - leftBox.append(new Gtk.Label({ wrap: true, valign: Gtk.Align.START, halign: Gtk.Align.END, justify: Gtk.Justification.RIGHT, - use_markup: true, label: "" + _("Forked by") + "" })); - rightBox.append(new Gtk.Label({ wrap: true, valign: Gtk.Align.START, halign: Gtk.Align.START, justify: Gtk.Justification.LEFT, - use_markup: true, label: "zhrexl" })); + leftBox.append(new Gtk.Label({ + wrap: true, valign: Gtk.Align.START, halign: Gtk.Align.END, justify: Gtk.Justification.RIGHT, + use_markup: true, label: "" + _("Forked by") + "" + })); + rightBox.append(new Gtk.Label({ + wrap: true, valign: Gtk.Align.START, halign: Gtk.Align.START, justify: Gtk.Justification.LEFT, + use_markup: true, label: "zhrexl" + })); creditBox.append(leftBox); creditBox.append(rightBox); @@ -90,6 +102,7 @@ var AboutPage = GObject.registerClass({ rightBox.append(new Gtk.Label({ wrap: true, valign: Gtk.Align.START, halign: Gtk.Align.START, justify: 0, use_markup: true, label: "" + _("translator-credits") + "" })); } this.add(aboutGroup); - } - }); + } +}); +export default AboutPage; \ No newline at end of file diff --git a/ui/areamanager.js b/ui/areamanager.js index 2fdd06e..c51e0ad 100644 --- a/ui/areamanager.js +++ b/ui/areamanager.js @@ -21,60 +21,59 @@ /* jslint esversion: 6 */ /* exported init */ -const GObject = imports.gi.GObject; -const Meta = imports.gi.Meta; -const Shell = imports.gi.Shell; -const St = imports.gi.St; -const Clutter = imports.gi.Clutter; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import St from 'gi://St'; +import Clutter from 'gi://Clutter'; -const Config = imports.misc.config; -const ExtensionUtils = imports.misc.extensionUtils; -const Main = imports.ui.main; -const OsdWindow = imports.ui.osdWindow; -const PanelMenu = imports.ui.panelMenu; +import * as Config from 'resource:///org/gnome/shell/misc/config.js' +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; +import * as OsdWindow from 'resource:///org/gnome/shell/ui/osdWindow.js'; +import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js'; -const Me = ExtensionUtils.getCurrentExtension(); -const Area = Me.imports.area; -const Files = Me.imports.files; -const Helper = Me.imports.helper; -const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; +import {gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js'; -const GS_VERSION = Config.PACKAGE_VERSION; -const HIDE_TIMEOUT_LONG = 2500; // ms, default is 1500 ms -const UUID = Me.uuid.replace(/@/gi, '_at_').replace(/[^a-z0-9+_-]/gi, '_'); +import * as Area from '../area.js'; +import * as Helper from '../helper.js'; -// custom Shell.ActionMode, assuming that they are unused -const DRAWING_ACTION_MODE = Math.pow(2,14); -const WRITING_ACTION_MODE = Math.pow(2,15); -// use 'login-dialog-message-warning' class in order to get GS theme warning color (default: #f57900) -const WARNING_COLOR_STYLE_CLASS_NAME = 'login-dialog-message-warning'; // AreaManager assigns one DrawingArea per monitor (updateAreas()), // distributes keybinding callbacks to the active area // and handles stylesheet and monitor changes. -var AreaManager = GObject.registerClass({ - GTypeName: `${UUID}-AreaManager`, -}, class AreaManager extends GObject.Object{ - _init() { +export class AreaManager { + + constructor(extension) { + this._extension = extension; + this._GS_VERSION = Config.PACKAGE_VERSION; + this._HIDE_TIMEOUT_LONG = 2500; // ms, default is 1500 ms + + // custom Shell.ActionMode, assuming that they are unused + this._DRAWING_ACTION_MODE = Math.pow(2,14); + this._WRITING_ACTION_MODE = Math.pow(2,15); + // use 'login-dialog-message-warning' class in order to get GS theme warning color (default: #f57900) + this._WARNING_COLOR_STYLE_CLASS_NAME = 'login-dialog-message-warning'; + } + + enable() { this.areas = []; this.activeArea = null; this.grab = null; Main.wm.addKeybinding('toggle-drawing', - Me.settings, + this._extension.getSettings(), Meta.KeyBindingFlags.NONE, Shell.ActionMode.ALL, this.toggleDrawing.bind(this)); Main.wm.addKeybinding('toggle-modal', - Me.settings, + this._extension.getSettings(), Meta.KeyBindingFlags.NONE, Shell.ActionMode.ALL, this.toggleModal.bind(this)); Main.wm.addKeybinding('erase-drawings', - Me.settings, + this._extension.getSettings(), Meta.KeyBindingFlags.NONE, Shell.ActionMode.ALL, this.eraseDrawings.bind(this)); @@ -83,35 +82,35 @@ var AreaManager = GObject.registerClass({ this.monitorChangedHandler = Main.layoutManager.connect('monitors-changed', this.updateAreas.bind(this)); this.updateIndicator(); - this.indicatorSettingHandler = Me.settings.connect('changed::indicator-disabled', this.updateIndicator.bind(this)); + this.indicatorSettingHandler = this._extension.getSettings().connect('changed::indicator-disabled', this.updateIndicator.bind(this)); - this.desktopSettingHandler = Me.settings.connect('changed::drawing-on-desktop', this.onDesktopSettingChanged.bind(this)); - this.persistentOverRestartsSettingHandler = Me.settings.connect('changed::persistent-over-restarts', this.onPersistentOverRestartsSettingChanged.bind(this)); - this.persistentOverTogglesSettingHandler = Me.settings.connect('changed::persistent-over-toggles', this.onPersistentOverTogglesSettingChanged.bind(this)); + this.desktopSettingHandler = this._extension.getSettings().connect('changed::drawing-on-desktop', this.onDesktopSettingChanged.bind(this)); + this.persistentOverRestartsSettingHandler = this._extension.getSettings().connect('changed::persistent-over-restarts', this.onPersistentOverRestartsSettingChanged.bind(this)); + this.persistentOverTogglesSettingHandler = this._extension.getSettings().connect('changed::persistent-over-toggles', this.onPersistentOverTogglesSettingChanged.bind(this)); } get persistentOverToggles() { - return Me.settings.get_boolean('persistent-over-toggles'); + return this._extension.getSettings().get_boolean('persistent-over-toggles'); } get persistentOverRestarts() { - return Me.settings.get_boolean('persistent-over-toggles') && Me.settings.get_boolean('persistent-over-restarts'); + return this._extension.getSettings().get_boolean('persistent-over-toggles') && this._extension.getSettings().get_boolean('persistent-over-restarts'); } get onDesktop() { - return Me.settings.get_boolean('persistent-over-toggles') && Me.settings.get_boolean('drawing-on-desktop'); + return this._extension.getSettings().get_boolean('persistent-over-toggles') && this._extension.getSettings().get_boolean('drawing-on-desktop'); } get toolPalette() { - return Me.drawingSettings.get_value('tool-palette').deep_unpack() + return this._extension.getSettings(this._extension.metadata['settings-schema'] + '.drawing').get_value('tool-palette').deep_unpack() } get toolColor() { - return Me.drawingSettings.get_string("tool-color") + return this._extension.getSettings(this._extension.metadata['settings-schema'] + '.drawing').get_string("tool-color") } get toolSize() { - return Me.drawingSettings.get_int('tool-size'); + return this._extension.getSettings(this._extension.metadata['settings-schema'] + '.drawing').get_int('tool-size'); } onDesktopSettingChanged() { @@ -139,8 +138,10 @@ var AreaManager = GObject.registerClass({ this.indicator.disable(); this.indicator = null; } - if (!Me.settings.get_boolean('indicator-disabled')) + if (!this._extension.getSettings().get_boolean('indicator-disabled')) { this.indicator = new DrawingIndicator(); + this.indicator.enable(); + } } updateAreas() { @@ -158,7 +159,7 @@ var AreaManager = GObject.registerClass({ for (let i = 0; i < this.monitors.length; i++) { let monitor = this.monitors[i]; - let helper = new Helper.DrawingHelper({ name: 'drawOnYourSreenHelper' + i }, monitor); + let helper = new Helper.DrawingHelper(this._extension, { name: 'drawOnYourScreenHelper' + i }, monitor); let loadPersistent = i == Main.layoutManager.primaryIndex && this.persistentOverRestarts; // Some utils for the drawing area menus. let areaManagerUtils = { @@ -166,7 +167,7 @@ var AreaManager = GObject.registerClass({ togglePanelAndDockOpacity: this.togglePanelAndDockOpacity.bind(this), openPreferences: this.openPreferences.bind(this) }; - let area = new Area.DrawingArea({ name: 'drawOnYourSreenArea' + i }, monitor, helper, areaManagerUtils, loadPersistent, toolConf); + let area = new Area.DrawingArea(this._extension, { name: 'drawOnYourScreenArea' + i }, monitor, helper, areaManagerUtils, loadPersistent, toolConf); Main.layoutManager._backgroundGroup.insert_child_above(area, Main.layoutManager._bgManagers[i].backgroundActor); if (!this.onDesktop) @@ -238,26 +239,26 @@ var AreaManager = GObject.registerClass({ for (let key in this.internalKeybindings1) { Main.wm.addKeybinding(key, - Me.internalShortcutSettings, + this._extension.getSettings(this._extension.metadata['settings-schema'] + '.internal-shortcuts'), Meta.KeyBindingFlags.NONE, - DRAWING_ACTION_MODE, + this._DRAWING_ACTION_MODE, this.internalKeybindings1[key]); } for (let key in this.internalKeybindings2) { Main.wm.addKeybinding(key, - Me.internalShortcutSettings, + this._extension.getSettings(this._extension.metadata['settings-schema'] + '.internal-shortcuts'), Meta.KeyBindingFlags.NONE, - DRAWING_ACTION_MODE | WRITING_ACTION_MODE, + this._DRAWING_ACTION_MODE | this._WRITING_ACTION_MODE, this.internalKeybindings2[key]); } for (let i = 1; i < 10; i++) { let iCaptured = i; Main.wm.addKeybinding('select-color' + i, - Me.internalShortcutSettings, + this._extension.getSettings(this._extension.metadata['settings-schema'] + '.internal-shortcuts'), Meta.KeyBindingFlags.NONE, - DRAWING_ACTION_MODE | WRITING_ACTION_MODE, + this._DRAWING_ACTION_MODE | this._WRITING_ACTION_MODE, this.activeArea.selectColor.bind(this.activeArea, iCaptured - 1)); } } @@ -276,21 +277,18 @@ var AreaManager = GObject.registerClass({ openPreferences() { if (this.activeArea) this.toggleDrawing(); - ExtensionUtils.openPrefs(); + this._extension.openPreferences(); } eraseDrawings() { - for (let i = 0; i < this.areas.length; i++) - this.areas[i].erase(); + this.areas.forEach(area => area.erase()); if (this.persistentOverRestarts) this.areas[Main.layoutManager.primaryIndex].savePersistent(); } togglePanelAndDockOpacity() { if (this.hiddenList) { - for (let i = 0; i < this.hiddenList.length; i++) { - this.hiddenList[i].actor.set_opacity(this.hiddenList[i].oldOpacity); - } + this.hiddenList.forEach(item => item.actor.set_opacity(item.oldOpacity)); this.hiddenList = null; } else { let activeIndex = this.areas.indexOf(this.activeArea); @@ -298,12 +296,11 @@ var AreaManager = GObject.registerClass({ // dash-to-dock let dtdContainers = Main.uiGroup.get_children().filter((actor) => { return actor.name && actor.name == 'dashtodockContainer' && - ((actor._delegate && - actor._delegate._monitorIndex !== undefined && - actor._delegate._monitorIndex == activeIndex) || - // dtd v68+ - (actor._monitorIndex !== undefined && - actor._monitorIndex == activeIndex)); + ((actor._delegate?._monitorIndex !== undefined && + actor._delegate?._monitorIndex == activeIndex) || + // dtd v68+ + (actor._monitorIndex !== undefined && + actor._monitorIndex == activeIndex)); }); // for simplicity, we assume that main dash-to-panel panel is displayed on primary monitor @@ -317,10 +314,10 @@ var AreaManager = GObject.registerClass({ let actorToHide = dtdContainers.concat(panelBoxes); this.hiddenList = []; - for (let i = 0; i < actorToHide.length; i++) { - this.hiddenList.push({ actor: actorToHide[i], oldOpacity: actorToHide[i].get_opacity() }); - actorToHide[i].set_opacity(0); - } + actorToHide.forEach(actor => { + this.hiddenList.push({ actor: actor, oldOpacity: actor.get_opacity() }); + actor.set_opacity(0); + }); } } @@ -350,11 +347,10 @@ var AreaManager = GObject.registerClass({ return; this.activeArea.closeMenu(); - - if (Main._findModal(this.grab) != -1) { + if (this._findModal(this.grab) != -1) { Main.popModal(this.grab); if (source && source == global.display) - this.showOsd(null, Files.Icons.UNGRAB, _("Keyboard and pointer released"), null, null, false); + this.showOsd(null, this._extension.FILES.ICONS.UNGRAB, _("Keyboard and pointer released"), null, null, false); // Translators: "released" as the opposite of "grabbed" @@ -364,7 +360,7 @@ var AreaManager = GObject.registerClass({ } else { // add Shell.ActionMode.NORMAL to keep system keybindings enabled (e.g. Alt + F2 ...) - let actionMode = (this.activeArea.isWriting ? WRITING_ACTION_MODE : DRAWING_ACTION_MODE) | Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW; + let actionMode = (this.activeArea.isWriting ? this._WRITING_ACTION_MODE : this._DRAWING_ACTION_MODE) | Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW; this.grab = Main.pushModal(this.activeArea, { actionMode: actionMode }); if (this.grab.get_seat_state() === Clutter.GrabState.NONE) { Main.popModal(this.grab); @@ -374,7 +370,7 @@ var AreaManager = GObject.registerClass({ this.activeArea.reactive = true; this.activeArea.initPointerCursor(); if (source && source == global.display) - this.showOsd(null, Files.Icons.GRAB, _("Keyboard and pointer grabbed"), null, null, false); + this.showOsd(null, this._extension.FILES.ICONS.GRAB, _("Keyboard and pointer grabbed"), null, null, false); } return true; @@ -386,13 +382,13 @@ var AreaManager = GObject.registerClass({ let save = activeIndex == Main.layoutManager.primaryIndex && this.persistentOverRestarts; let erase = !this.persistentOverToggles; - this.showOsd(null, Files.Icons.LEAVE, _("Leaving drawing mode")); + this.showOsd(null, this._extension.FILES.ICONS.LEAVE, _("Leaving drawing mode")); this.activeArea.leaveDrawingMode(save, erase); if (this.hiddenList) this.togglePanelAndDockOpacity(); - if (Main._findModal(this.grab) != -1) + if (this._findModal(this.grab) != -1) this.toggleModal(); this.toggleArea(); @@ -409,11 +405,11 @@ var AreaManager = GObject.registerClass({ } this.activeArea.enterDrawingMode(); - this.osdDisabled = Me.settings.get_boolean('osd-disabled'); + this.osdDisabled = this._extension.getSettings().get_boolean('osd-disabled'); // is a clutter/mutter 3.38 bug workaround: https://gitlab.gnome.org/GNOME/mutter/-/issues/1467 // Translators: %s is a key label let label = `${_("Press %s for help").format(this.activeArea.helper.helpKeyLabel)}\n\n${_("Entering drawing mode")}`; - this.showOsd(null, Files.Icons.ENTER, label, null, null, true); + this.showOsd(null, this._extension.FILES.ICONS.ENTER, label, null, null, true); } if (this.indicator) @@ -421,7 +417,7 @@ var AreaManager = GObject.registerClass({ } updateActionMode() { - Main.actionMode = (this.activeArea.isWriting ? WRITING_ACTION_MODE : DRAWING_ACTION_MODE) | Shell.ActionMode.NORMAL; + Main.actionMode = (this.activeArea.isWriting ? this._WRITING_ACTION_MODE : this._DRAWING_ACTION_MODE) | Shell.ActionMode.NORMAL; } // Use level -1 to set no level through a signal. @@ -430,11 +426,11 @@ var AreaManager = GObject.registerClass({ if (activeIndex == -1 || this.osdDisabled) return; - let hideTimeoutSave; - if (long && GS_VERSION >= '3.28.0') { - hideTimeoutSave = OsdWindow.HIDE_TIMEOUT; - OsdWindow.HIDE_TIMEOUT = HIDE_TIMEOUT_LONG; - } + // let hideTimeoutSave; + // if (long && this._GS_VERSION >= '3.28.0') { + // hideTimeoutSave = OsdWindow.HIDE_TIMEOUT; + // OsdWindow.HIDE_TIMEOUT = this._HIDE_TIMEOUT_LONG; + // } let maxLevel; if (level == -1) @@ -444,11 +440,11 @@ var AreaManager = GObject.registerClass({ // GS 3.32- : bar from 0 to 100 // GS 3.34+ : bar from 0 to 1 - if (level && GS_VERSION > '3.33.0') + if (level && this._GS_VERSION > '3.33.0') level = level / 100; if (!icon) - icon = Files.Icons.ENTER; + icon = this._extension.FILES.ICONS.ENTER; let osdWindow = Main.osdWindowManager._osdWindows[activeIndex]; @@ -466,16 +462,16 @@ var AreaManager = GObject.registerClass({ } if (level === 0) { - osdWindow._label.add_style_class_name(WARNING_COLOR_STYLE_CLASS_NAME); + osdWindow._label.add_style_class_name(this._WARNING_COLOR_STYLE_CLASS_NAME); // the same label is shared by all GS OSD so the style must be removed after being used let osdLabelChangedHandler = osdWindow._label.connect('notify::text', () => { - osdWindow._label.remove_style_class_name(WARNING_COLOR_STYLE_CLASS_NAME); + osdWindow._label.remove_style_class_name(this._WARNING_COLOR_STYLE_CLASS_NAME); osdWindow._label.disconnect(osdLabelChangedHandler); }); } - if (hideTimeoutSave) - OsdWindow.HIDE_TIMEOUT = hideTimeoutSave; + // if (hideTimeoutSave) + // OsdWindow.HIDE_TIMEOUT = hideTimeoutSave; } setCursor(sourceActor_, cursorName) { @@ -487,8 +483,7 @@ var AreaManager = GObject.registerClass({ } removeAreas() { - for (let i = 0; i < this.areas.length; i++) { - let area = this.areas[i]; + for (const area in this.areas) { area.disconnect(area.leaveDrawingHandler); area.disconnect(area.updateActionModeHandler); area.disconnect(area.showOsdHandler); @@ -503,19 +498,19 @@ var AreaManager = GObject.registerClass({ this.monitorChangedHandler = null; } if (this.indicatorSettingHandler) { - Me.settings.disconnect(this.indicatorSettingHandler); + this._extension.getSettings().disconnect(this.indicatorSettingHandler); this.indicatorSettingHandler = null; } if (this.desktopSettingHandler) { - Me.settings.disconnect(this.desktopSettingHandler); + this._extension.getSettings().disconnect(this.desktopSettingHandler); this.desktopSettingHandler = null; } if (this.persistentOverTogglesSettingHandler) { - Me.settings.disconnect(this.persistentOverTogglesSettingHandler); + this._extension.getSettings().disconnect(this.persistentOverTogglesSettingHandler); this.persistentOverTogglesSettingHandler = null; } if (this.persistentOverRestartsSettingHandler) { - Me.settings.disconnect(this.persistentOverRestartsSettingHandler); + this._extension.getSettings().disconnect(this.persistentOverRestartsSettingHandler); this.persistentOverRestartsSettingHandler = null; } @@ -525,22 +520,29 @@ var AreaManager = GObject.registerClass({ Main.wm.removeKeybinding('toggle-modal'); Main.wm.removeKeybinding('erase-drawings'); this.removeAreas(); - Files.Images.disable(); - Files.Jsons.disable(); + // this._extension.FILES.IMAGES.disable(); + // this._extension.FILES.JSONS.disable(); if (this.indicator) this.indicator.disable(); } -}); + + /** + * @private + * @param {Clutter.Grab} grab - grab + */ + _findModal(grab) { + return Main.modalActorFocusStack.findIndex(modal => modal.grab === grab); + } + +}; -const DrawingIndicator = GObject.registerClass({ - GTypeName: `${UUID}-Indicator`, -}, class DrawingIndicator extends GObject.Object{ +export class DrawingIndicator { - _init() { + enable() { let [menuAlignment, dontCreateMenu] = [0, true]; this.button = new PanelMenu.Button(menuAlignment, "Drawing Indicator", dontCreateMenu); - this.buttonActor = GS_VERSION < '3.33.0' ? this.button.actor: this.button; + this.buttonActor = this._GS_VERSION < '3.33.0' ? this.button.actor: this.button; Main.panel.addToStatusArea('draw-on-your-screen-indicator', this.button); this.icon = new St.Icon({ icon_name: 'applications-graphics-symbolic', @@ -556,6 +558,6 @@ const DrawingIndicator = GObject.registerClass({ disable() { this.button.destroy(); } -}); +}; diff --git a/ui/drawingpage.js b/ui/drawingpage.js index f0c697b..e7612c2 100644 --- a/ui/drawingpage.js +++ b/ui/drawingpage.js @@ -15,22 +15,27 @@ * along with this program. If not, see . * */ -const { Adw, Gdk, GLib, Gtk, GObject, Gio } = imports.gi; +import Adw from 'gi://Adw'; +import Gdk from 'gi://Gdk'; +import GLib from 'gi://GLib'; +import Gtk from 'gi://Gtk'; +import GObject from 'gi://GObject'; +import Gio from 'gi://Gio'; + +import {gettext as _} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'; + +import * as GimpPaletteParser from '../gimpPaletteParser.js'; +import { CURATED_UUID as UUID } from '../utils.js'; -const ExtensionUtils = imports.misc.extensionUtils; -const Me = ExtensionUtils.getCurrentExtension(); -const UUID = Me.uuid.replace(/@/gi, '_at_').replace(/[^a-z0-9+_-]/gi, '_'); -const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; -const GimpPaletteParser = Me.imports.gimpPaletteParser; const MARGIN = 10; const ROWBOX_MARGIN_PARAMS = { margin_top: MARGIN / 2, margin_bottom: MARGIN / 2, margin_start: MARGIN, margin_end: MARGIN, spacing: 4 }; -var DrawingPage = GObject.registerClass({ - GTypeName: 'Drawing' +const DrawingPage = GObject.registerClass({ + GTypeName: `${UUID}-Drawing` }, class DrawingPage extends Adw.PreferencesPage { - constructor(window) { + constructor(extensionPreferences, window) { super({}); this.window = window; @@ -38,7 +43,7 @@ var DrawingPage = GObject.registerClass({ this.set_name('drawing'); this.set_icon_name("applications-graphics-symbolic"); - this.settings = ExtensionUtils.getSettings(Me.metadata['settings-schema'] + '.drawing'); + this.settings = extensionPreferences.getSettings(extensionPreferences.metadata['settings-schema'] + '.drawing'); this.schema = this.settings.settings_schema; @@ -370,6 +375,7 @@ var DrawingPage = GObject.registerClass({ } }); + const PixelSpinButton = new GObject.Class({ Name: `${UUID}-PixelSpinButton2`, Extends: Gtk.SpinButton, @@ -415,6 +421,7 @@ const PixelSpinButton = new GObject.Class({ } }); + // A color button that can be easily bound with a color string setting. const ColorStringButton = new GObject.Class({ Name: `${UUID}-ColorStringButton2`, @@ -448,6 +455,7 @@ const ColorStringButton = new GObject.Class({ } }); + const FileChooserButton = new GObject.Class({ Name: `${UUID}-FileChooserButton2`, Extends: Gtk.Button, @@ -497,3 +505,5 @@ const FileChooserButton = new GObject.Class({ dialog.show(); } }); + +export default DrawingPage; \ No newline at end of file diff --git a/ui/preferencespage.js b/ui/preferencespage.js index f0679d8..048ff8b 100644 --- a/ui/preferencespage.js +++ b/ui/preferencespage.js @@ -15,161 +15,164 @@ * along with this program. If not, see . * */ -const { Adw, Gdk, GLib, Gtk, GObject, Gio } = imports.gi; - -const ExtensionUtils = imports.misc.extensionUtils; -const Me = ExtensionUtils.getCurrentExtension(); -const Shortcuts = Me.imports.shortcuts; -const UUID = Me.uuid.replace(/@/gi, '_at_').replace(/[^a-z0-9+_-]/gi, '_'); -const gettext = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; -const _ = function(string) { - if (!string) - return ""; - return gettext(string); -}; + +import Adw from 'gi://Adw'; +import Gdk from 'gi://Gdk'; +import Gtk from 'gi://Gtk'; +import GObject from 'gi://GObject'; + +import { gettext } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'; + +import { CURATED_UUID as UUID } from '../utils.js'; +import * as Shortcuts from '../shortcuts.js'; + + +const _ = (string) => string ? gettext(string) : ""; const MARGIN = 10; const ROWBOX_MARGIN_PARAMS = { margin_top: MARGIN / 2, margin_bottom: MARGIN / 2, margin_start: MARGIN, margin_end: MARGIN, spacing: 4 }; -var Preferences = GObject.registerClass({ - GTypeName: 'Preferences' + +const PreferencesPage = GObject.registerClass({ + GTypeName: `${UUID}-PreferencesPage` }, class Preferences extends Adw.PreferencesPage { - constructor() { - super({}); - this.set_title("Preferences"); - this.set_name('prefs'); - this.set_icon_name("preferences-system-symbolic"); - let settings = ExtensionUtils.getSettings(); - let schema = settings.settings_schema; + constructor(extensionPreferences) { + super({}); - let grp_Global = Adw.PreferencesGroup.new(); - grp_Global.set_title("Global"); + this.set_title("Preferences"); + this.set_name('prefs'); + this.set_icon_name("preferences-system-symbolic"); + let settings = extensionPreferences.getSettings(); + let schema = settings.settings_schema; - Shortcuts.GLOBAL_KEYBINDINGS.forEach((settingKeys) => { - let globalKeybindingsRow = new Adw.ActionRow(); - let globalKeybindingsWidget = new ShortCutWidget(settings, settingKeys); - let name = settings.settings_schema.get_key(settingKeys).get_summary(); + let grp_Global = Adw.PreferencesGroup.new(); + grp_Global.set_title("Global"); - globalKeybindingsRow.set_title(name); - globalKeybindingsRow.add_suffix(globalKeybindingsWidget); - globalKeybindingsWidget.valign = Gtk.Align.CENTER; + Shortcuts.GLOBAL_KEYBINDINGS.forEach((settingKeys) => { + let globalKeybindingsRow = new Adw.ActionRow(); + let globalKeybindingsWidget = new ShortCutWidget(settings, settingKeys); + let name = settings.settings_schema.get_key(settingKeys).get_summary(); - grp_Global.add(globalKeybindingsRow); - }); + globalKeybindingsRow.set_title(name); + globalKeybindingsRow.add_suffix(globalKeybindingsWidget); + globalKeybindingsWidget.valign = Gtk.Align.CENTER; + grp_Global.add(globalKeybindingsRow); + }); - Shortcuts.GLOBAL_KEYBINDINGS_SWITCHES.forEach((settingKeys) => { - let ActionRow = Adw.ActionRow.new(); - let ActionRow_switch = Gtk.Switch.new(); - let persistentOverTogglesKey = schema.get_key(settingKeys); - ActionRow.set_title(persistentOverTogglesKey.get_summary()); - let description = persistentOverTogglesKey.get_description(); + Shortcuts.GLOBAL_KEYBINDINGS_SWITCHES.forEach((settingKeys) => { + let ActionRow = Adw.ActionRow.new(); + let ActionRow_switch = Gtk.Switch.new(); + let persistentOverTogglesKey = schema.get_key(settingKeys); - if (description) - ActionRow.set_subtitle(persistentOverTogglesKey.get_description()); + ActionRow.set_title(persistentOverTogglesKey.get_summary()); + let description = persistentOverTogglesKey.get_description(); - ActionRow.add_suffix(ActionRow_switch); - ActionRow_switch.valign = Gtk.Align.CENTER; + if (description) + ActionRow.set_subtitle(persistentOverTogglesKey.get_description()); - settings.bind(settingKeys, ActionRow_switch, 'active', 0); + ActionRow.add_suffix(ActionRow_switch); + ActionRow_switch.valign = Gtk.Align.CENTER; - grp_Global.add(ActionRow); - }); + settings.bind(settingKeys, ActionRow_switch, 'active', 0); - let grp_Internal = Adw.PreferencesGroup.new(); - grp_Internal.set_title("Internal"); + grp_Global.add(ActionRow); + }); - let internalShortcutSettings = ExtensionUtils.getSettings(Me.metadata['settings-schema'] + '.internal-shortcuts'); + let grp_Internal = Adw.PreferencesGroup.new(); + grp_Internal.set_title("Internal"); - // TODO: Improve Shortcut Widget - Shortcuts.INTERNAL_KEYBINDINGS.forEach((settingKeys) => { - let globalKeybindingsRow = new Adw.ActionRow(); - let globalKeybindingsWidget = new ShortCutWidget(internalShortcutSettings, settingKeys); - let name = internalShortcutSettings.settings_schema.get_key(settingKeys).get_summary(); + let internalShortcutSettings = extensionPreferences.getSettings(extensionPreferences.metadata['settings-schema'] + '.internal-shortcuts'); - globalKeybindingsRow.set_title(name); - globalKeybindingsRow.add_suffix(globalKeybindingsWidget); - globalKeybindingsWidget.valign = Gtk.Align.CENTER; + // TODO: Improve Shortcut Widget + Shortcuts.INTERNAL_KEYBINDINGS.forEach((settingKeys) => { + let globalKeybindingsRow = new Adw.ActionRow(); + let globalKeybindingsWidget = new ShortCutWidget(internalShortcutSettings, settingKeys); + let name = internalShortcutSettings.settings_schema.get_key(settingKeys).get_summary(); - grp_Internal.add(globalKeybindingsRow); - }); + globalKeybindingsRow.set_title(name); + globalKeybindingsRow.add_suffix(globalKeybindingsWidget); + globalKeybindingsWidget.valign = Gtk.Align.CENTER; - let resetButton = new Gtk.Button({ label: _("Reset settings"), halign: Gtk.Align.CENTER }); - resetButton.get_style_context().add_class('destructive-action'); - resetButton.connect('clicked', () => { - internalShortcutSettings.settings_schema.list_keys().forEach(key => internalShortcutSettings.reset(key)); - settings.settings_schema.list_keys().forEach(key => settings.reset(key)); - }); + grp_Internal.add(globalKeybindingsRow); + }); - resetButton.set_margin_top(12); + let resetButton = new Gtk.Button({ label: _("Reset settings"), halign: Gtk.Align.CENTER }); + resetButton.get_style_context().add_class('destructive-action'); + resetButton.connect('clicked', () => { + internalShortcutSettings.settings_schema.list_keys().forEach(key => internalShortcutSettings.reset(key)); + settings.settings_schema.list_keys().forEach(key => settings.reset(key)); + }); + + resetButton.set_margin_top(12); + + this.add(grp_Global); + this.add(grp_Internal); + grp_Internal.add(resetButton); + } +}); - this.add(grp_Global); - this.add(grp_Internal); - grp_Internal.add(resetButton); - } - }); //TODO: ShortCutWidget should not allow two identicals shortcuts const ShortCutWidget = GObject.registerClass({ - GTypeName: 'ShortCutWidget' + GTypeName: `${UUID}-ShortCutWidget` }, class ShortCutWidget extends Gtk.Button { - constructor(settings, settingsKeys) { - super({}); - this._settings = settings; - this._settingsKeys = settingsKeys; - - let key = settings.get_strv(settingsKeys)[0]; - this.shortcutlabel = Gtk.ShortcutLabel.new(key); - this.evck = Gtk.EventControllerKey.new(); - this.allow_changes = false; - - this.add_controller(this.evck); - - //TODO: This needs testing - if (key == null) - this.set_label(_("Click here to set new shortcut...")); - else - this.set_child(this.shortcutlabel); - - this.get_style_context().add_class('flat'); - this.connect('clicked',this.clicked.bind(this)); - this.evck.connect('key-pressed', this.key_pressed.bind(this)); - this.evck.connect('key-released', this.key_released.bind(this)); - this._settings.connect('changed', this.on_settings_changed.bind(this)); - } - on_settings_changed() - { - let key = this._settings.get_strv(this._settingsKeys)[0]; - this.shortcutlabel.set_accelerator(key); - } - clicked() - { - this.set_label(_("Press the new shortcut...")); - this.allow_changes = true; - } - key_released(widget, keyval, keycode, state) - { - if (this.allow_changes == false) - return Gdk.EVENT_STOP; - else - this.allow_changes = false; - - let value = this.shortcutlabel.get_accelerator(); - this._settings.set_strv(this._settingsKeys, [value]); - } - key_pressed(widget, keyval, keycode, state) - { - if (this.allow_changes == false) - return Gdk.EVENT_STOP; - - let mask = state & Gtk.accelerator_get_default_mod_mask(); - let binding = Gtk.accelerator_name_with_keycode(null, keyval, keycode, mask); - - this.shortcutlabel.set_accelerator(binding); - this.set_child(this.shortcutlabel); - - return Gdk.EVENT_STOP; - } + constructor(settings, settingsKeys) { + super({}); + this._settings = settings; + this._settingsKeys = settingsKeys; + + let key = settings.get_strv(settingsKeys)[0]; + this.shortcutlabel = Gtk.ShortcutLabel.new(key); + this.evck = Gtk.EventControllerKey.new(); + this.allow_changes = false; + + this.add_controller(this.evck); + + //TODO: This needs testing + if (key == null) + this.set_label(_("Click here to set new shortcut...")); + else + this.set_child(this.shortcutlabel); + + this.get_style_context().add_class('flat'); + this.connect('clicked', this.clicked.bind(this)); + this.evck.connect('key-pressed', this.key_pressed.bind(this)); + this.evck.connect('key-released', this.key_released.bind(this)); + this._settings.connect('changed', this.on_settings_changed.bind(this)); + } + on_settings_changed() { + let key = this._settings.get_strv(this._settingsKeys)[0]; + this.shortcutlabel.set_accelerator(key); + } + clicked() { + this.set_label(_("Press the new shortcut...")); + this.allow_changes = true; + } + key_released(widget, keyval, keycode, state) { + if (this.allow_changes == false) + return Gdk.EVENT_STOP; + else + this.allow_changes = false; + + let value = this.shortcutlabel.get_accelerator(); + this._settings.set_strv(this._settingsKeys, [value]); + } + key_pressed(widget, keyval, keycode, state) { + if (this.allow_changes == false) + return Gdk.EVENT_STOP; + + let mask = state & Gtk.accelerator_get_default_mod_mask(); + let binding = Gtk.accelerator_name_with_keycode(null, keyval, keycode, mask); + + this.shortcutlabel.set_accelerator(binding); + this.set_child(this.shortcutlabel); + + return Gdk.EVENT_STOP; + } }); + + +export default PreferencesPage; \ No newline at end of file diff --git a/utils.js b/utils.js new file mode 100644 index 0000000..8029245 --- /dev/null +++ b/utils.js @@ -0,0 +1,2 @@ +export const UUID = 'draw-on-your-screen2@zhrexl.github.com'; +export const CURATED_UUID = UUID.replace(/@/gi, '_at_').replace(/[^a-z0-9+_-]/gi, '_'); \ No newline at end of file From 0bf9b58e53ca0c2dcf38574cb3be878c341c4afc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abram=C3=A9?= Date: Thu, 30 Nov 2023 18:00:45 +0100 Subject: [PATCH 2/4] #47 gnome 45 support added --- metadata.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/metadata.json b/metadata.json index 3a41f25..bcb246a 100644 --- a/metadata.json +++ b/metadata.json @@ -9,8 +9,6 @@ "persistent-file-name": "persistent", "svg-file-name": "DrawOnYourScreen", "shell-version": [ - "43", - "44", "45" ], "version": 12.7 From d1c7b516d2f8f1ede4270f830ac951cb29ee2625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abram=C3=A9?= Date: Sat, 2 Dec 2023 12:07:51 +0100 Subject: [PATCH 3/4] bug fixes in help and menu --- area.js | 7 ------- helper.js | 34 +++++++++++++++++----------------- menu.js | 2 -- ui/areamanager.js | 6 ------ 4 files changed, 17 insertions(+), 32 deletions(-) diff --git a/area.js b/area.js index 540628f..0e032b2 100644 --- a/area.js +++ b/area.js @@ -109,7 +109,6 @@ export const DrawingArea = GObject.registerClass({ Signals: { 'show-osd': { param_types: [Gio.Icon.$gtype, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_DOUBLE, GObject.TYPE_BOOLEAN] }, 'pointer-cursor-changed': { param_types: [GObject.TYPE_STRING] }, - 'update-action-mode': {}, 'leave-drawing-mode': {} }, }, class DrawingArea extends St.Widget { @@ -806,7 +805,6 @@ export const DrawingArea = GObject.registerClass({ this.textEntry = new St.Entry({ opacity: 1, x: stageX + x, y: stageY + y }); this.insert_child_below(this.textEntry, null); this.textEntry.grab_key_focus(); - this.updateActionMode(); this.updatePointerCursor(); let ibusCandidatePopup = Main.layoutManager.uiGroup.get_children().find(child => @@ -860,7 +858,6 @@ export const DrawingArea = GObject.registerClass({ this.textEntry.destroy(); delete this.textEntry; this.grab_key_focus(); - this.updateActionMode(); this.updatePointerCursor(); this._redisplay(); @@ -1287,10 +1284,6 @@ export const DrawingArea = GObject.registerClass({ delete this.areaManagerUtils; } - updateActionMode() { - this.emit('update-action-mode'); - } - enterDrawingMode() { this.keyPressedHandler = this.connect('key-press-event', this._onKeyPressed.bind(this)); this.buttonPressedHandler = this.connect('button-press-event', this._onButtonPressed.bind(this)); diff --git a/helper.js b/helper.js index 6325ea4..58cb589 100644 --- a/helper.js +++ b/helper.js @@ -73,7 +73,7 @@ export const DrawingHelper = GObject.registerClass({ _updateHelpKeyLabel() { try { - let [keyval, mods] = Gtk.accelerator_parse(this._extension.internalShortcutSettings.get_strv('toggle-help')[0] || ''); + let [, keyval, mods] = Gtk.accelerator_parse(this._extension.internalShortcutSettings.get_strv('toggle-help')[0] || ''); this._helpKeyLabel = Gtk.accelerator_get_label(keyval, mods); } catch(e) { logError(e); @@ -102,7 +102,7 @@ export const DrawingHelper = GObject.registerClass({ return; let hbox = new St.BoxLayout({ vertical: false }); - let [keyval, mods] = Gtk.accelerator_parse(this._extension.settings.get_strv(settingKeys)[0] || ''); + let [, keyval, mods] = Gtk.accelerator_parse(this._extension.settings.get_strv(settingKeys)[0] || ''); hbox.add_child(new St.Label({ text: this._extension.settings.settings_schema.get_key(settingKeys).get_summary() })); hbox.add_child(new St.Label({ text: Gtk.accelerator_get_label(keyval, mods), x_expand: true })); this.vbox.add_child(hbox); @@ -112,21 +112,21 @@ export const DrawingHelper = GObject.registerClass({ this.vbox.add_child(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-helper-separator' })); this.vbox.add_child(new St.Label({ text: _("Internal") })); - Shortcuts.OTHERS.forEach((pairs, index) => { - if (index) - this.vbox.add_child(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-helper-separator' })); + // Shortcuts.OTHERS.forEach((pairs, index) => { + // if (index) + // this.vbox.add_child(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-helper-separator' })); - pairs.forEach(pair => { - let [action, shortcut] = pair; - let hbox = new St.BoxLayout({ vertical: false }); - hbox.add_child(new St.Label({ text: action })); - hbox.add_child(new St.Label({ text: shortcut, x_expand: true })); - hbox.get_children()[0].get_clutter_text().set_use_markup(true); - this.vbox.add_child(hbox); - }); - }); + // pairs.forEach(pair => { + // let [action, shortcut] = pair; + // let hbox = new St.BoxLayout({ vertical: false }); + // hbox.add_child(new St.Label({ text: action })); + // hbox.add_child(new St.Label({ text: shortcut, x_expand: true })); + // hbox.get_children()[0].get_clutter_text().set_use_markup(true); + // this.vbox.add_child(hbox); + // }); + // }); - this.vbox.add_child(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-helper-separator' })); + // this.vbox.add_child(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-helper-separator' })); Shortcuts.INTERNAL_KEYBINDINGS.forEach((settingKeys) => { //if (index) @@ -137,7 +137,7 @@ export const DrawingHelper = GObject.registerClass({ return; let hbox = new St.BoxLayout({ vertical: false }); - let [keyval, mods] = Gtk.accelerator_parse(this._extension.internalShortcutSettings.get_strv(settingKeys)[0] || ''); + let [, keyval, mods] = Gtk.accelerator_parse(this._extension.internalShortcutSettings.get_strv(settingKeys)[0] || ''); hbox.add_child(new St.Label({ text: this._extension.internalShortcutSettings.settings_schema.get_key(settingKeys).get_summary() })); hbox.add_child(new St.Label({ text: Gtk.accelerator_get_label(keyval, mods), x_expand: true })); this.vbox.add_child(hbox); @@ -156,7 +156,7 @@ export const DrawingHelper = GObject.registerClass({ let shortcut = GS_VERSION < '3.33.0' ? mediaKeysSettings.get_string(settingKey) : mediaKeysSettings.get_strv(settingKey)[0]; if (!shortcut) continue; - let [keyval, mods] = Gtk.accelerator_parse(shortcut || ''); + let [, keyval, mods] = Gtk.accelerator_parse(shortcut || ''); let hbox = new St.BoxLayout({ vertical: false }); hbox.add_child(new St.Label({ text: mediaKeysSettings.settings_schema.get_key(settingKey).get_summary() })); hbox.add_child(new St.Label({ text: Gtk.accelerator_get_label(keyval, mods), x_expand: true })); diff --git a/menu.js b/menu.js index ffb40da..5c829f3 100644 --- a/menu.js +++ b/menu.js @@ -189,8 +189,6 @@ export const DrawingMenu = GObject.registerClass({ this.area.setPointerCursor('DEFAULT'); } else { this.area.updatePointerCursor(); - // actionMode has changed, set previous actionMode in order to keep internal shortcuts working - this.area.updateActionMode(); this.area.grab_key_focus(); } diff --git a/ui/areamanager.js b/ui/areamanager.js index c51e0ad..264cdf8 100644 --- a/ui/areamanager.js +++ b/ui/areamanager.js @@ -176,7 +176,6 @@ export class AreaManager { area.set_position(monitor.x, monitor.y); area.set_size(monitor.width, monitor.height); area.leaveDrawingHandler = area.connect('leave-drawing-mode', this.toggleDrawing.bind(this)); - area.updateActionModeHandler = area.connect('update-action-mode', this.updateActionMode.bind(this)); area.pointerCursorChangedHandler = area.connect('pointer-cursor-changed', this.setCursor.bind(this)); area.showOsdHandler = area.connect('show-osd', this.showOsd.bind(this)); this.areas.push(area); @@ -416,10 +415,6 @@ export class AreaManager { this.indicator.sync(Boolean(this.activeArea)); } - updateActionMode() { - Main.actionMode = (this.activeArea.isWriting ? this._WRITING_ACTION_MODE : this._DRAWING_ACTION_MODE) | Shell.ActionMode.NORMAL; - } - // Use level -1 to set no level through a signal. showOsd(emitter, icon, label, color, level, long) { let activeIndex = this.areas.indexOf(this.activeArea); @@ -485,7 +480,6 @@ export class AreaManager { removeAreas() { for (const area in this.areas) { area.disconnect(area.leaveDrawingHandler); - area.disconnect(area.updateActionModeHandler); area.disconnect(area.showOsdHandler); area.destroy(); } From fb605f298c484be310da61c2c3e5fdb26d87850e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Abram=C3=A9?= Date: Mon, 4 Dec 2023 09:36:36 +0100 Subject: [PATCH 4/4] resolve bug on disabling extension --- ui/areamanager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/areamanager.js b/ui/areamanager.js index 264cdf8..782e992 100644 --- a/ui/areamanager.js +++ b/ui/areamanager.js @@ -478,7 +478,7 @@ export class AreaManager { } removeAreas() { - for (const area in this.areas) { + for (const area of this.areas) { area.disconnect(area.leaveDrawingHandler); area.disconnect(area.showOsdHandler); area.destroy();