diff --git a/NEWS b/NEWS index 0e706ab..65de969 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,10 @@ +v8.1 - October 2020 +=================== + +* Fix unsanitized GType names #46 +* Quick free drawings are smoother #45 +* Transformations are undoable/redoable + v8 - September 2020 =================== diff --git a/area.js b/area.js index 3b5444c..4eb284b 100644 --- a/area.js +++ b/area.js @@ -47,10 +47,12 @@ const pgettext = imports.gettext.domain(Me.metadata['gettext-domain']).pgettext; const CAIRO_DEBUG_EXTENDS = false; const SVG_DEBUG_EXTENDS = false; +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 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 { Shapes, Transformations } = Elements; const { DisplayStrings } = Menu; @@ -85,7 +87,7 @@ const getColorFromString = function(string, fallback) { // It creates and manages a DrawingElement for each "brushstroke". // It handles pointer/mouse/(touch?) events and some keyboard events. var DrawingArea = new Lang.Class({ - Name: `${Me.uuid}.DrawingArea`, + Name: `${UUID}-DrawingArea`, Extends: St.DrawingArea, Signals: { 'show-osd': { param_types: [Gio.Icon.$gtype, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_DOUBLE, GObject.TYPE_BOOLEAN] }, 'update-action-mode': {}, @@ -393,8 +395,14 @@ var DrawingArea = new Lang.Class({ }, _onStageKeyPressed: function(actor, event) { - if (event.get_key_symbol() == Clutter.KEY_space) + if (event.get_key_symbol() == Clutter.KEY_Escape) { + if (this.helper.visible) + this.toggleHelp(); + else + this.emit('leave-drawing-mode'); + } else if (event.get_key_symbol() == Clutter.KEY_space) { this.spaceKeyPressed = true; + } return Clutter.EVENT_PROPAGATE; }, @@ -407,38 +415,28 @@ var DrawingArea = new Lang.Class({ }, _onKeyPressed: function(actor, event) { - if (this.currentElement && this.currentElement.shape == Shapes.LINE) { - if (event.get_key_symbol() == Clutter.KEY_Return || - 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.currentElement.addPoint(); - this.updatePointerCursor(true); - this._redisplay(); - return Clutter.EVENT_STOP; - } else { - return Clutter.EVENT_PROPAGATE; - } - + if (this.currentElement && this.currentElement.shape == Shapes.LINE && + (event.get_key_symbol() == Clutter.KEY_Return || + 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.currentElement.addPoint(); + this.updatePointerCursor(true); + this._redisplay(); + return Clutter.EVENT_STOP; } else if (this.currentElement && (this.currentElement.shape == Shapes.POLYGON || this.currentElement.shape == Shapes.POLYLINE) && (event.get_key_symbol() == Clutter.KEY_Return || event.get_key_symbol() == Clutter.KEY_KP_Enter)) { + this.currentElement.addPoint(); return Clutter.EVENT_STOP; - - } else if (event.get_key_symbol() == Clutter.KEY_Escape) { - if (this.helper.visible) - this.toggleHelp(); - else - this.emit('leave-drawing-mode'); - return Clutter.EVENT_STOP; - - } else { - return Clutter.EVENT_PROPAGATE; } + + return Clutter.EVENT_PROPAGATE; }, _onScroll: function(actor, event) { @@ -535,16 +533,17 @@ var DrawingArea = new Lang.Class({ this.grabbedElement = copy; } + let undoable = !duplicate; + if (this.currentTool == Manipulations.MOVE) - this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformations.ROTATION : Transformations.TRANSLATION); + this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformations.ROTATION : Transformations.TRANSLATION, undoable); else if (this.currentTool == Manipulations.RESIZE) - this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformations.STRETCH : Transformations.SCALE_PRESERVE); + this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformations.STRETCH : Transformations.SCALE_PRESERVE, undoable); else if (this.currentTool == Manipulations.MIRROR) { - this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformations.INVERSION : Transformations.REFLECTION); + this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformations.INVERSION : Transformations.REFLECTION, undoable); this._redisplay(); } - this.motionHandler = this.connect('motion-event', (actor, event) => { if (this.spaceKeyPressed) return; @@ -559,28 +558,30 @@ var DrawingArea = new Lang.Class({ }, _updateTransforming: function(x, y, controlPressed) { + let undoable = this.grabbedElement.lastTransformation.undoable || false; + if (controlPressed && this.grabbedElement.lastTransformation.type == Transformations.TRANSLATION) { this.grabbedElement.stopTransformation(); - this.grabbedElement.startTransformation(x, y, Transformations.ROTATION); + this.grabbedElement.startTransformation(x, y, Transformations.ROTATION, undoable); } else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformations.ROTATION) { this.grabbedElement.stopTransformation(); - this.grabbedElement.startTransformation(x, y, Transformations.TRANSLATION); + this.grabbedElement.startTransformation(x, y, Transformations.TRANSLATION, undoable); } if (controlPressed && this.grabbedElement.lastTransformation.type == Transformations.SCALE_PRESERVE) { this.grabbedElement.stopTransformation(); - this.grabbedElement.startTransformation(x, y, Transformations.STRETCH); + this.grabbedElement.startTransformation(x, y, Transformations.STRETCH, undoable); } else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformations.STRETCH) { this.grabbedElement.stopTransformation(); - this.grabbedElement.startTransformation(x, y, Transformations.SCALE_PRESERVE); + this.grabbedElement.startTransformation(x, y, Transformations.SCALE_PRESERVE, undoable); } if (controlPressed && this.grabbedElement.lastTransformation.type == Transformations.REFLECTION) { this.grabbedElement.transformations.pop(); - this.grabbedElement.startTransformation(x, y, Transformations.INVERSION); + this.grabbedElement.startTransformation(x, y, Transformations.INVERSION, undoable); } else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformations.INVERSION) { this.grabbedElement.transformations.pop(); - this.grabbedElement.startTransformation(x, y, Transformations.REFLECTION); + this.grabbedElement.startTransformation(x, y, Transformations.REFLECTION, undoable); } this.grabbedElement.updateTransformation(x, y); @@ -655,6 +656,11 @@ var DrawingArea = new Lang.Class({ } this.motionHandler = this.connect('motion-event', (actor, event) => { + if (this.motionTimeout) { + GLib.source_remove(this.motionTimeout); + this.motionTimeout = null; + } + if (this.spaceKeyPressed) return; @@ -664,6 +670,30 @@ var DrawingArea = new Lang.Class({ return; let controlPressed = event.has_control_modifier(); this._updateDrawing(x, y, controlPressed); + + if (this.currentTool == Shapes.NONE) { + let device = event.get_device(); + let sequence = event.get_event_sequence(); + + // Minimum time between two motion events is about 33 ms. + // Add intermediate points to make quick free drawings smoother. + this.motionTimeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, MOTION_TIME, () => { + let [success, coords] = device.get_coords(sequence); + if (!success) + return GLib.SOURCE_CONTINUE; + + let [s, x, y] = this.transform_stage_point(coords.x, coords.y); + if (!s) + return GLib.SOURCE_CONTINUE; + + // Important: do not call this._updateDrawing because the area MUST NOT BE REDISPLAYED at this step. + // It would lead to critical issues (bad performances and shell crashes). + // The area will be redisplayed, including the intermediate points, at the next motion event. + this.currentElement.addIntermediatePoint(x, y, controlPressed); + + return GLib.SOURCE_CONTINUE; + }); + } }); }, @@ -678,6 +708,10 @@ var DrawingArea = new Lang.Class({ }, _stopDrawing: function() { + if (this.motionTimeout) { + GLib.source_remove(this.motionTimeout); + this.motionTimeout = null; + } if (this.motionHandler) { this.disconnect(this.motionHandler); this.motionHandler = null; @@ -872,18 +906,36 @@ var DrawingArea = new Lang.Class({ deleteLastElement: function() { this._stopAll(); this.elements.pop(); + + if (this.elements.length) + this.elements[this.elements.length - 1].resetUndoneTransformations(); + this._redisplay(); }, undo: function() { - if (this.elements.length > 0) + 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: function() { - if (this.undoneElements.length > 0) + 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(); }, @@ -1130,6 +1182,21 @@ var DrawingArea = new Lang.Class({ 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)); + } else { + if (this.stageKeyPressedHandler) { + global.stage.disconnect(this.stageKeyPressedHandler); + this.stageKeyPressedHandler = null; + } + if (this.stageKeyReleasedHandler) { + global.stage.disconnect(this.stageKeyReleasedHandler); + this.stageKeyReleasedHandler = null; + } + this.spaceKeyPressed = false; + } }, _onDestroy: function() { @@ -1144,8 +1211,6 @@ var DrawingArea = new Lang.Class({ }, enterDrawingMode: function() { - this.stageKeyPressedHandler = global.stage.connect('key-press-event', this._onStageKeyPressed.bind(this)); - this.stageKeyReleasedHandler = global.stage.connect('key-release-event', this._onStageKeyReleased.bind(this)); this.keyPressedHandler = this.connect('key-press-event', this._onKeyPressed.bind(this)); this.buttonPressedHandler = this.connect('button-press-event', this._onButtonPressed.bind(this)); this.keyboardPopupMenuHandler = this.connect('popup-menu', this._onKeyboardPopupMenu.bind(this)); @@ -1154,14 +1219,6 @@ var DrawingArea = new Lang.Class({ }, leaveDrawingMode: function(save, erase) { - if (this.stageKeyPressedHandler) { - global.stage.disconnect(this.stageKeyPressedHandler); - this.stageKeyPressedHandler = null; - } - if (this.stageKeyReleasedHandler) { - global.stage.disconnect(this.stageKeyReleasedHandler); - this.stageKeyReleasedHandler = null; - } if (this.keyPressedHandler) { this.disconnect(this.keyPressedHandler); this.keyPressedHandler = null; diff --git a/elements.js b/elements.js index b0d1868..2bbcaeb 100644 --- a/elements.js +++ b/elements.js @@ -28,9 +28,10 @@ const Pango = imports.gi.Pango; const PangoCairo = imports.gi.PangoCairo; const Me = imports.misc.extensionUtils.getCurrentExtension(); +const UUID = Me.uuid.replace(/@/gi, '_at_').replace(/[^a-z0-9+_-]/gi, '_'); var Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6, IMAGE: 7 }; -var Transformations = { TRANSLATION: 0, ROTATION: 1, SCALE_PRESERVE: 2, STRETCH: 3, REFLECTION: 4, INVERSION: 5 }; +var Transformations = { TRANSLATION: 0, ROTATION: 1, SCALE_PRESERVE: 2, STRETCH: 3, REFLECTION: 4, INVERSION: 5, SMOOTH: 100 }; var getAllFontFamilies = function() { return PangoCairo.font_map_get_default().list_families().map(fontFamily => fontFamily.get_name()).sort((a,b) => a.localeCompare(b)); @@ -61,6 +62,7 @@ const MIN_REFLECTION_LINE_LENGTH = 10; // px const MIN_TRANSLATION_DISTANCE = 1; // px const MIN_ROTATION_ANGLE = Math.PI / 1000; // rad const MIN_DRAWING_SIZE = 3; // px +const MIN_INTERMEDIATE_POINT_DISTANCE = 1; // px, the higher it is, the fewer points there will be var DrawingElement = function(params) { return params.shape == Shapes.TEXT ? new TextElement(params) : @@ -72,7 +74,7 @@ var DrawingElement = function(params) { // It can be converted into a cairo path as well as a svg element. // See DrawingArea._startDrawing() to know its params. const _DrawingElement = new Lang.Class({ - Name: `${Me.uuid}.DrawingElement`, + Name: `${UUID}-DrawingElement`, _init: function(params) { for (let key in params) @@ -121,7 +123,8 @@ const _DrawingElement = new Lang.Class({ fill: this.fill, fillRule: this.fillRule, eraser: this.eraser, - transformations: this.transformations, + transformations: this.transformations.filter(transformation => transformation.type != Transformations.SMOOTH) + .map(transformation => Object.assign({}, transformation, { undoable: undefined })), points: this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100]) }; }, @@ -400,9 +403,19 @@ const _DrawingElement = new Lang.Class({ }, smoothAll: function() { - for (let i = 0; i < this.points.length; i++) { + let oldPoints = this.points.slice(); + + for (let i = 0; i < this.points.length; i++) this._smooth(i); - } + + let newPoints = this.points.slice(); + + this.transformations.push({ type: Transformations.SMOOTH, undoable: true, + undo: () => this.points = oldPoints, + redo: () => this.points = newPoints }); + + if (this._undoneTransformations) + this._undoneTransformations = this._undoneTransformations.filter(transformation => transformation.type != Transformations.SMOOTH); }, addPoint: function() { @@ -421,6 +434,17 @@ const _DrawingElement = new Lang.Class({ } }, + // For free drawing only. + addIntermediatePoint: function(x, y, transform) { + let points = this.points; + if (getNearness(points[points.length - 1], [x, y], MIN_INTERMEDIATE_POINT_DISTANCE)) + return; + + points.push([x, y]); + if (transform) + this._smooth(points.length - 1); + }, + startDrawing: function(startX, startY) { this.points.push([startX, startY]); @@ -480,18 +504,18 @@ const _DrawingElement = new Lang.Class({ this.transformations.shift(); }, - startTransformation: function(startX, startY, type) { + startTransformation: function(startX, startY, type, undoable) { if (type == Transformations.TRANSLATION) - this.transformations.push({ startX: startX, startY: startY, type: type, slideX: 0, slideY: 0 }); + this.transformations.push({ startX, startY, type, undoable, slideX: 0, slideY: 0 }); else if (type == Transformations.ROTATION) - this.transformations.push({ startX: startX, startY: startY, type: type, angle: 0 }); + this.transformations.push({ startX, startY, type, undoable, angle: 0 }); else if (type == Transformations.SCALE_PRESERVE || type == Transformations.STRETCH) - this.transformations.push({ startX: startX, startY: startY, type: type, scaleX: 1, scaleY: 1, angle: 0 }); + this.transformations.push({ startX, startY, type, undoable, scaleX: 1, scaleY: 1, angle: 0 }); else if (type == Transformations.REFLECTION) - this.transformations.push({ startX: startX, startY: startY, endX: startX, endY: startY, type: type, + this.transformations.push({ startX, startY, endX: startX, endY: startY, type, undoable, scaleX: 1, scaleY: 1, slideX: 0, slideY: 0, angle: 0 }); else if (type == Transformations.INVERSION) - this.transformations.push({ startX: startX, startY: startY, endX: startX, endY: startY, type: type, + this.transformations.push({ startX, startY, endX: startX, endY: startY, type, undoable, scaleX: -1, scaleY: -1, slideX: startX, slideY: startY, angle: Math.PI + Math.atan(startY / (startX || 1)) }); @@ -573,6 +597,52 @@ const _DrawingElement = new Lang.Class({ } }, + undoTransformation: function() { + if (this.transformations && this.transformations.length) { + // Do not undo initial transformations (transformations made during the drawing step). + if (!this.lastTransformation.undoable) + return false; + + if (!this._undoneTransformations) + this._undoneTransformations = []; + + let transformation = this.transformations.pop(); + if (transformation.type == Transformations.SMOOTH) + transformation.undo(); + + this._undoneTransformations.push(transformation); + + return true; + } + + return false; + }, + + redoTransformation: function() { + if (this._undoneTransformations && this._undoneTransformations.length) { + if (!this.transformations) + this.transformations = []; + + let transformation = this._undoneTransformations.pop(); + if (transformation.type == Transformations.SMOOTH) + transformation.redo(); + + this.transformations.push(transformation); + + return true; + } + + return false; + }, + + resetUndoneTransformations: function() { + delete this._undoneTransformations; + }, + + get canUndo() { + return this._undoneTransformations && this._undoneTransformations.length ? true : false; + }, + // The figure rotation center before transformations (original). // this.textWidth is computed during Cairo building. _getOriginalCenter: function() { @@ -626,7 +696,7 @@ const _DrawingElement = new Lang.Class({ }); const TextElement = new Lang.Class({ - Name: `${Me.uuid}.TextElement`, + Name: `${UUID}-TextElement`, Extends: _DrawingElement, toJSON: function() { @@ -766,7 +836,7 @@ const TextElement = new Lang.Class({ }); const ImageElement = new Lang.Class({ - Name: `${Me.uuid}.ImageElement`, + Name: `${UUID}-ImageElement`, Extends: _DrawingElement, toJSON: function() { diff --git a/extension.js b/extension.js index 23360d7..a6d2744 100644 --- a/extension.js +++ b/extension.js @@ -41,6 +41,7 @@ const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; 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, '_'); // custom Shell.ActionMode, assuming that they are unused const DRAWING_ACTION_MODE = Math.pow(2,14); @@ -53,7 +54,7 @@ function init() { } const Extension = new Lang.Class({ - Name: `${Me.uuid}.Extension`, + Name: `${UUID}-Extension`, _init: function() { Convenience.initTranslations(); @@ -81,7 +82,7 @@ const Extension = new Lang.Class({ // distributes keybinding callbacks to the active area // and handles stylesheet and monitor changes. const AreaManager = new Lang.Class({ - Name: `${Me.uuid}.AreaManager`, + Name: `${UUID}-AreaManager`, _init: function() { this.areas = []; @@ -560,7 +561,7 @@ const AreaManager = new Lang.Class({ // The same as the original, without forcing a ratio of 1. const OsdWindowConstraint = new Lang.Class({ - Name: `${Me.uuid}.OsdWindowConstraint`, + Name: `${UUID}-OsdWindowConstraint`, Extends: OsdWindow.OsdWindowConstraint, vfunc_update_allocation: function(actor, actorBox) { @@ -582,7 +583,7 @@ const OsdWindowConstraint = new Lang.Class({ }); const DrawingIndicator = new Lang.Class({ - Name: `${Me.uuid}.Indicator`, + Name: `${UUID}-Indicator`, _init: function() { let [menuAlignment, dontCreateMenu] = [0, true]; diff --git a/files.js b/files.js index 8334601..1f31691 100644 --- a/files.js +++ b/files.js @@ -31,6 +31,7 @@ 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(); @@ -98,7 +99,7 @@ const replaceColor = function (contents, color) { // 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 = new Lang.Class({ - Name: `${Me.uuid}.Image`, + Name: `${UUID}-Image`, _init: function(params) { for (let key in params) @@ -193,7 +194,7 @@ var Image = new Lang.Class({ // Add a gicon generator to Image. It is used with image files and it takes { file, info } as params. const ImageWithGicon = new Lang.Class({ - Name: `${Me.uuid}.ImageWithGicon`, + Name: `${UUID}-ImageWithGicon`, Extends: Image, get displayName() { @@ -247,7 +248,7 @@ const ImageWithGicon = new Lang.Class({ // It is directly generated from a Json object, without an image file. It takes { bytes, displayName, gicon } as params. const ImageFromJson = new Lang.Class({ - Name: `${Me.uuid}.ImageFromJson`, + Name: `${UUID}-ImageFromJson`, Extends: Image, contentType: 'image/svg+xml', @@ -394,7 +395,7 @@ var Images = { // Wrapper around a json file (drawing saves). var Json = new Lang.Class({ - Name: `${Me.uuid}.Json`, + Name: `${UUID}-Json`, _init: function(params) { for (let key in params) diff --git a/helper.js b/helper.js index 70343e4..8616adb 100644 --- a/helper.js +++ b/helper.js @@ -40,11 +40,12 @@ 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 var DrawingHelper = new Lang.Class({ - Name: `${Me.uuid}.DrawingHelper`, + Name: `${UUID}-DrawingHelper`, Extends: St.ScrollView, _init: function(params, monitor) { diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index 3ad0099..5e87946 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -10,7 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: Draw On Your Screen\n" "Report-Msgid-Bugs-To: https://framagit.org/abakkk/DrawOnYourScreen/issues\n" -"POT-Creation-Date: 2020-09-19 15:32+0200\n" +"POT-Creation-Date: 2020-10-04 22:45+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -265,12 +265,6 @@ msgctxt "drawing-tool" msgid "Mirror" msgstr "" -msgid "Undo" -msgstr "" - -msgid "Redo" -msgstr "" - msgid "Erase" msgstr "" @@ -586,7 +580,7 @@ msgstr "" msgid "Add images from the clipboard" msgstr "" -msgid "Redo last brushstroke" +msgid "Redo" msgstr "" msgid "Save drawing" @@ -710,5 +704,5 @@ msgstr "" msgid "Square drawing area" msgstr "" -msgid "Undo last brushstroke" +msgid "Undo" msgstr "" diff --git a/menu.js b/menu.js index 2688f27..4c1c8ea 100644 --- a/menu.js +++ b/menu.js @@ -46,6 +46,7 @@ const GS_VERSION = Config.PACKAGE_VERSION; 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 getActor = function(object) { return GS_VERSION < '3.33.0' ? object.actor : object; @@ -139,7 +140,7 @@ var DisplayStrings = { }; var DrawingMenu = new Lang.Class({ - Name: `${Me.uuid}.DrawingMenu`, + Name: `${UUID}-DrawingMenu`, _init: function(area, monitor, drawingTools) { this.area = area; @@ -229,12 +230,11 @@ var DrawingMenu = new Lang.Class({ 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(_("Undo"), 'edit-undo-symbolic', this.area.undo.bind(this.area), this._updateActionSensitivity.bind(this)); - this.redoButton = new ActionButton(_("Redo"), 'edit-redo-symbolic', this.area.redo.bind(this.area), this._updateActionSensitivity.bind(this)); + 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.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.eraseButton.child.add_style_class_name('draw-on-your-screen-menu-destructive-button'); - this.smoothButton.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); @@ -308,7 +308,7 @@ var DrawingMenu = new Lang.Class({ _updateActionSensitivity: function() { this.undoButton.child.reactive = this.area.elements.length > 0; - this.redoButton.child.reactive = this.area.undoneElements.length > 0; + this.redoButton.child.reactive = this.area.undoneElements.length > 0 || (this.area.elements.length && this.area.elements[this.area.elements.length - 1].canUndo); this.eraseButton.child.reactive = this.area.elements.length > 0; this.smoothButton.child.reactive = this.area.elements.length > 0 && this.area.elements[this.area.elements.length - 1].shape == this.drawingTools.NONE; this.saveButton.child.reactive = this.area.elements.length > 0; @@ -742,7 +742,7 @@ const updateSubMenuAdjustment = function(itemActor) { // An action button that uses upstream dash item tooltips. const ActionButton = new Lang.Class({ - Name: `${Me.uuid}.DrawingMenuActionButton`, + Name: `${UUID}-DrawingMenuActionButton`, Extends: St.Bin, _labelShowing: false, _resetHoverTimeoutId: 0, @@ -787,7 +787,7 @@ const ActionButton = new Lang.Class({ // based on searchItem.js, https://github.com/leonardo-bartoli/gnome-shell-extension-Recents const Entry = new Lang.Class({ - Name: `${Me.uuid}.DrawingMenuEntry`, + Name: `${UUID}-DrawingMenuEntry`, _init: function(params) { this.params = params; diff --git a/metadata.json b/metadata.json index fd0f6ae..7846d9c 100644 --- a/metadata.json +++ b/metadata.json @@ -18,5 +18,5 @@ "3.36", "3.38" ], - "version": 8 + "version": 8.1 } diff --git a/prefs.js b/prefs.js index eb01b24..434c9ab 100644 --- a/prefs.js +++ b/prefs.js @@ -43,11 +43,7 @@ const _GTK = imports.gettext.domain('gtk30').gettext; const MARGIN = 10; const ROWBOX_MARGIN_PARAMS = { margin_top: MARGIN / 2, margin_bottom: MARGIN / 2, margin_left: MARGIN, margin_right: MARGIN }; - -// GTypeName is not sanitized in GS 3.28- -const sanitizeGType = function(name) { - return `Gjs_${name.replace(/@/gi, '_at_').replace(/[^a-z0-9+_-]/gi, '_')}`; -} +const UUID = Me.uuid.replace(/@/gi, '_at_').replace(/[^a-z0-9+_-]/gi, '_'); function init() { Convenience.initTranslations(); @@ -68,8 +64,7 @@ function buildPrefsWidget() { } const TopStack = new GObject.Class({ - Name: `${Me.uuid}.TopStack`, - GTypeName: sanitizeGType(`${Me.uuid}-TopStack`), + Name: `${UUID}-TopStack`, Extends: Gtk.Stack, _init: function(params) { @@ -87,8 +82,7 @@ const TopStack = new GObject.Class({ }); const AboutPage = new GObject.Class({ - Name: `${Me.uuid}.AboutPage`, - GTypeName: sanitizeGType(`${Me.uuid}-AboutPage`), + Name: `${UUID}-AboutPage`, Extends: Gtk.ScrolledWindow, _init: function(params) { @@ -141,8 +135,7 @@ const AboutPage = new GObject.Class({ }); const DrawingPage = new GObject.Class({ - Name: `${Me.uuid}.DrawingPage`, - GTypeName: sanitizeGType(`${Me.uuid}-DrawingPage`), + Name: `${UUID}-DrawingPage`, Extends: Gtk.ScrolledWindow, _init: function(params) { @@ -394,8 +387,7 @@ const DrawingPage = new GObject.Class({ }); const PrefsPage = new GObject.Class({ - Name: `${Me.uuid}.PrefsPage`, - GTypeName: sanitizeGType(`${Me.uuid}-PrefsPage`), + Name: `${UUID}-PrefsPage`, Extends: Gtk.ScrolledWindow, _init: function(params) { @@ -508,8 +500,7 @@ const PrefsPage = new GObject.Class({ }); const Frame = new GObject.Class({ - Name: `${Me.uuid}.Frame`, - GTypeName: sanitizeGType(`${Me.uuid}-Frame`), + Name: `${UUID}-Frame`, Extends: Gtk.Frame, _init: function(params) { @@ -524,8 +515,7 @@ const Frame = new GObject.Class({ }); const PrefRow = new GObject.Class({ - Name: `${Me.uuid}.PrefRow`, - GTypeName: sanitizeGType(`${Me.uuid}-PrefRow`), + Name: `${UUID}-PrefRow`, Extends: Gtk.ListBoxRow, _init: function(params) { @@ -569,8 +559,7 @@ const PrefRow = new GObject.Class({ }); const PixelSpinButton = new GObject.Class({ - Name: `${Me.uuid}.PixelSpinButton`, - GTypeName: sanitizeGType(`${Me.uuid}-PixelSpinButton`), + Name: `${UUID}-PixelSpinButton`, Extends: Gtk.SpinButton, Properties: { 'range': GObject.param_spec_variant('range', 'range', 'GSettings range', @@ -609,8 +598,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: `${Me.uuid}.ColorStringButton`, - GTypeName: sanitizeGType(`${Me.uuid}-ColorStringButton`), + Name: `${UUID}-ColorStringButton`, Extends: Gtk.ColorButton, Properties: { 'color-string': GObject.ParamSpec.string('color-string', 'colorString', 'A string that describes the color', @@ -642,8 +630,7 @@ const ColorStringButton = new GObject.Class({ }); const FileChooserButton = new GObject.Class({ - Name: `${Me.uuid}.FileChooserButton`, - GTypeName: sanitizeGType(`${Me.uuid}-FileChooserButton`), + Name: `${UUID}-FileChooserButton`, Extends: Gtk.FileChooserButton, Properties: { 'location': GObject.ParamSpec.string('location', 'location', 'location', @@ -674,8 +661,7 @@ const FileChooserButton = new GObject.Class({ // this code comes from Sticky Notes View by Sam Bull, https://extensions.gnome.org/extension/568/notes/ const KeybindingsWidget = new GObject.Class({ - Name: `${Me.uuid}.KeybindingsWidget`, - GTypeName: sanitizeGType(`${Me.uuid}-KeybindingsWidget`), + Name: `${UUID}-KeybindingsWidget`, Extends: Gtk.Box, _init: function(settingKeys, settings) { diff --git a/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml b/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml index e13b393..1b26430 100644 --- a/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml +++ b/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml @@ -170,7 +170,7 @@ ["<Primary><Shift>z"] - Redo last brushstroke + Redo ["<Primary>s"] @@ -340,7 +340,7 @@ ["<Primary>z"] - Undo last brushstroke + Undo