Merge branch 'pile' into 'face'

v11

See merge request abakkk/DrawOnYourScreen!22
This commit is contained in:
abakkk 2021-02-20 00:26:42 +00:00
commit e9abbbb1c4
13 changed files with 727 additions and 506 deletions

11
NEWS
View File

@ -1,3 +1,14 @@
v11 - February 2021
===================
* GS 40 compatibility (40.beta)
* Gtk4 port (preferences)
* Toggle animations (background, grid and square area)
* Multi-line text elements
* Start a new line with "Enter" (text tool)
* Add "centered" text alignment
* Convenient marks for rotation and stretch transformations
v10 - October 2020
==================

263
area.js
View File

@ -1,5 +1,5 @@
/* jslint esversion: 6 */
/* exported Tools, DrawingArea */
/* exported Tool, DrawingArea */
/*
* Copyright 2019 Abakkk
@ -48,21 +48,22 @@ const pgettext = imports.gettext.domain(Me.metadata['gettext-domain']).pgettext;
const MOTION_TIME = 1; // ms, time accuracy for free drawing, max is about 33 ms. The lower it is, the smoother the drawing is.
const TEXT_CURSOR_TIME = 600; // ms
const ELEMENT_GRABBER_TIME = 80; // ms, default is about 16 ms
const TOGGLE_ANIMATION_DURATION = 300; // ms
const GRID_TILES_HORIZONTAL_NUMBER = 30;
const COLOR_PICKER_EXTENSION_UUID = 'color-picker@tuberry';
const UUID = Me.uuid.replace(/@/gi, '_at_').replace(/[^a-z0-9+_-]/gi, '_');
const { Shapes, Transformations } = Elements;
const { Shape, TextAlignment, Transformation } = Elements;
const { DisplayStrings } = Menu;
const FontGenericFamilies = ['Sans-Serif', 'Serif', 'Monospace', 'Cursive', 'Fantasy'];
const Manipulations = { MOVE: 100, RESIZE: 101, MIRROR: 102 };
var Tools = Object.assign({
const Manipulation = { MOVE: 100, RESIZE: 101, MIRROR: 102 };
var Tool = Object.assign({
getNameOf: function(value) {
return Object.keys(this).find(key => this[key] == value);
}
}, Shapes, Manipulations);
Object.defineProperty(Tools, 'getNameOf', { enumerable: false });
}, Shape, Manipulation);
Object.defineProperty(Tool, 'getNameOf', { enumerable: false });
// toJSON provides a string suitable for SVG color attribute whereas
// toString provides a string suitable for displaying the color name to the user.
@ -143,14 +144,15 @@ var DrawingArea = new Lang.Class({
this.layerContainer.add_child(this.foreLayer);
this.gridLayer = new DrawingLayer(this._repaintGrid.bind(this));
this.gridLayer.hide();
this.gridLayer.opacity = 0;
this.layerContainer.add_child(this.gridLayer);
this.elements = [];
this.undoneElements = [];
this.currentElement = null;
this.currentTool = Shapes.NONE;
this.currentTool = Shape.NONE;
this.currentImage = null;
this.currentTextRightAligned = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL;
this.currentTextAlignment = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL ? TextAlignment.RIGHT : TextAlignment.LEFT;
let fontName = St.Settings && St.Settings.get().font_name || Convenience.getSettings('org.gnome.desktop.interface').get_string('font-name');
this.currentFont = Pango.FontDescription.from_string(fontName);
this.currentFont.unset_fields(Pango.FontMask.SIZE);
@ -176,7 +178,7 @@ var DrawingArea = new Lang.Class({
get menu() {
if (!this._menu)
this._menu = new Menu.DrawingMenu(this, this.monitor, Tools, this.areaManagerUtils);
this._menu = new Menu.DrawingMenu(this, this.monitor, Tool, this.areaManagerUtils);
return this._menu;
},
@ -249,7 +251,7 @@ var DrawingArea = new Lang.Class({
get hasManipulationTool() {
// No Object.values method in GS 3.24.
return Object.keys(Manipulations).map(key => Manipulations[key]).indexOf(this.currentTool) != -1;
return Object.keys(Manipulation).map(key => Manipulation[key]).indexOf(this.currentTool) != -1;
},
// Boolean wrapper for switch menu item.
@ -315,25 +317,26 @@ var DrawingArea = new Lang.Class({
for (let i = 0; i < this.elements.length; i++) {
cr.save();
this.elements[i].buildCairo(cr, { showTextRectangle: this.grabbedElement && this.grabbedElement == this.elements[i],
drawTextRectangle: this.grabPoint ? true : false });
this.elements[i].buildCairo(cr, { showElementBounds: this.grabbedElement && this.grabbedElement == this.elements[i],
drawElementBounds: this.grabPoint ? true : false });
if (this.grabPoint)
this._searchElementToGrab(cr, this.elements[i]);
if (this.elements[i].fill && !this.elements[i].isStraightLine) {
cr.fillPreserve();
if (this.elements[i].shape == Shapes.NONE || this.elements[i].shape == Shapes.LINE)
if (this.elements[i].shape == Shape.NONE || this.elements[i].shape == Shape.LINE)
cr.closePath();
}
cr.stroke();
this.elements[i]._addMarks(cr);
cr.restore();
}
if (this.currentElement && this.currentElement.eraser) {
this.currentElement.buildCairo(cr, { showTextCursor: this.textHasCursor,
showTextRectangle: this.currentElement.shape != Shapes.TEXT || !this.isWriting,
showElementBounds: this.currentElement.shape != Shape.TEXT || !this.isWriting,
dummyStroke: this.currentElement.fill && this.currentElement.line.lineWidth == 0 });
cr.stroke();
}
@ -344,7 +347,7 @@ var DrawingArea = new Lang.Class({
return;
this.currentElement.buildCairo(cr, { showTextCursor: this.textHasCursor,
showTextRectangle: this.currentElement.shape != Shapes.TEXT || !this.isWriting,
showElementBounds: this.currentElement.shape != Shape.TEXT || !this.isWriting,
dummyStroke: this.currentElement.fill && this.currentElement.line.lineWidth == 0 });
cr.stroke();
},
@ -377,11 +380,11 @@ var DrawingArea = new Lang.Class({
},
_getHasImageBack: function() {
return this.elements.some(element => element.shape == Shapes.IMAGE);
return this.elements.some(element => element.shape == Shape.IMAGE);
},
_getHasImageFore: function() {
return this.currentElement && this.currentElement.shape == Shapes.IMAGE || false;
return this.currentElement && this.currentElement.shape == Shape.IMAGE || false;
},
_redisplay: function() {
@ -409,7 +412,7 @@ var DrawingArea = new Lang.Class({
let controlPressed = event.has_control_modifier();
let shiftPressed = event.has_shift_modifier();
if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.isWriting)
if (this.currentElement && this.currentElement.shape == Shape.TEXT && this.isWriting)
// finish writing
this._stopWriting();
@ -469,7 +472,7 @@ var DrawingArea = new Lang.Class({
},
_onKeyPressed: function(actor, event) {
if (this.currentElement && this.currentElement.shape == Shapes.LINE &&
if (this.currentElement && this.currentElement.shape == Shape.LINE &&
(event.get_key_symbol() == Clutter.KEY_Return ||
event.get_key_symbol() == Clutter.KEY_KP_Enter ||
event.get_key_symbol() == Clutter.KEY_Control_L)) {
@ -483,7 +486,7 @@ var DrawingArea = new Lang.Class({
this._redisplay();
return Clutter.EVENT_STOP;
} else if (this.currentElement &&
(this.currentElement.shape == Shapes.POLYGON || this.currentElement.shape == Shapes.POLYLINE) &&
(this.currentElement.shape == Shape.POLYGON || this.currentElement.shape == Shape.POLYLINE) &&
(event.get_key_symbol() == Clutter.KEY_Return || event.get_key_symbol() == Clutter.KEY_KP_Enter)) {
this.currentElement.addPoint();
@ -558,7 +561,7 @@ var DrawingArea = new Lang.Class({
if (!success)
return;
if (this.currentTool == Manipulations.MIRROR) {
if (this.currentTool == Manipulation.MIRROR) {
this.grabbedElementLocked = !this.grabbedElementLocked;
if (this.grabbedElementLocked) {
this.updatePointerCursor();
@ -589,12 +592,12 @@ var DrawingArea = new Lang.Class({
let undoable = !duplicate;
if (this.currentTool == Manipulations.MOVE)
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, undoable);
else if (this.currentTool == Manipulations.MIRROR) {
this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformations.INVERSION : Transformations.REFLECTION, undoable);
if (this.currentTool == Manipulation.MOVE)
this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformation.ROTATION : Transformation.TRANSLATION, undoable);
else if (this.currentTool == Manipulation.RESIZE)
this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformation.STRETCH : Transformation.SCALE_PRESERVE, undoable);
else if (this.currentTool == Manipulation.MIRROR) {
this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformation.INVERSION : Transformation.REFLECTION, undoable);
this._redisplay();
}
@ -614,28 +617,28 @@ 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) {
if (controlPressed && this.grabbedElement.lastTransformation.type == Transformation.TRANSLATION) {
this.grabbedElement.stopTransformation();
this.grabbedElement.startTransformation(x, y, Transformations.ROTATION, undoable);
} else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformations.ROTATION) {
this.grabbedElement.startTransformation(x, y, Transformation.ROTATION, undoable);
} else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformation.ROTATION) {
this.grabbedElement.stopTransformation();
this.grabbedElement.startTransformation(x, y, Transformations.TRANSLATION, undoable);
this.grabbedElement.startTransformation(x, y, Transformation.TRANSLATION, undoable);
}
if (controlPressed && this.grabbedElement.lastTransformation.type == Transformations.SCALE_PRESERVE) {
if (controlPressed && this.grabbedElement.lastTransformation.type == Transformation.SCALE_PRESERVE) {
this.grabbedElement.stopTransformation();
this.grabbedElement.startTransformation(x, y, Transformations.STRETCH, undoable);
} else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformations.STRETCH) {
this.grabbedElement.startTransformation(x, y, Transformation.STRETCH, undoable);
} else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformation.STRETCH) {
this.grabbedElement.stopTransformation();
this.grabbedElement.startTransformation(x, y, Transformations.SCALE_PRESERVE, undoable);
this.grabbedElement.startTransformation(x, y, Transformation.SCALE_PRESERVE, undoable);
}
if (controlPressed && this.grabbedElement.lastTransformation.type == Transformations.REFLECTION) {
if (controlPressed && this.grabbedElement.lastTransformation.type == Transformation.REFLECTION) {
this.grabbedElement.transformations.pop();
this.grabbedElement.startTransformation(x, y, Transformations.INVERSION, undoable);
} else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformations.INVERSION) {
this.grabbedElement.startTransformation(x, y, Transformation.INVERSION, undoable);
} else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformation.INVERSION) {
this.grabbedElement.transformations.pop();
this.grabbedElement.startTransformation(x, y, Transformations.REFLECTION, undoable);
this.grabbedElement.startTransformation(x, y, Transformation.REFLECTION, undoable);
}
this.grabbedElement.updateTransformation(x, y);
@ -667,7 +670,7 @@ var DrawingArea = new Lang.Class({
this._stopDrawing();
});
if (this.currentTool == Shapes.TEXT) {
if (this.currentTool == Shape.TEXT) {
this.currentElement = new Elements.DrawingElement({
shape: this.currentTool,
color: this.currentColor,
@ -675,10 +678,10 @@ var DrawingArea = new Lang.Class({
font: this.currentFont.copy(),
// Translators: initial content of the text area
text: pgettext("text-area-content", "Text"),
textRightAligned: this.currentTextRightAligned,
textAlignment: this.currentTextAlignment,
points: []
});
} else if (this.currentTool == Shapes.IMAGE) {
} else if (this.currentTool == Shape.IMAGE) {
this.currentElement = new Elements.DrawingElement({
shape: this.currentTool,
color: this.currentColor,
@ -701,8 +704,8 @@ var DrawingArea = new Lang.Class({
this.currentElement.startDrawing(startX, startY);
if (this.currentTool == Shapes.POLYGON || this.currentTool == Shapes.POLYLINE) {
let icon = Files.Icons[this.currentTool == Shapes.POLYGON ? 'TOOL_POLYGON' : 'TOOL_POLYLINE'];
if (this.currentTool == Shape.POLYGON || this.currentTool == Shape.POLYLINE) {
let icon = Files.Icons[this.currentTool == Shape.POLYGON ? 'TOOL_POLYGON' : 'TOOL_POLYLINE'];
// Translators: %s is a key label
this.emit('show-osd', icon, _("Press <i>%s</i> to mark vertices")
.format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1, true);
@ -725,16 +728,24 @@ var DrawingArea = new Lang.Class({
let controlPressed = event.has_control_modifier();
this._updateDrawing(x, y, controlPressed);
if (this.currentTool == Shapes.NONE) {
if (this.currentTool == Shape.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 success, coords;
if (device.get_coords) {
[success, coords] = device.get_coords(sequence);
if (!success)
return GLib.SOURCE_CONTINUE;
} else {
// GS 40+, device.get_coords() has been removed
// and seat.query_state() is unusable with null sequences.
let pointer = global.get_pointer();
coords = { x: pointer[0], y: pointer[1] };
}
let [s, x, y] = this._transformStagePoint(coords.x, coords.y);
if (!s)
@ -779,14 +790,14 @@ var DrawingArea = new Lang.Class({
}
// skip when a polygon has not at least 3 points
if (this.currentElement && this.currentElement.shape == Shapes.POLYGON && this.currentElement.points.length < 3)
if (this.currentElement && this.currentElement.shape == Shape.POLYGON && this.currentElement.points.length < 3)
this.currentElement = null;
if (this.currentElement)
this.currentElement.stopDrawing();
if (this.currentElement && this.currentElement.points.length >= 2) {
if (this.currentElement.shape == Shapes.TEXT && !this.isWriting) {
if (this.currentElement.shape == Shape.TEXT && !this.isWriting) {
this._startWriting();
return;
}
@ -806,12 +817,14 @@ var DrawingArea = new Lang.Class({
this.currentElement.cursorPosition = 0;
// Translators: %s is a key label
this.emit('show-osd', Files.Icons.TOOL_TEXT, _("Press <i>%s</i>\nto start a new line")
.format(Gtk.accelerator_get_label(Clutter.KEY_Return, 1)), "", -1, true);
.format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1, true);
this._updateTextCursorTimeout();
this.textHasCursor = true;
this._redisplay();
// Do not hide and do not set opacity to 0 because ibusCandidatePopup need a mapped text entry to init correctly its position.
// Do not hide and do not set opacity to 0 because:
// 1. ibusCandidatePopup need a mapped text entry to init correctly its position.
// 2. 'cursor-changed' signal is no emitted if the text entry is not visible.
this.textEntry = new St.Entry({ opacity: 1, x: stageX + x, y: stageY + y });
this.insert_child_below(this.textEntry, null);
this.textEntry.grab_key_focus();
@ -830,17 +843,27 @@ var DrawingArea = new Lang.Class({
this.textEntry.connect('destroy', () => ibusCandidatePopup.disconnect(this.ibusHandler));
}
this.textEntry.clutterText.set_single_line_mode(false);
this.textEntry.clutterText.set_activatable(false);
this.textEntry.clutterText.connect('activate', (clutterText) => {
this._stopWriting();
});
this.textEntry.clutterText.connect('text-changed', (clutterText) => {
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
this.currentElement.text = clutterText.text;
this.currentElement.cursorPosition = clutterText.cursorPosition;
this._updateTextCursorTimeout();
this._redisplay();
});
let showCursorOnPositionChanged = true;
this.textEntry.clutterText.connect('text-changed', clutterText => {
this.textEntry.y = stageY + y + (this.textEntry.clutterText.get_layout().get_line_count() - 1) * this.currentElement.height;
this.currentElement.text = clutterText.text;
showCursorOnPositionChanged = false;
this._redisplay();
});
this.textEntry.clutterText.connect('cursor-changed', clutterText => {
this.currentElement.cursorPosition = clutterText.cursorPosition;
this._updateTextCursorTimeout();
let cursorPosition = clutterText.cursorPosition == -1 ? clutterText.text.length : clutterText.cursorPosition;
this.textHasCursor = showCursorOnPositionChanged || GLib.unichar_isspace(clutterText.text.charAt(cursorPosition - 1));
showCursorOnPositionChanged = true;
this._redisplay();
});
this.textEntry.clutterText.connect('key-press-event', (clutterText, event) => {
@ -848,56 +871,23 @@ var DrawingArea = new Lang.Class({
this.currentElement.text = "";
this._stopWriting();
return Clutter.EVENT_STOP;
} else if (event.has_shift_modifier() &&
(event.get_key_symbol() == Clutter.KEY_Return ||
event.get_key_symbol() == Clutter.KEY_KP_Enter)) {
let startNewLine = true;
this._stopWriting(startNewLine);
clutterText.text = "";
return Clutter.EVENT_STOP;
}
// 'cursor-changed' signal is not emitted if the text entry is not visible.
// So key events related to the cursor must be listened.
if (event.get_key_symbol() == Clutter.KEY_Left || event.get_key_symbol() == Clutter.KEY_Right ||
event.get_key_symbol() == Clutter.KEY_Home || event.get_key_symbol() == Clutter.KEY_End) {
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
this.currentElement.cursorPosition = clutterText.cursorPosition;
this._updateTextCursorTimeout();
this.textHasCursor = true;
this._redisplay();
});
}
return Clutter.EVENT_PROPAGATE;
});
},
_stopWriting: function(startNewLine) {
_stopWriting: function() {
if (this.currentElement.text.length > 0)
this.elements.push(this.currentElement);
if (startNewLine && this.currentElement.points.length == 2) {
this.currentElement.lineIndex = this.currentElement.lineIndex || 0;
// copy object, the original keep existing in this.elements
this.currentElement = Object.create(this.currentElement);
this.currentElement.lineIndex ++;
// define a new 'points' array, the original keep existing in this.elements
this.currentElement.points = [
[this.currentElement.points[0][0], this.currentElement.points[0][1] + this.currentElement.height],
[this.currentElement.points[1][0], this.currentElement.points[1][1] + this.currentElement.height]
];
this.currentElement.text = "";
this.textEntry.set_y(this.currentElement.y);
} else {
this.currentElement = null;
this._stopTextCursorTimeout();
this.textEntry.destroy();
delete this.textEntry;
this.grab_key_focus();
this.updateActionMode();
this.updatePointerCursor();
}
this.currentElement = null;
this._stopTextCursorTimeout();
this.textEntry.destroy();
delete this.textEntry;
this.grab_key_focus();
this.updateActionMode();
this.updatePointerCursor();
this._redisplay();
},
@ -910,15 +900,15 @@ var DrawingArea = new Lang.Class({
},
updatePointerCursor: function(controlPressed) {
if (this.currentTool == Manipulations.MIRROR && this.grabbedElementLocked)
if (this.currentTool == Manipulation.MIRROR && this.grabbedElementLocked)
this.setPointerCursor('CROSSHAIR');
else if (this.hasManipulationTool)
this.setPointerCursor(this.grabbedElement ? 'MOVE_OR_RESIZE_WINDOW' : 'DEFAULT');
else if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.isWriting)
else if (this.currentElement && this.currentElement.shape == Shape.TEXT && this.isWriting)
this.setPointerCursor('IBEAM');
else if (!this.currentElement)
this.setPointerCursor(this.currentTool == Shapes.NONE ? 'POINTING_HAND' : 'CROSSHAIR');
else if (this.currentElement.shape != Shapes.NONE && controlPressed)
this.setPointerCursor(this.currentTool == Shape.NONE ? 'POINTING_HAND' : 'CROSSHAIR');
else if (this.currentElement.shape != Shape.NONE && controlPressed)
this.setPointerCursor('MOVE_OR_RESIZE_WINDOW');
},
@ -1007,7 +997,7 @@ var DrawingArea = new Lang.Class({
},
smoothLastElement: function() {
if (this.elements.length > 0 && this.elements[this.elements.length - 1].shape == Shapes.NONE) {
if (this.elements.length > 0 && this.elements[this.elements.length - 1].shape == Shape.NONE) {
this.elements[this.elements.length - 1].smoothAll();
this._redisplay();
}
@ -1015,7 +1005,16 @@ var DrawingArea = new Lang.Class({
toggleBackground: function() {
this.hasBackground = !this.hasBackground;
this.set_background_color(this.hasBackground ? this.areaBackgroundColor : null);
let backgroundColor = this.hasBackground ? this.areaBackgroundColor : Clutter.Color.get_static(Clutter.StaticColor.TRANSPARENT);
if (this.ease) {
this.remove_all_transitions();
this.ease({ backgroundColor,
duration: TOGGLE_ANIMATION_DURATION,
transition: Clutter.AnimationMode.EASE_IN_OUT_QUAD });
} else {
this.set_background_color(backgroundColor);
}
},
get hasGrid() {
@ -1024,19 +1023,43 @@ var DrawingArea = new Lang.Class({
toggleGrid: function() {
// The grid layer is repainted when the visibility changes.
this.gridLayer.visible = !this.gridLayer.visible;
if (this.gridLayer.ease) {
this.gridLayer.remove_all_transitions();
let visible = !this.gridLayer.visible;
this.gridLayer.visible = true;
this.gridLayer.ease({ opacity: visible ? 255 : 0,
duration: TOGGLE_ANIMATION_DURATION,
transition: Clutter.AnimationMode.EASE_IN_OUT_QUAD,
onStopped: () => this.gridLayer.visible = visible });
} else {
this.gridLayer.visible = !this.gridLayer.visible;
}
},
toggleSquareArea: function() {
this.isSquareArea = !this.isSquareArea;
let x, y, width, height, onComplete;
if (this.isSquareArea) {
this.layerContainer.set_position((this.monitor.width - this.squareAreaSize) / 2, (this.monitor.height - this.squareAreaSize) / 2);
this.layerContainer.set_size(this.squareAreaSize, this.squareAreaSize);
this.layerContainer.add_style_class_name('draw-on-your-screen-square-area');
[x, y] = [(this.monitor.width - this.squareAreaSize) / 2, (this.monitor.height - this.squareAreaSize) / 2];
width = height = this.squareAreaSize;
onComplete = () => {};
} else {
this.layerContainer.set_position(0, 0);
this.layerContainer.set_size(this.monitor.width, this.monitor.height);
this.layerContainer.remove_style_class_name('draw-on-your-screen-square-area');
x = y = 0;
[width, height] = [this.monitor.width, this.monitor.height];
onComplete = () => this.layerContainer.remove_style_class_name('draw-on-your-screen-square-area');
}
if (this.layerContainer.ease) {
this.layerContainer.remove_all_transitions();
this.layerContainer.ease({ x, y, width, height, onComplete,
duration: TOGGLE_ANIMATION_DURATION,
transition: Clutter.AnimationMode.EASE_OUT_QUAD });
} else {
this.layerContainer.set_position(x, y);
this.layerContainer.set_size(width, height);
onComplete();
}
},
@ -1055,7 +1078,7 @@ var DrawingArea = new Lang.Class({
selectTool: function(tool) {
this.currentTool = tool;
this.emit('show-osd', Files.Icons[`TOOL_${Tools.getNameOf(tool)}`] || null, DisplayStrings.Tool[tool], "", -1, false);
this.emit('show-osd', Files.Icons[`TOOL_${Tool.getNameOf(tool)}`] || null, DisplayStrings.Tool[tool], "", -1, false);
this.updatePointerCursor();
},
@ -1137,13 +1160,13 @@ var DrawingArea = new Lang.Class({
},
switchTextAlignment: function() {
this.currentTextRightAligned = !this.currentTextRightAligned;
if (this.currentElement && this.currentElement.textRightAligned !== undefined) {
this.currentElement.textRightAligned = this.currentTextRightAligned;
this.currentTextAlignment = this.currentTextAlignment == 2 ? 0 : this.currentTextAlignment + 1;
if (this.currentElement && this.currentElement.textAlignment != this.currentTextAlignment) {
this.currentElement.textAlignment = this.currentTextAlignment;
this._redisplay();
}
let icon = Files.Icons[this.currentTextRightAligned ? 'RIGHT_ALIGNED' : 'LEFT_ALIGNED'];
this.emit('show-osd', icon, DisplayStrings.getTextAlignment(this.currentTextRightAligned), "", -1, false);
let icon = Files.Icons[this.currentTextAlignment == TextAlignment.RIGHT ? 'RIGHT_ALIGNED' : this.currentTextAlignment == TextAlignment.CENTER ? 'CENTERED' : 'LEFT_ALIGNED'];
this.emit('show-osd', icon, DisplayStrings.TextAlignment[this.currentTextAlignment], "", -1, false);
},
switchImageFile: function(reverse) {
@ -1155,7 +1178,7 @@ var DrawingArea = new Lang.Class({
pasteImageFiles: function() {
Files.Images.addImagesFromClipboard(lastImage => {
this.currentImage = lastImage;
this.currentTool = Shapes.IMAGE;
this.currentTool = Shape.IMAGE;
this.updatePointerCursor();
this.emit('show-osd', this.currentImage.gicon, this.currentImage.toString(), "", -1, false);
});
@ -1355,7 +1378,7 @@ var DrawingArea = new Lang.Class({
this._stopAll();
let prefixes = 'xmlns="http://www.w3.org/2000/svg"';
if (this.elements.some(element => element.shape == Shapes.IMAGE))
if (this.elements.some(element => element.shape == Shape.IMAGE))
prefixes += ' xmlns:xlink="http://www.w3.org/1999/xlink"';
let content = `<svg viewBox="0 0 ${this.layerContainer.width} ${this.layerContainer.height}" ${prefixes}>`;
let backgroundColorString = this.hasBackground ? String(this.areaBackgroundColor) : 'transparent';

View File

@ -1,5 +1,5 @@
/* jslint esversion: 6 */
/* exported Shapes, Transformations, getAllFontFamilies, DrawingElement */
/* exported Shape, TextAlignment, Transformation, getAllFontFamilies, DrawingElement */
/*
* Copyright 2019 Abakkk
@ -30,8 +30,9 @@ 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, SMOOTH: 100 };
var Shape = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6, IMAGE: 7 };
var TextAlignment = { LEFT: 0, CENTER: 1, RIGHT: 2 };
var Transformation = { TRANSLATION: 0, ROTATION: 1, SCALE_PRESERVE: 2, STRETCH: 3, REFLECTION: 4, INVERSION: 5, SMOOTH: 100 };
var getAllFontFamilies = function() {
return PangoCairo.font_map_get_default().list_families().map(fontFamily => fontFamily.get_name()).sort((a,b) => a.localeCompare(b));
@ -63,10 +64,11 @@ 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
const MARK_COLOR = Clutter.Color.get_static(Clutter.StaticColor.BLUE);
var DrawingElement = function(params) {
return params.shape == Shapes.TEXT ? new TextElement(params) :
params.shape == Shapes.IMAGE ? new ImageElement(params) :
return params.shape == Shape.TEXT ? new TextElement(params) :
params.shape == Shape.IMAGE ? new ImageElement(params) :
new _DrawingElement(params);
};
@ -103,14 +105,19 @@ const _DrawingElement = new Lang.Class({
if (params.transform && params.transform.center) {
let angle = (params.transform.angle || 0) + (params.transform.startAngle || 0);
if (angle)
this.transformations.push({ type: Transformations.ROTATION, angle: angle });
this.transformations.push({ type: Transformation.ROTATION, angle: angle });
}
if (params.shape == Shapes.ELLIPSE && params.transform && params.transform.ratio && params.transform.ratio != 1 && params.points.length >= 2) {
if (params.shape == Shape.ELLIPSE && params.transform && params.transform.ratio && params.transform.ratio != 1 && params.points.length >= 2) {
let [ratio, p0, p1] = [params.transform.ratio, params.points[0], params.points[1]];
// Add a fake point that will give the right ellipse ratio when building the element.
this.points.push([ratio * (p1[0] - p0[0]) + p0[0], ratio * (p1[1] - p0[1]) + p0[1]]);
}
delete this.transform;
// v10-
if (this.textRightAligned)
this.textAlignment = TextAlignment.RIGHT;
delete this.textRightAligned;
},
// toJSON is called by JSON.stringify
@ -123,7 +130,7 @@ const _DrawingElement = new Lang.Class({
fill: this.fill,
fillRule: this.fillRule,
eraser: this.eraser,
transformations: this.transformations.filter(transformation => transformation.type != Transformations.SMOOTH)
transformations: this.transformations.filter(transformation => transformation.type != Transformation.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])
};
@ -133,18 +140,6 @@ const _DrawingElement = new Lang.Class({
if (this.color)
Clutter.cairo_set_source_color(cr, this.color);
if (this.showSymmetryElement) {
let transformation = this.lastTransformation;
setDummyStroke(cr);
if (transformation.type == Transformations.REFLECTION) {
cr.moveTo(transformation.startX, transformation.startY);
cr.lineTo(transformation.endX, transformation.endY);
} else {
cr.arc(transformation.endX, transformation.endY, INVERSION_CIRCLE_RADIUS, 0, 2 * Math.PI);
}
cr.stroke();
}
if (this.line) {
cr.setLineCap(this.line.lineCap);
cr.setLineJoin(this.line.lineJoin);
@ -171,21 +166,21 @@ const _DrawingElement = new Lang.Class({
}
this.transformations.slice(0).reverse().forEach(transformation => {
if (transformation.type == Transformations.TRANSLATION) {
if (transformation.type == Transformation.TRANSLATION) {
cr.translate(transformation.slideX, transformation.slideY);
} else if (transformation.type == Transformations.ROTATION) {
} else if (transformation.type == Transformation.ROTATION) {
let center = this._getTransformedCenter(transformation);
cr.translate(center[0], center[1]);
cr.rotate(transformation.angle);
cr.translate(-center[0], -center[1]);
} else if (transformation.type == Transformations.SCALE_PRESERVE || transformation.type == Transformations.STRETCH) {
} else if (transformation.type == Transformation.SCALE_PRESERVE || transformation.type == Transformation.STRETCH) {
let center = this._getTransformedCenter(transformation);
cr.translate(center[0], center[1]);
cr.rotate(transformation.angle);
cr.scale(transformation.scaleX, transformation.scaleY);
cr.rotate(-transformation.angle);
cr.translate(-center[0], -center[1]);
} else if (transformation.type == Transformations.REFLECTION || transformation.type == Transformations.INVERSION) {
} else if (transformation.type == Transformation.REFLECTION || transformation.type == Transformation.INVERSION) {
cr.translate(transformation.slideX, transformation.slideY);
cr.rotate(transformation.angle);
cr.scale(transformation.scaleX, transformation.scaleY);
@ -199,24 +194,61 @@ const _DrawingElement = new Lang.Class({
cr.identityMatrix();
},
_addMarks: function(cr) {
if (this.showSymmetryElement) {
setDummyStroke(cr);
Clutter.cairo_set_source_color(cr, MARK_COLOR);
let transformation = this.lastTransformation;
if (transformation.type == Transformation.REFLECTION) {
cr.moveTo(transformation.startX, transformation.startY);
cr.lineTo(transformation.endX, transformation.endY);
} else {
cr.arc(transformation.endX, transformation.endY, INVERSION_CIRCLE_RADIUS, 0, 2 * Math.PI);
}
cr.stroke();
}
if (this.showRotationCenter) {
setDummyStroke(cr);
Clutter.cairo_set_source_color(cr, MARK_COLOR);
let center = this._getTransformedCenter(this.lastTransformation);
cr.arc(center[0], center[1], INVERSION_CIRCLE_RADIUS, 0, 2 * Math.PI);
cr.stroke();
}
if (this.showStretchAxes) {
setDummyStroke(cr);
Clutter.cairo_set_source_color(cr, MARK_COLOR);
let center = this._getTransformedCenter(this.lastTransformation);
for (let i = 0; i <=1; i++) {
cr.moveTo(center[0] - 1000 * Math.cos(i * Math.PI / 2), center[1] - 1000 * Math.sin(i * Math.PI / 2));
cr.lineTo(center[0] + 1000 * Math.cos(i * Math.PI / 2), center[1] + 1000 * Math.sin(i * Math.PI / 2));
}
cr.stroke();
}
},
_drawCairo: function(cr, params) {
let [points, shape] = [this.points, this.shape];
if (shape == Shapes.LINE && points.length == 3) {
if (shape == Shape.LINE && points.length == 3) {
cr.moveTo(points[0][0], points[0][1]);
cr.curveTo(points[0][0], points[0][1], points[1][0], points[1][1], points[2][0], points[2][1]);
} else if (shape == Shapes.LINE && points.length == 4) {
} else if (shape == Shape.LINE && points.length == 4) {
cr.moveTo(points[0][0], points[0][1]);
cr.curveTo(points[1][0], points[1][1], points[2][0], points[2][1], points[3][0], points[3][1]);
} else if (shape == Shapes.NONE || shape == Shapes.LINE) {
} else if (shape == Shape.NONE || shape == Shape.LINE) {
cr.moveTo(points[0][0], points[0][1]);
for (let j = 1; j < points.length; j++) {
cr.lineTo(points[j][0], points[j][1]);
}
} else if (shape == Shapes.ELLIPSE && points.length >= 2) {
} else if (shape == Shape.ELLIPSE && points.length >= 2) {
let radius = Math.hypot(points[1][0] - points[0][0], points[1][1] - points[0][1]);
let ratio = 1;
@ -232,15 +264,15 @@ const _DrawingElement = new Lang.Class({
} else
cr.arc(points[0][0], points[0][1], radius, 0, 2 * Math.PI);
} else if (shape == Shapes.RECTANGLE && points.length == 2) {
} else if (shape == Shape.RECTANGLE && points.length == 2) {
cr.rectangle(points[0][0], points[0][1], points[1][0] - points[0][0], points[1][1] - points[0][1]);
} else if ((shape == Shapes.POLYGON || shape == Shapes.POLYLINE) && points.length >= 2) {
} else if ((shape == Shape.POLYGON || shape == Shape.POLYLINE) && points.length >= 2) {
cr.moveTo(points[0][0], points[0][1]);
for (let j = 1; j < points.length; j++) {
cr.lineTo(points[j][0], points[j][1]);
}
if (shape == Shapes.POLYGON)
if (shape == Shape.POLYGON)
cr.closePath();
}
@ -262,19 +294,19 @@ const _DrawingElement = new Lang.Class({
this.transformations.slice(0).reverse().forEach(transformation => {
let center = this._getTransformedCenter(transformation);
if (transformation.type == Transformations.TRANSLATION) {
if (transformation.type == Transformation.TRANSLATION) {
transforms.push(['translate', transformation.slideX, transformation.slideY]);
} else if (transformation.type == Transformations.ROTATION) {
} else if (transformation.type == Transformation.ROTATION) {
transforms.push(['translate', center[0], center[1]]);
transforms.push(['rotate', transformation.angle * RADIAN]);
transforms.push(['translate', -center[0], -center[1]]);
} else if (transformation.type == Transformations.SCALE_PRESERVE || transformation.type == Transformations.STRETCH) {
} else if (transformation.type == Transformation.SCALE_PRESERVE || transformation.type == Transformation.STRETCH) {
transforms.push(['translate', center[0], center[1]]);
transforms.push(['rotate', transformation.angle * RADIAN]);
transforms.push(['scale', transformation.scaleX, transformation.scaleY]);
transforms.push(['rotate', -transformation.angle * RADIAN]);
transforms.push(['translate', -center[0], -center[1]]);
} else if (transformation.type == Transformations.REFLECTION || transformation.type == Transformations.INVERSION) {
} else if (transformation.type == Transformation.REFLECTION || transformation.type == Transformation.INVERSION) {
transforms.push(['translate', transformation.slideX, transformation.slideY]);
transforms.push(['rotate', transformation.angle * RADIAN]);
transforms.push(['scale', transformation.scaleX, transformation.scaleY]);
@ -342,45 +374,45 @@ const _DrawingElement = new Lang.Class({
attributes += ` stroke-dasharray="${this.dash.array[0]} ${this.dash.array[1]}" stroke-dashoffset="${this.dash.offset}"`;
}
if (this.shape == Shapes.LINE && points.length == 4) {
if (this.shape == Shape.LINE && points.length == 4) {
row += `<path ${attributes} d="M${points[0][0]} ${points[0][1]}`;
row += ` C ${points[1][0]} ${points[1][1]}, ${points[2][0]} ${points[2][1]}, ${points[3][0]} ${points[3][1]}`;
row += `${fill ? 'z' : ''}"${transAttribute}/>`;
} else if (this.shape == Shapes.LINE && points.length == 3) {
} else if (this.shape == Shape.LINE && points.length == 3) {
row += `<path ${attributes} d="M${points[0][0]} ${points[0][1]}`;
row += ` C ${points[0][0]} ${points[0][1]}, ${points[1][0]} ${points[1][1]}, ${points[2][0]} ${points[2][1]}`;
row += `${fill ? 'z' : ''}"${transAttribute}/>`;
} else if (this.shape == Shapes.LINE) {
} else if (this.shape == Shape.LINE) {
row += `<line ${attributes} x1="${points[0][0]}" y1="${points[0][1]}" x2="${points[1][0]}" y2="${points[1][1]}"${transAttribute}/>`;
} else if (this.shape == Shapes.NONE) {
} else if (this.shape == Shape.NONE) {
row += `<path ${attributes} d="M${points[0][0]} ${points[0][1]}`;
for (let i = 1; i < points.length; i++)
row += ` L ${points[i][0]} ${points[i][1]}`;
row += `${fill ? 'z' : ''}"${transAttribute}/>`;
} else if (this.shape == Shapes.ELLIPSE && points.length == 3) {
} else if (this.shape == Shape.ELLIPSE && points.length == 3) {
let ry = Math.hypot(points[1][0] - points[0][0], points[1][1] - points[0][1]);
let rx = Math.hypot(points[2][0] - points[0][0], points[2][1] - points[0][1]);
row += `<ellipse ${attributes} cx="${points[0][0]}" cy="${points[0][1]}" rx="${rx}" ry="${ry}"${transAttribute}/>`;
} else if (this.shape == Shapes.ELLIPSE && points.length == 2) {
} else if (this.shape == Shape.ELLIPSE && points.length == 2) {
let r = Math.hypot(points[1][0] - points[0][0], points[1][1] - points[0][1]);
row += `<circle ${attributes} cx="${points[0][0]}" cy="${points[0][1]}" r="${r}"${transAttribute}/>`;
} else if (this.shape == Shapes.RECTANGLE && points.length == 2) {
} else if (this.shape == Shape.RECTANGLE && points.length == 2) {
row += `<rect ${attributes} x="${Math.min(points[0][0], points[1][0])}" y="${Math.min(points[0][1], points[1][1])}" ` +
`width="${Math.abs(points[1][0] - points[0][0])}" height="${Math.abs(points[1][1] - points[0][1])}"${transAttribute}/>`;
} else if (this.shape == Shapes.POLYGON && points.length >= 3) {
} else if (this.shape == Shape.POLYGON && points.length >= 3) {
row += `<polygon ${attributes} points="`;
for (let i = 0; i < points.length; i++)
row += ` ${points[i][0]},${points[i][1]}`;
row += `"${transAttribute}/>`;
} else if (this.shape == Shapes.POLYLINE && points.length >= 2) {
} else if (this.shape == Shape.POLYLINE && points.length >= 2) {
row += `<polyline ${attributes} points="`;
for (let i = 0; i < points.length; i++)
row += ` ${points[i][0]},${points[i][1]}`;
@ -399,7 +431,7 @@ const _DrawingElement = new Lang.Class({
},
get isStraightLine() {
return this.shape == Shapes.LINE && this.points.length == 2;
return this.shape == Shape.LINE && this.points.length == 2;
},
smoothAll: function() {
@ -410,21 +442,21 @@ const _DrawingElement = new Lang.Class({
let newPoints = this.points.slice();
this.transformations.push({ type: Transformations.SMOOTH, undoable: true,
this.transformations.push({ type: Transformation.SMOOTH, undoable: true,
undo: () => this.points = oldPoints,
redo: () => this.points = newPoints });
if (this._undoneTransformations)
this._undoneTransformations = this._undoneTransformations.filter(transformation => transformation.type != Transformations.SMOOTH);
this._undoneTransformations = this._undoneTransformations.filter(transformation => transformation.type != Transformation.SMOOTH);
},
addPoint: function() {
if (this.shape == Shapes.POLYGON || this.shape == Shapes.POLYLINE) {
if (this.shape == Shape.POLYGON || this.shape == Shape.POLYLINE) {
// copy last point
let [lastPoint, secondToLastPoint] = [this.points[this.points.length - 1], this.points[this.points.length - 2]];
if (!getNearness(secondToLastPoint, lastPoint, MIN_DRAWING_SIZE))
this.points.push([lastPoint[0], lastPoint[1]]);
} else if (this.shape == Shapes.LINE) {
} else if (this.shape == Shape.LINE) {
if (this.points.length == 2) {
this.points[2] = this.points[1];
} else if (this.points.length == 3) {
@ -448,7 +480,7 @@ const _DrawingElement = new Lang.Class({
startDrawing: function(startX, startY) {
this.points.push([startX, startY]);
if (this.shape == Shapes.POLYGON || this.shape == Shapes.POLYLINE)
if (this.shape == Shape.POLYGON || this.shape == Shape.POLYLINE)
this.points.push([startX, startY]);
},
@ -459,29 +491,29 @@ const _DrawingElement = new Lang.Class({
transform = transform || this.transformations.length >= 1;
if (this.shape == Shapes.NONE) {
if (this.shape == Shape.NONE) {
points.push([x, y]);
if (transform)
this._smooth(points.length - 1);
} else if ((this.shape == Shapes.RECTANGLE || this.shape == Shapes.POLYGON || this.shape == Shapes.POLYLINE) && transform) {
} else if ((this.shape == Shape.RECTANGLE || this.shape == Shape.POLYGON || this.shape == Shape.POLYLINE) && transform) {
if (points.length < 2)
return;
let center = this._getOriginalCenter();
this.transformations[0] = { type: Transformations.ROTATION,
this.transformations[0] = { type: Transformation.ROTATION,
angle: getAngle(center[0], center[1], points[points.length - 1][0], points[points.length - 1][1], x, y) };
} else if (this.shape == Shapes.ELLIPSE && transform) {
} else if (this.shape == Shape.ELLIPSE && transform) {
if (points.length < 2)
return;
points[2] = [x, y];
let center = this._getOriginalCenter();
this.transformations[0] = { type: Transformations.ROTATION,
this.transformations[0] = { type: Transformation.ROTATION,
angle: getAngle(center[0], center[1], center[0] + 1, center[1], x, y) };
} else if (this.shape == Shapes.POLYGON || this.shape == Shapes.POLYLINE) {
} else if (this.shape == Shape.POLYGON || this.shape == Shape.POLYLINE) {
points[points.length - 1] = [x, y];
} else {
@ -492,51 +524,55 @@ const _DrawingElement = new Lang.Class({
stopDrawing: function() {
// skip when the size is too small to be visible (3px) (except for free drawing)
if (this.shape != Shapes.NONE && this.points.length >= 2) {
if (this.shape != Shape.NONE && this.points.length >= 2) {
let lastPoint = this.points[this.points.length - 1];
let secondToLastPoint = this.points[this.points.length - 2];
if (getNearness(secondToLastPoint, lastPoint, MIN_DRAWING_SIZE))
this.points.pop();
}
if (this.transformations[0] && this.transformations[0].type == Transformations.ROTATION &&
if (this.transformations[0] && this.transformations[0].type == Transformation.ROTATION &&
Math.abs(this.transformations[0].angle) < MIN_ROTATION_ANGLE)
this.transformations.shift();
},
startTransformation: function(startX, startY, type, undoable) {
if (type == Transformations.TRANSLATION)
if (type == Transformation.TRANSLATION)
this.transformations.push({ startX, startY, type, undoable, slideX: 0, slideY: 0 });
else if (type == Transformations.ROTATION)
else if (type == Transformation.ROTATION)
this.transformations.push({ startX, startY, type, undoable, angle: 0 });
else if (type == Transformations.SCALE_PRESERVE || type == Transformations.STRETCH)
else if (type == Transformation.SCALE_PRESERVE || type == Transformation.STRETCH)
this.transformations.push({ startX, startY, type, undoable, scaleX: 1, scaleY: 1, angle: 0 });
else if (type == Transformations.REFLECTION)
else if (type == Transformation.REFLECTION)
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)
else if (type == Transformation.INVERSION)
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)) });
if (type == Transformations.REFLECTION || type == Transformations.INVERSION)
if (type == Transformation.REFLECTION || type == Transformation.INVERSION)
this.showSymmetryElement = true;
else if (type == Transformation.ROTATION)
this.showRotationCenter = true;
else if (type == Transformation.STRETCH)
this.showStretchAxes = true;
},
updateTransformation: function(x, y) {
let transformation = this.lastTransformation;
if (transformation.type == Transformations.TRANSLATION) {
if (transformation.type == Transformation.TRANSLATION) {
transformation.slideX = x - transformation.startX;
transformation.slideY = y - transformation.startY;
} else if (transformation.type == Transformations.ROTATION) {
} else if (transformation.type == Transformation.ROTATION) {
let center = this._getTransformedCenter(transformation);
transformation.angle = getAngle(center[0], center[1], transformation.startX, transformation.startY, x, y);
} else if (transformation.type == Transformations.SCALE_PRESERVE) {
} else if (transformation.type == Transformation.SCALE_PRESERVE) {
let center = this._getTransformedCenter(transformation);
let scale = Math.hypot(x - center[0], y - center[1]) / Math.hypot(transformation.startX - center[0], transformation.startY - center[1]) || 1;
[transformation.scaleX, transformation.scaleY] = [scale, scale];
} else if (transformation.type == Transformations.STRETCH) {
} else if (transformation.type == Transformation.STRETCH) {
let center = this._getTransformedCenter(transformation);
let startAngle = getAngle(center[0], center[1], center[0] + 1, center[1], transformation.startX, transformation.startY);
let vertical = Math.abs(Math.sin(startAngle)) >= Math.sin(Math.PI / 2 - STRETCH_TOLERANCE);
@ -545,7 +581,7 @@ const _DrawingElement = new Lang.Class({
transformation.scaleX = vertical ? 1 : scale;
transformation.scaleY = !vertical ? 1 : scale;
transformation.angle = vertical || horizontal ? 0 : getAngle(center[0], center[1], center[0] + 1, center[1], x, y);
} else if (transformation.type == Transformations.REFLECTION) {
} else if (transformation.type == Transformation.REFLECTION) {
[transformation.endX, transformation.endY] = [x, y];
if (getNearness([transformation.startX, transformation.startY], [x, y], MIN_REFLECTION_LINE_LENGTH)) {
// do nothing to avoid jumps (no transformation at starting and locked transformation after)
@ -568,7 +604,7 @@ const _DrawingElement = new Lang.Class({
[transformation.slideX, transformation.slideY] = [transformation.startX - transformation.startY * tan, 0];
transformation.angle = Math.PI - Math.atan(tan);
}
} else if (transformation.type == Transformations.INVERSION) {
} else if (transformation.type == Transformation.INVERSION) {
[transformation.endX, transformation.endY] = [x, y];
[transformation.scaleX, transformation.scaleY] = [-1, -1];
[transformation.slideX, transformation.slideY] = [x, y];
@ -578,16 +614,18 @@ const _DrawingElement = new Lang.Class({
stopTransformation: function() {
this.showSymmetryElement = false;
this.showRotationCenter = false;
this.showStretchAxes = false;
// Clean transformations
let transformation = this.lastTransformation;
if (!transformation)
return;
if (transformation.type == Transformations.REFLECTION &&
if (transformation.type == Transformation.REFLECTION &&
getNearness([transformation.startX, transformation.startY], [transformation.endX, transformation.endY], MIN_REFLECTION_LINE_LENGTH) ||
transformation.type == Transformations.TRANSLATION && Math.hypot(transformation.slideX, transformation.slideY) < MIN_TRANSLATION_DISTANCE ||
transformation.type == Transformations.ROTATION && Math.abs(transformation.angle) < MIN_ROTATION_ANGLE) {
transformation.type == Transformation.TRANSLATION && Math.hypot(transformation.slideX, transformation.slideY) < MIN_TRANSLATION_DISTANCE ||
transformation.type == Transformation.ROTATION && Math.abs(transformation.angle) < MIN_ROTATION_ANGLE) {
this.transformations.pop();
} else {
@ -608,7 +646,7 @@ const _DrawingElement = new Lang.Class({
this._undoneTransformations = [];
let transformation = this.transformations.pop();
if (transformation.type == Transformations.SMOOTH)
if (transformation.type == Transformation.SMOOTH)
transformation.undo();
this._undoneTransformations.push(transformation);
@ -625,7 +663,7 @@ const _DrawingElement = new Lang.Class({
this.transformations = [];
let transformation = this._undoneTransformations.pop();
if (transformation.type == Transformations.SMOOTH)
if (transformation.type == Transformation.SMOOTH)
transformation.redo();
this.transformations.push(transformation);
@ -645,13 +683,12 @@ const _DrawingElement = new Lang.Class({
},
// The figure rotation center before transformations (original).
// this.textWidth is computed during Cairo building.
_getOriginalCenter: function() {
if (!this._originalCenter) {
let points = this.points;
this._originalCenter = this.shape == Shapes.ELLIPSE ? [points[0][0], points[0][1]] :
this.shape == Shapes.LINE && points.length == 4 ? getCurveCenter(points[0], points[1], points[2], points[3]) :
this.shape == Shapes.LINE && points.length == 3 ? getCurveCenter(points[0], points[0], points[1], points[2]) :
this._originalCenter = this.shape == Shape.ELLIPSE ? [points[0][0], points[0][1]] :
this.shape == Shape.LINE && points.length == 4 ? getCurveCenter(points[0], points[1], points[2], points[3]) :
this.shape == Shape.LINE && points.length == 3 ? getCurveCenter(points[0], points[0], points[1], points[2]) :
points.length >= 3 ? getCentroid(points) :
getNaiveCenter(points);
}
@ -667,13 +704,13 @@ const _DrawingElement = new Lang.Class({
// Apply transformations to the matrice in reverse order
// because Pango multiply matrices by the left when applying a transformation
this.transformations.slice(0, this.transformations.indexOf(transformation)).reverse().forEach(transformation => {
if (transformation.type == Transformations.TRANSLATION) {
if (transformation.type == Transformation.TRANSLATION) {
matrix.translate(transformation.slideX, transformation.slideY);
} else if (transformation.type == Transformations.ROTATION) {
} else if (transformation.type == Transformation.ROTATION) {
// nothing, the center position is preserved.
} else if (transformation.type == Transformations.SCALE_PRESERVE || transformation.type == Transformations.STRETCH) {
} else if (transformation.type == Transformation.SCALE_PRESERVE || transformation.type == Transformation.STRETCH) {
// nothing, the center position is preserved.
} else if (transformation.type == Transformations.REFLECTION || transformation.type == Transformations.INVERSION) {
} else if (transformation.type == Transformation.REFLECTION || transformation.type == Transformation.INVERSION) {
matrix.translate(transformation.slideX, transformation.slideY);
matrix.rotate(-transformation.angle * RADIAN);
matrix.scale(transformation.scaleX, transformation.scaleY);
@ -710,8 +747,7 @@ const TextElement = new Lang.Class({
eraser: this.eraser,
transformations: this.transformations,
text: this.text,
lineIndex: this.lineIndex !== undefined ? this.lineIndex : undefined,
textRightAligned: this.textRightAligned,
textAlignment: this.textAlignment,
font: this.font.to_string(),
points: this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100])
};
@ -719,7 +755,11 @@ const TextElement = new Lang.Class({
get x() {
// this.textWidth is computed during Cairo building.
return this.points[1][0] - (this.textRightAligned ? this.textWidth : 0);
let offset = this.textAlignment == TextAlignment.RIGHT ? this.textWidth :
this.textAlignment == TextAlignment.CENTER ? this.textWidth / 2 :
0;
return this.points[1][0] - offset;
},
get y() {
@ -730,42 +770,54 @@ const TextElement = new Lang.Class({
return Math.abs(this.points[1][1] - this.points[0][1]);
},
// When rotating grouped lines, lineOffset is used to retrieve the rotation center of the first line.
get lineOffset() {
return (this.lineIndex || 0) * this.height;
// this.lineWidths is computed during Cairo building.
_getLineX: function(index) {
let offset = this.textAlignment == TextAlignment.RIGHT && this.lineWidths && this.lineWidths[index] ? this.lineWidths[index] :
this.textAlignment == TextAlignment.CENTER && this.lineWidths && this.lineWidths[index] ? this.lineWidths[index] / 2 :
0;
return this.points[1][0] - offset;
},
_drawCairo: function(cr, params) {
if (this.points.length == 2) {
let layout = PangoCairo.create_layout(cr);
let fontSize = this.height * Pango.SCALE;
this.font.set_absolute_size(fontSize);
layout.set_font_description(this.font);
layout.set_text(this.text, -1);
this.textWidth = layout.get_pixel_size()[0];
cr.moveTo(this.x, this.y);
layout.set_text(this.text, -1);
PangoCairo.show_layout_line(cr, layout.get_line(0));
if (this.points.length != 2)
return;
let layout = PangoCairo.create_layout(cr);
let fontSize = this.height * Pango.SCALE;
this.font.set_absolute_size(fontSize);
layout.set_font_description(this.font);
layout.set_text(this.text, -1);
this.textWidth = layout.get_pixel_size()[0];
this.lineWidths = layout.get_lines_readonly().map(layoutLine => layoutLine.get_pixel_extents()[1].width);
layout.get_lines_readonly().forEach((layoutLine, index) => {
cr.moveTo(this._getLineX(index), this.y + this.height * index);
PangoCairo.show_layout_line(cr, layoutLine);
});
// Cannot use 'layout.index_to_line_x(cursorPosition, 0)' because character position != byte index
if (params.showTextCursor) {
let layoutCopy = layout.copy();
if (this.cursorPosition != -1)
layoutCopy.set_text(this.text.slice(0, this.cursorPosition), -1);
if (params.showTextCursor) {
let cursorPosition = this.cursorPosition == -1 ? this.text.length : this.cursorPosition;
layout.set_text(this.text.slice(0, cursorPosition), -1);
let width = layout.get_pixel_size()[0];
cr.rectangle(this.x + width, this.y,
this.height / 25, - this.height);
cr.fill();
}
if (params.showTextRectangle) {
cr.rectangle(this.x, this.y - this.lineOffset,
this.textWidth, - this.height);
setDummyStroke(cr);
} else if (params.drawTextRectangle) {
cr.rectangle(this.x, this.y,
this.textWidth, - this.height);
// Only draw the rectangle to find the element, not to show it.
cr.setLineWidth(0);
}
let cursorLineIndex = layoutCopy.get_line_count() - 1;
let cursorX = this._getLineX(cursorLineIndex) + layoutCopy.get_line_readonly(cursorLineIndex).get_pixel_extents()[1].width;
let cursorY = this.y + this.height * cursorLineIndex;
cr.rectangle(cursorX, cursorY, this.height / 25, - this.height);
cr.fill();
}
if (params.showElementBounds) {
cr.rectangle(this.x, this.y - this.height,
this.textWidth, this.height * layout.get_line_count());
setDummyStroke(cr);
} else if (params.drawElementBounds) {
cr.rectangle(this.x, this.y - this.height,
this.textWidth, this.height * layout.get_line_count());
// Only draw the rectangle to find the element, not to show it.
cr.setLineWidth(0);
}
},
@ -774,32 +826,48 @@ const TextElement = new Lang.Class({
},
_drawSvg: function(transAttribute, bgcolorString) {
let row = "\n ";
let [x, y, height] = [Math.round(this.x*100)/100, Math.round(this.y*100)/100, Math.round(this.height*100)/100];
if (this.points.length != 2)
return "";
let row = "";
let height = Math.round(this.height * 100) / 100;
let color = this.eraser ? bgcolorString : this.color.toJSON();
let attributes = this.eraser ? `class="eraser" ` : '';
attributes += `fill="${color}" ` +
`font-size="${height}" ` +
`font-family="${this.font.get_family()}"`;
if (this.points.length == 2) {
attributes += `fill="${color}" ` +
`font-size="${height}" ` +
`font-family="${this.font.get_family()}"`;
// this.font.to_string() is not valid to fill the svg 'font' shorthand property.
// Each property must be filled separately.
['Stretch', 'Style', 'Variant'].forEach(attribute => {
let lower = attribute.toLowerCase();
if (this.font[`get_${lower}`]() != Pango[attribute].NORMAL) {
let font = new Pango.FontDescription();
font[`set_${lower}`](this.font[`get_${lower}`]());
attributes += ` font-${lower}="${font.to_string()}"`;
}
});
if (this.font.get_weight() != Pango.Weight.NORMAL)
attributes += ` font-weight="${this.font.get_weight()}"`;
row += `<text ${attributes} x="${x}" `;
row += `y="${y}"${transAttribute}>${this.text}</text>`;
// this.font.to_string() is not valid to fill the svg 'font' shorthand property.
// Each property must be filled separately.
['Stretch', 'Style', 'Variant'].forEach(attribute => {
let lower = attribute.toLowerCase();
if (this.font[`get_${lower}`]() != Pango[attribute].NORMAL) {
let font = new Pango.FontDescription();
font[`set_${lower}`](this.font[`get_${lower}`]());
attributes += ` font-${lower}="${font.to_string()}"`;
}
});
if (this.font.get_weight() != Pango.Weight.NORMAL)
attributes += ` font-weight="${this.font.get_weight()}"`;
// It is a fallback for thumbnails. The following layout is not the same than the Cairo one and
// layoutLine.get_pixel_extents does not return the correct value with non-latin fonts.
// An alternative would be to store this.lineWidths in the json.
if (this.textAlignment != TextAlignment.LEFT && !this.lineWidths) {
let clutterText = new Clutter.Text({ text: this.text });
let layout = clutterText.get_layout();
let fontSize = height * Pango.SCALE;
this.font.set_absolute_size(fontSize);
layout.set_font_description(this.font);
this.lineWidths = layout.get_lines_readonly().map(layoutLine => layoutLine.get_pixel_extents()[1].width);
}
this.text.split(/\r\n|\r|\n/).forEach((text, index) => {
let x = Math.round(this._getLineX(index) * 100) / 100;
let y = Math.round((this.y + this.height * index) * 100) / 100;
row += `\n <text ${attributes} x="${x}" y="${y}"${transAttribute}>${text}</text>`;
});
return row;
},
@ -827,7 +895,7 @@ const TextElement = new Lang.Class({
_getOriginalCenter: function() {
if (!this._originalCenter) {
let points = this.points;
this._originalCenter = this.textWidth ? [points[1][0], Math.max(points[0][1], points[1][1]) - this.lineOffset] :
this._originalCenter = points.length == 2 ? [points[1][0], Math.max(points[0][1], points[1][1])] :
points.length >= 3 ? getCentroid(points) :
getNaiveCenter(points);
}
@ -869,10 +937,10 @@ const ImageElement = new Lang.Class({
cr.fill();
cr.restore();
if (params.showTextRectangle) {
if (params.showElementBounds) {
cr.rectangle(x, y, width, height);
setDummyStroke(cr);
} else if (params.drawTextRectangle) {
} else if (params.drawElementBounds) {
cr.rectangle(x, y, width, height);
// Only draw the rectangle to find the element, not to show it.
cr.setLineWidth(0);

View File

@ -171,7 +171,7 @@ const AreaManager = new Lang.Class({
let loadPersistent = i == Main.layoutManager.primaryIndex && this.persistentOverRestarts;
// Some utils for the drawing area menus.
let areaManagerUtils = {
getHiddenList: () => this.hiddenList,
getHiddenList: () => this.hiddenList || null,
togglePanelAndDockOpacity: this.togglePanelAndDockOpacity.bind(this),
openPreferences: this.openPreferences.bind(this)
};
@ -210,17 +210,17 @@ const AreaManager = new Lang.Class({
'switch-fill' : this.activeArea.switchFill.bind(this.activeArea),
'switch-image-file' : this.activeArea.switchImageFile.bind(this.activeArea, false),
'switch-image-file-reverse' : this.activeArea.switchImageFile.bind(this.activeArea, true),
'select-none-shape': () => this.activeArea.selectTool(Area.Tools.NONE),
'select-line-shape': () => this.activeArea.selectTool(Area.Tools.LINE),
'select-ellipse-shape': () => this.activeArea.selectTool(Area.Tools.ELLIPSE),
'select-rectangle-shape': () => this.activeArea.selectTool(Area.Tools.RECTANGLE),
'select-text-shape': () => this.activeArea.selectTool(Area.Tools.TEXT),
'select-image-shape': () => this.activeArea.selectTool(Area.Tools.IMAGE),
'select-polygon-shape': () => this.activeArea.selectTool(Area.Tools.POLYGON),
'select-polyline-shape': () => this.activeArea.selectTool(Area.Tools.POLYLINE),
'select-move-tool': () => this.activeArea.selectTool(Area.Tools.MOVE),
'select-resize-tool': () => this.activeArea.selectTool(Area.Tools.RESIZE),
'select-mirror-tool': () => this.activeArea.selectTool(Area.Tools.MIRROR)
'select-none-shape': () => this.activeArea.selectTool(Area.Tool.NONE),
'select-line-shape': () => this.activeArea.selectTool(Area.Tool.LINE),
'select-ellipse-shape': () => this.activeArea.selectTool(Area.Tool.ELLIPSE),
'select-rectangle-shape': () => this.activeArea.selectTool(Area.Tool.RECTANGLE),
'select-text-shape': () => this.activeArea.selectTool(Area.Tool.TEXT),
'select-image-shape': () => this.activeArea.selectTool(Area.Tool.IMAGE),
'select-polygon-shape': () => this.activeArea.selectTool(Area.Tool.POLYGON),
'select-polyline-shape': () => this.activeArea.selectTool(Area.Tool.POLYLINE),
'select-move-tool': () => this.activeArea.selectTool(Area.Tool.MOVE),
'select-resize-tool': () => this.activeArea.selectTool(Area.Tool.RESIZE),
'select-mirror-tool': () => this.activeArea.selectTool(Area.Tool.MIRROR)
};
// available when writing

View File

@ -41,13 +41,13 @@ const ICON_NAMES = [
'arc', 'color', 'dashed-line', 'document-export', 'fillrule-evenodd', 'fillrule-nonzero', 'fill', 'full-line', 'linecap', 'linejoin', 'palette', 'smooth', 'stroke',
'tool-ellipse', 'tool-line', 'tool-mirror', 'tool-move', 'tool-none', 'tool-polygon', 'tool-polyline', 'tool-rectangle', 'tool-resize',
];
const ThemedIconNames = {
const ThemedIconName = {
COLOR_PICKER: 'color-select-symbolic',
ENTER: 'applications-graphics', LEAVE: 'application-exit',
GRAB: 'input-touchpad', UNGRAB: 'touchpad-disabled',
OPEN: 'document-open', SAVE: 'document-save',
FONT_FAMILY: 'font-x-generic', FONT_STYLE: 'format-text-italic', FONT_WEIGHT:'format-text-bold',
LEFT_ALIGNED: 'format-justify-left', RIGHT_ALIGNED: 'format-justify-right',
LEFT_ALIGNED: 'format-justify-left', CENTERED: 'format-justify-center',RIGHT_ALIGNED: 'format-justify-right',
TOOL_IMAGE: 'insert-image', TOOL_TEXT: 'insert-text',
};
@ -74,11 +74,11 @@ ICON_NAMES.forEach(name => {
});
});
Object.keys(ThemedIconNames).forEach(key => {
Object.keys(ThemedIconName).forEach(key => {
Object.defineProperty(Icons, key, {
get: function() {
if (!this[`_${key}`])
this[`_${key}`] = new Gio.ThemedIcon({ name: `${ThemedIconNames[key]}-symbolic` });
this[`_${key}`] = new Gio.ThemedIcon({ name: `${ThemedIconName[key]}-symbolic` });
return this[`_${key}`];
}
});

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-10-04 22:45+0200\n"
"POT-Creation-Date: 2021-02-17 14:14+0100\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"
@ -44,7 +44,9 @@ msgstr ""
#. Translators: %s is a key label
#, javascript-format
msgid "Press <i>%s</i>\nto start a new line"
msgid ""
"Press <i>%s</i>\n"
"to start a new line"
msgstr ""
#. Translators: It is displayed in an OSD notification to ask the user to start picking, so it should use the imperative mood.
@ -214,11 +216,13 @@ msgstr ""
msgid "%f px"
msgstr ""
#. Translators: text alignment
msgid "Right aligned"
msgid "Left aligned"
msgstr ""
msgid "Left aligned"
msgid "Centered"
msgstr ""
msgid "Right aligned"
msgstr ""
msgctxt "drawing-tool"
@ -685,7 +689,7 @@ msgstr ""
msgid "Change linejoin"
msgstr ""
msgid "Toggle text alignment"
msgid "Change text alignment"
msgstr ""
msgid "Add a drawing background"

View File

@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Draw On Your Screen 6.1\n"
"Report-Msgid-Bugs-To: https://framagit.org/abakkk/DrawOnYourScreen/issues\n"
"POT-Creation-Date: 2020-09-19 15:32+0200\n"
"PO-Revision-Date: 2020-10-21 09:23+0200\n"
"POT-Creation-Date: 2021-02-17 14:14+0100\n"
"PO-Revision-Date: 2021-02-20 01:06+0100\n"
"Last-Translator: Abakkk\n"
"Language-Team: \n"
"Language: fr\n"
@ -226,13 +226,15 @@ msgstr "Biseauté"
msgid "%f px"
msgstr "%f px"
#. Translators: text alignment
msgid "Right aligned"
msgstr "Aligné à droite"
msgid "Left aligned"
msgstr "Aligné à gauche"
msgid "Centered"
msgstr "Centré"
msgid "Right aligned"
msgstr "Aligné à droite"
msgctxt "drawing-tool"
msgid "Free drawing"
msgstr "Dessin libre"
@ -277,12 +279,6 @@ msgctxt "drawing-tool"
msgid "Mirror"
msgstr "Miroir"
msgid "Undo"
msgstr "Retirer"
msgid "Redo"
msgstr "Rétablir"
msgid "Erase"
msgstr "Effacer"
@ -601,8 +597,8 @@ msgstr "Ouvrir le dessin précédent"
msgid "Add images from the clipboard"
msgstr "Ajouter des images depuis le presse-papiers"
msgid "Redo last brushstroke"
msgstr "Rétablir le dernier tracé"
msgid "Redo"
msgstr "Rétablir"
msgid "Save drawing"
msgstr "Enregistrer le dessin"
@ -706,8 +702,8 @@ msgstr "Modifier la forme de lextrémité des lignes"
msgid "Change linejoin"
msgstr "Modifier la jointure des segments de ligne"
msgid "Toggle text alignment"
msgstr "Alterner lalignement du texte"
msgid "Change text alignment"
msgstr "Modifier lalignement du texte"
msgid "Add a drawing background"
msgstr "Ajouter une couleur de fond au dessin"
@ -725,5 +721,5 @@ msgstr "Cacher le panneau et le dock"
msgid "Square drawing area"
msgstr "Rendre la zone de dessin carrée"
msgid "Undo last brushstroke"
msgstr "Retirer le dernier tracé"
msgid "Undo"
msgstr "Retirer"

114
menu.js
View File

@ -47,6 +47,7 @@ const FONT_FAMILY_STYLE = true;
// use 'login-dialog-message-warning' class in order to get GS theme warning color (default: #f57900)
const WARNING_COLOR_STYLE_CLASS_NAME = 'login-dialog-message-warning';
const UUID = Me.uuid.replace(/@/gi, '_at_').replace(/[^a-z0-9+_-]/gi, '_');
const TextAlignmentIcon = { 0: Files.Icons.LEFT_ALIGNED, 1: Files.Icons.CENTERED, 2: Files.Icons.RIGHT_ALIGNED };
const getActor = function(object) {
return GS_VERSION < '3.33.0' ? object.actor : object;
@ -71,52 +72,52 @@ var DisplayStrings = {
},
get FillRule() {
if (!this._fillRules)
if (!this._FillRule)
// Translators: fill-rule SVG attribute
this._fillRules = { 0: _("Nonzero"), 1: _("Evenodd") };
return this._fillRules;
this._FillRule = { 0: _("Nonzero"), 1: _("Evenodd") };
return this._FillRule;
},
getFontFamily: function(family) {
if (!this._fontGenericFamilies)
if (!this._FontGenericFamily)
// Translators: generic font-family SVG attribute
this._fontGenericFamilies = { 'Sans-Serif': pgettext("font-family", "Sans-Serif"), 'Serif': pgettext("font-family", "Serif"),
'Monospace': pgettext("font-family", "Monospace"), 'Cursive': pgettext("font-family", "Cursive"),
'Fantasy': pgettext("font-family", "Fantasy") };
return this._fontGenericFamilies[family] || family;
this._FontGenericFamily = { 'Sans-Serif': pgettext("font-family", "Sans-Serif"), 'Serif': pgettext("font-family", "Serif"),
'Monospace': pgettext("font-family", "Monospace"), 'Cursive': pgettext("font-family", "Cursive"),
'Fantasy': pgettext("font-family", "Fantasy") };
return this._FontGenericFamily[family] || family;
},
get FontStyle() {
if (!this._fontStyles)
if (!this._FontStyle)
// Translators: font-style SVG attribute
this._fontStyles = { 0: pgettext("font-style", "Normal"), 1: pgettext("font-style", "Oblique"), 2: pgettext("font-style", "Italic") };
return this._fontStyles;
this._FontStyle = { 0: pgettext("font-style", "Normal"), 1: pgettext("font-style", "Oblique"), 2: pgettext("font-style", "Italic") };
return this._FontStyle;
},
FontStyleMarkup: { 0: 'normal', 1: 'oblique', 2: 'italic' },
get FontWeight() {
if (!this._fontWeights)
if (!this._FontWeight)
// Translators: font-weight SVG attribute
this._fontWeights = { 100: pgettext("font-weight", "Thin"), 200: pgettext("font-weight", "Ultra Light"), 300: pgettext("font-weight", "Light"),
350: pgettext("font-weight", "Semi Light"), 380: pgettext("font-weight", "Book"), 400: pgettext("font-weight", "Normal"),
500: pgettext("font-weight", "Medium"), 600: pgettext("font-weight", "Semi Bold"), 700: pgettext("font-weight", "Bold"),
800: pgettext("font-weight", "Ultra Bold"), 900: pgettext("font-weight", "Heavy"), 1000: pgettext("font-weight", "Ultra Heavy") };
return this._fontWeights;
this._FontWeight = { 100: pgettext("font-weight", "Thin"), 200: pgettext("font-weight", "Ultra Light"), 300: pgettext("font-weight", "Light"),
350: pgettext("font-weight", "Semi Light"), 380: pgettext("font-weight", "Book"), 400: pgettext("font-weight", "Normal"),
500: pgettext("font-weight", "Medium"), 600: pgettext("font-weight", "Semi Bold"), 700: pgettext("font-weight", "Bold"),
800: pgettext("font-weight", "Ultra Bold"), 900: pgettext("font-weight", "Heavy"), 1000: pgettext("font-weight", "Ultra Heavy") };
return this._FontWeight;
},
get LineCap() {
if (!this._lineCaps)
if (!this._LineCap)
// Translators: stroke-linecap SVG attribute
this._lineCaps = { 0: pgettext("stroke-linecap", "Butt"), 1: pgettext("stroke-linecap", "Round"), 2: pgettext("stroke-linecap", "Square") };
return this._lineCaps;
this._LineCap = { 0: pgettext("stroke-linecap", "Butt"), 1: pgettext("stroke-linecap", "Round"), 2: pgettext("stroke-linecap", "Square") };
return this._LineCap;
},
get LineJoin() {
if (!this._lineJoins)
if (!this._LineJoin)
// Translators: stroke-linejoin SVG attribute
this._lineJoins = { 0: pgettext("stroke-linejoin", "Miter"), 1: pgettext("stroke-linejoin", "Round"), 2: pgettext("stroke-linejoin", "Bevel") };
return this._lineJoins;
this._LineJoin = { 0: pgettext("stroke-linejoin", "Miter"), 1: pgettext("stroke-linejoin", "Round"), 2: pgettext("stroke-linejoin", "Bevel") };
return this._LineJoin;
},
getPixels(value) {
@ -124,28 +125,31 @@ var DisplayStrings = {
return _("%f px").format(value);
},
getTextAlignment: function(rightAligned) {
get TextAlignment() {
// Translators: text alignment
return rightAligned ? _("Right aligned") : _("Left aligned");
if (!this._TextAlignment)
this._TextAlignment = { 0: _("Left aligned"), 1: _("Centered"), 2: _("Right aligned") };
return this._TextAlignment;
},
get Tool() {
if (!this._tools)
this._tools = { 0: pgettext("drawing-tool", "Free drawing"), 1: pgettext("drawing-tool", "Line"), 2: pgettext("drawing-tool", "Ellipse"),
3: pgettext("drawing-tool", "Rectangle"), 4: pgettext("drawing-tool", "Text"), 5: pgettext("drawing-tool", "Polygon"),
6: pgettext("drawing-tool", "Polyline"), 7: pgettext("drawing-tool", "Image"),
100: pgettext("drawing-tool", "Move"), 101: pgettext("drawing-tool", "Resize"), 102: pgettext("drawing-tool", "Mirror") };
return this._tools;
if (!this._Tool)
this._Tool = { 0: pgettext("drawing-tool", "Free drawing"), 1: pgettext("drawing-tool", "Line"), 2: pgettext("drawing-tool", "Ellipse"),
3: pgettext("drawing-tool", "Rectangle"), 4: pgettext("drawing-tool", "Text"), 5: pgettext("drawing-tool", "Polygon"),
6: pgettext("drawing-tool", "Polyline"), 7: pgettext("drawing-tool", "Image"),
100: pgettext("drawing-tool", "Move"), 101: pgettext("drawing-tool", "Resize"), 102: pgettext("drawing-tool", "Mirror") };
return this._Tool;
}
};
var DrawingMenu = new Lang.Class({
Name: `${UUID}-DrawingMenu`,
_init: function(area, monitor, drawingTools, areaManagerUtils) {
_init: function(area, monitor, DrawingTool, areaManagerUtils) {
this.area = area;
this.monitor = monitor;
this.drawingTools = drawingTools;
this.DrawingTool = DrawingTool;
this.areaManagerUtils = areaManagerUtils;
let side = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL ? St.Side.RIGHT : St.Side.LEFT;
@ -178,7 +182,7 @@ var DrawingMenu = new Lang.Class({
disable: function() {
delete this.area;
delete this.drawingTools;
delete this.DrawingTool;
delete this.areaManagerUtils;
this.menuManager.removeMenu(this.menu);
Main.layoutManager.uiGroup.remove_actor(this.menu.actor);
@ -268,7 +272,7 @@ var DrawingMenu = new Lang.Class({
this._addFontFamilySubMenuItem(fontSection, Files.Icons.FONT_FAMILY);
this._addSubMenuItem(fontSection, Files.Icons.FONT_WEIGHT, DisplayStrings.FontWeight, this.area, 'currentFontWeight');
this._addSubMenuItem(fontSection, Files.Icons.FONT_STYLE, DisplayStrings.FontStyle, this.area, 'currentFontStyle');
this._addSwitchItem(fontSection, DisplayStrings.getTextAlignment(true), Files.Icons.LEFT_ALIGNED, Files.Icons.RIGHT_ALIGNED, this.area, 'currentTextRightAligned');
this._addTextAlignmentSubMenuItem(fontSection);
this._addSeparator(fontSection);
this.menu.addMenuItem(fontSection);
fontSection.itemActivated = () => {};
@ -311,14 +315,14 @@ var DrawingMenu = new Lang.Class({
this.undoButton.child.reactive = this.area.elements.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.smoothButton.child.reactive = this.area.elements.length > 0 && this.area.elements[this.area.elements.length - 1].shape == this.DrawingTool.NONE;
this.saveButton.child.reactive = this.area.elements.length > 0;
this.svgButton.child.reactive = this.area.elements.length > 0;
this.saveDrawingSubMenuItem.setSensitive(this.area.elements.length > 0);
},
_updateSectionVisibility: function() {
let [isText, isImage] = [this.area.currentTool == this.drawingTools.TEXT, this.area.currentTool == this.drawingTools.IMAGE];
let [isText, isImage] = [this.area.currentTool == this.DrawingTool.TEXT, this.area.currentTool == this.DrawingTool.IMAGE];
this.lineSection.actor.visible = !isText && !isImage;
this.fontSection.actor.visible = isText;
this.imageSection.actor.visible = isImage;
@ -421,7 +425,7 @@ var DrawingMenu = new Lang.Class({
let item = new PopupMenu.PopupSubMenuMenuItem('', true);
item.update = () => {
item.label.set_text(DisplayStrings.Tool[this.area.currentTool]);
let toolName = this.drawingTools.getNameOf(this.area.currentTool);
let toolName = this.DrawingTool.getNameOf(this.area.currentTool);
item.icon.set_gicon(Files.Icons[`TOOL_${toolName}`]);
};
item.update();
@ -431,7 +435,7 @@ var DrawingMenu = new Lang.Class({
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
Object.keys(DisplayStrings.Tool).forEach(key => {
let text = DisplayStrings.Tool[key];
let toolName = this.drawingTools.getNameOf(key);
let toolName = this.DrawingTool.getNameOf(key);
let subItemIcon = Files.Icons[`TOOL_${toolName}`];
let subItem = item.menu.addAction(text, () => {
this.area.currentTool = Number(key);
@ -443,10 +447,10 @@ var DrawingMenu = new Lang.Class({
getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment);
// change the display order of tools
if (key == this.drawingTools.POLYGON)
item.menu.moveMenuItem(subItem, Number(this.drawingTools.TEXT));
else if (key == this.drawingTools.POLYLINE)
item.menu.moveMenuItem(subItem, Number(this.drawingTools.TEXT) + 1);
if (key == this.DrawingTool.POLYGON)
item.menu.moveMenuItem(subItem, Number(this.DrawingTool.TEXT));
else if (key == this.DrawingTool.POLYLINE)
item.menu.moveMenuItem(subItem, Number(this.DrawingTool.TEXT) + 1);
});
return GLib.SOURCE_REMOVE;
});
@ -553,6 +557,28 @@ var DrawingMenu = new Lang.Class({
menu.addMenuItem(item);
},
_addTextAlignmentSubMenuItem: function(menu) {
let item = new PopupMenu.PopupSubMenuMenuItem(DisplayStrings.TextAlignment[this.area.currentTextAlignment], true);
item.icon.set_gicon(TextAlignmentIcon[this.area.currentTextAlignment]);
item.menu.itemActivated = item.menu.close;
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
Object.keys(TextAlignmentIcon).forEach(key => {
let subItem = item.menu.addAction(DisplayStrings.TextAlignment[key], () => {
item.label.set_text(DisplayStrings.TextAlignment[key]);
this.area.currentTextAlignment = key;
item.icon.set_gicon(TextAlignmentIcon[key]);
});
getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment);
});
return GLib.SOURCE_REMOVE;
});
menu.addMenuItem(item);
},
_addImageSubMenuItem: function(menu, images) {
let item = new PopupMenu.PopupSubMenuMenuItem('', true);
item.update = () => {
@ -654,7 +680,7 @@ var DrawingMenu = new Lang.Class({
let insertCallback = () => {
this.area.currentImage = json.image;
this.imageItem.update();
this.area.currentTool = this.drawingTools.IMAGE;
this.area.currentTool = this.DrawingTool.IMAGE;
this.toolItem.update();
this._updateSectionVisibility();
};

View File

@ -16,7 +16,8 @@
"3.32",
"3.34",
"3.36",
"3.38"
"3.38",
"40"
],
"version": 10
"version": 11
}

399
prefs.js
View File

@ -21,12 +21,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
const Atk = imports.gi.Atk;
const Gdk = imports.gi.Gdk;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;
const IS_GTK3 = Gtk.get_major_version() == 3;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
@ -39,27 +39,66 @@ const _ = function(string) {
return "";
return gettext(string);
};
const _GTK = imports.gettext.domain('gtk30').gettext;
const _GTK = imports.gettext.domain(IS_GTK3 ? 'gtk30' : 'gtk40').gettext;
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_start: MARGIN, margin_end: MARGIN, spacing: 4 };
const UUID = Me.uuid.replace(/@/gi, '_at_').replace(/[^a-z0-9+_-]/gi, '_');
if (IS_GTK3) {
Gtk.Container.prototype.append = Gtk.Container.prototype.add;
Gtk.Bin.prototype.set_child = Gtk.Container.prototype.add;
}
const setAccessibleLabel = function(widget, label) {
if (IS_GTK3)
widget.get_accessible().set_name(label);
else
widget.update_property([Gtk.AccessibleProperty.LABEL], [label]);
};
const setAccessibleDescription = function(widget, description) {
if (IS_GTK3)
widget.get_accessible().set_description(description);
else
widget.update_property([Gtk.AccessibleProperty.DESCRIPTION], [description]);
};
const getChildrenOf = function(widget) {
if (IS_GTK3) {
return widget.get_children();
} else {
let listModel = widget.observe_children();
let i = 0;
let children = [];
let child;
while (!!(child = listModel.get_item(i))) {
children.push(child);
i++;
}
return children;
}
};
function init() {
Convenience.initTranslations();
}
function buildPrefsWidget() {
let topStack = new TopStack();
let switcher = new Gtk.StackSwitcher({halign: Gtk.Align.CENTER, visible: true, stack: topStack});
let switcher = new Gtk.StackSwitcher({ halign: Gtk.Align.CENTER, visible: true, stack: topStack });
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
let window = topStack.get_toplevel();
let headerBar = window.get_titlebar();
headerBar.custom_title = switcher;
return false;
if (IS_GTK3)
topStack.get_toplevel().get_titlebar().set_custom_title(switcher);
else
topStack.get_root().get_titlebar().set_title_widget(switcher);
return GLib.SOURCE_REMOVE;
});
topStack.show_all();
if (IS_GTK3)
topStack.show_all();
return topStack;
}
@ -68,7 +107,8 @@ const TopStack = new GObject.Class({
Extends: Gtk.Stack,
_init: function(params) {
this.parent({ transition_type: 1, transition_duration: 500, expand: true });
this.parent({ transition_type: Gtk.StackTransitionType.CROSSFADE, transition_duration: 500, hexpand: true, vexpand: true });
this.prefsPage = new PrefsPage();
// Translators: "Preferences" page in preferences
this.add_titled(this.prefsPage, 'prefs', _("Preferences"));
@ -88,8 +128,8 @@ const AboutPage = new GObject.Class({
_init: function(params) {
this.parent({ hscrollbar_policy: Gtk.PolicyType.NEVER });
let vbox= new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, margin: MARGIN * 3 });
this.add(vbox);
let vbox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, margin_top: 3 * MARGIN, margin_bottom: 3 * MARGIN, margin_start: 3 * MARGIN, margin_end: 3 * MARGIN });
this.set_child(vbox);
// Translators: you are free to translate the extension name, that is displayed in About page, or not
let name = "<b> " + _("Draw On You Screen") + "</b>";
@ -102,21 +142,21 @@ const AboutPage = new GObject.Class({
let licenceLink = "https://www.gnu.org/licenses/old-licenses/gpl-2.0.html";
let licence = "<small>" + _GTK("This program comes with absolutely no warranty.\nSee the <a href=\"%s\">%s</a> for details.").format(licenceLink, licenceName) + "</small>";
let aboutLabel = new Gtk.Label({ wrap: true, justify: 2, use_markup: true, label:
let aboutLabel = new Gtk.Label({ wrap: true, justify: Gtk.Justification.CENTER, use_markup: true, label:
name + "\n\n" + version + "\n\n" + description + "\n\n" + link + "\n\n" + licence + "\n" });
vbox.add(aboutLabel);
vbox.append(aboutLabel);
let creditBox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL, margin: 2 * MARGIN });
let leftBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL });
let rightBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL });
let leftLabel = new Gtk.Label({ wrap: true, valign: 1, halign: 2, justify: 1, use_markup: true, label: "<small>" + _GTK("Created by") + "</small>" });
let rightLabel = new Gtk.Label({ wrap: true, valign: 1, halign: 1, justify: 0, use_markup: true, label: "<small><a href=\"https://framagit.org/abakkk\">Abakkk</a></small>" });
leftBox.pack_start(leftLabel, false, false, 0);
rightBox.pack_start(rightLabel, false, false, 0);
creditBox.pack_start(leftBox, true, true, 5);
creditBox.pack_start(rightBox, true, true, 5);
vbox.add(creditBox);
let creditBox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL, margin_top: 2 * MARGIN, margin_bottom: 2 * MARGIN, margin_start: 2 * MARGIN, margin_end: 2 * MARGIN, spacing: 5 });
let leftBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, hexpand: true });
let rightBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, hexpand: true });
leftBox.append(new Gtk.Label({ wrap: true, valign: Gtk.Align.START, halign: Gtk.Align.END, justify: Gtk.Justification.RIGHT,
use_markup: true, label: "<small>" + _GTK("Created by") + "</small>" }));
rightBox.append(new Gtk.Label({ wrap: true, valign: Gtk.Align.START, halign: Gtk.Align.START, justify: Gtk.Justification.LEFT,
use_markup: true, label: "<small><a href=\"https://framagit.org/abakkk\">Abakkk</a></small>" }));
creditBox.append(leftBox);
creditBox.append(rightBox);
vbox.append(creditBox);
// Translators: add your name here or keep it empty, it will be displayed in about page, e.g.
// msgstr ""
@ -124,12 +164,10 @@ const AboutPage = new GObject.Class({
// "<a href=\"mailto:translator2@mail.org\">translator2</a>\n"
// "<a href=\"https://...\">translator3</a>"
if (_("translator-credits") != "translator-credits" && _("translator-credits") != "") {
leftBox.pack_start(new Gtk.Label(), false, false, 0);
rightBox.pack_start(new Gtk.Label(), false, false, 0);
leftLabel = new Gtk.Label({ wrap: true, valign: 1, halign: 2, justify: 1, use_markup: true, label: "<small>" + _GTK("Translated by") + "</small>" });
rightLabel = new Gtk.Label({ wrap: true, valign: 1, halign: 1, justify: 0, use_markup: true, label: "<small>" + _("translator-credits") + "</small>" });
leftBox.pack_start(leftLabel, false, false, 0);
rightBox.pack_start(rightLabel, false, false, 0);
leftBox.append(new Gtk.Label());
rightBox.append(new Gtk.Label());
leftBox.append(new Gtk.Label({ wrap: true, valign: Gtk.Align.START, halign: Gtk.Align.END, justify: 1, use_markup: true, label: "<small>" + _GTK("Translated by") + "</small>" }));
rightBox.append(new Gtk.Label({ wrap: true, valign: Gtk.Align.START, halign: Gtk.Align.START, justify: 0, use_markup: true, label: "<small>" + _("translator-credits") + "</small>" }));
}
}
});
@ -144,44 +182,46 @@ const DrawingPage = new GObject.Class({
this.settings = Convenience.getSettings(Me.metadata['settings-schema'] + '.drawing');
this.schema = this.settings.settings_schema;
let box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, margin: 3 * MARGIN, spacing: 3 * MARGIN });
this.add(box);
let box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, margin_top: 3 * MARGIN, margin_bottom: 3 * MARGIN, margin_start: 3 * MARGIN, margin_end: 3 * MARGIN, spacing: 3 * MARGIN });
this.set_child(box);
let palettesFrame = new Frame({ label: _("Palettes") });
box.add(palettesFrame);
box.append(palettesFrame);
let palettesFrameBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL });
palettesFrame.add(palettesFrameBox);
palettesFrame.set_child(palettesFrameBox);
let palettesScrolledWindow = new Gtk.ScrolledWindow({ vscrollbar_policy: Gtk.PolicyType.NEVER });
palettesFrameBox.add(palettesScrolledWindow);
let palettesViewport = new Gtk.Viewport({ margin_top: MARGIN / 2, margin_bottom: MARGIN / 2 });
palettesScrolledWindow.add(palettesViewport);
palettesFrameBox.append(palettesScrolledWindow);
this.palettesListBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true });
this.palettesListBox.get_style_context().add_class('background');
this.palettesListBox.get_accessible().set_name(this.schema.get_key('palettes').get_summary());
this.palettesListBox.get_accessible().set_description(this.schema.get_key('palettes').get_description());
palettesViewport.add(this.palettesListBox);
setAccessibleLabel(this.palettesListBox, this.schema.get_key('palettes').get_summary());
setAccessibleDescription(this.palettesListBox, this.schema.get_key('palettes').get_description());
palettesScrolledWindow.set_child(this.palettesListBox);
palettesScrolledWindow.get_child().set_margin_top(MARGIN / 2);
palettesScrolledWindow.get_child().set_margin_bottom(MARGIN / 2);
this.settings.connect('changed::palettes', this._updatePalettes.bind(this));
this._updatePalettes();
let addBox = new Gtk.Box(ROWBOX_MARGIN_PARAMS);
let addButton = Gtk.Button.new_from_icon_name('list-add-symbolic', Gtk.IconSize.BUTTON);
let addButton = IS_GTK3 ? Gtk.Button.new_from_icon_name('list-add-symbolic', Gtk.IconSize.BUTTON) : Gtk.Button.new_from_icon_name('list-add-symbolic');
addButton.set_tooltip_text(_("Add a new palette"));
addBox.pack_start(addButton, true, true, 4);
addButton.set_hexpand(true);
addBox.append(addButton);
addButton.connect('clicked', this._addNewPalette.bind(this));
let importButton = Gtk.Button.new_from_icon_name('document-open-symbolic', Gtk.IconSize.BUTTON);
let importButton = IS_GTK3 ? Gtk.Button.new_from_icon_name('document-open-symbolic', Gtk.IconSize.BUTTON) : Gtk.Button.new_from_icon_name('document-open-symbolic');
importButton.set_tooltip_text(_GTK("Select a File"));
addBox.pack_start(importButton, true, true, 4);
importButton.set_hexpand(true);
addBox.append(importButton);
importButton.connect('clicked', this._importPalette.bind(this));
palettesFrameBox.add(addBox);
palettesFrameBox.append(addBox);
let areaFrame = new Frame({ label: _("Area") });
box.add(areaFrame);
box.append(areaFrame);
let areaListBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true, margin_top: MARGIN / 2, margin_bottom: MARGIN / 2 });
areaListBox.get_style_context().add_class('background');
areaFrame.add(areaListBox);
areaFrame.set_child(areaListBox);
let squareAreaRow = new PrefRow({ label: this.schema.get_key('square-area-size').get_summary() });
let squareAreaAutoButton = new Gtk.CheckButton({ label: _("Auto"),
@ -196,7 +236,7 @@ const DrawingPage = new GObject.Class({
squareAreaAutoButton.bind_property('active', squareAreaSizeButton, 'sensitive', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN);
squareAreaRow.addWidget(squareAreaAutoButton);
squareAreaRow.addWidget(squareAreaSizeButton);
areaListBox.add(squareAreaRow);
areaListBox.insert(squareAreaRow, -1);
let backgroundColorRow = new PrefRow({ label: this.schema.get_key('background-color').get_summary() });
let backgroundColorButton = new ColorStringButton({ use_alpha: true, show_editor: true,
@ -204,7 +244,7 @@ const DrawingPage = new GObject.Class({
tooltip_text: this.schema.get_key('background-color').get_description() });
this.settings.bind('background-color', backgroundColorButton, 'color-string', 0);
backgroundColorRow.addWidget(backgroundColorButton);
areaListBox.add(backgroundColorRow);
areaListBox.insert(backgroundColorRow, -1);
let gridLineRow = new PrefRow({ label: _("Grid overlay line") });
let gridLineAutoButton = new Gtk.CheckButton({ label: _("Auto"),
@ -226,7 +266,7 @@ const DrawingPage = new GObject.Class({
gridLineRow.addWidget(gridLineAutoButton);
gridLineRow.addWidget(gridLineWidthButton);
gridLineRow.addWidget(gridLineSpacingButton);
areaListBox.add(gridLineRow);
areaListBox.insert(gridLineRow, -1);
let gridColorRow = new PrefRow({ label: this.schema.get_key('grid-color').get_summary() });
let gridColorButton = new ColorStringButton({ use_alpha: true, show_editor: true,
@ -234,14 +274,14 @@ const DrawingPage = new GObject.Class({
tooltip_text: this.schema.get_key('grid-color').get_description() });
this.settings.bind('grid-color', gridColorButton, 'color-string', 0);
gridColorRow.addWidget(gridColorButton);
areaListBox.add(gridColorRow);
areaListBox.insert(gridColorRow, -1);
let toolsFrame = new Frame({ label: _("Tools") });
box.add(toolsFrame);
box.append(toolsFrame);
let toolsListBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true, margin_top: MARGIN / 2, margin_bottom: MARGIN / 2 });
toolsListBox.get_style_context().add_class('background');
toolsFrame.add(toolsListBox);
toolsFrame.set_child(toolsListBox);
let dashArrayRow = new PrefRow({ label: _("Dash array") });
let dashArrayAutoButton = new Gtk.CheckButton({ label: _("Auto"),
@ -263,7 +303,7 @@ const DrawingPage = new GObject.Class({
dashArrayRow.addWidget(dashArrayAutoButton);
dashArrayRow.addWidget(dashArrayOnButton);
dashArrayRow.addWidget(dashArrayOffButton);
toolsListBox.add(dashArrayRow);
toolsListBox.insert(dashArrayRow, -1);
let dashOffsetRow = new PrefRow({ label: this.schema.get_key('dash-offset').get_summary() });
let dashOffsetButton = new PixelSpinButton({ width_chars: 5, digits: 1, step: 0.1,
@ -272,7 +312,7 @@ const DrawingPage = new GObject.Class({
tooltip_text: this.schema.get_key('dash-offset').get_description() });
this.settings.bind('dash-offset', dashOffsetButton, 'value', 0);
dashOffsetRow.addWidget(dashOffsetButton);
toolsListBox.add(dashOffsetRow);
toolsListBox.insert(dashOffsetRow, -1);
let imageLocationRow = new PrefRow({ label: this.schema.get_key('image-location').get_summary() });
let imageLocationButton = new FileChooserButton({ action: Gtk.FileChooserAction.SELECT_FOLDER,
@ -280,19 +320,19 @@ const DrawingPage = new GObject.Class({
tooltip_text: this.schema.get_key('image-location').get_description() });
this.settings.bind('image-location', imageLocationButton, 'location', 0);
imageLocationRow.addWidget(imageLocationButton);
toolsListBox.add(imageLocationRow);
toolsListBox.insert(imageLocationRow, -1);
let resetButton = new Gtk.Button({ label: _("Reset settings"), halign: Gtk.Align.CENTER });
resetButton.get_style_context().add_class('destructive-action');
resetButton.connect('clicked', () => this.schema.list_keys().forEach(key => this.settings.reset(key)));
box.add(resetButton);
box.append(resetButton);
},
_updatePalettes: function() {
this.palettes = this.settings.get_value('palettes').deep_unpack();
this.palettesListBox.get_children().slice(this.palettes.length)
getChildrenOf(this.palettesListBox).slice(this.palettes.length)
.forEach(row => this.palettesListBox.remove(row));
let paletteBoxes = this.palettesListBox.get_children().map(row => row.get_child());
let paletteBoxes = getChildrenOf(this.palettesListBox).map(row => row.get_child());
this.palettes.forEach((palette, paletteIndex) => {
let [name, colors] = palette;
@ -300,22 +340,24 @@ const DrawingPage = new GObject.Class({
if (paletteBoxes[paletteIndex]) {
paletteBox = paletteBoxes[paletteIndex];
let nameEntry = paletteBox.get_children()[0];
let nameEntry = getChildrenOf(paletteBox)[0];
if (nameEntry.get_text() !== _(name)) {
GObject.signal_handler_block(nameEntry, nameEntry.paletteNameChangedHandler);
nameEntry.set_text(_(name));
GObject.signal_handler_unblock(nameEntry, nameEntry.paletteNameChangedHandler);
}
} else {
let nameEntry = new Gtk.Entry({ text: name, halign: Gtk.Align.START, tooltip_text: _("Rename the palette") });
let nameEntry = new Gtk.Entry({ text: name, halign: Gtk.Align.START, tooltip_text: _("Rename the palette"), hexpand: true });
nameEntry.paletteNameChangedHandler = nameEntry.connect('changed', this._onPaletteNameChanged.bind(this, paletteIndex));
let removeButton = Gtk.Button.new_from_icon_name('list-remove-symbolic', Gtk.IconSize.BUTTON);
// Minimum size, for Gtk4
nameEntry.set_size_request(nameEntry.get_preferred_size()[1].width, -1);
let removeButton = IS_GTK3 ? Gtk.Button.new_from_icon_name('list-remove-symbolic', Gtk.IconSize.BUTTON) : Gtk.Button.new_from_icon_name('list-remove-symbolic');
removeButton.set_tooltip_text(_("Remove the palette"));
removeButton.connect('clicked', this._removePalette.bind(this, paletteIndex));
paletteBox = new Gtk.Box(ROWBOX_MARGIN_PARAMS);
paletteBox.pack_start(nameEntry, true, true, 4);
paletteBox.pack_start(new Gtk.Box({ spacing: 4 }), false, false, 4);
paletteBox.pack_start(removeButton, false, false, 4);
paletteBox.append(nameEntry);
paletteBox.append(new Gtk.Box({ spacing: 4 }));
paletteBox.append(removeButton);
this.palettesListBox.insert(paletteBox, paletteIndex);
paletteBox.get_parent().set_activatable(false);
}
@ -323,8 +365,8 @@ const DrawingPage = new GObject.Class({
while (colors.length < 9)
colors.push('transparent');
let colorsBox = paletteBox.get_children()[1];
let colorButtons = colorsBox.get_children();
let colorsBox = getChildrenOf(paletteBox)[1];
let colorButtons = getChildrenOf(colorsBox);
colors.forEach((color, colorIndex) => {
let [colorString, displayName] = color.split(':');
if (colorButtons[colorIndex]) {
@ -335,11 +377,12 @@ const DrawingPage = new GObject.Class({
use_alpha: true, show_editor: true,
halign: Gtk.Align.START, hexpand: false });
colorButton.connect('notify::color-string', this._onPaletteColorChanged.bind(this, paletteIndex, colorIndex));
colorsBox.add(colorButton);
colorsBox.append(colorButton);
}
});
paletteBox.show_all();
if (IS_GTK3)
paletteBox.show_all();
});
},
@ -367,17 +410,31 @@ const DrawingPage = new GObject.Class({
},
_importPalette: function() {
let dialog = new Gtk.FileChooserNative({ action: Gtk.FileChooserAction.OPEN, title: _GTK("Select a File") });
let dialog = new Gtk.FileChooserDialog({
title: _GTK("Select a File"),
action: Gtk.FileChooserAction.OPEN,
modal: true,
transient_for: IS_GTK3 ? this.get_toplevel() : this.get_root(),
});
dialog.add_button(_GTK("_Cancel"), Gtk.ResponseType.CANCEL);
dialog.add_button(_GTK("_Open"), Gtk.ResponseType.ACCEPT);
let filter = new Gtk.FileFilter();
filter.set_name("GIMP Palette (*.gpl)");
filter.add_pattern('*.gpl');
dialog.add_filter(filter);
if (dialog.run() == Gtk.ResponseType.ACCEPT) {
let file = dialog.get_file();
let palettes = GimpPaletteParser.parseFile(file);
palettes.forEach(palette => this.palettes.push(palette));
this._savePalettes();
}
dialog.connect('response', (dialog, response) => {
if (response == Gtk.ResponseType.ACCEPT) {
let file = dialog.get_file();
let palettes = GimpPaletteParser.parseFile(file);
palettes.forEach(palette => this.palettes.push(palette));
this._savePalettes();
}
dialog.destroy();
});
dialog.show();
},
_removePalette: function(paletteIndex) {
@ -397,24 +454,24 @@ const PrefsPage = new GObject.Class({
let schema = settings.settings_schema;
let internalShortcutSettings = Convenience.getSettings(Me.metadata['settings-schema'] + '.internal-shortcuts');
let box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, margin: MARGIN * 3, spacing: 3 * MARGIN });
this.add(box);
let box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, margin_top: 3 * MARGIN, margin_bottom: 3 * MARGIN, margin_start: 3 * MARGIN, margin_end: 3 * MARGIN, spacing: 3 * MARGIN });
this.set_child(box);
let globalFrame = new Frame({ label: _("Global") });
box.add(globalFrame);
box.append(globalFrame);
let listBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true, margin_top: MARGIN, margin_bottom: MARGIN / 2 });
listBox.get_style_context().add_class('background');
globalFrame.add(listBox);
globalFrame.set_child(listBox);
Shortcuts.GLOBAL_KEYBINDINGS.forEach((settingKeys, index) => {
if (index)
listBox.add(new Gtk.Box(ROWBOX_MARGIN_PARAMS));
listBox.insert(new Gtk.Box(ROWBOX_MARGIN_PARAMS), -1);
let globalKeybindingsRow = new Gtk.ListBoxRow({ activatable: false });
let globalKeybindingsWidget = new KeybindingsWidget(settingKeys, settings);
globalKeybindingsRow.add(globalKeybindingsWidget);
listBox.add(globalKeybindingsRow);
globalKeybindingsRow.set_child(globalKeybindingsWidget);
listBox.insert(globalKeybindingsRow, -1);
});
let persistentOverTogglesKey = schema.get_key('persistent-over-toggles');
@ -422,7 +479,7 @@ const PrefsPage = new GObject.Class({
let persistentOverTogglesSwitch = new Gtk.Switch();
settings.bind('persistent-over-toggles', persistentOverTogglesSwitch, 'active', 0);
persistentOverTogglesRow.addWidget(persistentOverTogglesSwitch, true);
listBox.add(persistentOverTogglesRow);
listBox.insert(persistentOverTogglesRow, -1);
let persistentOverRestartsKey = schema.get_key('persistent-over-restarts');
let persistentOverRestartsRow = new PrefRow({ label: persistentOverRestartsKey.get_summary(), desc: persistentOverRestartsKey.get_description() });
@ -430,7 +487,7 @@ const PrefsPage = new GObject.Class({
settings.bind('persistent-over-restarts', persistentOverRestartsSwitch, 'active', 0);
persistentOverRestartsRow.addWidget(persistentOverRestartsSwitch, true);
persistentOverTogglesSwitch.bind_property('active', persistentOverRestartsSwitch, 'sensitive', GObject.BindingFlags.SYNC_CREATE);
listBox.add(persistentOverRestartsRow);
listBox.insert(persistentOverRestartsRow, -1);
let desktopKey = schema.get_key('drawing-on-desktop');
let desktopRow = new PrefRow({ label: desktopKey.get_summary(), desc: desktopKey.get_description() });
@ -438,56 +495,53 @@ const PrefsPage = new GObject.Class({
settings.bind('drawing-on-desktop', desktopSwitch, 'active', 0);
desktopRow.addWidget(desktopSwitch, true);
persistentOverTogglesSwitch.bind_property('active', desktopSwitch, 'sensitive', GObject.BindingFlags.SYNC_CREATE);
listBox.add(desktopRow);
listBox.insert(desktopRow, -1);
let osdKey = schema.get_key('osd-disabled');
let osdRow = new PrefRow({ label: osdKey.get_summary(), desc: osdKey.get_description() });
let osdSwitch = new Gtk.Switch();
settings.bind('osd-disabled', osdSwitch, 'active', 0);
osdRow.addWidget(osdSwitch, true);
listBox.add(osdRow);
listBox.insert(osdRow, -1);
let indicatorKey = schema.get_key('indicator-disabled');
let indicatorRow = new PrefRow({ label: indicatorKey.get_summary(), desc: indicatorKey.get_description() });
let indicatorSwitch = new Gtk.Switch();
settings.bind('indicator-disabled', indicatorSwitch, 'active', 0);
indicatorRow.addWidget(indicatorSwitch, true);
listBox.add(indicatorRow);
listBox.insert(indicatorRow, -1);
let internalFrame = new Frame({ label: _("Internal"), desc: _("In drawing mode") });
box.add(internalFrame);
box.append(internalFrame);
listBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true, margin_top: MARGIN, margin_bottom: MARGIN });
listBox.get_style_context().add_class('background');
internalFrame.add(listBox);
internalFrame.set_child(listBox);
Shortcuts.OTHERS.forEach((pairs, index) => {
if (index)
listBox.add(new Gtk.Box(ROWBOX_MARGIN_PARAMS));
listBox.insert(new Gtk.Box(ROWBOX_MARGIN_PARAMS), -1);
pairs.forEach(pair => {
let [action, shortcut] = pair;
let otherBox = new Gtk.Box({ margin_left: MARGIN, margin_right: MARGIN });
let otherLabel = new Gtk.Label({ label: action, use_markup: true });
otherLabel.set_halign(1);
let otherLabel2 = new Gtk.Label({ label: shortcut });
otherBox.pack_start(otherLabel, true, true, 4);
otherBox.pack_start(otherLabel2, false, false, 4);
listBox.add(otherBox);
let otherBox = new Gtk.Box({ margin_start: MARGIN, margin_end: MARGIN, spacing: 4 });
otherBox.append(new Gtk.Label({ label: action, use_markup: true, halign: Gtk.Align.START, hexpand: true }));
otherBox.append(new Gtk.Label({ label: shortcut }));
listBox.insert(otherBox, -1);
});
});
listBox.add(new Gtk.Box(ROWBOX_MARGIN_PARAMS));
listBox.insert(new Gtk.Box(ROWBOX_MARGIN_PARAMS), -1);
Shortcuts.INTERNAL_KEYBINDINGS.forEach((settingKeys, index) => {
if (index)
listBox.add(new Gtk.Box(ROWBOX_MARGIN_PARAMS));
listBox.insert(new Gtk.Box(ROWBOX_MARGIN_PARAMS), -1);
let internalKeybindingsWidget = new KeybindingsWidget(settingKeys, internalShortcutSettings);
listBox.add(internalKeybindingsWidget);
listBox.insert(internalKeybindingsWidget, -1);
});
listBox.get_children().forEach(row => row.set_activatable(false));
getChildrenOf(listBox).forEach(row => row.set_activatable(false));
let resetButton = new Gtk.Button({ label: _("Reset settings"), halign: Gtk.Align.CENTER });
resetButton.get_style_context().add_class('destructive-action');
@ -495,7 +549,7 @@ const PrefsPage = new GObject.Class({
internalShortcutSettings.settings_schema.list_keys().forEach(key => internalShortcutSettings.reset(key));
settings.settings_schema.list_keys().forEach(key => settings.reset(key));
});
box.add(resetButton);
box.append(resetButton);
}
});
@ -504,12 +558,12 @@ const Frame = new GObject.Class({
Extends: Gtk.Frame,
_init: function(params) {
let labelWidget = new Gtk.Label({ margin_bottom: MARGIN / 2, use_markup: true, label: `<b><big>${params.label}</big></b>` });
this.parent({ label_yalign: 1.0, label_widget: labelWidget });
let labelWidget = new Gtk.Label({ use_markup: true, label: `<b><big>${params.label}</big></b>` });
this.parent({ label_widget: labelWidget });
if (params.desc) {
labelWidget.set_tooltip_text(params.desc);
this.get_accessible().set_description(params.desc);
setAccessibleDescription(this, params.desc);
}
}
});
@ -522,43 +576,43 @@ const PrefRow = new GObject.Class({
this.parent({ activatable: false });
let hbox = new Gtk.Box(ROWBOX_MARGIN_PARAMS);
this.add(hbox);
this.set_child(hbox);
let labelBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL });
hbox.pack_start(labelBox, true, true, 4);
let labelBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, hexpand: true });
hbox.append(labelBox);
this.widgetBox = new Gtk.Box({ spacing: 4 });
hbox.pack_start(this.widgetBox, false, false, 4);
hbox.append(this.widgetBox);
this.label = new Gtk.Label({ use_markup: true, label: params.label, halign: Gtk.Align.START });
labelBox.pack_start(this.label, true, true, 0);
this.label = new Gtk.Label({ use_markup: true, label: params.label, halign: Gtk.Align.START, vexpand: true });
labelBox.append(this.label);
if (params.desc) {
this.desc = new Gtk.Label({ use_markup: true, label: `<small>${params.desc}</small>`, halign: Gtk.Align.START, wrap: true, xalign: 0 });
this.desc = new Gtk.Label({ use_markup: true, label: `<small>${params.desc}</small>`, halign: Gtk.Align.START, wrap: true, xalign: 0, vexpand: true });
this.desc.get_style_context().add_class('dim-label');
labelBox.pack_start(this.desc, true, true, 0);
labelBox.append(this.desc);
this.widgetBox.set_valign(Gtk.Align.START);
}
},
addWidget: function(widget, setRelationship) {
this.widgetBox.add(widget);
this.widgetBox.append(widget);
if (widget.name)
widget.get_accessible().set_name(widget.name);
setAccessibleLabel(widget, widget.name);
if (setRelationship) {
this.label.get_accessible().add_relationship(Atk.RelationType.LABEL_FOR, widget.get_accessible());
widget.get_accessible().add_relationship(Atk.RelationType.LABELLED_BY, this.label.get_accessible());
if (setRelationship && IS_GTK3) {
this.label.get_accessible().add_relationship(imports.gi.Atk.RelationType.LABEL_FOR, widget.get_accessible());
widget.get_accessible().add_relationship(imports.gi.Atk.RelationType.LABELLED_BY, this.label.get_accessible());
if (this.desc) {
this.desc.get_accessible().add_relationship(Atk.RelationType.DESCRIPTION_FOR, widget.get_accessible());
widget.get_accessible().add_relationship(Atk.RelationType.DESCRIBED_BY, this.desc.get_accessible());
this.desc.get_accessible().add_relationship(imports.gi.Atk.RelationType.DESCRIPTION_FOR, widget.get_accessible());
widget.get_accessible().add_relationship(imports.gi.Atk.RelationType.DESCRIBED_BY, this.desc.get_accessible());
}
}
}
});
const PixelSpinButton = new GObject.Class({
const PixelSpinButton = new GObject.Class(Object.assign({
Name: `${UUID}-PixelSpinButton`,
Extends: Gtk.SpinButton,
Properties: {
@ -584,17 +638,22 @@ const PixelSpinButton = new GObject.Class({
this.adjustment.set_page_increment(step * 10);
},
// Add 'px' unit.
vfunc_output: function() {
this.text = _("%f px").format(Number(this.value).toFixed(2));
return true;
},
vfunc_constructed() {
this.parent();
// No virtual functions with Gtk4 :/.
// Add 'px' unit.
this.connect('output', spinButton => {
this.text = _("%f px").format(Number(spinButton.value).toFixed(2));
return true;
});
}
}, IS_GTK3 ? {
// Prevent accidental scrolling.
vfunc_scroll_event: function(event) {
return this.has_focus ? this.parent(event) : Gdk.EVENT_PROPAGATE;
}
});
} : {}));
// A color button that can be easily bound with a color string setting.
const ColorStringButton = new GObject.Class({
@ -617,49 +676,75 @@ const ColorStringButton = new GObject.Class({
this.set_rgba(newRgba);
},
// Do nothing if the new color is equivalent to the old color (e.g. "black" and "rgb(0,0,0)").
vfunc_color_set(args) {
let oldRgba = new Gdk.RGBA();
oldRgba.parse(this.color_string);
vfunc_constructed() {
this.parent();
if (!this.rgba.equal(oldRgba)) {
this._color_string = this.rgba.to_string();
this.notify('color-string');
}
// No virtual functions with Gtk4 :/.
// Do nothing if the new color is equivalent to the old color (e.g. "black" and "rgb(0,0,0)").
this.connect('color-set', colorButton => {
let oldRgba = new Gdk.RGBA();
oldRgba.parse(colorButton.color_string);
if (!this.rgba.equal(oldRgba)) {
this._color_string = colorButton.rgba.to_string();
this.notify('color-string');
}
});
}
});
const FileChooserButton = new GObject.Class({
Name: `${UUID}-FileChooserButton`,
Extends: Gtk.FileChooserButton,
Extends: Gtk.Button,
Properties: {
'action': GObject.ParamSpec.enum('action', 'action', 'action',
GObject.ParamFlags.READWRITE,
Gtk.FileChooserAction.$gtype,
Gtk.FileChooserAction.SELECT_FOLDER),
'location': GObject.ParamSpec.string('location', 'location', 'location',
GObject.ParamFlags.READWRITE, '')
},
get location() {
return this.get_file().get_path();
return this._location || "";
},
set location(location) {
if (!location) {
this.unselect_all();
if (this.get_file())
this.set_file(Gio.File.new_for_path('aFileThatDoesNotExist'));
return;
if (!this._location || this._location != location) {
this._location = location;
this.label = location ?
Gio.File.new_for_commandline_arg(location).query_info('standard::display-name', Gio.FileQueryInfoFlags.NONE, null).get_display_name() :
_GTK("(None)");
this.notify('location');
}
let file = Gio.File.new_for_commandline_arg(location);
if (file.query_exists(null))
this.set_file(file);
},
vfunc_file_set: function(args) {
this.notify('location');
vfunc_clicked: function() {
let dialog = new Gtk.FileChooserDialog({
title: _(this.name),
action: this.action,
modal: true,
transient_for: IS_GTK3 ? this.get_toplevel() : this.get_root(),
});
dialog.add_button(_GTK("_Cancel"), Gtk.ResponseType.CANCEL);
dialog.add_button(_GTK("_Select"), Gtk.ResponseType.ACCEPT);
if (this.location)
dialog.set_file(Gio.File.new_for_commandline_arg(this.location));
dialog.connect('response', (dialog, response) => {
if (response == Gtk.ResponseType.ACCEPT)
this.location = dialog.get_file().get_path();
dialog.destroy();
});
dialog.show();
}
});
// this code comes from Sticky Notes View by Sam Bull, https://extensions.gnome.org/extension/568/notes/
// From Sticky Notes View by Sam Bull, https://extensions.gnome.org/extension/568/notes/
const KeybindingsWidget = new GObject.Class({
Name: `${UUID}-KeybindingsWidget`,
Extends: Gtk.Box,
@ -713,7 +798,7 @@ const KeybindingsWidget = new GObject.Class({
let [success, iterator ] =
this._store.get_iter_from_string(iter);
if(!success) {
if (!success) {
printerr("Can't change keybinding");
}
@ -745,7 +830,7 @@ const KeybindingsWidget = new GObject.Class({
this._tree_view.columns_autosize();
this._tree_view.set_headers_visible(false);
this.add(this._tree_view);
this.append(this._tree_view);
this.keybinding_column = keybinding_column;
this.action_column = action_column;
@ -768,9 +853,11 @@ const KeybindingsWidget = new GObject.Class({
this._store.clear();
this._settingKeys.forEach(settingKey => {
let [key, mods] = Gtk.accelerator_parse(
this._settings.get_strv(settingKey)[0] || ''
);
let success_, key, mods;
if (IS_GTK3)
[key, mods] = Gtk.accelerator_parse(this._settings.get_strv(settingKey)[0] || '');
else
[success_, key, mods] = Gtk.accelerator_parse(this._settings.get_strv(settingKey)[0] || '');
let iter = this._store.append();
this._store.set(iter,

View File

@ -315,7 +315,7 @@
</key>
<key type="as" name="switch-text-alignment">
<default>["&lt;Primary&gt;&lt;Alt&gt;a"]</default>
<summary>Toggle text alignment</summary>
<summary>Change text alignment</summary>
</key>
<key type="as" name="toggle-background">
<default>["&lt;Primary&gt;b"]</default>

View File

@ -22,6 +22,7 @@
*/
const Gtk = imports.gi.Gtk;
const IS_GTK3 = Gtk.get_major_version() == 3;
const GS_VERSION = imports.misc.config.PACKAGE_VERSION;
const ExtensionUtils = imports.misc.extensionUtils;
@ -32,7 +33,11 @@ const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext;
const internalShortcutsSchema = Convenience.getSettings(Me.metadata['settings-schema'] + '.internal-shortcuts').settings_schema;
const getKeyLabel = function(accel) {
let [keyval, mods] = Gtk.accelerator_parse(accel);
let success_, keyval, mods;
if (IS_GTK3)
[keyval, mods] = Gtk.accelerator_parse(accel);
else
[success_, keyval, mods] = Gtk.accelerator_parse(accel);
return Gtk.accelerator_get_label(keyval, mods);
};