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
===================

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 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 <i>%s</i> 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 <i>%s</i> 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;

View File

@ -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() {

View File

@ -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];

View File

@ -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)

View File

@ -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) {

View File

@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

14
menu.js
View File

@ -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;

View File

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

View File

@ -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) {

View File

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