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