Merge branch 'pile' into 'face'

v11

See merge request abakkk/DrawOnYourScreen!22
This commit is contained in:
abakkk 2021-02-20 00:26:28 +00:00
commit c989c3c0a1
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 v10 - October 2020
================== ==================

263
area.js
View File

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

View File

@ -1,5 +1,5 @@
/* jslint esversion: 6 */ /* jslint esversion: 6 */
/* exported Shapes, Transformations, getAllFontFamilies, DrawingElement */ /* exported Shape, TextAlignment, Transformation, getAllFontFamilies, DrawingElement */
/* /*
* Copyright 2019 Abakkk * Copyright 2019 Abakkk
@ -30,8 +30,9 @@ const PangoCairo = imports.gi.PangoCairo;
const Me = imports.misc.extensionUtils.getCurrentExtension(); const Me = imports.misc.extensionUtils.getCurrentExtension();
const UUID = Me.uuid.replace(/@/gi, '_at_').replace(/[^a-z0-9+_-]/gi, '_'); 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 Shape = { 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 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() { var getAllFontFamilies = function() {
return PangoCairo.font_map_get_default().list_families().map(fontFamily => fontFamily.get_name()).sort((a,b) => a.localeCompare(b)); return PangoCairo.font_map_get_default().list_families().map(fontFamily => fontFamily.get_name()).sort((a,b) => a.localeCompare(b));
@ -63,10 +64,11 @@ const MIN_TRANSLATION_DISTANCE = 1; // px
const MIN_ROTATION_ANGLE = Math.PI / 1000; // rad const MIN_ROTATION_ANGLE = Math.PI / 1000; // rad
const MIN_DRAWING_SIZE = 3; // px const MIN_DRAWING_SIZE = 3; // px
const MIN_INTERMEDIATE_POINT_DISTANCE = 1; // px, the higher it is, the fewer points there will be 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) { var DrawingElement = function(params) {
return params.shape == Shapes.TEXT ? new TextElement(params) : return params.shape == Shape.TEXT ? new TextElement(params) :
params.shape == Shapes.IMAGE ? new ImageElement(params) : params.shape == Shape.IMAGE ? new ImageElement(params) :
new _DrawingElement(params); new _DrawingElement(params);
}; };
@ -103,14 +105,19 @@ const _DrawingElement = new Lang.Class({
if (params.transform && params.transform.center) { if (params.transform && params.transform.center) {
let angle = (params.transform.angle || 0) + (params.transform.startAngle || 0); let angle = (params.transform.angle || 0) + (params.transform.startAngle || 0);
if (angle) 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]]; 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. // 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]]); this.points.push([ratio * (p1[0] - p0[0]) + p0[0], ratio * (p1[1] - p0[1]) + p0[1]]);
} }
delete this.transform; delete this.transform;
// v10-
if (this.textRightAligned)
this.textAlignment = TextAlignment.RIGHT;
delete this.textRightAligned;
}, },
// toJSON is called by JSON.stringify // toJSON is called by JSON.stringify
@ -123,7 +130,7 @@ const _DrawingElement = new Lang.Class({
fill: this.fill, fill: this.fill,
fillRule: this.fillRule, fillRule: this.fillRule,
eraser: this.eraser, eraser: this.eraser,
transformations: this.transformations.filter(transformation => transformation.type != Transformations.SMOOTH) transformations: this.transformations.filter(transformation => transformation.type != Transformation.SMOOTH)
.map(transformation => Object.assign({}, transformation, { undoable: undefined })), .map(transformation => Object.assign({}, transformation, { undoable: undefined })),
points: this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100]) points: this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100])
}; };
@ -133,18 +140,6 @@ const _DrawingElement = new Lang.Class({
if (this.color) if (this.color)
Clutter.cairo_set_source_color(cr, 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) { if (this.line) {
cr.setLineCap(this.line.lineCap); cr.setLineCap(this.line.lineCap);
cr.setLineJoin(this.line.lineJoin); cr.setLineJoin(this.line.lineJoin);
@ -171,21 +166,21 @@ const _DrawingElement = new Lang.Class({
} }
this.transformations.slice(0).reverse().forEach(transformation => { this.transformations.slice(0).reverse().forEach(transformation => {
if (transformation.type == Transformations.TRANSLATION) { if (transformation.type == Transformation.TRANSLATION) {
cr.translate(transformation.slideX, transformation.slideY); cr.translate(transformation.slideX, transformation.slideY);
} else if (transformation.type == Transformations.ROTATION) { } else if (transformation.type == Transformation.ROTATION) {
let center = this._getTransformedCenter(transformation); let center = this._getTransformedCenter(transformation);
cr.translate(center[0], center[1]); cr.translate(center[0], center[1]);
cr.rotate(transformation.angle); cr.rotate(transformation.angle);
cr.translate(-center[0], -center[1]); 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); let center = this._getTransformedCenter(transformation);
cr.translate(center[0], center[1]); cr.translate(center[0], center[1]);
cr.rotate(transformation.angle); cr.rotate(transformation.angle);
cr.scale(transformation.scaleX, transformation.scaleY); cr.scale(transformation.scaleX, transformation.scaleY);
cr.rotate(-transformation.angle); cr.rotate(-transformation.angle);
cr.translate(-center[0], -center[1]); 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.translate(transformation.slideX, transformation.slideY);
cr.rotate(transformation.angle); cr.rotate(transformation.angle);
cr.scale(transformation.scaleX, transformation.scaleY); cr.scale(transformation.scaleX, transformation.scaleY);
@ -199,24 +194,61 @@ const _DrawingElement = new Lang.Class({
cr.identityMatrix(); 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) { _drawCairo: function(cr, params) {
let [points, shape] = [this.points, this.shape]; 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.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]); 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.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]); 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]); cr.moveTo(points[0][0], points[0][1]);
for (let j = 1; j < points.length; j++) { for (let j = 1; j < points.length; j++) {
cr.lineTo(points[j][0], points[j][1]); 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 radius = Math.hypot(points[1][0] - points[0][0], points[1][1] - points[0][1]);
let ratio = 1; let ratio = 1;
@ -232,15 +264,15 @@ const _DrawingElement = new Lang.Class({
} else } else
cr.arc(points[0][0], points[0][1], radius, 0, 2 * Math.PI); 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]); 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]); cr.moveTo(points[0][0], points[0][1]);
for (let j = 1; j < points.length; j++) { for (let j = 1; j < points.length; j++) {
cr.lineTo(points[j][0], points[j][1]); cr.lineTo(points[j][0], points[j][1]);
} }
if (shape == Shapes.POLYGON) if (shape == Shape.POLYGON)
cr.closePath(); cr.closePath();
} }
@ -262,19 +294,19 @@ const _DrawingElement = new Lang.Class({
this.transformations.slice(0).reverse().forEach(transformation => { this.transformations.slice(0).reverse().forEach(transformation => {
let center = this._getTransformedCenter(transformation); let center = this._getTransformedCenter(transformation);
if (transformation.type == Transformations.TRANSLATION) { if (transformation.type == Transformation.TRANSLATION) {
transforms.push(['translate', transformation.slideX, transformation.slideY]); 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(['translate', center[0], center[1]]);
transforms.push(['rotate', transformation.angle * RADIAN]); transforms.push(['rotate', transformation.angle * RADIAN]);
transforms.push(['translate', -center[0], -center[1]]); 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(['translate', center[0], center[1]]);
transforms.push(['rotate', transformation.angle * RADIAN]); transforms.push(['rotate', transformation.angle * RADIAN]);
transforms.push(['scale', transformation.scaleX, transformation.scaleY]); transforms.push(['scale', transformation.scaleX, transformation.scaleY]);
transforms.push(['rotate', -transformation.angle * RADIAN]); transforms.push(['rotate', -transformation.angle * RADIAN]);
transforms.push(['translate', -center[0], -center[1]]); 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(['translate', transformation.slideX, transformation.slideY]);
transforms.push(['rotate', transformation.angle * RADIAN]); transforms.push(['rotate', transformation.angle * RADIAN]);
transforms.push(['scale', transformation.scaleX, transformation.scaleY]); 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}"`; 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 += `<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 += ` C ${points[1][0]} ${points[1][1]}, ${points[2][0]} ${points[2][1]}, ${points[3][0]} ${points[3][1]}`;
row += `${fill ? 'z' : ''}"${transAttribute}/>`; 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 += `<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 += ` C ${points[0][0]} ${points[0][1]}, ${points[1][0]} ${points[1][1]}, ${points[2][0]} ${points[2][1]}`;
row += `${fill ? 'z' : ''}"${transAttribute}/>`; 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}/>`; 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]}`; row += `<path ${attributes} d="M${points[0][0]} ${points[0][1]}`;
for (let i = 1; i < points.length; i++) for (let i = 1; i < points.length; i++)
row += ` L ${points[i][0]} ${points[i][1]}`; row += ` L ${points[i][0]} ${points[i][1]}`;
row += `${fill ? 'z' : ''}"${transAttribute}/>`; 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 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]); 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}/>`; 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]); 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}/>`; 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])}" ` + 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}/>`; `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="`; row += `<polygon ${attributes} points="`;
for (let i = 0; i < points.length; i++) for (let i = 0; i < points.length; i++)
row += ` ${points[i][0]},${points[i][1]}`; row += ` ${points[i][0]},${points[i][1]}`;
row += `"${transAttribute}/>`; row += `"${transAttribute}/>`;
} else if (this.shape == Shapes.POLYLINE && points.length >= 2) { } else if (this.shape == Shape.POLYLINE && points.length >= 2) {
row += `<polyline ${attributes} points="`; row += `<polyline ${attributes} points="`;
for (let i = 0; i < points.length; i++) for (let i = 0; i < points.length; i++)
row += ` ${points[i][0]},${points[i][1]}`; row += ` ${points[i][0]},${points[i][1]}`;
@ -399,7 +431,7 @@ const _DrawingElement = new Lang.Class({
}, },
get isStraightLine() { get isStraightLine() {
return this.shape == Shapes.LINE && this.points.length == 2; return this.shape == Shape.LINE && this.points.length == 2;
}, },
smoothAll: function() { smoothAll: function() {
@ -410,21 +442,21 @@ const _DrawingElement = new Lang.Class({
let newPoints = this.points.slice(); 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, undo: () => this.points = oldPoints,
redo: () => this.points = newPoints }); redo: () => this.points = newPoints });
if (this._undoneTransformations) 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() { addPoint: function() {
if (this.shape == Shapes.POLYGON || this.shape == Shapes.POLYLINE) { if (this.shape == Shape.POLYGON || this.shape == Shape.POLYLINE) {
// copy last point // copy last point
let [lastPoint, secondToLastPoint] = [this.points[this.points.length - 1], this.points[this.points.length - 2]]; let [lastPoint, secondToLastPoint] = [this.points[this.points.length - 1], this.points[this.points.length - 2]];
if (!getNearness(secondToLastPoint, lastPoint, MIN_DRAWING_SIZE)) if (!getNearness(secondToLastPoint, lastPoint, MIN_DRAWING_SIZE))
this.points.push([lastPoint[0], lastPoint[1]]); this.points.push([lastPoint[0], lastPoint[1]]);
} else if (this.shape == Shapes.LINE) { } else if (this.shape == Shape.LINE) {
if (this.points.length == 2) { if (this.points.length == 2) {
this.points[2] = this.points[1]; this.points[2] = this.points[1];
} else if (this.points.length == 3) { } else if (this.points.length == 3) {
@ -448,7 +480,7 @@ const _DrawingElement = new Lang.Class({
startDrawing: function(startX, startY) { startDrawing: function(startX, startY) {
this.points.push([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]); this.points.push([startX, startY]);
}, },
@ -459,29 +491,29 @@ const _DrawingElement = new Lang.Class({
transform = transform || this.transformations.length >= 1; transform = transform || this.transformations.length >= 1;
if (this.shape == Shapes.NONE) { if (this.shape == Shape.NONE) {
points.push([x, y]); points.push([x, y]);
if (transform) if (transform)
this._smooth(points.length - 1); 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) if (points.length < 2)
return; return;
let center = this._getOriginalCenter(); 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) }; 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) if (points.length < 2)
return; return;
points[2] = [x, y]; points[2] = [x, y];
let center = this._getOriginalCenter(); 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) }; 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]; points[points.length - 1] = [x, y];
} else { } else {
@ -492,51 +524,55 @@ const _DrawingElement = new Lang.Class({
stopDrawing: function() { stopDrawing: function() {
// skip when the size is too small to be visible (3px) (except for free drawing) // 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 lastPoint = this.points[this.points.length - 1];
let secondToLastPoint = this.points[this.points.length - 2]; let secondToLastPoint = this.points[this.points.length - 2];
if (getNearness(secondToLastPoint, lastPoint, MIN_DRAWING_SIZE)) if (getNearness(secondToLastPoint, lastPoint, MIN_DRAWING_SIZE))
this.points.pop(); 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) Math.abs(this.transformations[0].angle) < MIN_ROTATION_ANGLE)
this.transformations.shift(); this.transformations.shift();
}, },
startTransformation: function(startX, startY, type, undoable) { 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 }); 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 }); 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 }); 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, this.transformations.push({ startX, startY, endX: startX, endY: startY, type, undoable,
scaleX: 1, scaleY: 1, slideX: 0, slideY: 0, angle: 0 }); scaleX: 1, scaleY: 1, slideX: 0, slideY: 0, angle: 0 });
else if (type == Transformations.INVERSION) else if (type == Transformation.INVERSION)
this.transformations.push({ startX, startY, endX: startX, endY: startY, type, undoable, this.transformations.push({ startX, startY, endX: startX, endY: startY, type, undoable,
scaleX: -1, scaleY: -1, slideX: startX, slideY: startY, scaleX: -1, scaleY: -1, slideX: startX, slideY: startY,
angle: Math.PI + Math.atan(startY / (startX || 1)) }); angle: Math.PI + Math.atan(startY / (startX || 1)) });
if (type == Transformations.REFLECTION || type == Transformations.INVERSION) if (type == Transformation.REFLECTION || type == Transformation.INVERSION)
this.showSymmetryElement = true; this.showSymmetryElement = true;
else if (type == Transformation.ROTATION)
this.showRotationCenter = true;
else if (type == Transformation.STRETCH)
this.showStretchAxes = true;
}, },
updateTransformation: function(x, y) { updateTransformation: function(x, y) {
let transformation = this.lastTransformation; let transformation = this.lastTransformation;
if (transformation.type == Transformations.TRANSLATION) { if (transformation.type == Transformation.TRANSLATION) {
transformation.slideX = x - transformation.startX; transformation.slideX = x - transformation.startX;
transformation.slideY = y - transformation.startY; transformation.slideY = y - transformation.startY;
} else if (transformation.type == Transformations.ROTATION) { } else if (transformation.type == Transformation.ROTATION) {
let center = this._getTransformedCenter(transformation); let center = this._getTransformedCenter(transformation);
transformation.angle = getAngle(center[0], center[1], transformation.startX, transformation.startY, x, y); 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 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; 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]; [transformation.scaleX, transformation.scaleY] = [scale, scale];
} else if (transformation.type == Transformations.STRETCH) { } else if (transformation.type == Transformation.STRETCH) {
let center = this._getTransformedCenter(transformation); let center = this._getTransformedCenter(transformation);
let startAngle = getAngle(center[0], center[1], center[0] + 1, center[1], transformation.startX, transformation.startY); 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); 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.scaleX = vertical ? 1 : scale;
transformation.scaleY = !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); 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]; [transformation.endX, transformation.endY] = [x, y];
if (getNearness([transformation.startX, transformation.startY], [x, y], MIN_REFLECTION_LINE_LENGTH)) { 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) // 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.slideX, transformation.slideY] = [transformation.startX - transformation.startY * tan, 0];
transformation.angle = Math.PI - Math.atan(tan); 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.endX, transformation.endY] = [x, y];
[transformation.scaleX, transformation.scaleY] = [-1, -1]; [transformation.scaleX, transformation.scaleY] = [-1, -1];
[transformation.slideX, transformation.slideY] = [x, y]; [transformation.slideX, transformation.slideY] = [x, y];
@ -578,16 +614,18 @@ const _DrawingElement = new Lang.Class({
stopTransformation: function() { stopTransformation: function() {
this.showSymmetryElement = false; this.showSymmetryElement = false;
this.showRotationCenter = false;
this.showStretchAxes = false;
// Clean transformations // Clean transformations
let transformation = this.lastTransformation; let transformation = this.lastTransformation;
if (!transformation) if (!transformation)
return; return;
if (transformation.type == Transformations.REFLECTION && if (transformation.type == Transformation.REFLECTION &&
getNearness([transformation.startX, transformation.startY], [transformation.endX, transformation.endY], MIN_REFLECTION_LINE_LENGTH) || 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 == Transformation.TRANSLATION && Math.hypot(transformation.slideX, transformation.slideY) < MIN_TRANSLATION_DISTANCE ||
transformation.type == Transformations.ROTATION && Math.abs(transformation.angle) < MIN_ROTATION_ANGLE) { transformation.type == Transformation.ROTATION && Math.abs(transformation.angle) < MIN_ROTATION_ANGLE) {
this.transformations.pop(); this.transformations.pop();
} else { } else {
@ -608,7 +646,7 @@ const _DrawingElement = new Lang.Class({
this._undoneTransformations = []; this._undoneTransformations = [];
let transformation = this.transformations.pop(); let transformation = this.transformations.pop();
if (transformation.type == Transformations.SMOOTH) if (transformation.type == Transformation.SMOOTH)
transformation.undo(); transformation.undo();
this._undoneTransformations.push(transformation); this._undoneTransformations.push(transformation);
@ -625,7 +663,7 @@ const _DrawingElement = new Lang.Class({
this.transformations = []; this.transformations = [];
let transformation = this._undoneTransformations.pop(); let transformation = this._undoneTransformations.pop();
if (transformation.type == Transformations.SMOOTH) if (transformation.type == Transformation.SMOOTH)
transformation.redo(); transformation.redo();
this.transformations.push(transformation); this.transformations.push(transformation);
@ -645,13 +683,12 @@ const _DrawingElement = new Lang.Class({
}, },
// The figure rotation center before transformations (original). // The figure rotation center before transformations (original).
// this.textWidth is computed during Cairo building.
_getOriginalCenter: function() { _getOriginalCenter: function() {
if (!this._originalCenter) { if (!this._originalCenter) {
let points = this.points; let points = this.points;
this._originalCenter = this.shape == Shapes.ELLIPSE ? [points[0][0], points[0][1]] : this._originalCenter = this.shape == Shape.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 == Shape.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.shape == Shape.LINE && points.length == 3 ? getCurveCenter(points[0], points[0], points[1], points[2]) :
points.length >= 3 ? getCentroid(points) : points.length >= 3 ? getCentroid(points) :
getNaiveCenter(points); getNaiveCenter(points);
} }
@ -667,13 +704,13 @@ const _DrawingElement = new Lang.Class({
// Apply transformations to the matrice in reverse order // Apply transformations to the matrice in reverse order
// because Pango multiply matrices by the left when applying a transformation // because Pango multiply matrices by the left when applying a transformation
this.transformations.slice(0, this.transformations.indexOf(transformation)).reverse().forEach(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); matrix.translate(transformation.slideX, transformation.slideY);
} else if (transformation.type == Transformations.ROTATION) { } else if (transformation.type == Transformation.ROTATION) {
// nothing, the center position is preserved. // 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. // 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.translate(transformation.slideX, transformation.slideY);
matrix.rotate(-transformation.angle * RADIAN); matrix.rotate(-transformation.angle * RADIAN);
matrix.scale(transformation.scaleX, transformation.scaleY); matrix.scale(transformation.scaleX, transformation.scaleY);
@ -710,8 +747,7 @@ const TextElement = new Lang.Class({
eraser: this.eraser, eraser: this.eraser,
transformations: this.transformations, transformations: this.transformations,
text: this.text, text: this.text,
lineIndex: this.lineIndex !== undefined ? this.lineIndex : undefined, textAlignment: this.textAlignment,
textRightAligned: this.textRightAligned,
font: this.font.to_string(), font: this.font.to_string(),
points: this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100]) points: this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100])
}; };
@ -719,7 +755,11 @@ const TextElement = new Lang.Class({
get x() { get x() {
// this.textWidth is computed during Cairo building. // 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() { get y() {
@ -730,42 +770,54 @@ const TextElement = new Lang.Class({
return Math.abs(this.points[1][1] - this.points[0][1]); 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. // this.lineWidths is computed during Cairo building.
get lineOffset() { _getLineX: function(index) {
return (this.lineIndex || 0) * this.height; 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) { _drawCairo: function(cr, params) {
if (this.points.length == 2) { if (this.points.length != 2)
let layout = PangoCairo.create_layout(cr); return;
let fontSize = this.height * Pango.SCALE;
this.font.set_absolute_size(fontSize); let layout = PangoCairo.create_layout(cr);
layout.set_font_description(this.font); let fontSize = this.height * Pango.SCALE;
layout.set_text(this.text, -1); this.font.set_absolute_size(fontSize);
this.textWidth = layout.get_pixel_size()[0]; layout.set_font_description(this.font);
cr.moveTo(this.x, this.y); layout.set_text(this.text, -1);
layout.set_text(this.text, -1); this.textWidth = layout.get_pixel_size()[0];
PangoCairo.show_layout_line(cr, layout.get_line(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 cursorLineIndex = layoutCopy.get_line_count() - 1;
let cursorPosition = this.cursorPosition == -1 ? this.text.length : this.cursorPosition; let cursorX = this._getLineX(cursorLineIndex) + layoutCopy.get_line_readonly(cursorLineIndex).get_pixel_extents()[1].width;
layout.set_text(this.text.slice(0, cursorPosition), -1); let cursorY = this.y + this.height * cursorLineIndex;
let width = layout.get_pixel_size()[0]; cr.rectangle(cursorX, cursorY, this.height / 25, - this.height);
cr.rectangle(this.x + width, this.y, cr.fill();
this.height / 25, - this.height); }
cr.fill();
} if (params.showElementBounds) {
cr.rectangle(this.x, this.y - this.height,
if (params.showTextRectangle) { this.textWidth, this.height * layout.get_line_count());
cr.rectangle(this.x, this.y - this.lineOffset, setDummyStroke(cr);
this.textWidth, - this.height); } else if (params.drawElementBounds) {
setDummyStroke(cr); cr.rectangle(this.x, this.y - this.height,
} else if (params.drawTextRectangle) { this.textWidth, this.height * layout.get_line_count());
cr.rectangle(this.x, this.y, // Only draw the rectangle to find the element, not to show it.
this.textWidth, - this.height); cr.setLineWidth(0);
// 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) { _drawSvg: function(transAttribute, bgcolorString) {
let row = "\n "; if (this.points.length != 2)
let [x, y, height] = [Math.round(this.x*100)/100, Math.round(this.y*100)/100, Math.round(this.height*100)/100]; return "";
let row = "";
let height = Math.round(this.height * 100) / 100;
let color = this.eraser ? bgcolorString : this.color.toJSON(); let color = this.eraser ? bgcolorString : this.color.toJSON();
let attributes = this.eraser ? `class="eraser" ` : ''; let attributes = this.eraser ? `class="eraser" ` : '';
attributes += `fill="${color}" ` +
`font-size="${height}" ` +
`font-family="${this.font.get_family()}"`;
if (this.points.length == 2) { // this.font.to_string() is not valid to fill the svg 'font' shorthand property.
attributes += `fill="${color}" ` + // Each property must be filled separately.
`font-size="${height}" ` + ['Stretch', 'Style', 'Variant'].forEach(attribute => {
`font-family="${this.font.get_family()}"`; let lower = attribute.toLowerCase();
if (this.font[`get_${lower}`]() != Pango[attribute].NORMAL) {
// this.font.to_string() is not valid to fill the svg 'font' shorthand property. let font = new Pango.FontDescription();
// Each property must be filled separately. font[`set_${lower}`](this.font[`get_${lower}`]());
['Stretch', 'Style', 'Variant'].forEach(attribute => { attributes += ` font-${lower}="${font.to_string()}"`;
let lower = attribute.toLowerCase(); }
if (this.font[`get_${lower}`]() != Pango[attribute].NORMAL) { });
let font = new Pango.FontDescription(); if (this.font.get_weight() != Pango.Weight.NORMAL)
font[`set_${lower}`](this.font[`get_${lower}`]()); attributes += ` font-weight="${this.font.get_weight()}"`;
attributes += ` font-${lower}="${font.to_string()}"`;
} // 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.
if (this.font.get_weight() != Pango.Weight.NORMAL) // An alternative would be to store this.lineWidths in the json.
attributes += ` font-weight="${this.font.get_weight()}"`; if (this.textAlignment != TextAlignment.LEFT && !this.lineWidths) {
row += `<text ${attributes} x="${x}" `; let clutterText = new Clutter.Text({ text: this.text });
row += `y="${y}"${transAttribute}>${this.text}</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; return row;
}, },
@ -827,7 +895,7 @@ const TextElement = new Lang.Class({
_getOriginalCenter: function() { _getOriginalCenter: function() {
if (!this._originalCenter) { if (!this._originalCenter) {
let points = this.points; 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) : points.length >= 3 ? getCentroid(points) :
getNaiveCenter(points); getNaiveCenter(points);
} }
@ -869,10 +937,10 @@ const ImageElement = new Lang.Class({
cr.fill(); cr.fill();
cr.restore(); cr.restore();
if (params.showTextRectangle) { if (params.showElementBounds) {
cr.rectangle(x, y, width, height); cr.rectangle(x, y, width, height);
setDummyStroke(cr); setDummyStroke(cr);
} else if (params.drawTextRectangle) { } else if (params.drawElementBounds) {
cr.rectangle(x, y, width, height); cr.rectangle(x, y, width, height);
// Only draw the rectangle to find the element, not to show it. // Only draw the rectangle to find the element, not to show it.
cr.setLineWidth(0); cr.setLineWidth(0);

View File

@ -171,7 +171,7 @@ const AreaManager = new Lang.Class({
let loadPersistent = i == Main.layoutManager.primaryIndex && this.persistentOverRestarts; let loadPersistent = i == Main.layoutManager.primaryIndex && this.persistentOverRestarts;
// Some utils for the drawing area menus. // Some utils for the drawing area menus.
let areaManagerUtils = { let areaManagerUtils = {
getHiddenList: () => this.hiddenList, getHiddenList: () => this.hiddenList || null,
togglePanelAndDockOpacity: this.togglePanelAndDockOpacity.bind(this), togglePanelAndDockOpacity: this.togglePanelAndDockOpacity.bind(this),
openPreferences: this.openPreferences.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-fill' : this.activeArea.switchFill.bind(this.activeArea),
'switch-image-file' : this.activeArea.switchImageFile.bind(this.activeArea, false), 'switch-image-file' : this.activeArea.switchImageFile.bind(this.activeArea, false),
'switch-image-file-reverse' : this.activeArea.switchImageFile.bind(this.activeArea, true), 'switch-image-file-reverse' : this.activeArea.switchImageFile.bind(this.activeArea, true),
'select-none-shape': () => this.activeArea.selectTool(Area.Tools.NONE), 'select-none-shape': () => this.activeArea.selectTool(Area.Tool.NONE),
'select-line-shape': () => this.activeArea.selectTool(Area.Tools.LINE), 'select-line-shape': () => this.activeArea.selectTool(Area.Tool.LINE),
'select-ellipse-shape': () => this.activeArea.selectTool(Area.Tools.ELLIPSE), 'select-ellipse-shape': () => this.activeArea.selectTool(Area.Tool.ELLIPSE),
'select-rectangle-shape': () => this.activeArea.selectTool(Area.Tools.RECTANGLE), 'select-rectangle-shape': () => this.activeArea.selectTool(Area.Tool.RECTANGLE),
'select-text-shape': () => this.activeArea.selectTool(Area.Tools.TEXT), 'select-text-shape': () => this.activeArea.selectTool(Area.Tool.TEXT),
'select-image-shape': () => this.activeArea.selectTool(Area.Tools.IMAGE), 'select-image-shape': () => this.activeArea.selectTool(Area.Tool.IMAGE),
'select-polygon-shape': () => this.activeArea.selectTool(Area.Tools.POLYGON), 'select-polygon-shape': () => this.activeArea.selectTool(Area.Tool.POLYGON),
'select-polyline-shape': () => this.activeArea.selectTool(Area.Tools.POLYLINE), 'select-polyline-shape': () => this.activeArea.selectTool(Area.Tool.POLYLINE),
'select-move-tool': () => this.activeArea.selectTool(Area.Tools.MOVE), 'select-move-tool': () => this.activeArea.selectTool(Area.Tool.MOVE),
'select-resize-tool': () => this.activeArea.selectTool(Area.Tools.RESIZE), 'select-resize-tool': () => this.activeArea.selectTool(Area.Tool.RESIZE),
'select-mirror-tool': () => this.activeArea.selectTool(Area.Tools.MIRROR) 'select-mirror-tool': () => this.activeArea.selectTool(Area.Tool.MIRROR)
}; };
// available when writing // 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', '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', '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', COLOR_PICKER: 'color-select-symbolic',
ENTER: 'applications-graphics', LEAVE: 'application-exit', ENTER: 'applications-graphics', LEAVE: 'application-exit',
GRAB: 'input-touchpad', UNGRAB: 'touchpad-disabled', GRAB: 'input-touchpad', UNGRAB: 'touchpad-disabled',
OPEN: 'document-open', SAVE: 'document-save', OPEN: 'document-open', SAVE: 'document-save',
FONT_FAMILY: 'font-x-generic', FONT_STYLE: 'format-text-italic', FONT_WEIGHT:'format-text-bold', 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', 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, { Object.defineProperty(Icons, key, {
get: function() { get: function() {
if (!this[`_${key}`]) if (!this[`_${key}`])
this[`_${key}`] = new Gio.ThemedIcon({ name: `${ThemedIconNames[key]}-symbolic` }); this[`_${key}`] = new Gio.ThemedIcon({ name: `${ThemedIconName[key]}-symbolic` });
return this[`_${key}`]; return this[`_${key}`];
} }
}); });

View File

@ -10,7 +10,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Draw On Your Screen\n" "Project-Id-Version: Draw On Your Screen\n"
"Report-Msgid-Bugs-To: https://framagit.org/abakkk/DrawOnYourScreen/issues\n" "Report-Msgid-Bugs-To: https://framagit.org/abakkk/DrawOnYourScreen/issues\n"
"POT-Creation-Date: 2020-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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -44,7 +44,9 @@ msgstr ""
#. Translators: %s is a key label #. Translators: %s is a key label
#, javascript-format #, javascript-format
msgid "Press <i>%s</i>\nto start a new line" msgid ""
"Press <i>%s</i>\n"
"to start a new line"
msgstr "" msgstr ""
#. Translators: It is displayed in an OSD notification to ask the user to start picking, so it should use the imperative mood. #. 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" msgid "%f px"
msgstr "" msgstr ""
#. Translators: text alignment msgid "Left aligned"
msgid "Right aligned"
msgstr "" msgstr ""
msgid "Left aligned" msgid "Centered"
msgstr ""
msgid "Right aligned"
msgstr "" msgstr ""
msgctxt "drawing-tool" msgctxt "drawing-tool"
@ -685,7 +689,7 @@ msgstr ""
msgid "Change linejoin" msgid "Change linejoin"
msgstr "" msgstr ""
msgid "Toggle text alignment" msgid "Change text alignment"
msgstr "" msgstr ""
msgid "Add a drawing background" msgid "Add a drawing background"

View File

@ -8,8 +8,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Draw On Your Screen 6.1\n" "Project-Id-Version: Draw On Your Screen 6.1\n"
"Report-Msgid-Bugs-To: https://framagit.org/abakkk/DrawOnYourScreen/issues\n" "Report-Msgid-Bugs-To: https://framagit.org/abakkk/DrawOnYourScreen/issues\n"
"POT-Creation-Date: 2020-09-19 15:32+0200\n" "POT-Creation-Date: 2021-02-17 14:14+0100\n"
"PO-Revision-Date: 2020-10-21 09:23+0200\n" "PO-Revision-Date: 2021-02-20 01:06+0100\n"
"Last-Translator: Abakkk\n" "Last-Translator: Abakkk\n"
"Language-Team: \n" "Language-Team: \n"
"Language: fr\n" "Language: fr\n"
@ -226,13 +226,15 @@ msgstr "Biseauté"
msgid "%f px" msgid "%f px"
msgstr "%f px" msgstr "%f px"
#. Translators: text alignment
msgid "Right aligned"
msgstr "Aligné à droite"
msgid "Left aligned" msgid "Left aligned"
msgstr "Aligné à gauche" msgstr "Aligné à gauche"
msgid "Centered"
msgstr "Centré"
msgid "Right aligned"
msgstr "Aligné à droite"
msgctxt "drawing-tool" msgctxt "drawing-tool"
msgid "Free drawing" msgid "Free drawing"
msgstr "Dessin libre" msgstr "Dessin libre"
@ -277,12 +279,6 @@ msgctxt "drawing-tool"
msgid "Mirror" msgid "Mirror"
msgstr "Miroir" msgstr "Miroir"
msgid "Undo"
msgstr "Retirer"
msgid "Redo"
msgstr "Rétablir"
msgid "Erase" msgid "Erase"
msgstr "Effacer" msgstr "Effacer"
@ -601,8 +597,8 @@ msgstr "Ouvrir le dessin précédent"
msgid "Add images from the clipboard" msgid "Add images from the clipboard"
msgstr "Ajouter des images depuis le presse-papiers" msgstr "Ajouter des images depuis le presse-papiers"
msgid "Redo last brushstroke" msgid "Redo"
msgstr "Rétablir le dernier tracé" msgstr "Rétablir"
msgid "Save drawing" msgid "Save drawing"
msgstr "Enregistrer le dessin" msgstr "Enregistrer le dessin"
@ -706,8 +702,8 @@ msgstr "Modifier la forme de lextrémité des lignes"
msgid "Change linejoin" msgid "Change linejoin"
msgstr "Modifier la jointure des segments de ligne" msgstr "Modifier la jointure des segments de ligne"
msgid "Toggle text alignment" msgid "Change text alignment"
msgstr "Alterner lalignement du texte" msgstr "Modifier lalignement du texte"
msgid "Add a drawing background" msgid "Add a drawing background"
msgstr "Ajouter une couleur de fond au dessin" msgstr "Ajouter une couleur de fond au dessin"
@ -725,5 +721,5 @@ msgstr "Cacher le panneau et le dock"
msgid "Square drawing area" msgid "Square drawing area"
msgstr "Rendre la zone de dessin carrée" msgstr "Rendre la zone de dessin carrée"
msgid "Undo last brushstroke" msgid "Undo"
msgstr "Retirer le dernier tracé" 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) // use 'login-dialog-message-warning' class in order to get GS theme warning color (default: #f57900)
const WARNING_COLOR_STYLE_CLASS_NAME = 'login-dialog-message-warning'; const WARNING_COLOR_STYLE_CLASS_NAME = 'login-dialog-message-warning';
const UUID = Me.uuid.replace(/@/gi, '_at_').replace(/[^a-z0-9+_-]/gi, '_'); const 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) { const getActor = function(object) {
return GS_VERSION < '3.33.0' ? object.actor : object; return GS_VERSION < '3.33.0' ? object.actor : object;
@ -71,52 +72,52 @@ var DisplayStrings = {
}, },
get FillRule() { get FillRule() {
if (!this._fillRules) if (!this._FillRule)
// Translators: fill-rule SVG attribute // Translators: fill-rule SVG attribute
this._fillRules = { 0: _("Nonzero"), 1: _("Evenodd") }; this._FillRule = { 0: _("Nonzero"), 1: _("Evenodd") };
return this._fillRules; return this._FillRule;
}, },
getFontFamily: function(family) { getFontFamily: function(family) {
if (!this._fontGenericFamilies) if (!this._FontGenericFamily)
// Translators: generic font-family SVG attribute // Translators: generic font-family SVG attribute
this._fontGenericFamilies = { 'Sans-Serif': pgettext("font-family", "Sans-Serif"), 'Serif': pgettext("font-family", "Serif"), this._FontGenericFamily = { 'Sans-Serif': pgettext("font-family", "Sans-Serif"), 'Serif': pgettext("font-family", "Serif"),
'Monospace': pgettext("font-family", "Monospace"), 'Cursive': pgettext("font-family", "Cursive"), 'Monospace': pgettext("font-family", "Monospace"), 'Cursive': pgettext("font-family", "Cursive"),
'Fantasy': pgettext("font-family", "Fantasy") }; 'Fantasy': pgettext("font-family", "Fantasy") };
return this._fontGenericFamilies[family] || family; return this._FontGenericFamily[family] || family;
}, },
get FontStyle() { get FontStyle() {
if (!this._fontStyles) if (!this._FontStyle)
// Translators: font-style SVG attribute // Translators: font-style SVG attribute
this._fontStyles = { 0: pgettext("font-style", "Normal"), 1: pgettext("font-style", "Oblique"), 2: pgettext("font-style", "Italic") }; this._FontStyle = { 0: pgettext("font-style", "Normal"), 1: pgettext("font-style", "Oblique"), 2: pgettext("font-style", "Italic") };
return this._fontStyles; return this._FontStyle;
}, },
FontStyleMarkup: { 0: 'normal', 1: 'oblique', 2: 'italic' }, FontStyleMarkup: { 0: 'normal', 1: 'oblique', 2: 'italic' },
get FontWeight() { get FontWeight() {
if (!this._fontWeights) if (!this._FontWeight)
// Translators: font-weight SVG attribute // Translators: font-weight SVG attribute
this._fontWeights = { 100: pgettext("font-weight", "Thin"), 200: pgettext("font-weight", "Ultra Light"), 300: pgettext("font-weight", "Light"), 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"), 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"), 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") }; 800: pgettext("font-weight", "Ultra Bold"), 900: pgettext("font-weight", "Heavy"), 1000: pgettext("font-weight", "Ultra Heavy") };
return this._fontWeights; return this._FontWeight;
}, },
get LineCap() { get LineCap() {
if (!this._lineCaps) if (!this._LineCap)
// Translators: stroke-linecap SVG attribute // Translators: stroke-linecap SVG attribute
this._lineCaps = { 0: pgettext("stroke-linecap", "Butt"), 1: pgettext("stroke-linecap", "Round"), 2: pgettext("stroke-linecap", "Square") }; this._LineCap = { 0: pgettext("stroke-linecap", "Butt"), 1: pgettext("stroke-linecap", "Round"), 2: pgettext("stroke-linecap", "Square") };
return this._lineCaps; return this._LineCap;
}, },
get LineJoin() { get LineJoin() {
if (!this._lineJoins) if (!this._LineJoin)
// Translators: stroke-linejoin SVG attribute // Translators: stroke-linejoin SVG attribute
this._lineJoins = { 0: pgettext("stroke-linejoin", "Miter"), 1: pgettext("stroke-linejoin", "Round"), 2: pgettext("stroke-linejoin", "Bevel") }; this._LineJoin = { 0: pgettext("stroke-linejoin", "Miter"), 1: pgettext("stroke-linejoin", "Round"), 2: pgettext("stroke-linejoin", "Bevel") };
return this._lineJoins; return this._LineJoin;
}, },
getPixels(value) { getPixels(value) {
@ -124,28 +125,31 @@ var DisplayStrings = {
return _("%f px").format(value); return _("%f px").format(value);
}, },
getTextAlignment: function(rightAligned) { get TextAlignment() {
// Translators: text alignment // 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() { get Tool() {
if (!this._tools) if (!this._Tool)
this._tools = { 0: pgettext("drawing-tool", "Free drawing"), 1: pgettext("drawing-tool", "Line"), 2: pgettext("drawing-tool", "Ellipse"), 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"), 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"), 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") }; 100: pgettext("drawing-tool", "Move"), 101: pgettext("drawing-tool", "Resize"), 102: pgettext("drawing-tool", "Mirror") };
return this._tools; return this._Tool;
} }
}; };
var DrawingMenu = new Lang.Class({ var DrawingMenu = new Lang.Class({
Name: `${UUID}-DrawingMenu`, Name: `${UUID}-DrawingMenu`,
_init: function(area, monitor, drawingTools, areaManagerUtils) { _init: function(area, monitor, DrawingTool, areaManagerUtils) {
this.area = area; this.area = area;
this.monitor = monitor; this.monitor = monitor;
this.drawingTools = drawingTools; this.DrawingTool = DrawingTool;
this.areaManagerUtils = areaManagerUtils; this.areaManagerUtils = areaManagerUtils;
let side = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL ? St.Side.RIGHT : St.Side.LEFT; 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() { disable: function() {
delete this.area; delete this.area;
delete this.drawingTools; delete this.DrawingTool;
delete this.areaManagerUtils; delete this.areaManagerUtils;
this.menuManager.removeMenu(this.menu); this.menuManager.removeMenu(this.menu);
Main.layoutManager.uiGroup.remove_actor(this.menu.actor); 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._addFontFamilySubMenuItem(fontSection, Files.Icons.FONT_FAMILY);
this._addSubMenuItem(fontSection, Files.Icons.FONT_WEIGHT, DisplayStrings.FontWeight, this.area, 'currentFontWeight'); 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._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._addSeparator(fontSection);
this.menu.addMenuItem(fontSection); this.menu.addMenuItem(fontSection);
fontSection.itemActivated = () => {}; fontSection.itemActivated = () => {};
@ -311,14 +315,14 @@ var DrawingMenu = new Lang.Class({
this.undoButton.child.reactive = this.area.elements.length > 0; this.undoButton.child.reactive = this.area.elements.length > 0;
this.redoButton.child.reactive = this.area.undoneElements.length > 0 || (this.area.elements.length && this.area.elements[this.area.elements.length - 1].canUndo); this.redoButton.child.reactive = this.area.undoneElements.length > 0 || (this.area.elements.length && this.area.elements[this.area.elements.length - 1].canUndo);
this.eraseButton.child.reactive = this.area.elements.length > 0; this.eraseButton.child.reactive = this.area.elements.length > 0;
this.smoothButton.child.reactive = this.area.elements.length > 0 && this.area.elements[this.area.elements.length - 1].shape == this.drawingTools.NONE; this.smoothButton.child.reactive = this.area.elements.length > 0 && this.area.elements[this.area.elements.length - 1].shape == this.DrawingTool.NONE;
this.saveButton.child.reactive = this.area.elements.length > 0; this.saveButton.child.reactive = this.area.elements.length > 0;
this.svgButton.child.reactive = this.area.elements.length > 0; this.svgButton.child.reactive = this.area.elements.length > 0;
this.saveDrawingSubMenuItem.setSensitive(this.area.elements.length > 0); this.saveDrawingSubMenuItem.setSensitive(this.area.elements.length > 0);
}, },
_updateSectionVisibility: function() { _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.lineSection.actor.visible = !isText && !isImage;
this.fontSection.actor.visible = isText; this.fontSection.actor.visible = isText;
this.imageSection.actor.visible = isImage; this.imageSection.actor.visible = isImage;
@ -421,7 +425,7 @@ var DrawingMenu = new Lang.Class({
let item = new PopupMenu.PopupSubMenuMenuItem('', true); let item = new PopupMenu.PopupSubMenuMenuItem('', true);
item.update = () => { item.update = () => {
item.label.set_text(DisplayStrings.Tool[this.area.currentTool]); 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.icon.set_gicon(Files.Icons[`TOOL_${toolName}`]);
}; };
item.update(); item.update();
@ -431,7 +435,7 @@ var DrawingMenu = new Lang.Class({
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
Object.keys(DisplayStrings.Tool).forEach(key => { Object.keys(DisplayStrings.Tool).forEach(key => {
let text = DisplayStrings.Tool[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 subItemIcon = Files.Icons[`TOOL_${toolName}`];
let subItem = item.menu.addAction(text, () => { let subItem = item.menu.addAction(text, () => {
this.area.currentTool = Number(key); this.area.currentTool = Number(key);
@ -443,10 +447,10 @@ var DrawingMenu = new Lang.Class({
getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment); getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment);
// change the display order of tools // change the display order of tools
if (key == this.drawingTools.POLYGON) if (key == this.DrawingTool.POLYGON)
item.menu.moveMenuItem(subItem, Number(this.drawingTools.TEXT)); item.menu.moveMenuItem(subItem, Number(this.DrawingTool.TEXT));
else if (key == this.drawingTools.POLYLINE) else if (key == this.DrawingTool.POLYLINE)
item.menu.moveMenuItem(subItem, Number(this.drawingTools.TEXT) + 1); item.menu.moveMenuItem(subItem, Number(this.DrawingTool.TEXT) + 1);
}); });
return GLib.SOURCE_REMOVE; return GLib.SOURCE_REMOVE;
}); });
@ -553,6 +557,28 @@ var DrawingMenu = new Lang.Class({
menu.addMenuItem(item); 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) { _addImageSubMenuItem: function(menu, images) {
let item = new PopupMenu.PopupSubMenuMenuItem('', true); let item = new PopupMenu.PopupSubMenuMenuItem('', true);
item.update = () => { item.update = () => {
@ -654,7 +680,7 @@ var DrawingMenu = new Lang.Class({
let insertCallback = () => { let insertCallback = () => {
this.area.currentImage = json.image; this.area.currentImage = json.image;
this.imageItem.update(); this.imageItem.update();
this.area.currentTool = this.drawingTools.IMAGE; this.area.currentTool = this.DrawingTool.IMAGE;
this.toolItem.update(); this.toolItem.update();
this._updateSectionVisibility(); this._updateSectionVisibility();
}; };

View File

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

View File

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

View File

@ -22,6 +22,7 @@
*/ */
const Gtk = imports.gi.Gtk; const Gtk = imports.gi.Gtk;
const IS_GTK3 = Gtk.get_major_version() == 3;
const GS_VERSION = imports.misc.config.PACKAGE_VERSION; const GS_VERSION = imports.misc.config.PACKAGE_VERSION;
const ExtensionUtils = imports.misc.extensionUtils; 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 internalShortcutsSchema = Convenience.getSettings(Me.metadata['settings-schema'] + '.internal-shortcuts').settings_schema;
const getKeyLabel = function(accel) { 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); return Gtk.accelerator_get_label(keyval, mods);
}; };