Merge branch 'dev' into 'master'

v8.1

Closes #46 and #45

See merge request abakkk/DrawOnYourScreen!18
This commit is contained in:
abakkk 2020-10-06 17:31:03 +02:00
commit b3fffc193e
11 changed files with 233 additions and 116 deletions

7
NEWS
View File

@ -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 v8 - September 2020
=================== ===================

157
area.js
View File

@ -47,10 +47,12 @@ const pgettext = imports.gettext.domain(Me.metadata['gettext-domain']).pgettext;
const CAIRO_DEBUG_EXTENDS = false; const CAIRO_DEBUG_EXTENDS = false;
const SVG_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 TEXT_CURSOR_TIME = 600; // ms
const ELEMENT_GRABBER_TIME = 80; // ms, default is about 16 ms const ELEMENT_GRABBER_TIME = 80; // ms, default is about 16 ms
const GRID_TILES_HORIZONTAL_NUMBER = 30; const GRID_TILES_HORIZONTAL_NUMBER = 30;
const COLOR_PICKER_EXTENSION_UUID = 'color-picker@tuberry'; 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 { Shapes, Transformations } = Elements;
const { DisplayStrings } = Menu; const { DisplayStrings } = Menu;
@ -85,7 +87,7 @@ const getColorFromString = function(string, fallback) {
// It creates and manages a DrawingElement for each "brushstroke". // It creates and manages a DrawingElement for each "brushstroke".
// It handles pointer/mouse/(touch?) events and some keyboard events. // It handles pointer/mouse/(touch?) events and some keyboard events.
var DrawingArea = new Lang.Class({ var DrawingArea = new Lang.Class({
Name: `${Me.uuid}.DrawingArea`, Name: `${UUID}-DrawingArea`,
Extends: St.DrawingArea, Extends: St.DrawingArea,
Signals: { 'show-osd': { param_types: [Gio.Icon.$gtype, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_DOUBLE, GObject.TYPE_BOOLEAN] }, Signals: { 'show-osd': { param_types: [Gio.Icon.$gtype, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_DOUBLE, GObject.TYPE_BOOLEAN] },
'update-action-mode': {}, 'update-action-mode': {},
@ -393,8 +395,14 @@ var DrawingArea = new Lang.Class({
}, },
_onStageKeyPressed: function(actor, event) { _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; this.spaceKeyPressed = true;
}
return Clutter.EVENT_PROPAGATE; return Clutter.EVENT_PROPAGATE;
}, },
@ -407,38 +415,28 @@ var DrawingArea = new Lang.Class({
}, },
_onKeyPressed: function(actor, event) { _onKeyPressed: function(actor, event) {
if (this.currentElement && this.currentElement.shape == Shapes.LINE) { if (this.currentElement && this.currentElement.shape == Shapes.LINE &&
if (event.get_key_symbol() == Clutter.KEY_Return || (event.get_key_symbol() == Clutter.KEY_Return ||
event.get_key_symbol() == Clutter.KEY_KP_Enter || event.get_key_symbol() == Clutter.KEY_KP_Enter ||
event.get_key_symbol() == Clutter.KEY_Control_L) { event.get_key_symbol() == Clutter.KEY_Control_L)) {
if (this.currentElement.points.length == 2)
// Translators: %s is a key label if (this.currentElement.points.length == 2)
this.emit('show-osd', Files.Icons.ARC, _("Press <i>%s</i> to get\na fourth control point") // Translators: %s is a key label
.format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1, true); this.emit('show-osd', Files.Icons.ARC, _("Press <i>%s</i> to get\na fourth control point")
this.currentElement.addPoint(); .format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1, true);
this.updatePointerCursor(true); this.currentElement.addPoint();
this._redisplay(); this.updatePointerCursor(true);
return Clutter.EVENT_STOP; this._redisplay();
} else { return Clutter.EVENT_STOP;
return Clutter.EVENT_PROPAGATE;
}
} else if (this.currentElement && } else if (this.currentElement &&
(this.currentElement.shape == Shapes.POLYGON || this.currentElement.shape == Shapes.POLYLINE) && (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)) { (event.get_key_symbol() == Clutter.KEY_Return || event.get_key_symbol() == Clutter.KEY_KP_Enter)) {
this.currentElement.addPoint(); this.currentElement.addPoint();
return Clutter.EVENT_STOP; 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) { _onScroll: function(actor, event) {
@ -535,16 +533,17 @@ var DrawingArea = new Lang.Class({
this.grabbedElement = copy; this.grabbedElement = copy;
} }
let undoable = !duplicate;
if (this.currentTool == Manipulations.MOVE) 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) 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) { 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._redisplay();
} }
this.motionHandler = this.connect('motion-event', (actor, event) => { this.motionHandler = this.connect('motion-event', (actor, event) => {
if (this.spaceKeyPressed) if (this.spaceKeyPressed)
return; return;
@ -559,28 +558,30 @@ var DrawingArea = new Lang.Class({
}, },
_updateTransforming: function(x, y, controlPressed) { _updateTransforming: function(x, y, controlPressed) {
let undoable = this.grabbedElement.lastTransformation.undoable || false;
if (controlPressed && this.grabbedElement.lastTransformation.type == Transformations.TRANSLATION) { if (controlPressed && this.grabbedElement.lastTransformation.type == Transformations.TRANSLATION) {
this.grabbedElement.stopTransformation(); 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) { } else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformations.ROTATION) {
this.grabbedElement.stopTransformation(); 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) { if (controlPressed && this.grabbedElement.lastTransformation.type == Transformations.SCALE_PRESERVE) {
this.grabbedElement.stopTransformation(); 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) { } else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformations.STRETCH) {
this.grabbedElement.stopTransformation(); 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) { if (controlPressed && this.grabbedElement.lastTransformation.type == Transformations.REFLECTION) {
this.grabbedElement.transformations.pop(); 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) { } else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformations.INVERSION) {
this.grabbedElement.transformations.pop(); this.grabbedElement.transformations.pop();
this.grabbedElement.startTransformation(x, y, Transformations.REFLECTION); this.grabbedElement.startTransformation(x, y, Transformations.REFLECTION, undoable);
} }
this.grabbedElement.updateTransformation(x, y); this.grabbedElement.updateTransformation(x, y);
@ -655,6 +656,11 @@ var DrawingArea = new Lang.Class({
} }
this.motionHandler = this.connect('motion-event', (actor, event) => { this.motionHandler = this.connect('motion-event', (actor, event) => {
if (this.motionTimeout) {
GLib.source_remove(this.motionTimeout);
this.motionTimeout = null;
}
if (this.spaceKeyPressed) if (this.spaceKeyPressed)
return; return;
@ -664,6 +670,30 @@ var DrawingArea = new Lang.Class({
return; return;
let controlPressed = event.has_control_modifier(); let controlPressed = event.has_control_modifier();
this._updateDrawing(x, y, controlPressed); 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() { _stopDrawing: function() {
if (this.motionTimeout) {
GLib.source_remove(this.motionTimeout);
this.motionTimeout = null;
}
if (this.motionHandler) { if (this.motionHandler) {
this.disconnect(this.motionHandler); this.disconnect(this.motionHandler);
this.motionHandler = null; this.motionHandler = null;
@ -872,18 +906,36 @@ var DrawingArea = new Lang.Class({
deleteLastElement: function() { deleteLastElement: function() {
this._stopAll(); this._stopAll();
this.elements.pop(); this.elements.pop();
if (this.elements.length)
this.elements[this.elements.length - 1].resetUndoneTransformations();
this._redisplay(); this._redisplay();
}, },
undo: function() { 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()); this.undoneElements.push(this.elements.pop());
if (this.elements.length)
this.elements[this.elements.length - 1].resetUndoneTransformations();
}
this._redisplay(); this._redisplay();
}, },
redo: function() { 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.elements.push(this.undoneElements.pop());
this._redisplay(); this._redisplay();
}, },
@ -1130,6 +1182,21 @@ var DrawingArea = new Lang.Class({
this.toggleHelp(); this.toggleHelp();
if (this.textEntry && this.reactive) if (this.textEntry && this.reactive)
this.textEntry.grab_key_focus(); 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() { _onDestroy: function() {
@ -1144,8 +1211,6 @@ var DrawingArea = new Lang.Class({
}, },
enterDrawingMode: function() { 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.keyPressedHandler = this.connect('key-press-event', this._onKeyPressed.bind(this));
this.buttonPressedHandler = this.connect('button-press-event', this._onButtonPressed.bind(this)); this.buttonPressedHandler = this.connect('button-press-event', this._onButtonPressed.bind(this));
this.keyboardPopupMenuHandler = this.connect('popup-menu', this._onKeyboardPopupMenu.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) { 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) { if (this.keyPressedHandler) {
this.disconnect(this.keyPressedHandler); this.disconnect(this.keyPressedHandler);
this.keyPressedHandler = null; this.keyPressedHandler = null;

View File

@ -28,9 +28,10 @@ const Pango = imports.gi.Pango;
const PangoCairo = imports.gi.PangoCairo; const PangoCairo = imports.gi.PangoCairo;
const Me = imports.misc.extensionUtils.getCurrentExtension(); 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 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() { var getAllFontFamilies = function() {
return PangoCairo.font_map_get_default().list_families().map(fontFamily => fontFamily.get_name()).sort((a,b) => a.localeCompare(b)); 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_TRANSLATION_DISTANCE = 1; // px
const MIN_ROTATION_ANGLE = Math.PI / 1000; // rad const MIN_ROTATION_ANGLE = Math.PI / 1000; // rad
const MIN_DRAWING_SIZE = 3; // px 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) { var DrawingElement = function(params) {
return params.shape == Shapes.TEXT ? new TextElement(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. // It can be converted into a cairo path as well as a svg element.
// See DrawingArea._startDrawing() to know its params. // See DrawingArea._startDrawing() to know its params.
const _DrawingElement = new Lang.Class({ const _DrawingElement = new Lang.Class({
Name: `${Me.uuid}.DrawingElement`, Name: `${UUID}-DrawingElement`,
_init: function(params) { _init: function(params) {
for (let key in params) for (let key in params)
@ -121,7 +123,8 @@ const _DrawingElement = new Lang.Class({
fill: this.fill, fill: this.fill,
fillRule: this.fillRule, fillRule: this.fillRule,
eraser: this.eraser, 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]) 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() { 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); 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() { 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) { startDrawing: function(startX, startY) {
this.points.push([startX, startY]); this.points.push([startX, startY]);
@ -480,18 +504,18 @@ const _DrawingElement = new Lang.Class({
this.transformations.shift(); this.transformations.shift();
}, },
startTransformation: function(startX, startY, type) { startTransformation: function(startX, startY, type, undoable) {
if (type == Transformations.TRANSLATION) 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) 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) 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) 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 }); scaleX: 1, scaleY: 1, slideX: 0, slideY: 0, angle: 0 });
else if (type == Transformations.INVERSION) 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, scaleX: -1, scaleY: -1, slideX: startX, slideY: startY,
angle: Math.PI + Math.atan(startY / (startX || 1)) }); 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). // The figure rotation center before transformations (original).
// this.textWidth is computed during Cairo building. // this.textWidth is computed during Cairo building.
_getOriginalCenter: function() { _getOriginalCenter: function() {
@ -626,7 +696,7 @@ const _DrawingElement = new Lang.Class({
}); });
const TextElement = new Lang.Class({ const TextElement = new Lang.Class({
Name: `${Me.uuid}.TextElement`, Name: `${UUID}-TextElement`,
Extends: _DrawingElement, Extends: _DrawingElement,
toJSON: function() { toJSON: function() {
@ -766,7 +836,7 @@ const TextElement = new Lang.Class({
}); });
const ImageElement = new Lang.Class({ const ImageElement = new Lang.Class({
Name: `${Me.uuid}.ImageElement`, Name: `${UUID}-ImageElement`,
Extends: _DrawingElement, Extends: _DrawingElement,
toJSON: function() { toJSON: function() {

View File

@ -41,6 +41,7 @@ const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext;
const GS_VERSION = Config.PACKAGE_VERSION; const GS_VERSION = Config.PACKAGE_VERSION;
const HIDE_TIMEOUT_LONG = 2500; // ms, default is 1500 ms 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 // custom Shell.ActionMode, assuming that they are unused
const DRAWING_ACTION_MODE = Math.pow(2,14); const DRAWING_ACTION_MODE = Math.pow(2,14);
@ -53,7 +54,7 @@ function init() {
} }
const Extension = new Lang.Class({ const Extension = new Lang.Class({
Name: `${Me.uuid}.Extension`, Name: `${UUID}-Extension`,
_init: function() { _init: function() {
Convenience.initTranslations(); Convenience.initTranslations();
@ -81,7 +82,7 @@ const Extension = new Lang.Class({
// distributes keybinding callbacks to the active area // distributes keybinding callbacks to the active area
// and handles stylesheet and monitor changes. // and handles stylesheet and monitor changes.
const AreaManager = new Lang.Class({ const AreaManager = new Lang.Class({
Name: `${Me.uuid}.AreaManager`, Name: `${UUID}-AreaManager`,
_init: function() { _init: function() {
this.areas = []; this.areas = [];
@ -560,7 +561,7 @@ const AreaManager = new Lang.Class({
// The same as the original, without forcing a ratio of 1. // The same as the original, without forcing a ratio of 1.
const OsdWindowConstraint = new Lang.Class({ const OsdWindowConstraint = new Lang.Class({
Name: `${Me.uuid}.OsdWindowConstraint`, Name: `${UUID}-OsdWindowConstraint`,
Extends: OsdWindow.OsdWindowConstraint, Extends: OsdWindow.OsdWindowConstraint,
vfunc_update_allocation: function(actor, actorBox) { vfunc_update_allocation: function(actor, actorBox) {
@ -582,7 +583,7 @@ const OsdWindowConstraint = new Lang.Class({
}); });
const DrawingIndicator = new Lang.Class({ const DrawingIndicator = new Lang.Class({
Name: `${Me.uuid}.Indicator`, Name: `${UUID}-Indicator`,
_init: function() { _init: function() {
let [menuAlignment, dontCreateMenu] = [0, true]; let [menuAlignment, dontCreateMenu] = [0, true];

View File

@ -31,6 +31,7 @@ const St = imports.gi.St;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension(); 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 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 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 = 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) // 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. // and it takes { displayName, contentType, base64, hash } as params.
var Image = new Lang.Class({ var Image = new Lang.Class({
Name: `${Me.uuid}.Image`, Name: `${UUID}-Image`,
_init: function(params) { _init: function(params) {
for (let key in 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. // Add a gicon generator to Image. It is used with image files and it takes { file, info } as params.
const ImageWithGicon = new Lang.Class({ const ImageWithGicon = new Lang.Class({
Name: `${Me.uuid}.ImageWithGicon`, Name: `${UUID}-ImageWithGicon`,
Extends: Image, Extends: Image,
get displayName() { 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. // It is directly generated from a Json object, without an image file. It takes { bytes, displayName, gicon } as params.
const ImageFromJson = new Lang.Class({ const ImageFromJson = new Lang.Class({
Name: `${Me.uuid}.ImageFromJson`, Name: `${UUID}-ImageFromJson`,
Extends: Image, Extends: Image,
contentType: 'image/svg+xml', contentType: 'image/svg+xml',
@ -394,7 +395,7 @@ var Images = {
// Wrapper around a json file (drawing saves). // Wrapper around a json file (drawing saves).
var Json = new Lang.Class({ var Json = new Lang.Class({
Name: `${Me.uuid}.Json`, Name: `${UUID}-Json`,
_init: function(params) { _init: function(params) {
for (let key in params) for (let key in params)

View File

@ -40,11 +40,12 @@ const Tweener = GS_VERSION < '3.33.0' ? imports.ui.tweener : null;
const HELPER_ANIMATION_TIME = 0.25; const HELPER_ANIMATION_TIME = 0.25;
const MEDIA_KEYS_SCHEMA = 'org.gnome.settings-daemon.plugins.media-keys'; const MEDIA_KEYS_SCHEMA = 'org.gnome.settings-daemon.plugins.media-keys';
const MEDIA_KEYS_KEYS = ['screenshot', 'screenshot-clip', 'area-screenshot', 'area-screenshot-clip']; 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) // DrawingHelper provides the "help osd" (Ctrl + F1)
// It uses the same texts as in prefs // It uses the same texts as in prefs
var DrawingHelper = new Lang.Class({ var DrawingHelper = new Lang.Class({
Name: `${Me.uuid}.DrawingHelper`, Name: `${UUID}-DrawingHelper`,
Extends: St.ScrollView, Extends: St.ScrollView,
_init: function(params, monitor) { _init: function(params, monitor) {

View File

@ -10,7 +10,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Draw On Your Screen\n" "Project-Id-Version: Draw On Your Screen\n"
"Report-Msgid-Bugs-To: https://framagit.org/abakkk/DrawOnYourScreen/issues\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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -265,12 +265,6 @@ msgctxt "drawing-tool"
msgid "Mirror" msgid "Mirror"
msgstr "" msgstr ""
msgid "Undo"
msgstr ""
msgid "Redo"
msgstr ""
msgid "Erase" msgid "Erase"
msgstr "" msgstr ""
@ -586,7 +580,7 @@ msgstr ""
msgid "Add images from the clipboard" msgid "Add images from the clipboard"
msgstr "" msgstr ""
msgid "Redo last brushstroke" msgid "Redo"
msgstr "" msgstr ""
msgid "Save drawing" msgid "Save drawing"
@ -710,5 +704,5 @@ msgstr ""
msgid "Square drawing area" msgid "Square drawing area"
msgstr "" msgstr ""
msgid "Undo last brushstroke" msgid "Undo"
msgstr "" msgstr ""

14
menu.js
View File

@ -46,6 +46,7 @@ const GS_VERSION = Config.PACKAGE_VERSION;
const FONT_FAMILY_STYLE = true; const FONT_FAMILY_STYLE = true;
// use 'login-dialog-message-warning' class in order to get GS theme warning color (default: #f57900) // 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 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) { const getActor = function(object) {
return GS_VERSION < '3.33.0' ? object.actor : object; return GS_VERSION < '3.33.0' ? object.actor : object;
@ -139,7 +140,7 @@ var DisplayStrings = {
}; };
var DrawingMenu = new Lang.Class({ var DrawingMenu = new Lang.Class({
Name: `${Me.uuid}.DrawingMenu`, Name: `${UUID}-DrawingMenu`,
_init: function(area, monitor, drawingTools) { _init: function(area, monitor, drawingTools) {
this.area = area; this.area = area;
@ -229,12 +230,11 @@ var DrawingMenu = new Lang.Class({
this.menu.removeAll(); this.menu.removeAll();
let groupItem = new PopupMenu.PopupBaseMenuItem({ reactive: false, can_focus: false, style_class: 'draw-on-your-screen-menu-group-item' }); 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.undoButton = new ActionButton(getSummary('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.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.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"), 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.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.undoButton);
getActor(groupItem).add_child(this.redoButton); getActor(groupItem).add_child(this.redoButton);
getActor(groupItem).add_child(this.eraseButton); getActor(groupItem).add_child(this.eraseButton);
@ -308,7 +308,7 @@ var DrawingMenu = new Lang.Class({
_updateActionSensitivity: function() { _updateActionSensitivity: function() {
this.undoButton.child.reactive = this.area.elements.length > 0; 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.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.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; 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. // An action button that uses upstream dash item tooltips.
const ActionButton = new Lang.Class({ const ActionButton = new Lang.Class({
Name: `${Me.uuid}.DrawingMenuActionButton`, Name: `${UUID}-DrawingMenuActionButton`,
Extends: St.Bin, Extends: St.Bin,
_labelShowing: false, _labelShowing: false,
_resetHoverTimeoutId: 0, _resetHoverTimeoutId: 0,
@ -787,7 +787,7 @@ const ActionButton = new Lang.Class({
// based on searchItem.js, https://github.com/leonardo-bartoli/gnome-shell-extension-Recents // based on searchItem.js, https://github.com/leonardo-bartoli/gnome-shell-extension-Recents
const Entry = new Lang.Class({ const Entry = new Lang.Class({
Name: `${Me.uuid}.DrawingMenuEntry`, Name: `${UUID}-DrawingMenuEntry`,
_init: function(params) { _init: function(params) {
this.params = params; this.params = params;

View File

@ -18,5 +18,5 @@
"3.36", "3.36",
"3.38" "3.38"
], ],
"version": 8 "version": 8.1
} }

View File

@ -43,11 +43,7 @@ const _GTK = imports.gettext.domain('gtk30').gettext;
const MARGIN = 10; const MARGIN = 10;
const ROWBOX_MARGIN_PARAMS = { margin_top: MARGIN / 2, margin_bottom: MARGIN / 2, margin_left: MARGIN, margin_right: MARGIN }; const ROWBOX_MARGIN_PARAMS = { margin_top: MARGIN / 2, margin_bottom: MARGIN / 2, margin_left: MARGIN, margin_right: MARGIN };
const UUID = Me.uuid.replace(/@/gi, '_at_').replace(/[^a-z0-9+_-]/gi, '_');
// GTypeName is not sanitized in GS 3.28-
const sanitizeGType = function(name) {
return `Gjs_${name.replace(/@/gi, '_at_').replace(/[^a-z0-9+_-]/gi, '_')}`;
}
function init() { function init() {
Convenience.initTranslations(); Convenience.initTranslations();
@ -68,8 +64,7 @@ function buildPrefsWidget() {
} }
const TopStack = new GObject.Class({ const TopStack = new GObject.Class({
Name: `${Me.uuid}.TopStack`, Name: `${UUID}-TopStack`,
GTypeName: sanitizeGType(`${Me.uuid}-TopStack`),
Extends: Gtk.Stack, Extends: Gtk.Stack,
_init: function(params) { _init: function(params) {
@ -87,8 +82,7 @@ const TopStack = new GObject.Class({
}); });
const AboutPage = new GObject.Class({ const AboutPage = new GObject.Class({
Name: `${Me.uuid}.AboutPage`, Name: `${UUID}-AboutPage`,
GTypeName: sanitizeGType(`${Me.uuid}-AboutPage`),
Extends: Gtk.ScrolledWindow, Extends: Gtk.ScrolledWindow,
_init: function(params) { _init: function(params) {
@ -141,8 +135,7 @@ const AboutPage = new GObject.Class({
}); });
const DrawingPage = new GObject.Class({ const DrawingPage = new GObject.Class({
Name: `${Me.uuid}.DrawingPage`, Name: `${UUID}-DrawingPage`,
GTypeName: sanitizeGType(`${Me.uuid}-DrawingPage`),
Extends: Gtk.ScrolledWindow, Extends: Gtk.ScrolledWindow,
_init: function(params) { _init: function(params) {
@ -394,8 +387,7 @@ const DrawingPage = new GObject.Class({
}); });
const PrefsPage = new GObject.Class({ const PrefsPage = new GObject.Class({
Name: `${Me.uuid}.PrefsPage`, Name: `${UUID}-PrefsPage`,
GTypeName: sanitizeGType(`${Me.uuid}-PrefsPage`),
Extends: Gtk.ScrolledWindow, Extends: Gtk.ScrolledWindow,
_init: function(params) { _init: function(params) {
@ -508,8 +500,7 @@ const PrefsPage = new GObject.Class({
}); });
const Frame = new GObject.Class({ const Frame = new GObject.Class({
Name: `${Me.uuid}.Frame`, Name: `${UUID}-Frame`,
GTypeName: sanitizeGType(`${Me.uuid}-Frame`),
Extends: Gtk.Frame, Extends: Gtk.Frame,
_init: function(params) { _init: function(params) {
@ -524,8 +515,7 @@ const Frame = new GObject.Class({
}); });
const PrefRow = new GObject.Class({ const PrefRow = new GObject.Class({
Name: `${Me.uuid}.PrefRow`, Name: `${UUID}-PrefRow`,
GTypeName: sanitizeGType(`${Me.uuid}-PrefRow`),
Extends: Gtk.ListBoxRow, Extends: Gtk.ListBoxRow,
_init: function(params) { _init: function(params) {
@ -569,8 +559,7 @@ const PrefRow = new GObject.Class({
}); });
const PixelSpinButton = new GObject.Class({ const PixelSpinButton = new GObject.Class({
Name: `${Me.uuid}.PixelSpinButton`, Name: `${UUID}-PixelSpinButton`,
GTypeName: sanitizeGType(`${Me.uuid}-PixelSpinButton`),
Extends: Gtk.SpinButton, Extends: Gtk.SpinButton,
Properties: { Properties: {
'range': GObject.param_spec_variant('range', 'range', 'GSettings range', '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. // A color button that can be easily bound with a color string setting.
const ColorStringButton = new GObject.Class({ const ColorStringButton = new GObject.Class({
Name: `${Me.uuid}.ColorStringButton`, Name: `${UUID}-ColorStringButton`,
GTypeName: sanitizeGType(`${Me.uuid}-ColorStringButton`),
Extends: Gtk.ColorButton, Extends: Gtk.ColorButton,
Properties: { Properties: {
'color-string': GObject.ParamSpec.string('color-string', 'colorString', 'A string that describes the color', '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({ const FileChooserButton = new GObject.Class({
Name: `${Me.uuid}.FileChooserButton`, Name: `${UUID}-FileChooserButton`,
GTypeName: sanitizeGType(`${Me.uuid}-FileChooserButton`),
Extends: Gtk.FileChooserButton, Extends: Gtk.FileChooserButton,
Properties: { Properties: {
'location': GObject.ParamSpec.string('location', 'location', 'location', '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/ // this code comes from Sticky Notes View by Sam Bull, https://extensions.gnome.org/extension/568/notes/
const KeybindingsWidget = new GObject.Class({ const KeybindingsWidget = new GObject.Class({
Name: `${Me.uuid}.KeybindingsWidget`, Name: `${UUID}-KeybindingsWidget`,
GTypeName: sanitizeGType(`${Me.uuid}-KeybindingsWidget`),
Extends: Gtk.Box, Extends: Gtk.Box,
_init: function(settingKeys, settings) { _init: function(settingKeys, settings) {

View File

@ -170,7 +170,7 @@
</key> </key>
<key type="as" name="redo"> <key type="as" name="redo">
<default>["&lt;Primary&gt;&lt;Shift&gt;z"]</default> <default>["&lt;Primary&gt;&lt;Shift&gt;z"]</default>
<summary>Redo last brushstroke</summary> <summary>Redo</summary>
</key> </key>
<key type="as" name="save-as-json"> <key type="as" name="save-as-json">
<default>["&lt;Primary&gt;s"]</default> <default>["&lt;Primary&gt;s"]</default>
@ -340,7 +340,7 @@
</key> </key>
<key type="as" name="undo"> <key type="as" name="undo">
<default>["&lt;Primary&gt;z"]</default> <default>["&lt;Primary&gt;z"]</default>
<summary>Undo last brushstroke</summary> <summary>Undo</summary>
</key> </key>
</schema> </schema>
</schemalist> </schemalist>